From 97a775252cff74dabeb3e4da79888b4d3b197114 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 6 Aug 2021 22:00:23 +0000 Subject: [PATCH 01/99] Add alternative bad debt settlement methods --- libraries/chain/asset_evaluator.cpp | 12 +++++++++ libraries/chain/asset_object.cpp | 2 ++ libraries/chain/hardfork.d/CORE_2467.hf | 6 +++++ .../include/graphene/chain/asset_object.hpp | 20 ++++++++++----- .../chain/include/graphene/chain/config.hpp | 2 +- .../include/graphene/chain/market_object.hpp | 5 ++-- libraries/chain/market_object.cpp | 1 + libraries/chain/proposal_evaluator.cpp | 4 +++ .../include/graphene/protocol/asset_ops.hpp | 25 +++++++++++++++++++ 9 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_2467.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index e71ae325fd..60c59e787c 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -144,6 +144,14 @@ namespace detail { "Collateral-denominated fees are not yet active and therefore cannot be claimed." ); } + void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options) + { + // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: + FC_ASSERT( !options.extensions.value.bad_debt_settlement_method.valid() + || HARDFORK_CORE_2467_PASSED(next_maint_time), + "A BitAsset's bad debt settlement method cannot be set before Hardfork core-2467" ); + } + } // graphene::chain::detail void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) @@ -151,6 +159,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o const database& d = db(); const time_point_sec now = d.head_block_time(); + const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; // Hardfork Checks: detail::check_asset_options_hf_1774(now, op.common_options); @@ -161,6 +170,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o detail::check_bitasset_options_hf_bsip74( now, *op.bitasset_opts ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip77( now, *op.bitasset_opts ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip87( now, *op.bitasset_opts ); // HF_REMOVABLE + detail::check_bitasset_opts_hf_core2467( next_maint_time, *op.bitasset_opts ); // HF_REMOVABLE } // TODO move as many validations as possible to validate() if not triggered before hardfork @@ -628,12 +638,14 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita { try { const database& d = db(); const time_point_sec now = d.head_block_time(); + const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; // Hardfork Checks: detail::check_bitasset_options_hf_bsip_48_75( now, op.new_options ); detail::check_bitasset_options_hf_bsip74( now, op.new_options ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip77( now, op.new_options ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip87( now, op.new_options ); // HF_REMOVABLE + detail::check_bitasset_opts_hf_core2467( next_maint_time, op.new_options ); // HF_REMOVABLE const asset_object& asset_obj = op.asset_to_update(d); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index e85a951a4e..8fe311cecc 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -232,6 +232,8 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (gr (is_prediction_market) (settlement_price) (settlement_fund) + (individual_settlement_debt) + (individual_settlement_fund) (asset_cer_updated) (feed_cer_updated) ) diff --git a/libraries/chain/hardfork.d/CORE_2467.hf b/libraries/chain/hardfork.d/CORE_2467.hf new file mode 100644 index 0000000000..8d575bb5d0 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2467.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2467 Alternative bad debt settlement methods +#ifndef HARDFORK_CORE_2467_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2467_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2467_PASSED(now) (now > HARDFORK_CORE_2467_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index eee7def65f..8a670dcb2c 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -299,18 +299,26 @@ namespace graphene { namespace chain { bool has_settlement()const { return !settlement_price.is_null(); } /** - * In the event of a black swan, the swan price is saved in the settlement price, and all margin positions - * are settled at the same price with the siezed collateral being moved into the settlement fund. From this - * point on no further updates to the asset are permitted (no feeds, etc) and forced settlement occurs - * immediately when requested, using the settlement price and fund. + * 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. */ ///@{ - /// Price at which force settlements of a black swanned asset will occur + /// Price at which force settlements of a globally settled asset will occur price settlement_price; - /// Amount of collateral which is available for force settlement + /// Amount of collateral which is available for force settlement due to global settlement share_type settlement_fund; ///@} + /// In the event of individual settlements, debt and collateral of the margin positions which got settled + /// are moved here. + ///@{ + /// 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; + ///@} + /// Track whether core_exchange_rate in corresponding asset_object has updated bool asset_cer_updated = false; diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index dc0c0a2351..99b7da3599 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -32,7 +32,7 @@ #define GRAPHENE_MAX_NESTED_OBJECTS (200) -const std::string GRAPHENE_CURRENT_DB_VERSION = "20210713"; +const std::string GRAPHENE_CURRENT_DB_VERSION = "20210806"; #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index d2ce79df0e..62876889f6 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -34,12 +34,12 @@ namespace graphene { namespace chain { using namespace graphene::db; /** - * @brief an offer to sell a amount of a asset at a specified exchange rate by a certain time + * @brief an offer to sell an amount of an asset at a specified exchange rate by a certain time * @ingroup object * @ingroup protocol * @ingroup market * - * This limit_order_objects are indexed by @ref expiration and is automatically deleted on the first block after expiration. + * The objects are indexed by @ref expiration and are automatically deleted on the first block after expiration. */ class limit_order_object : public abstract_object { @@ -53,6 +53,7 @@ class limit_order_object : public abstract_object price sell_price; share_type deferred_fee; ///< fee converted to CORE asset deferred_paid_fee; ///< originally paid fee + bool is_settled_debt = false; ///< Whether this order is a bad-debt settlement fund pair get_market()const { diff --git a/libraries/chain/market_object.cpp b/libraries/chain/market_object.cpp index 99d9110fe2..42ec31d5d0 100644 --- a/libraries/chain/market_object.cpp +++ b/libraries/chain/market_object.cpp @@ -309,6 +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) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::call_order_object, (graphene::db::object), diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 1b23666dfc..b4a537e32d 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -50,6 +50,8 @@ namespace detail { void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op); // HF_REMOVABLE + + void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options); } struct proposal_operation_hardfork_visitor @@ -74,6 +76,7 @@ struct proposal_operation_hardfork_visitor detail::check_bitasset_options_hf_bsip74( block_time, *v.bitasset_opts ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip77( block_time, *v.bitasset_opts ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip87( block_time, *v.bitasset_opts ); // HF_REMOVABLE + detail::check_bitasset_opts_hf_core2467( next_maintenance_time, *v.bitasset_opts ); // HF_REMOVABLE } // TODO move as many validations as possible to validate() if not triggered before hardfork @@ -103,6 +106,7 @@ struct proposal_operation_hardfork_visitor detail::check_bitasset_options_hf_bsip74( block_time, v.new_options ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip77( block_time, v.new_options ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip87( block_time, v.new_options ); // HF_REMOVABLE + detail::check_bitasset_opts_hf_core2467( next_maintenance_time, v.new_options ); // HF_REMOVABLE } void operator()(const graphene::chain::asset_claim_fees_operation &v) const { diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 386dd73917..a7e535ad1f 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -108,6 +108,28 @@ namespace graphene { namespace protocol { */ struct bitasset_options { + /// Defines what will happen when bad debt appears + enum class bad_debt_settlement_type + { + /// All debt positions are closed, all or some collateral is moved to a global-settlement fund. + /// Debt asset holders can claim collateral via force-settlement. + /// It is not allowed to create new debt positions when the fund is not empty. + global_settlement = 0, + /// No debt position is closed, and the derived settlement price is dynamically capped at the collateral + /// ratio of the debt position with the least collateral ratio so that all debt positions are able to pay + /// off their debt when being margin called or force-settled. + /// Also known as "Global Settlement Protection". + no_settlement = 1, + /// Only the undercollateralized debt positions are closed and their collateral is moved to a fund which + /// can be claimed via force-settlement. The derived settlement price is capped at the fund's collateral + /// ratio so that remaining debt positions will not be margin called or force-settled at a worse price. + individual_settlement_to_fund = 2, + /// Only the undercollateralized debt positions are closed and their collateral is moved to a limit order + /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining + /// debt positions could be margin called at a worse price. + individual_settlement_to_order = 3 + }; + struct ext { /// After BSIP77, when creating a new debt position or updating an existing position, @@ -120,6 +142,8 @@ namespace graphene { namespace protocol { fc::optional maximum_short_squeeze_ratio; // BSIP-75 fc::optional margin_call_fee_ratio; // BSIP 74 fc::optional force_settle_fee_percent; // BSIP-87 + // https://github.com/bitshares/bitshares-core/issues/2467 + fc::optional bad_debt_settlement_method; }; /// Time before a price feed expires @@ -601,6 +625,7 @@ FC_REFLECT( graphene::protocol::bitasset_options::ext, (maximum_short_squeeze_ratio) (margin_call_fee_ratio) (force_settle_fee_percent) + (bad_debt_settlement_method) ) FC_REFLECT( graphene::protocol::bitasset_options, From b177cbcbcf61df9559ac328f2efd3a36ea674095 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 7 Aug 2021 17:04:57 +0000 Subject: [PATCH 02/99] Add disable_bdsm_update bit in issuer permission --- libraries/chain/asset_evaluator.cpp | 22 ++++++++++++++++--- .../include/graphene/chain/asset_object.hpp | 2 ++ libraries/chain/proposal_evaluator.cpp | 3 +++ libraries/protocol/asset_ops.cpp | 2 ++ .../include/graphene/protocol/types.hpp | 13 +++++++---- tests/common/database_fixture.hpp | 1 + tests/tests/bsip48_75_tests.cpp | 21 +++++++++++++++--- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 60c59e787c..7e6c3675f5 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -144,12 +144,25 @@ namespace detail { "Collateral-denominated fees are not yet active and therefore cannot be claimed." ); } + void check_asset_options_hf_core2467(const fc::time_point_sec& next_maint_time, const asset_options& options) + { + // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: + if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) ) + { + // new issuer permissions should not be set until activation of the hardfork + FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_bdsm_update), + "New asset issuer permission bits should not be set before Hardfork core-2467" ); + } + } + void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options) { // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: - FC_ASSERT( !options.extensions.value.bad_debt_settlement_method.valid() - || HARDFORK_CORE_2467_PASSED(next_maint_time), - "A BitAsset's bad debt settlement method cannot be set before Hardfork core-2467" ); + if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) ) + { + FC_ASSERT( !options.extensions.value.bad_debt_settlement_method.valid(), + "A BitAsset's bad debt settlement method cannot be set before Hardfork core-2467" ); + } } } // graphene::chain::detail @@ -165,6 +178,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o detail::check_asset_options_hf_1774(now, op.common_options); detail::check_asset_options_hf_bsip_48_75(now, op.common_options); detail::check_asset_options_hf_bsip81(now, op.common_options); + detail::check_asset_options_hf_core2467( next_maint_time, op.common_options ); // HF_REMOVABLE if( op.bitasset_opts ) { detail::check_bitasset_options_hf_bsip_48_75( now, *op.bitasset_opts ); detail::check_bitasset_options_hf_bsip74( now, *op.bitasset_opts ); // HF_REMOVABLE @@ -409,11 +423,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) { try { const database& d = db(); const time_point_sec now = d.head_block_time(); + const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; // Hardfork Checks: detail::check_asset_options_hf_1774(now, o.new_options); detail::check_asset_options_hf_bsip_48_75(now, o.new_options); detail::check_asset_options_hf_bsip81(now, o.new_options); + detail::check_asset_options_hf_core2467( next_maint_time, o.new_options ); // HF_REMOVABLE detail::check_asset_update_extensions_hf_bsip_48_75( now, o.extensions.value ); bool hf_bsip_48_75_passed = ( HARDFORK_BSIP_48_75_PASSED( now ) ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 8a670dcb2c..9bd265fb1f 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -111,6 +111,8 @@ namespace graphene { namespace chain { bool can_owner_update_icr()const { return !(options.issuer_permissions & disable_icr_update); } /// @return true if the asset owner can update MSSR directly bool can_owner_update_mssr()const { return !(options.issuer_permissions & disable_mssr_update); } + /// @return true if the asset owner can change bad debt settlement method + bool can_owner_update_bdsm()const { return !(options.issuer_permissions & disable_bdsm_update); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index b4a537e32d..da4e959e63 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -51,6 +51,7 @@ namespace detail { void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op); // HF_REMOVABLE + void check_asset_options_hf_core2467(const fc::time_point_sec& next_maint_time, const asset_options& options); void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options); } @@ -71,6 +72,7 @@ struct proposal_operation_hardfork_visitor detail::check_asset_options_hf_1774(block_time, v.common_options); detail::check_asset_options_hf_bsip_48_75(block_time, v.common_options); detail::check_asset_options_hf_bsip81(block_time, v.common_options); + detail::check_asset_options_hf_core2467( next_maintenance_time, v.common_options ); // HF_REMOVABLE if( v.bitasset_opts.valid() ) { detail::check_bitasset_options_hf_bsip_48_75( block_time, *v.bitasset_opts ); detail::check_bitasset_options_hf_bsip74( block_time, *v.bitasset_opts ); // HF_REMOVABLE @@ -90,6 +92,7 @@ struct proposal_operation_hardfork_visitor detail::check_asset_options_hf_1774(block_time, v.new_options); detail::check_asset_options_hf_bsip_48_75(block_time, v.new_options); detail::check_asset_options_hf_bsip81(block_time, v.new_options); + detail::check_asset_options_hf_core2467( next_maintenance_time, v.new_options ); // HF_REMOVABLE detail::check_asset_update_extensions_hf_bsip_48_75( block_time, v.extensions.value ); diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 5b719efdf6..5dc5f04323 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -309,6 +309,8 @@ void asset_options::validate_flags( bool is_market_issued )const "Can not set disable_icr_update flag, it is for issuer permission only" ); FC_ASSERT( !(flags & disable_mssr_update), "Can not set disable_mssr_update flag, it is for issuer permission only" ); + FC_ASSERT( !(flags & disable_bdsm_update), + "Can not set disable_mssr_update flag, it is for issuer permission only" ); if( !is_market_issued ) { FC_ASSERT( !(flags & ~UIA_ASSET_ISSUER_PERMISSION_MASK), diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 1cbbd25285..b98c0eccdf 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -179,7 +179,8 @@ enum asset_issuer_permission_flags { ///@{ disable_mcr_update = 0x800, ///< the bitasset owner can not update MCR, permisison only disable_icr_update = 0x1000, ///< the bitasset owner can not update ICR, permisison only - disable_mssr_update = 0x2000 ///< the bitasset owner can not update MSSR, permisison only + disable_mssr_update = 0x2000, ///< the bitasset owner can not update MSSR, permisison only + disable_bdsm_update = 0x4000 ///< the bitasset owner can not update BDSM, permission only ///@} ///@} }; @@ -199,7 +200,8 @@ const static uint16_t ASSET_ISSUER_PERMISSION_MASK = | disable_new_supply | disable_mcr_update | disable_icr_update - | disable_mssr_update; + | disable_mssr_update + | disable_bdsm_update; // The "enable" bits for non-UIA assets const static uint16_t ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK = charge_market_fee @@ -217,7 +219,8 @@ const static uint16_t ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK = | disable_new_supply | disable_mcr_update | disable_icr_update - | disable_mssr_update; + | disable_mssr_update + | disable_bdsm_update; // The bits that can be used in asset issuer permissions for UIA assets const static uint16_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee @@ -242,7 +245,8 @@ const static uint16_t PERMISSION_ONLY_MASK = global_settle | disable_mcr_update | disable_icr_update - | disable_mssr_update; + | disable_mssr_update + | disable_bdsm_update; // The bits that can be used in flags for non-UIA assets const static uint16_t VALID_FLAGS_MASK = ASSET_ISSUER_PERMISSION_MASK & ~PERMISSION_ONLY_MASK; // the bits that can be used in flags for UIA assets @@ -360,6 +364,7 @@ FC_REFLECT_ENUM(graphene::protocol::asset_issuer_permission_flags, (disable_mcr_update) (disable_icr_update) (disable_mssr_update) + (disable_bdsm_update) ) namespace fc { namespace raw { diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 45c1c0dc58..445520d913 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -219,6 +219,7 @@ struct database_fixture_base { bool skip_key_index_test = false; uint32_t anon_acct_count; bool hf1270 = false; + bool hf2467 = false; bool hf2481 = false; bool bsip77 = false; diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 8fda0a6d87..6721fd0837 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK & ~disable_bdsm_update; uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set @@ -992,7 +992,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK & ~disable_bdsm_update; uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set @@ -1062,6 +1062,8 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // advance to bsip48/75 hard fork generate_blocks( HARDFORK_BSIP_48_75_TIME ); + if( hf2467 ) + generate_blocks( HARDFORK_CORE_2467_TIME ); set_expiration( db, trx ); // take a look at flags of UIA @@ -1071,6 +1073,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) auop.new_options = samcoin_id(db).options; for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { + idump( (bit) ); auop.new_options.flags = UIA_VALID_FLAGS_MASK | bit; if( auop.new_options.flags == UIA_VALID_FLAGS_MASK ) continue; @@ -1102,6 +1105,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) auop2.new_options = sambit_id(db).options; for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { + idump( (bit) ); auop2.new_options.flags = valid_bitflag | bit; if( auop2.new_options.flags == valid_bitflag ) continue; @@ -1155,7 +1159,8 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // Unable to create a new MPA with an unknown bit in flags acop2.symbol = "NEWSAMBIT"; // With all possible bits in permissions set to 1 - acop2.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_MASK; + acop2.common_options.issuer_permissions = hf2467 ? ASSET_ISSUER_PERMISSION_MASK + : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update ); for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { acop2.common_options.flags = valid_bitflag | bit; @@ -1181,6 +1186,10 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_CHECK( !newsambit_id(db).can_owner_update_icr() ); BOOST_CHECK( !newsambit_id(db).can_owner_update_mcr() ); BOOST_CHECK( !newsambit_id(db).can_owner_update_mssr() ); + if( hf2467 ) + BOOST_CHECK( !newsambit_id(db).can_owner_update_bdsm() ); + else + BOOST_CHECK( newsambit_id(db).can_owner_update_bdsm() ); // Able to propose too propose( acop2 ); @@ -1193,6 +1202,12 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) } } +BOOST_AUTO_TEST_CASE( invalid_flags_in_asset_after_hf2467 ) +{ + hf2467 = true; + INVOKE( invalid_flags_in_asset ); +} + BOOST_AUTO_TEST_CASE( update_asset_precision ) { try { From 29925a89d4131d53a050258c2487306f5c79a4a2 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 8 Aug 2021 09:57:58 +0000 Subject: [PATCH 03/99] Refactor asset_object::update_median_feeds(...) --- libraries/chain/asset_evaluator.cpp | 8 +-- libraries/chain/asset_object.cpp | 42 +++++++-------- libraries/chain/db_maint.cpp | 26 +++------- libraries/chain/db_update.cpp | 51 ++++++++++--------- .../include/graphene/chain/asset_object.hpp | 25 +++++---- .../chain/include/graphene/chain/database.hpp | 2 + 6 files changed, 76 insertions(+), 78 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 7e6c3675f5..3966d3dacd 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -815,7 +815,7 @@ static bool update_bitasset_object_options( if( op.new_options.minimum_feeds != bdo.options.minimum_feeds ) should_update_feeds = true; - // after hardfork core-868-890, we also should call update_median_feeds if the feed_lifetime_sec changed + // after hardfork core-868-890, we also should call update_asset_current_feed if the feed_lifetime_sec changed if( after_hf_core_868_890 && op.new_options.feed_lifetime_sec != bdo.options.feed_lifetime_sec ) { @@ -895,7 +895,7 @@ static bool update_bitasset_object_options( if( should_update_feeds ) { const auto old_feed = bdo.current_feed; - bdo.update_median_feeds( db.head_block_time(), next_maint_time ); + db.update_asset_current_feed( asset_to_update, bdo ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); @@ -984,8 +984,8 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f { a.feeds[acc]; } - a.update_median_feeds( head_time, next_maint_time ); }); + d.update_asset_current_feed( *asset_to_update, bitasset_to_update ); // Process margin calls, allow black swan, not for a new limit order d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); @@ -1212,8 +1212,8 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope d.modify( bad , [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed, o.extensions.value.initial_collateral_ratio ) ); - a.update_median_feeds( head_time, next_maint_time ); }); + d.update_asset_current_feed( base, bad ); if( !old_feed.margin_call_params_equal(bad.current_feed) ) { diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 8fe311cecc..669381f633 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -49,25 +49,25 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin { bool after_core_hardfork_1270 = ( next_maintenance_time > HARDFORK_CORE_1270_TIME ); // call price caching issue current_feed_publication_time = current_time; - vector> current_feeds; + vector> effective_feeds; // find feeds that were alive at current_time for( const pair>& f : feeds ) { if( (current_time - f.second.first).to_seconds() < options.feed_lifetime_sec && f.second.first != time_point_sec() ) { - current_feeds.emplace_back(f.second.second); + effective_feeds.emplace_back(f.second.second); current_feed_publication_time = std::min(current_feed_publication_time, f.second.first); } } // If there are no valid feeds, or the number available is less than the minimum to calculate a median... - if( current_feeds.size() < options.minimum_feeds ) + if( effective_feeds.size() < options.minimum_feeds ) { //... don't calculate a median, and set a null feed feed_cer_updated = false; // new median cer is null, won't update asset_object anyway, set to false for better performance current_feed_publication_time = current_time; - current_feed = price_feed_with_icr(); + median_feed = price_feed_with_icr(); if( after_core_hardfork_1270 ) { // update data derived from MCR, ICR and etc @@ -75,21 +75,22 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin } return; } - if( current_feeds.size() == 1 ) + + if( effective_feeds.size() == 1 ) { - if( current_feed.core_exchange_rate != current_feeds.front().get().core_exchange_rate ) + if( median_feed.core_exchange_rate != effective_feeds.front().get().core_exchange_rate ) feed_cer_updated = true; - current_feed = current_feeds.front(); + median_feed = effective_feeds.front(); // Note: perhaps can defer updating current_maintenance_collateralization for better performance if( after_core_hardfork_1270 ) { const auto& exts = options.extensions.value; if( exts.maintenance_collateral_ratio.valid() ) - current_feed.maintenance_collateral_ratio = *exts.maintenance_collateral_ratio; + median_feed.maintenance_collateral_ratio = *exts.maintenance_collateral_ratio; if( exts.maximum_short_squeeze_ratio.valid() ) - current_feed.maximum_short_squeeze_ratio = *exts.maximum_short_squeeze_ratio; + median_feed.maximum_short_squeeze_ratio = *exts.maximum_short_squeeze_ratio; if( exts.initial_collateral_ratio.valid() ) - current_feed.initial_collateral_ratio = *exts.initial_collateral_ratio; + median_feed.initial_collateral_ratio = *exts.initial_collateral_ratio; // update data derived from MCR, ICR and etc refresh_cache(); } @@ -97,18 +98,18 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin } // *** Begin Median Calculations *** - price_feed_with_icr median_feed; - const auto median_itr = current_feeds.begin() + current_feeds.size() / 2; + price_feed_with_icr tmp_median_feed; + const auto median_itr = effective_feeds.begin() + effective_feeds.size() / 2; #define CALCULATE_MEDIAN_VALUE(r, data, field_name) \ - std::nth_element( current_feeds.begin(), median_itr, current_feeds.end(), \ + std::nth_element( effective_feeds.begin(), median_itr, effective_feeds.end(), \ [](const price_feed_with_icr& a, const price_feed_with_icr& b) { \ return a.field_name < b.field_name; \ }); \ - median_feed.field_name = median_itr->get().field_name; + tmp_median_feed.field_name = median_itr->get().field_name; #define CHECK_AND_CALCULATE_MEDIAN_VALUE(r, data, field_name) \ if( options.extensions.value.field_name.valid() ) { \ - median_feed.field_name = *options.extensions.value.field_name; \ + tmp_median_feed.field_name = *options.extensions.value.field_name; \ } else { \ CALCULATE_MEDIAN_VALUE(r, data, field_name); \ } @@ -120,9 +121,9 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin #undef CALCULATE_MEDIAN_VALUE // *** End Median Calculations *** - if( current_feed.core_exchange_rate != median_feed.core_exchange_rate ) + if( median_feed.core_exchange_rate != tmp_median_feed.core_exchange_rate ) feed_cer_updated = true; - current_feed = median_feed; + median_feed = tmp_median_feed; // Note: perhaps can defer updating current_maintenance_collateralization for better performance if( after_core_hardfork_1270 ) { @@ -133,9 +134,9 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin void asset_bitasset_data_object::refresh_cache() { - current_maintenance_collateralization = current_feed.maintenance_collateralization(); - if( current_feed.initial_collateral_ratio > current_feed.maintenance_collateral_ratio ) // if ICR is above MCR - current_initial_collateralization = current_feed.calculate_initial_collateralization(); + current_maintenance_collateralization = median_feed.maintenance_collateralization(); + if( median_feed.initial_collateral_ratio > median_feed.maintenance_collateral_ratio ) // if ICR is above MCR + current_initial_collateralization = median_feed.calculate_initial_collateralization(); else // if ICR is not above MCR current_initial_collateralization = current_maintenance_collateralization; } @@ -223,6 +224,7 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_dynamic_data_object, (gra FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (graphene::db::object), (asset_id) (feeds) + (median_feed) (current_feed) (current_feed_publication_time) (current_maintenance_collateralization) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 96464c4476..7ad359c50d 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -991,19 +991,11 @@ void process_hf_2103( database& db ) } } -void update_median_feeds(database& db) +static void update_asset_current_feeds(database& db) { - time_point_sec head_time = db.head_block_time(); - time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; - - const auto update_bitasset = [head_time, next_maint_time]( asset_bitasset_data_object &o ) + for( const auto& bitasset : db.get_index_type().indices() ) { - o.update_median_feeds( head_time, next_maint_time ); - }; - - for( const auto& d : db.get_index_type().indices() ) - { - db.modify( d, update_bitasset ); + db.update_asset_current_feed( bitasset.asset_id(db), bitasset ); } } @@ -1027,8 +1019,6 @@ void update_median_feeds(database& db) // feeds were found. void process_hf_868_890( database& db, bool skip_check_call_orders ) { - const auto next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; - const auto head_time = db.head_block_time(); // for each market issued asset const auto& asset_idx = db.get_index_type().indices().get(); for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_idx.end(); ++asset_itr ) @@ -1074,11 +1064,9 @@ void process_hf_868_890( database& db, bool skip_check_call_orders ) } // end loop of each feed // always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890 - db.modify( bitasset_data, [head_time,next_maint_time]( asset_bitasset_data_object &obj ) { - obj.update_median_feeds( head_time, next_maint_time ); - // NOTE: Normally we should call check_call_orders() after called update_median_feeds(), but for - // mainnet actually check_call_orders() would do nothing, so we skipped it for better performance. - }); + db.update_asset_current_feed( current_asset, bitasset_data ); + // NOTE: Normally we should call check_call_orders() after called update_asset_current_feed(), but for + // mainnet actually check_call_orders() would do nothing, so we skipped it for better performance. } // for each market issued asset } @@ -1497,7 +1485,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if( to_update_and_match_call_orders_for_hf_1270 ) { update_call_orders_hf_1270(*this); - update_median_feeds(*this); + update_asset_current_feeds(*this); match_call_orders(*this); } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 9bd8aeb4fd..97137a4891 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -323,6 +323,18 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s return false; } +void database::update_asset_current_feed( const asset_object& mia, const asset_bitasset_data_object& bitasset ) +{ + modify( bitasset, [this,&mia]( asset_bitasset_data_object& abdo ) { + const auto& head_time = head_block_time(); + const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; + //bool after_core_hardfork_2467 = HARDFORK_CORE_2467_PASSED( maint_time ); // bad debt settlement methods + abdo.update_median_feeds( head_time, maint_time ); + // TODO add logic related to bdsm + abdo.current_feed = abdo.median_feed; + } ); +} + void database::clear_expired_orders() { try { //Cancel expired limit orders @@ -549,7 +561,6 @@ void database::clear_expired_force_settlements() void database::update_expired_feeds() { const auto head_time = head_block_time(); - const auto next_maint_time = get_dynamic_global_properties().next_maintenance_time; bool after_hardfork_615 = ( head_time >= HARDFORK_615_TIME ); const auto& idx = get_index_type().indices().get(); @@ -558,40 +569,32 @@ void database::update_expired_feeds() { const asset_bitasset_data_object& b = *itr; ++itr; // not always process begin() because old code skipped updating some assets before hf 615 - bool update_cer = false; // for better performance, to only update bitasset once, also check CER in this function - const asset_object* asset_ptr = nullptr; // update feeds, check margin calls if( after_hardfork_615 || b.feed_is_expired_before_hardfork_615( head_time ) ) { auto old_median_feed = b.current_feed; - modify( b, [head_time,next_maint_time,&update_cer]( asset_bitasset_data_object& abdo ) - { - abdo.update_median_feeds( head_time, next_maint_time ); - if( abdo.need_to_update_cer() ) - { - update_cer = true; - abdo.asset_cer_updated = false; - abdo.feed_cer_updated = false; - } - }); + const asset_object& asset_obj = b.asset_id( *this ); + update_asset_current_feed( asset_obj, b ); if( !b.current_feed.settlement_price.is_null() && !b.current_feed.margin_call_params_equal( old_median_feed ) ) { - asset_ptr = &b.asset_id( *this ); - check_call_orders( *asset_ptr, true, false, &b, true ); + check_call_orders( asset_obj, true, false, &b, true ); } - } - // update CER - if( update_cer ) - { - if( !asset_ptr ) - asset_ptr = &b.asset_id( *this ); - if( asset_ptr->options.core_exchange_rate != b.current_feed.core_exchange_rate ) + // update CER + if( b.need_to_update_cer() ) { - modify( *asset_ptr, [&b]( asset_object& ao ) + modify( b, []( asset_bitasset_data_object& abdo ) { - ao.options.core_exchange_rate = b.current_feed.core_exchange_rate; + abdo.asset_cer_updated = false; + abdo.feed_cer_updated = false; }); + if( asset_obj.options.core_exchange_rate != b.current_feed.core_exchange_rate ) + { + modify( asset_obj, [&b]( asset_object& ao ) + { + ao.options.core_exchange_rate = b.current_feed.core_exchange_rate; + }); + } } } } // for each asset whose feed is expired diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 9bd265fb1f..faeb21ef75 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -265,12 +265,13 @@ namespace graphene { namespace chain { /// The tunable options for BitAssets are stored in this field. bitasset_options options; - /// Feeds published for this asset. If issuer is not committee, the keys in this map are the feed publishing - /// accounts; otherwise, the feed publishers are the currently active committee_members and witnesses and this map - /// should be treated as an implementation detail. The timestamp on each feed is the time it was published. + /// Feeds published for this asset. + /// The keys in this map are the feed publishing accounts. + /// The timestamp on each feed is the time it was published. flat_map> feeds; - /// This is the currently active price feed, calculated as the median of values from the currently active - /// feeds. + /// This is the median of values from the currently active feeds. + price_feed_with_icr median_feed; + /// This is the currently active price feed, calculated from @ref median_feed and other parameters. price_feed_with_icr current_feed; /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; @@ -285,10 +286,6 @@ namespace graphene { namespace chain { /// should be kept consistent. price current_initial_collateralization; - /// Derive @ref current_maintenance_collateralization and @ref current_initial_collateralization from - /// other member variables. - void refresh_cache(); - /// True if this asset implements a @ref prediction_market bool is_prediction_market = false; @@ -352,13 +349,19 @@ namespace graphene { namespace chain { * * This calculates the median feed from @ref feeds, feed_lifetime_sec * in @ref options, and the given parameters. - * It may update the current_feed_publication_time, current_feed and - * current_maintenance_collateralization member variables. + * It may update the @ref median_feed, @ref current_feed_publication_time and + * @ref current_maintenance_collateralization member variables. * * @param current_time the current time to use in the calculations * @param next_maintenance_time the next chain maintenance time + * + * @note Called by @ref database::update_asset_current_feed() which updates @ref current_feed afterwards. */ void update_median_feeds(time_point_sec current_time, time_point_sec next_maintenance_time); + private: + /// Derive @ref current_maintenance_collateralization and @ref current_initial_collateralization from + /// other member variables. + void refresh_cache(); }; // key extractor for short backing asset diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index a082ffab7b..794be6a452 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -672,6 +672,8 @@ namespace graphene { namespace chain { //////////////////// db_update.cpp //////////////////// public: generic_operation_result process_tickets(); + /// Derive @ref asset_bitasset_data_object::current_feed from other data in the database + void update_asset_current_feed( const asset_object& mia, const asset_bitasset_data_object& bitasset ); private: void update_global_dynamic_data( const signed_block& b, const uint32_t missed_blocks ); void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block); From 013cea1d6650bb5c41aca81dff8aa93ed72b21f8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 8 Aug 2021 22:53:07 +0000 Subject: [PATCH 04/99] Partially implement BDSM::no_settlement --- libraries/chain/asset_evaluator.cpp | 8 +- libraries/chain/db_maint.cpp | 10 +-- libraries/chain/db_update.cpp | 76 ++++++++++++++++--- .../include/graphene/chain/asset_object.hpp | 20 +++-- .../chain/include/graphene/chain/database.hpp | 5 +- libraries/chain/market_evaluator.cpp | 7 +- libraries/protocol/asset_ops.cpp | 6 ++ .../include/graphene/protocol/asset_ops.hpp | 4 +- 8 files changed, 109 insertions(+), 27 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 3966d3dacd..4339ce92e0 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -815,7 +815,7 @@ static bool update_bitasset_object_options( if( op.new_options.minimum_feeds != bdo.options.minimum_feeds ) should_update_feeds = true; - // after hardfork core-868-890, we also should call update_asset_current_feed if the feed_lifetime_sec changed + // after hardfork core-868-890, we also should call update_bitasset_current_feed if the feed_lifetime_sec changed if( after_hf_core_868_890 && op.new_options.feed_lifetime_sec != bdo.options.feed_lifetime_sec ) { @@ -895,7 +895,7 @@ static bool update_bitasset_object_options( if( should_update_feeds ) { const auto old_feed = bdo.current_feed; - db.update_asset_current_feed( asset_to_update, bdo ); + db.update_bitasset_current_feed( bdo ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); @@ -985,7 +985,7 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f a.feeds[acc]; } }); - d.update_asset_current_feed( *asset_to_update, bitasset_to_update ); + d.update_bitasset_current_feed( bitasset_to_update ); // Process margin calls, allow black swan, not for a new limit order d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); @@ -1213,7 +1213,7 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed, o.extensions.value.initial_collateral_ratio ) ); }); - d.update_asset_current_feed( base, bad ); + d.update_bitasset_current_feed( bad ); if( !old_feed.margin_call_params_equal(bad.current_feed) ) { diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 7ad359c50d..89f1bb2082 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -991,11 +991,11 @@ void process_hf_2103( database& db ) } } -static void update_asset_current_feeds(database& db) +static void update_bitasset_current_feeds(database& db) { for( const auto& bitasset : db.get_index_type().indices() ) { - db.update_asset_current_feed( bitasset.asset_id(db), bitasset ); + db.update_bitasset_current_feed( bitasset ); } } @@ -1064,8 +1064,8 @@ void process_hf_868_890( database& db, bool skip_check_call_orders ) } // end loop of each feed // always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890 - db.update_asset_current_feed( current_asset, bitasset_data ); - // NOTE: Normally we should call check_call_orders() after called update_asset_current_feed(), but for + db.update_bitasset_current_feed( bitasset_data ); + // NOTE: Normally we should call check_call_orders() after called update_bitasset_current_feed(), but for // mainnet actually check_call_orders() would do nothing, so we skipped it for better performance. } // for each market issued asset @@ -1485,7 +1485,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if( to_update_and_match_call_orders_for_hf_1270 ) { update_call_orders_hf_1270(*this); - update_asset_current_feeds(*this); + update_bitasset_current_feeds(*this); match_call_orders(*this); } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 97137a4891..a814a4ab00 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -279,7 +279,8 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // * if there is no force settlement, we check here with margin call fee in consideration. auto least_collateral = call_ptr->collateralization(); - if( ~least_collateral >= highest ) + bool gs = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); + if( gs ) { wdump( (*call_ptr) ); elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" @@ -323,15 +324,70 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s return false; } -void database::update_asset_current_feed( const asset_object& mia, const asset_bitasset_data_object& bitasset ) +// Helper function to check whether we need to udpate current_feed.settlement_price. +static optional get_derived_current_feed_price( const database& db, + const asset_bitasset_data_object& bitasset ) { - modify( bitasset, [this,&mia]( asset_bitasset_data_object& abdo ) { - const auto& head_time = head_block_time(); - const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; - //bool after_core_hardfork_2467 = HARDFORK_CORE_2467_PASSED( maint_time ); // bad debt settlement methods - abdo.update_median_feeds( head_time, maint_time ); - // TODO add logic related to bdsm - abdo.current_feed = abdo.median_feed; + optional result; + // check for null first + if( bitasset.median_feed.settlement_price.is_null() ) + return result; + + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + if( bdsm_type::no_settlement == bdsm ) + { + const auto& call_collateral_index = db.get_index_type().indices().get(); + auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); + auto call_itr = call_collateral_index.lower_bound( call_min ); + if( call_itr != call_collateral_index.end() && call_itr->debt_type() == bitasset.asset_id ) + { + // GS if : call_itr->collateralization() < ~( _bitasset_data->median_feed.max_short_squeeze_price() ) + auto least_collateral = call_itr->collateralization(); + auto lowest_callable_price = (~least_collateral) * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, + bitasset.median_feed.maximum_short_squeeze_ratio ); + if( bitasset.median_feed.settlement_price < lowest_callable_price + && bitasset.current_feed.settlement_price != lowest_callable_price ) + result = lowest_callable_price ; + } + } + else if( bdsm_type::individual_settlement_to_fund == bdsm && bitasset.individual_settlement_debt > 0 ) + { + price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) + / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + // FIXME : deal with MSSR and/or MCFR + if( bitasset.median_feed.settlement_price < fund_price + && bitasset.current_feed.settlement_price != fund_price ) + result = fund_price; + } + return result; +} + +void database::update_bitasset_current_feed( const asset_bitasset_data_object& bitasset, bool skip_median_update ) +{ + // For better performance, if nothing to update, we return + optional new_current_feed_price; + if( skip_median_update ) + { + new_current_feed_price = get_derived_current_feed_price( *this, bitasset ); + if( !new_current_feed_price.valid() ) + return; + } + + // We need to update the database + modify( bitasset, [this, skip_median_update, &new_current_feed_price] + ( asset_bitasset_data_object& abdo ) + { + if( !skip_median_update ) + { + const auto& head_time = head_block_time(); + const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; + abdo.update_median_feeds( head_time, maint_time ); + abdo.current_feed = abdo.median_feed; + new_current_feed_price = get_derived_current_feed_price( *this, abdo ); + } + if( new_current_feed_price.valid() ) + abdo.current_feed.settlement_price = *new_current_feed_price; } ); } @@ -574,7 +630,7 @@ void database::update_expired_feeds() { auto old_median_feed = b.current_feed; const asset_object& asset_obj = b.asset_id( *this ); - update_asset_current_feed( asset_obj, b ); + update_bitasset_current_feed( b ); if( !b.current_feed.settlement_price.is_null() && !b.current_feed.margin_call_params_equal( old_median_feed ) ) { diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index faeb21ef75..1aff7937cd 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -294,7 +294,7 @@ namespace graphene { namespace chain { /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply share_type max_force_settlement_volume(share_type current_supply)const; - /** return true if there has been a black swan, false otherwise */ + /** return true if the bitasset has been globally settled, false otherwise */ bool has_settlement()const { return !settlement_price.is_null(); } /** @@ -309,8 +309,8 @@ namespace graphene { namespace chain { share_type settlement_fund; ///@} - /// In the event of individual settlements, debt and collateral of the margin positions which got settled - /// are moved here. + /// In the event of individual settlements to fund, debt and collateral of the margin positions which got + /// settled are moved here. ///@{ /// Amount of debt due to individual settlements share_type individual_settlement_debt; @@ -318,6 +318,15 @@ namespace graphene { namespace chain { share_type individual_settlement_fund; ///@} + /// Get the effective bad debt settlement method of this bitasset + bitasset_options::bad_debt_settlement_type get_bad_debt_settlement_method() const + { + using bdsm_type = bitasset_options::bad_debt_settlement_type; + if( !options.extensions.value.bad_debt_settlement_method.valid() ) + return bdsm_type::global_settlement; + return static_cast( *options.extensions.value.bad_debt_settlement_method ); + } + /// Track whether core_exchange_rate in corresponding asset_object has updated bool asset_cer_updated = false; @@ -349,13 +358,14 @@ namespace graphene { namespace chain { * * This calculates the median feed from @ref feeds, feed_lifetime_sec * in @ref options, and the given parameters. - * It may update the @ref median_feed, @ref current_feed_publication_time and + * It may update the @ref median_feed, @ref current_feed_publication_time, + * @ref current_initial_collateralization and * @ref current_maintenance_collateralization member variables. * * @param current_time the current time to use in the calculations * @param next_maintenance_time the next chain maintenance time * - * @note Called by @ref database::update_asset_current_feed() which updates @ref current_feed afterwards. + * @note Called by @ref database::update_bitasset_current_feed() which updates @ref current_feed afterwards. */ void update_median_feeds(time_point_sec current_time, time_point_sec next_maintenance_time); private: diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 794be6a452..f04d42a4f5 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -673,7 +673,10 @@ namespace graphene { namespace chain { public: generic_operation_result process_tickets(); /// Derive @ref asset_bitasset_data_object::current_feed from other data in the database - void update_asset_current_feed( const asset_object& mia, const asset_bitasset_data_object& bitasset ); + /// @param bitasset The bitasset object + /// @param skip_median_update Whether to skip updating @ref asset_bitasset_data_object::median_feed + void update_bitasset_current_feed( const asset_bitasset_data_object& bitasset, + bool skip_median_update = false ); private: void update_global_dynamic_data( const signed_block& b, const uint32_t missed_blocks ); void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block); diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index bdce36ff2b..99dbedd63a 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -333,7 +333,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // force settlement order. if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) { - FC_ASSERT( call_obj->collateralization() > ~( _bitasset_data->current_feed.max_short_squeeze_price() ), + FC_ASSERT( call_obj->collateralization() >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), "Could not create a debt position which would trigger a blackswan event instantly" ); } // check to see if the order needs to be margin called now, but don't allow black swans and require there to be @@ -406,6 +406,11 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope ); } } + // Update current_feed if needed + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); + if( bdsm_type::no_settlement == bdsm ) + d.update_bitasset_current_feed( *_bitasset_data, true ); } return call_order_id; diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 5dc5f04323..648c71e715 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -258,6 +258,12 @@ void bitasset_options::validate() const if( extensions.value.force_settle_fee_percent.valid() ) FC_ASSERT( *extensions.value.force_settle_fee_percent <= GRAPHENE_100_PERCENT ); + if( extensions.value.bad_debt_settlement_method.valid() ) + { + auto bdsm_count = static_cast( bad_debt_settlement_type::BDSM_TYPE_COUNT ); + FC_ASSERT( *extensions.value.bad_debt_settlement_method < bdsm_count, + "bad_debt_settlement_method should be less than ${c}", ("c",bdsm_count) ); + } } 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 a7e535ad1f..00611c0d88 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -127,7 +127,9 @@ namespace graphene { namespace protocol { /// Only the undercollateralized debt positions are closed and their collateral is moved to a limit order /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining /// debt positions could be margin called at a worse price. - individual_settlement_to_order = 3 + individual_settlement_to_order = 3, + /// Total number of available bad debt settlement methods + BDSM_TYPE_COUNT = 4 }; struct ext From c0b3724761ded12655e003ff383ec424ff7a233a Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 10 Aug 2021 21:28:53 +0000 Subject: [PATCH 05/99] Implement BDSM::no_settlement --- libraries/chain/db_market.cpp | 49 ++++++++++------- libraries/chain/db_update.cpp | 54 ++++++++++++------- .../include/graphene/chain/asset_object.hpp | 18 +++++++ .../chain/include/graphene/chain/database.hpp | 13 ++++- libraries/chain/market_evaluator.cpp | 9 +++- libraries/protocol/asset.cpp | 28 ++++++---- .../include/graphene/protocol/asset.hpp | 11 +++- 7 files changed, 127 insertions(+), 55 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 32538f7d0e..229f1fd4df 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -533,8 +533,7 @@ bool database::apply_order(const limit_order_object& new_order_object) call_match_price = ~sell_abd->current_feed.max_short_squeeze_price_before_hf_1270(); call_pays_price = call_match_price; } else { - call_match_price = ~sell_abd->current_feed. - margin_call_order_price(sell_abd->options.extensions.value.margin_call_fee_ratio); + call_match_price = ~sell_abd->get_margin_call_order_price(); call_pays_price = ~sell_abd->current_feed.max_short_squeeze_price(); } if( ~new_order_object.sell_price <= call_match_price ) // If new limit order price is good enough to @@ -576,7 +575,7 @@ bool database::apply_order(const limit_order_object& new_order_object) sell_abd->current_feed.settlement_price, sell_abd->current_feed.maintenance_collateral_ratio, sell_abd->current_maintenance_collateralization, - call_pays_price); + *sell_abd, call_pays_price ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hf core-834), we no longer only check if the result is 2. @@ -605,7 +604,7 @@ bool database::apply_order(const limit_order_object& new_order_object) const auto match_result = match( new_order_object, *call_itr, call_match_price, sell_abd->current_feed.settlement_price, sell_abd->current_feed.maintenance_collateral_ratio, - optional() ); + optional(), *sell_abd ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hard fork core-834), @@ -654,8 +653,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Price at which margin calls sit on the books. // It is the MCOP, which may deviate from MSSP due to MCFR. - price call_match_price = bitasset.current_feed. - margin_call_order_price(bitasset.options.extensions.value.margin_call_fee_ratio); + price call_match_price = bitasset.get_margin_call_order_price(); // Price margin call actually relinquishes collateral at. Equals the MSSP and it may // differ from call_match_price if there is a Margin Call Fee. price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); @@ -685,7 +683,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Note: the call order should be able to pay at call_pays_price, // thus no need to pass in margin_call_pays_ratio - match( new_settlement, *call_itr, call_pays_price, max_debt_to_cover, call_match_price, true ); + match( new_settlement, *call_itr, call_pays_price, bitasset, max_debt_to_cover, call_match_price, true ); // Check whether the new order is gone finished = ( nullptr == find_object( new_obj_id ) ); @@ -789,6 +787,7 @@ database::match_result_type database::match( const limit_order_object& bid, cons const price& match_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization, + const asset_bitasset_data_object& bitasset, const price& call_pays_price ) { FC_ASSERT( bid.sell_asset_id() == ask.debt_type() ); @@ -842,6 +841,10 @@ database::match_result_type database::match( const limit_order_object& bid, cons bool taker_filled = fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false ); bool maker_filled = fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee ); + // Update current_feed after filled call order if needed + if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + update_bitasset_current_feed( bitasset, true ); + // Note: result can be none_filled when call order has target_collateral_ratio option set. match_result_type result = get_match_result( taker_filled, maker_filled ); return result; @@ -851,29 +854,32 @@ database::match_result_type database::match( const limit_order_object& bid, cons asset database::match( const force_settlement_object& settle, const call_order_object& call, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, bool is_margin_call, const ratio_type* margin_call_pays_ratio ) { - return match_impl( settle, call, match_price, max_settlement, fill_price, is_margin_call, true, + return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, is_margin_call, true, margin_call_pays_ratio ); } asset database::match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, const ratio_type* margin_call_pays_ratio ) { - return match_impl( settle, call, match_price, max_settlement, fill_price, true, false, + return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, true, false, margin_call_pays_ratio ); } asset database::match_impl( const force_settlement_object& settle, const call_order_object& call, const price& p_match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& p_fill_price, bool is_margin_call, @@ -1070,6 +1076,10 @@ asset database::match_impl( const force_settlement_object& settle, // do not pay force-settlement fee if the call is being margin called fill_settle_order( settle, settle_pays, settle_receives, fill_price, !settle_is_taker, !is_margin_call ); + // Update current_feed after filled call order if needed + if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + update_bitasset_current_feed( bitasset, true ); + if( cull_settle_order ) cancel_settle_order( settle ); @@ -1389,11 +1399,9 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // Stop when limit orders are selling too little USD for too much CORE. // Note that since BSIP74, margin calls offer somewhat less CORE per USD // if the issuer claims a Margin Call Fee. - auto min_price = ( before_core_hardfork_1270 ? + auto min_price = before_core_hardfork_1270 ? bitasset.current_feed.max_short_squeeze_price_before_hf_1270() - : bitasset.current_feed.margin_call_order_price( - bitasset.options.extensions.value.margin_call_fee_ratio ) - ); + : bitasset.get_margin_call_order_price(); // NOTE limit_price_index is sorted from greatest to least auto limit_itr = limit_price_index.lower_bound( max_price ); @@ -1462,8 +1470,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa price match_price = limit_order.sell_price; // There was a check `match_price.validate();` here, which is removed now because it always passes - price call_pays_price = match_price * bitasset.current_feed.margin_call_pays_ratio( - bitasset.options.extensions.value.margin_call_fee_ratio); + price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the // excess being kept by the asset issuer as a margin call fee. In what follows, we use // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, @@ -1600,6 +1607,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); + // Update current_feed after filled call order if needed + if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + update_bitasset_current_feed( bitasset, true ); + if( !before_core_hardfork_1270 ) call_collateral_itr = call_collateral_index.lower_bound( call_min ); else if( !before_core_hardfork_343 ) @@ -1650,15 +1661,13 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // Price at which margin calls sit on the books. // It is the MCOP, which may deviate from MSSP due to MCFR. // It is in debt/collateral . - price call_match_price = bitasset.current_feed. - margin_call_order_price(bitasset.options.extensions.value.margin_call_fee_ratio); + price call_match_price = bitasset.get_margin_call_order_price(); // Price margin call actually relinquishes collateral at. Equals the MSSP and it may // differ from call_match_price if there is a Margin Call Fee. // It is in debt/collateral . price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - auto margin_call_pays_ratio = bitasset.current_feed.margin_call_pays_ratio( - bitasset.options.extensions.value.margin_call_fee_ratio); + auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio(); bool margin_called = false; while( settle_itr != settle_end && call_itr != call_end ) @@ -1680,7 +1689,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // Note: if the call order's CR is too low, it is probably unable to fill at call_pays_price. // In this case, the call order pays at its CR, the settle order may receive less due to margin call fee. // It is processed inside the function. - auto result = match( call_order, settle_order, call_pays_price, max_debt_to_cover, call_match_price, + auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price, &margin_call_pays_ratio ); if( !margin_called && result.amount > 0 ) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index a814a4ab00..68be70bd6a 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -259,8 +259,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( after_core_hardfork_2481 ) { // due to margin call fee, we check with MCPP (margin call pays price) here - call_pays_price = call_pays_price * bitasset.current_feed.margin_call_pays_ratio( - bitasset.options.extensions.value.margin_call_fee_ratio ); + call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); } highest = std::max( call_pays_price, highest ); } @@ -331,7 +330,12 @@ static optional get_derived_current_feed_price( const database& db, optional result; // check for null first if( bitasset.median_feed.settlement_price.is_null() ) - return result; + { + if( bitasset.current_feed.settlement_price.is_null() ) + return result; + else + return bitasset.median_feed.settlement_price; + } using bdsm_type = bitasset_options::bad_debt_settlement_type; const auto bdsm = bitasset.get_bad_debt_settlement_method(); @@ -342,24 +346,31 @@ static optional get_derived_current_feed_price( const database& db, auto call_itr = call_collateral_index.lower_bound( call_min ); if( call_itr != call_collateral_index.end() && call_itr->debt_type() == bitasset.asset_id ) { - // GS if : call_itr->collateralization() < ~( _bitasset_data->median_feed.max_short_squeeze_price() ) + // GS if : call_itr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() ) auto least_collateral = call_itr->collateralization(); - auto lowest_callable_price = (~least_collateral) * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, - bitasset.median_feed.maximum_short_squeeze_ratio ); - if( bitasset.median_feed.settlement_price < lowest_callable_price - && bitasset.current_feed.settlement_price != lowest_callable_price ) - result = lowest_callable_price ; + auto lowest_callable_feed_price = (~least_collateral) / ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, + bitasset.current_feed.maximum_short_squeeze_ratio ); + result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); } + else // there is no call order of this bitasset + result = bitasset.median_feed.settlement_price; } - else if( bdsm_type::individual_settlement_to_fund == bdsm && bitasset.individual_settlement_debt > 0 ) + else if( bdsm_type::individual_settlement_to_fund == bdsm ) { - price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) - / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); - // FIXME : deal with MSSR and/or MCFR - if( bitasset.median_feed.settlement_price < fund_price - && bitasset.current_feed.settlement_price != fund_price ) - result = fund_price; + if( bitasset.individual_settlement_debt <= 0 ) + result = bitasset.median_feed.settlement_price; + else + { + // Now bitasset.individual_settlement_debt > 0 + price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) + / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + auto lowest_callable_feed_price = fund_price * bitasset.get_margin_call_order_ratio(); + result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); + } } + // Check whether it's necessary to update + if( result.valid() && (*result) == bitasset.current_feed.settlement_price ) + result.reset(); return result; } @@ -367,15 +378,19 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b { // For better performance, if nothing to update, we return optional new_current_feed_price; + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( skip_median_update ) { + if( bdsm_type::no_settlement != bdsm && bdsm_type::individual_settlement_to_fund != bdsm ) + return; new_current_feed_price = get_derived_current_feed_price( *this, bitasset ); if( !new_current_feed_price.valid() ) return; } // We need to update the database - modify( bitasset, [this, skip_median_update, &new_current_feed_price] + modify( bitasset, [this, skip_median_update, &new_current_feed_price, &bdsm] ( asset_bitasset_data_object& abdo ) { if( !skip_median_update ) @@ -384,7 +399,8 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; abdo.update_median_feeds( head_time, maint_time ); abdo.current_feed = abdo.median_feed; - new_current_feed_price = get_derived_current_feed_price( *this, abdo ); + if( bdsm_type::no_settlement == bdsm || bdsm_type::individual_settlement_to_fund == bdsm ) + new_current_feed_price = get_derived_current_feed_price( *this, abdo ); } if( new_current_feed_price.valid() ) abdo.current_feed.settlement_price = *new_current_feed_price; @@ -578,7 +594,7 @@ void database::clear_expired_force_settlements() break; } try { - asset new_settled = match(order, *itr, settlement_price, max_settlement, settlement_fill_price); + asset new_settled = match(order, *itr, settlement_price, mia, max_settlement, settlement_fill_price); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { if( find_object( order_id ) ) // the settle order hasn't been cancelled diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 1aff7937cd..99b8252d8b 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -327,6 +327,24 @@ namespace graphene { namespace chain { return static_cast( *options.extensions.value.bad_debt_settlement_method ); } + /// Get margin call order price (MCOP) of this bitasset + price get_margin_call_order_price() const + { + return current_feed.margin_call_order_price( options.extensions.value.margin_call_fee_ratio ); + } + + /// Get margin call order ratio (MCOR) of this bitasset + ratio_type get_margin_call_order_ratio() const + { + return current_feed.margin_call_order_ratio( options.extensions.value.margin_call_fee_ratio ); + } + + /// Get margin call pays ratio (MCPR) of this bitasset + ratio_type get_margin_call_pays_ratio() const + { + return current_feed.margin_call_pays_ratio( options.extensions.value.margin_call_fee_ratio ); + } + /// Track whether core_exchange_rate in corresponding asset_object has updated bool asset_cer_updated = false; diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index f04d42a4f5..6bfa9afc9c 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -403,6 +403,7 @@ namespace graphene { namespace chain { /// @param settle the force-settlement order /// @param call the call order /// @param match_price the price to calculate how much the call order pays + /// @param bitasset the bitasset object corresponding to debt asset of the call order /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives when the call order is being margin called @@ -413,6 +414,7 @@ namespace graphene { namespace chain { asset match_impl( const force_settlement_object& settle, const call_order_object& call, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, bool is_margin_call = false, @@ -473,6 +475,7 @@ namespace graphene { namespace chain { * @param feed_price the price of the current feed * @param maintenance_collateral_ratio the maintenance collateral ratio * @param maintenance_collateralization the maintenance collateralization + * @param bitasset the bitasset object corresponding to debt asset of the call order * @param call_pays_price price call order pays. Call order may pay more collateral * than limit order takes if call order subject to a margin call fee. * @returns 0 if no orders were matched, 1 if taker was filled, 2 if maker was filled, 3 if both were filled @@ -481,14 +484,16 @@ namespace graphene { namespace chain { const price& trade_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization, + const asset_bitasset_data_object& bitasset, const price& call_pays_price); /// If separate call_pays_price not provided, assume call pays at trade_price: match_result_type match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization) { + const optional& maintenance_collateralization, + const asset_bitasset_data_object& bitasset) { return match(taker, maker, trade_price, feed_price, maintenance_collateral_ratio, - maintenance_collateralization, trade_price); + maintenance_collateralization, bitasset, trade_price); } ///@} @@ -497,6 +502,7 @@ namespace graphene { namespace chain { /// @param settle the force-settlement order /// @param call the call order /// @param match_price the price to calculate how much the call order pays + /// @param bitasset the bitasset object corresponding to debt asset of the call order /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives when the call order is being margin called @@ -506,6 +512,7 @@ namespace graphene { namespace chain { asset match( const force_settlement_object& settle, const call_order_object& call, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, bool is_margin_call = false, @@ -515,6 +522,7 @@ namespace graphene { namespace chain { /// @param call the call order being margin called /// @param settle the force-settlement order /// @param match_price the price to calculate how much the call order pays + /// @param bitasset the bitasset object corresponding to debt asset of the call order /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives. @@ -523,6 +531,7 @@ namespace graphene { namespace chain { asset match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, const ratio_type* margin_call_pays_ratio = nullptr ); diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 99dbedd63a..bbcd11cf87 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -303,6 +303,12 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope { FC_ASSERT( new_collateral == 0, "Should claim all collateral when closing debt position" ); d.remove( *call_obj ); + + // Update current_feed if needed + const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); + if( bitasset_options::bad_debt_settlement_type::no_settlement == bdsm ) + d.update_bitasset_current_feed( *_bitasset_data, true ); + return call_order_id; } @@ -407,9 +413,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } } // Update current_feed if needed - using bdsm_type = bitasset_options::bad_debt_settlement_type; const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); - if( bdsm_type::no_settlement == bdsm ) + if( bitasset_options::bad_debt_settlement_type::no_settlement == bdsm ) d.update_bitasset_current_feed( *_bitasset_data, true ); } diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index 0e7418a99b..af811d8bb1 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -301,27 +301,35 @@ namespace graphene { namespace protocol { // Documentation in header. // Calculation: MCOP = settlement_price / (MSSR - MCFR); result is in debt/collateral - price price_feed::margin_call_order_price(const fc::optional maybe_mcfr)const + price price_feed::margin_call_order_price(const fc::optional& maybe_mcfr)const + { + return settlement_price / margin_call_order_ratio( maybe_mcfr ); + } + + // Calculation: MCOR = MSSR - MCFR, floor at 1.00 + uint16_t price_feed::get_margin_call_price_numerator(const fc::optional& maybe_mcfr)const { const uint16_t mcfr = maybe_mcfr.valid() ? *maybe_mcfr : 0; uint16_t numerator = (mcfr < maximum_short_squeeze_ratio) ? (maximum_short_squeeze_ratio - mcfr) : GRAPHENE_COLLATERAL_RATIO_DENOM; // won't underflow if (numerator < GRAPHENE_COLLATERAL_RATIO_DENOM) numerator = GRAPHENE_COLLATERAL_RATIO_DENOM; // floor at 1.00 - return settlement_price * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, numerator ); + return numerator; + } + + // Documentation in header. + // Calculation: MCOR = MSSR - MCFR + ratio_type price_feed::margin_call_order_ratio(const fc::optional& maybe_mcfr)const + { + auto numerator = get_margin_call_price_numerator( maybe_mcfr ); + return ratio_type( numerator, GRAPHENE_COLLATERAL_RATIO_DENOM ); } // Reason for this function is explained in header. // Calculation: (MSSR - MCFR) / MSSR - ratio_type price_feed::margin_call_pays_ratio(const fc::optional maybe_mcfr)const + ratio_type price_feed::margin_call_pays_ratio(const fc::optional& maybe_mcfr)const { - if (!maybe_mcfr.valid()) - return ratio_type(1,1); - const uint16_t mcfr = *maybe_mcfr; - uint16_t numerator = (mcfr < maximum_short_squeeze_ratio) ? - (maximum_short_squeeze_ratio - mcfr) : GRAPHENE_COLLATERAL_RATIO_DENOM; // won't underflow - if (numerator < GRAPHENE_COLLATERAL_RATIO_DENOM) - numerator = GRAPHENE_COLLATERAL_RATIO_DENOM; // floor at 1.00 + auto numerator = get_margin_call_price_numerator( maybe_mcfr ); return ratio_type( numerator, maximum_short_squeeze_ratio ); // Note: This ratio, if it multiplied margin_call_order_price, would yield the // max_short_squeeze_price, apart perhaps for truncation (rounding) error. diff --git a/libraries/protocol/include/graphene/protocol/asset.hpp b/libraries/protocol/include/graphene/protocol/asset.hpp index ffd26a8a1d..29a8e7e803 100644 --- a/libraries/protocol/include/graphene/protocol/asset.hpp +++ b/libraries/protocol/include/graphene/protocol/asset.hpp @@ -262,7 +262,11 @@ namespace graphene { namespace protocol { * * @return The MCOP in units of DEBT per COLLATERAL. */ - price margin_call_order_price(const fc::optional margin_call_fee_ratio)const; + price margin_call_order_price(const fc::optional& margin_call_fee_ratio)const; + + /// Compute the MCOR, the ratio between margin_call_order_price and feed price + /// @return MSSR - MCFR + ratio_type margin_call_order_ratio( const fc::optional& margin_call_fee_ratio )const; /** * Ratio between max_short_squeeze_price and margin_call_order_price. @@ -281,7 +285,7 @@ namespace graphene { namespace protocol { * * @return (MSSR - MCFR) / MSSR */ - ratio_type margin_call_pays_ratio(const fc::optional margin_call_fee_ratio)const; + ratio_type margin_call_pays_ratio(const fc::optional& margin_call_fee_ratio)const; /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call /// territory. @@ -301,6 +305,9 @@ namespace graphene { namespace protocol { void validate() const; bool is_for( asset_id_type asset_id ) const; + private: + /// Helper function for other functions e.g. @ref margin_call_order_price + uint16_t get_margin_call_price_numerator(const fc::optional& margin_call_fee_ratio)const; }; } } From 055f7c5591b285ebefe4f3171ad46421e95c6a96 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 14 Aug 2021 14:16:51 +0000 Subject: [PATCH 06/99] Implement force-settlement against individual pool --- libraries/chain/asset_evaluator.cpp | 149 ++++++++++++++---- .../graphene/chain/asset_evaluator.hpp | 27 ++-- .../include/graphene/chain/asset_object.hpp | 13 +- 3 files changed, 147 insertions(+), 42 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 4339ce92e0..d6fbcb800d 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1029,16 +1029,35 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op asset_to_settle = &op.amount.asset_id(d); FC_ASSERT( asset_to_settle->is_market_issued(), "Can only force settle a predition market or a market issued asset" ); + const auto& bitasset = asset_to_settle->bitasset_data(d); - FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.has_settlement(), - "Either the asset need to have the force_settle flag enabled, or it need to be globally settled" ); + FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.has_settlement() + || bitasset.has_individual_settlement(), + "Either the asset need to have the force_settle flag enabled, or it need to be globally settled, " + "or the individual bad debt settlement pool is not empty" ); + if( bitasset.is_prediction_market ) + { FC_ASSERT( bitasset.has_settlement(), "Global settlement must occur before force settling a prediction market" ); - else if( bitasset.current_feed.settlement_price.is_null() - && ( d.head_block_time() <= HARDFORK_CORE_216_TIME // TODO check whether the HF check can be removed - || !bitasset.has_settlement() ) ) - FC_THROW_EXCEPTION(insufficient_feeds, "Cannot force settle with no price feed."); + } + else if( bitasset.current_feed.settlement_price.is_null() ) + { + // TODO check whether the HF check can be removed + if( d.head_block_time() <= HARDFORK_CORE_216_TIME ) + { + FC_THROW_EXCEPTION( insufficient_feeds, + "Before the core-216 hard fork, unable to force settle when there is no sufficient " + " price feeds, no matter if the asset has been globally settled" ); + } + if( !bitasset.has_settlement() && !bitasset.has_individual_settlement() ) + { + FC_THROW_EXCEPTION( insufficient_feeds, + "Cannot force settle with no price feed if the asset is not globally settled and the " + "individual bad debt settlement pool is not empty" ); + } + } + FC_ASSERT( d.get_balance( op.account, op.amount.asset_id ) >= op.amount, "Insufficient balance" ); // Since hard fork core-973, check asset authorization limitations @@ -1061,37 +1080,95 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: const auto head_time = d.head_block_time(); const auto& bitasset = *bitasset_ptr; + + extendable_operation_result result; + + // Process individual bad debt settlement pool first + asset to_settle = op.amount; + asset individual_settled( 0, bitasset.options.short_backing_asset ); + const asset_object* backing_asset_ptr = nullptr; + const asset_dynamic_data_object* mia_dyn = nullptr; + if( bitasset.has_individual_settlement() ) + { + asset pays; + if( to_settle.amount < bitasset.individual_settlement_debt ) + { + auto settlement_price = bitasset.get_individual_settlement_price(); + individual_settled = to_settle * settlement_price; // round down, in favor of settlement fund + FC_ASSERT( individual_settled.amount > 0, "Settle amount is too small to receive anything due to rounding" ); + pays = individual_settled.multiply_and_round_up( settlement_price ); + } + else + { + pays = bitasset.individual_settlement_debt; + individual_settled = bitasset.individual_settlement_fund; + } + + to_settle -= pays; + d.adjust_balance( op.account, -pays ); + d.modify( bitasset, [&pays,&individual_settled]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt -= pays.amount; + obj.individual_settlement_fund -= individual_settled.amount; + }); + mia_dyn = &asset_to_settle->dynamic_asset_data_id(d); + d.modify( *mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ + obj.current_supply -= pays.amount; + }); + backing_asset_ptr = &bitasset.options.short_backing_asset(d); + auto issuer_fees = d.pay_market_fees( fee_paying_account, *backing_asset_ptr, individual_settled, false ); + individual_settled -= issuer_fees; + + if( individual_settled.amount > 0 ) + d.adjust_balance( op.account, individual_settled ); + + result.value.paid = vector({ pays }); + result.value.received = vector({ individual_settled }); + result.value.fees = vector({ issuer_fees }); + + if( 0 == to_settle.amount ) + return result; + } + + // Then process global settlement fund or others + auto maint_time = d.get_dynamic_global_properties().next_maintenance_time; if( bitasset.has_settlement() ) { - const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); + if( !mia_dyn ) + mia_dyn = &asset_to_settle->dynamic_asset_data_id(d); - auto settled_amount = op.amount * bitasset.settlement_price; // round down, in favor of global settlement fund - if( op.amount.amount == mia_dyn.current_supply ) - settled_amount.amount = bitasset.settlement_fund; // avoid rounding problems - else - // should be strictly < except for PM with zero outcome - FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund ); + asset settled_amount = ( to_settle.amount == mia_dyn->current_supply ) + ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset ) + : to_settle * bitasset.settlement_price; // round down, favors global settlement fund + if( to_settle.amount != mia_dyn->current_supply ) + { + // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero + FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund, + "Internal error: amount in the global settlement fund is not sufficient to pay the settlement" ); + } - if( settled_amount.amount == 0 && !bitasset.is_prediction_market ) + if( 0 == settled_amount.amount && !bitasset.is_prediction_market ) { - if( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + if( maint_time > HARDFORK_CORE_184_TIME && 0 == individual_settled.amount ) FC_THROW( "Settle amount is too small to receive anything due to rounding" ); + else if( maint_time > HARDFORK_CORE_184_TIME ) // and individual_settled.amount > 0 + return result; // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur } - asset pays = op.amount; - if( op.amount.amount != mia_dyn.current_supply + asset pays = to_settle; + if( to_settle.amount != mia_dyn->current_supply && settled_amount.amount != 0 - && d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_342_TIME ) + && maint_time > HARDFORK_CORE_342_TIME ) { pays = settled_amount.multiply_and_round_up( bitasset.settlement_price ); } d.adjust_balance( op.account, -pays ); + asset issuer_fees( 0, bitasset.options.short_backing_asset ); if( settled_amount.amount > 0 ) { - d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){ + d.modify( bitasset, [&settled_amount]( asset_bitasset_data_object& obj ){ obj.settlement_fund -= settled_amount.amount; }); @@ -1102,8 +1179,9 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: // performance loss. Needs testing. if( head_time >= HARDFORK_CORE_1780_TIME ) { - auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), - settled_amount, false ); + if( !backing_asset_ptr ) + backing_asset_ptr = &bitasset.options.short_backing_asset(d); + issuer_fees = d.pay_market_fees( fee_paying_account, *backing_asset_ptr, settled_amount, false ); settled_amount -= issuer_fees; } @@ -1111,27 +1189,42 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: d.adjust_balance( op.account, settled_amount ); } - d.modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ + d.modify( *mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ obj.current_supply -= pays.amount; }); - return settled_amount; + if( to_settle.amount == op.amount.amount ) // it means there was no individual settlement + { + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); + } + else + { + result.value.paid->push_back( pays ); + result.value.received->push_back( settled_amount ); + result.value.fees->push_back( issuer_fees ); + } + return result; } else { - d.adjust_balance( op.account, -op.amount ); - const auto& settle = d.create([&op,&head_time,&bitasset](force_settlement_object& s) { + d.adjust_balance( op.account, -to_settle ); + const auto& settle = d.create( + [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { s.owner = op.account; - s.balance = op.amount; + s.balance = to_settle; s.settlement_date = head_time + bitasset.options.force_settlement_delay_sec; }); auto id = settle.id; - auto maint_time = d.get_dynamic_global_properties().next_maintenance_time; if( HARDFORK_CORE_2481_PASSED( maint_time ) ) { d.apply_force_settlement( settle, bitasset ); } - return id; + + result.value.new_objects = flat_set({ id }); + + return result; } } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index ede754db74..d765185a42 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -34,7 +34,7 @@ namespace graphene { namespace chain { class asset_create_evaluator : public evaluator { public: - typedef asset_create_operation operation_type; + using operation_type = asset_create_operation; void_result do_evaluate( const asset_create_operation& o ); object_id_type do_apply( const asset_create_operation& o ); @@ -50,7 +50,7 @@ namespace graphene { namespace chain { class asset_issue_evaluator : public evaluator { public: - typedef asset_issue_operation operation_type; + using operation_type = asset_issue_operation; void_result do_evaluate( const asset_issue_operation& o ); void_result do_apply( const asset_issue_operation& o ); @@ -61,7 +61,7 @@ namespace graphene { namespace chain { class asset_reserve_evaluator : public evaluator { public: - typedef asset_reserve_operation operation_type; + using operation_type = asset_reserve_operation; void_result do_evaluate( const asset_reserve_operation& o ); void_result do_apply( const asset_reserve_operation& o ); @@ -73,7 +73,7 @@ namespace graphene { namespace chain { class asset_update_evaluator : public evaluator { public: - typedef asset_update_operation operation_type; + using operation_type = asset_update_operation; void_result do_evaluate( const asset_update_operation& o ); void_result do_apply( const asset_update_operation& o ); @@ -85,7 +85,7 @@ namespace graphene { namespace chain { class asset_update_issuer_evaluator : public evaluator { public: - typedef asset_update_issuer_operation operation_type; + using operation_type = asset_update_issuer_operation; void_result do_evaluate( const asset_update_issuer_operation& o ); void_result do_apply( const asset_update_issuer_operation& o ); @@ -96,7 +96,7 @@ namespace graphene { namespace chain { class asset_update_bitasset_evaluator : public evaluator { public: - typedef asset_update_bitasset_operation operation_type; + using operation_type = asset_update_bitasset_operation; void_result do_evaluate( const asset_update_bitasset_operation& o ); void_result do_apply( const asset_update_bitasset_operation& o ); @@ -108,7 +108,7 @@ namespace graphene { namespace chain { class asset_update_feed_producers_evaluator : public evaluator { public: - typedef asset_update_feed_producers_operation operation_type; + using operation_type = asset_update_feed_producers_operation; void_result do_evaluate( const operation_type& o ); void_result do_apply( const operation_type& o ); @@ -119,7 +119,7 @@ namespace graphene { namespace chain { class asset_fund_fee_pool_evaluator : public evaluator { public: - typedef asset_fund_fee_pool_operation operation_type; + using operation_type = asset_fund_fee_pool_operation; void_result do_evaluate(const asset_fund_fee_pool_operation& op); void_result do_apply(const asset_fund_fee_pool_operation& op); @@ -130,7 +130,7 @@ namespace graphene { namespace chain { class asset_global_settle_evaluator : public evaluator { public: - typedef asset_global_settle_operation operation_type; + using operation_type = asset_global_settle_operation; void_result do_evaluate(const operation_type& op); void_result do_apply(const operation_type& op); @@ -140,11 +140,12 @@ namespace graphene { namespace chain { class asset_settle_evaluator : public evaluator { public: - typedef asset_settle_operation operation_type; + using operation_type = asset_settle_operation; void_result do_evaluate(const operation_type& op); operation_result do_apply(const operation_type& op); + private: const asset_object* asset_to_settle = nullptr; const asset_bitasset_data_object* bitasset_ptr = nullptr; }; @@ -152,7 +153,7 @@ namespace graphene { namespace chain { class asset_publish_feeds_evaluator : public evaluator { public: - typedef asset_publish_feed_operation operation_type; + using operation_type = asset_publish_feed_operation; void_result do_evaluate( const asset_publish_feed_operation& o ); void_result do_apply( const asset_publish_feed_operation& o ); @@ -164,7 +165,7 @@ namespace graphene { namespace chain { class asset_claim_fees_evaluator : public evaluator { public: - typedef asset_claim_fees_operation operation_type; + using operation_type = asset_claim_fees_operation; void_result do_evaluate( const asset_claim_fees_operation& o ); void_result do_apply( const asset_claim_fees_operation& o ); @@ -176,7 +177,7 @@ namespace graphene { namespace chain { class asset_claim_pool_evaluator : public evaluator { public: - typedef asset_claim_pool_operation operation_type; + using operation_type = asset_claim_pool_operation; void_result do_evaluate( const asset_claim_pool_operation& o ); void_result do_apply( const asset_claim_pool_operation& o ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 99b8252d8b..b757018a85 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -294,7 +294,7 @@ namespace graphene { namespace chain { /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply share_type max_force_settlement_volume(share_type current_supply)const; - /** return true if the bitasset has been globally settled, false otherwise */ + /// return true if the bitasset has been globally settled, false otherwise bool has_settlement()const { return !settlement_price.is_null(); } /** @@ -309,6 +309,7 @@ namespace graphene { namespace chain { share_type settlement_fund; ///@} + /// The individual bad debt settlement pool. /// In the event of individual settlements to fund, debt and collateral of the margin positions which got /// settled are moved here. ///@{ @@ -318,6 +319,16 @@ namespace graphene { namespace chain { share_type individual_settlement_fund; ///@} + /// return true if the individual bad debt settlement pool is not empty, false otherwise + bool has_individual_settlement()const { return ( individual_settlement_debt != 0 ); } + + /// Get the price of the individual bad debt settlement pool + price get_individual_settlement_price() const + { + return asset( individual_settlement_debt, asset_id ) + / asset( individual_settlement_fund, options.short_backing_asset ); + } + /// Get the effective bad debt settlement method of this bitasset bitasset_options::bad_debt_settlement_type get_bad_debt_settlement_method() const { From e83f8420f69cf0ee84a83384506f8a2e8137a39e Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 14 Aug 2021 15:42:29 +0000 Subject: [PATCH 07/99] Update tests to adapt settle_op return type change --- tests/tests/database_api_tests.cpp | 5 +- tests/tests/force_settle_fee_tests.cpp | 18 ++++--- tests/tests/force_settle_match_tests.cpp | 66 ++++++++++++++++-------- tests/tests/operation_tests2.cpp | 9 ++-- tests/tests/settle_tests.cpp | 50 ++++++++++-------- 5 files changed, 93 insertions(+), 55 deletions(-) diff --git a/tests/tests/database_api_tests.cpp b/tests/tests/database_api_tests.cpp index 7dbf2f73b2..72979d1626 100644 --- a/tests/tests/database_api_tests.cpp +++ b/tests/tests/database_api_tests.cpp @@ -1695,9 +1695,10 @@ BOOST_AUTO_TEST_CASE( get_settle_orders_by_account ) { auto settlements = db_api.get_settle_orders_by_account("settler", force_settlement_id_type(), 100); BOOST_CHECK(settlements.size() == 1); - BOOST_CHECK(settlements[0].id == result.get()); + BOOST_CHECK(settlements[0].id == *result.get().value.new_objects->begin()); - GRAPHENE_CHECK_THROW(db_api.get_settle_orders_by_account("nosuchaccount", force_settlement_id_type(), 100), fc::exception); + GRAPHENE_CHECK_THROW( db_api.get_settle_orders_by_account("nosuchaccount", force_settlement_id_type(), 100), + fc::exception ); } catch (fc::exception& e) { edump((e.to_detail_string())); diff --git a/tests/tests/force_settle_fee_tests.cpp b/tests/tests/force_settle_fee_tests.cpp index d03fd8595c..ea2f732ab5 100644 --- a/tests/tests/force_settle_fee_tests.cpp +++ b/tests/tests/force_settle_fee_tests.cpp @@ -252,7 +252,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t rachel_settle_amount = 20 * bitusd_unit; operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); - force_settlement_id_type rachel_settle_id = result.get(); + force_settlement_id_type rachel_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount); // Check Rachel's balance @@ -497,7 +498,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t rachel_settle_amount = 2 * bitusd_unit; operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); - force_settlement_id_type rachel_settle_id = result.get(); + force_settlement_id_type rachel_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount); // Advance time to complete the force settlement and to update the price feed @@ -596,7 +598,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t michael_settle_amount = 5 * bitusd_unit; result = force_settle(michael, bitusd.amount(michael_settle_amount)); - force_settlement_id_type michael_settle_id = result.get(); + force_settlement_id_type michael_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(michael_settle_id(db).balance.amount.value, michael_settle_amount); // Advance time to complete the force settlement and to update the price feed @@ -683,7 +686,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t yanna_settle_amount = 10 * bitusd_unit; result = force_settle(yanna, bitusd.amount(yanna_settle_amount)); - force_settlement_id_type yanna_settle_id = result.get(); + force_settlement_id_type yanna_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(yanna_settle_id(db).balance.amount.value, yanna_settle_amount); // Advance time to complete the force settlement and to update the price feed @@ -772,7 +776,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t vikram_settle_amount = 10 * bitusd_unit; result = force_settle(vikram, bitusd.amount(vikram_settle_amount)); - force_settlement_id_type vikram_settle_id = result.get(); + force_settlement_id_type vikram_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(vikram_settle_id(db).balance.amount.value, vikram_settle_amount); // Advance time to complete the force settlement and to update the price feed @@ -1119,7 +1124,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t rachel_settle_amount = 2 * bitusd_unit; // 200 satoshi bitusd operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); - force_settlement_id_type rachel_settle_id = result.get(); + force_settlement_id_type rachel_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount); // Advance time to complete the force settlement and to update the price feed diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 4df32e3686..97299f4c0a 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -152,8 +152,10 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call) // Create a force settlement, will be matched with several call orders auto result = force_settle( seller, bitusd.amount(700*4) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) != nullptr ); // buy orders won't change @@ -295,8 +297,10 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // Create a force settlement, will be matched with the call order share_type amount_to_settle = 117; auto result = force_settle( seller, bitusd.amount(amount_to_settle) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) == nullptr ); // the settle order will match with call2, at mssp: 100/11, @@ -338,8 +342,10 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // Settle again share_type amount_to_settle2 = 100; result = force_settle( seller, bitusd.amount(amount_to_settle2) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle2_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle2_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle2_id ) == nullptr ); // the settle order will match with call, at mssp: 100/11 @@ -382,8 +388,10 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // Settle again with a much smaller amount share_type amount_to_settle3 = 9; result = force_settle( seller, bitusd.amount(amount_to_settle3) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle3_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle3_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle3_id ) == nullptr ); // the settle order will match with call, at mssp @@ -415,8 +423,10 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // Settle again with a tiny amount that would receive nothing share_type amount_to_settle4 = 5; result = force_settle( seller, bitusd.amount(amount_to_settle4) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle4_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle4_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle4_id ) == nullptr ); // the settle order will match with call, at mssp @@ -542,8 +552,10 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // Create a force settlement, will be matched with several call orders later auto result = force_settle( seller, bitusd.amount(2400) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) != nullptr ); // prepare price feed to get call and call2 (but not call3) into margin call territory @@ -759,8 +771,10 @@ BOOST_AUTO_TEST_CASE(hf2481_cross_test) // Create a force settlement, will be matched with several call orders later auto result = force_settle( seller, bitusd.amount(2400) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) != nullptr ); BOOST_CHECK_EQUAL( 2400, settle_id(db).balance.amount.value ); @@ -1023,22 +1037,28 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // Create a force settlement, will be matched with several call orders later auto result = force_settle( seller, bitusd.amount(40000) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) != nullptr ); expected_seller_usd_balance -= 40000; // Create another force settlement result = force_settle( seller, bitusd.amount(10000) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle2_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle2_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle2_id ) != nullptr ); expected_seller_usd_balance -= 10000; // Create the third force settlement which is small result = force_settle( seller, bitusd.amount(3) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle3_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle3_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle3_id ) != nullptr ); expected_seller_usd_balance -= 3; @@ -1049,8 +1069,10 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // Create the fourth force settlement which is a little bigger but still small // Note: different execution path than settle3 result = force_settle( seller, bitusd.amount(5) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle4_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle4_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle4_id ) != nullptr ); expected_seller_usd_balance -= 5; diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index e972dafac8..e86498b383 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -1512,7 +1512,8 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) BOOST_TEST_MESSAGE( "Verify partial settlement of call" ); // Partially settle a call - force_settlement_id_type settle_id = force_settle( nathan_id, asset( 50, bitusd_id ) ).get< object_id_type >(); + force_settlement_id_type settle_id = *force_settle( nathan_id, asset( 50, bitusd_id ) ) + .get< extendable_operation_result >().value.new_objects->begin(); // Call does not take effect immediately BOOST_CHECK_EQUAL( get_balance(nathan_id, bitusd_id), 14950); @@ -1536,7 +1537,8 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) BOOST_TEST_MESSAGE( "Verify pending settlement is cancelled when asset's force_settle is disabled" ); // Ensure pending settlement is cancelled when force settle is disabled - settle_id = force_settle( nathan_id, asset( 50, bitusd_id ) ).get< object_id_type >(); + settle_id = *force_settle( nathan_id, asset( 50, bitusd_id ) ) + .get< extendable_operation_result >().value.new_objects->begin(); BOOST_CHECK( !db.get_index_type().indices().empty() ); update_asset_options( bitusd_id, [&]( asset_options& new_options ) @@ -1546,7 +1548,8 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) { new_options.flags &= ~disable_force_settle; } ); BOOST_TEST_MESSAGE( "Perform iterative settlement" ); - settle_id = force_settle( nathan_id, asset( 12500, bitusd_id ) ).get< object_id_type >(); + settle_id = *force_settle( nathan_id, asset( 12500, bitusd_id ) ) + .get< extendable_operation_result >().value.new_objects->begin(); // c3 2950 : 5731 1.9427 fully settled // c5 5000 : 9800 1.9600 fully settled diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 5d3312c089..4bad2a299b 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -89,7 +89,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) // add settle order and check rounding issue operation_result result = force_settle(rachel, bitusd.amount(4)); - force_settlement_id_type settle_id = result.get(); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 4 ); BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); @@ -139,7 +139,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) set_expiration( db, trx ); operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34)); - force_settlement_id_type settle_id2 = result2.get(); + force_settlement_id_type settle_id2 = *result2.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id2(db).balance.amount.value, 34 ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); @@ -194,13 +194,13 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) const operation_result result4 = force_settle(rachel_id(db), bitusd_id(db).amount(434)); const operation_result result5 = force_settle(rachel_id(db), bitusd_id(db).amount(5)); - force_settlement_id_type settle_id3 = result3.get(); + force_settlement_id_type settle_id3 = *result3.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id3(db).balance.amount.value, 3 ); - force_settlement_id_type settle_id4 = result4.get(); + force_settlement_id_type settle_id4 = *result4.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 434 ); - force_settlement_id_type settle_id5 = result5.get(); + force_settlement_id_type settle_id5 = *result5.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); @@ -386,13 +386,13 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) const operation_result result7 = force_settle(ted_id(db), bitusd_id(db).amount(21)); const operation_result result8 = force_settle(ted_id(db), bitusd_id(db).amount(22)); - force_settlement_id_type settle_id6 = result6.get(); + force_settlement_id_type settle_id6 = *result6.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); - force_settlement_id_type settle_id7 = result7.get(); + force_settlement_id_type settle_id7 = *result7.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); - force_settlement_id_type settle_id8 = result8.get(); + force_settlement_id_type settle_id8 = *result8.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); @@ -403,13 +403,16 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) const operation_result result102 = force_settle(joe_id(db), bitcny_id(db).amount(1000)); const operation_result result103 = force_settle(joe_id(db), bitcny_id(db).amount(300)); - force_settlement_id_type settle_id101 = result101.get(); + force_settlement_id_type settle_id101 = *result101.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id101(db).balance.amount.value, 100 ); - force_settlement_id_type settle_id102 = result102.get(); + force_settlement_id_type settle_id102 = *result102.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 1000 ); - force_settlement_id_type settle_id103 = result103.get(); + force_settlement_id_type settle_id103 = *result103.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0); @@ -700,7 +703,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) // add settle order and check rounding issue const operation_result result = force_settle(rachel, bitusd.amount(4)); - force_settlement_id_type settle_id = result.get(); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 4 ); BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); @@ -750,7 +753,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) set_expiration( db, trx ); const operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34)); - force_settlement_id_type settle_id2 = result2.get(); + force_settlement_id_type settle_id2 = *result2.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id2(db).balance.amount.value, 34 ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); @@ -805,13 +808,13 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) const operation_result result4 = force_settle(rachel_id(db), bitusd_id(db).amount(434)); const operation_result result5 = force_settle(rachel_id(db), bitusd_id(db).amount(5)); - force_settlement_id_type settle_id3 = result3.get(); + force_settlement_id_type settle_id3 = *result3.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id3(db).balance.amount.value, 3 ); - force_settlement_id_type settle_id4 = result4.get(); + force_settlement_id_type settle_id4 = *result4.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 434 ); - force_settlement_id_type settle_id5 = result5.get(); + force_settlement_id_type settle_id5 = *result5.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); @@ -999,13 +1002,13 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) const operation_result result7 = force_settle(ted_id(db), bitusd_id(db).amount(21)); const operation_result result8 = force_settle(ted_id(db), bitusd_id(db).amount(22)); - force_settlement_id_type settle_id6 = result6.get(); + force_settlement_id_type settle_id6 = *result6.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); - force_settlement_id_type settle_id7 = result7.get(); + force_settlement_id_type settle_id7 = *result7.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); - force_settlement_id_type settle_id8 = result8.get(); + force_settlement_id_type settle_id8 = *result8.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); @@ -1016,13 +1019,16 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) const operation_result result102 = force_settle(joe_id(db), bitcny_id(db).amount(1000)); const operation_result result103 = force_settle(joe_id(db), bitcny_id(db).amount(300)); - force_settlement_id_type settle_id101 = result101.get(); + force_settlement_id_type settle_id101 = *result101.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id101(db).balance.amount.value, 100 ); - force_settlement_id_type settle_id102 = result102.get(); + force_settlement_id_type settle_id102 = *result102.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 1000 ); - force_settlement_id_type settle_id103 = result103.get(); + force_settlement_id_type settle_id103 = *result103.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0); From 32276523e0782d26108e2b3534fdb6c0575cdb8c Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 15 Aug 2021 22:48:37 +0000 Subject: [PATCH 08/99] Implement BDSM::individual_settlement_to_fund --- libraries/chain/asset_evaluator.cpp | 14 +- libraries/chain/db_market.cpp | 120 +++++++++++++----- libraries/chain/db_update.cpp | 19 ++- .../chain/include/graphene/chain/database.hpp | 15 ++- 4 files changed, 131 insertions(+), 37 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index d6fbcb800d..2656959678 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -999,19 +999,22 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle FC_ASSERT( asset_to_settle->is_market_issued(), "Can only globally settle market-issued assets" ); FC_ASSERT( asset_to_settle->can_global_settle(), "The global_settle permission of this asset is disabled" ); FC_ASSERT( asset_to_settle->issuer == op.issuer, "Only asset issuer can globally settle an asset" ); - FC_ASSERT( asset_to_settle->dynamic_data(d).current_supply > 0, "Can not globally settle an asset with zero supply" ); + FC_ASSERT( asset_to_settle->dynamic_data(d).current_supply > 0, + "Can not globally settle an asset with zero supply" ); const asset_bitasset_data_object& _bitasset_data = asset_to_settle->bitasset_data(d); // if there is a settlement for this asset, then no further global settle may be taken FC_ASSERT( !_bitasset_data.has_settlement(), "This asset has settlement, cannot global settle again" ); + // FIXME due to individual_settlement_to_order, there can be no debt position const auto& idx = d.get_index_type().indices().get(); FC_ASSERT( !idx.empty(), "Internal error: no debt position found" ); auto itr = idx.lower_bound( price::min( _bitasset_data.options.short_backing_asset, op.asset_to_settle ) ); FC_ASSERT( itr != idx.end() && itr->debt_type() == op.asset_to_settle, "Internal error: no debt position found" ); const call_order_object& least_collateralized_short = *itr; - FC_ASSERT(least_collateralized_short.get_debt() * op.settle_price <= least_collateralized_short.get_collateral(), - "Cannot force settle at supplied price: least collateralized short lacks sufficient collateral to settle."); + FC_ASSERT( least_collateralized_short.get_debt() * op.settle_price <= least_collateralized_short.get_collateral(), + "Cannot force settle at supplied price: least collateralized short lacks " + "sufficient collateral to settle." ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -1121,6 +1124,11 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( individual_settled.amount > 0 ) d.adjust_balance( op.account, individual_settled ); + // Update current_feed if needed + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + if( bitasset_options::bad_debt_settlement_type::individual_settlement_to_fund == bdsm ) + d.update_bitasset_current_feed( bitasset, true ); + result.value.paid = vector({ pays }); result.value.received = vector({ individual_settled }); result.value.fees = vector({ issuer_fees }); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 229f1fd4df..01024ed9d4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -146,25 +146,51 @@ void database::globally_settle_asset_impl( const asset_object& mia, } // call order is maker - FC_ASSERT( fill_call_order( order, pays, order_debt, fund_receives_price, true, margin_call_fee ), - "Internal error: unable to close margin call" ); + FC_ASSERT( fill_call_order( order, pays, order_debt, fund_receives_price, true, margin_call_fee, false ), + "Internal error: unable to close margin call ${o}", ("o", order) ); } - modify( bitasset, [&mia,original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ - obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; - obj.settlement_fund = collateral_gathered.amount; - }); + // TODO move individual settlement fund and order to the GS fund + // TODO update BDSM to GS - /// After all margin positions are closed, the current supply will be reported as 0, but - /// that is a lie, the supply didn't change. We need to capture the current supply before - /// filling all call orders and then restore it afterward. Then in the force settlement - /// evaluator reduce the supply - modify( mia_dyn, [original_mia_supply]( asset_dynamic_data_object& obj ){ - obj.current_supply = original_mia_supply; - }); + modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ + obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; + obj.settlement_fund = collateral_gathered.amount; + }); } FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } +void database::individually_settle_to_fund( const asset_bitasset_data_object& bitasset, + const call_order_object& order ) +{ + auto order_debt = order.get_debt(); + auto order_collateral = order.get_collateral(); + auto fund_receives_price = (~order.collateralization()) / bitasset.get_margin_call_pays_ratio(); + auto fund_receives = order_debt.multiply_and_round_up( fund_receives_price ); + if( fund_receives.amount > order.collateral ) // should not happen, just be defensive + fund_receives.amount = order.collateral; + + auto margin_call_fee = order_collateral - fund_receives; + + modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt += order.debt; + obj.individual_settlement_fund += fund_receives.amount; + }); + + // call order is maker + FC_ASSERT( fill_call_order( order, order.get_collateral(), order_debt, + fund_receives_price, true, margin_call_fee, false ), + "Internal error: unable to close margin call ${o}", ("o", order) ); + + update_bitasset_current_feed( bitasset, true ); + +} + +void database::individually_settle_to_order( const asset_object& mia, const asset_bitasset_data_object& bitasset, + const call_order_object& call_order ) +{ +} + void database::revive_bitasset( const asset_object& bitasset ) { try { FC_ASSERT( bitasset.is_market_issued() ); @@ -558,6 +584,7 @@ bool database::apply_order(const limit_order_object& new_order_object) if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls + // FIXME there can be no debt position due to individual_settlement_to_order const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) @@ -661,6 +688,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle bool finished = false; // whether the new order is gone // check if there are margin calls + // FIXME there can be no debt position due to individual_settlement_to_order const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, new_settlement.balance.asset_id ); while( !finished ) @@ -1203,7 +1231,7 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p * @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 asset& margin_call_fee ) + const price& fill_price, const bool is_maker, const asset& margin_call_fee, bool reduce_current_supply ) { try { FC_ASSERT( order.debt_type() == receives.asset_id ); FC_ASSERT( order.collateral_type() == pays.asset_id ); @@ -1238,10 +1266,13 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay }); // 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 ){ + if( reduce_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; }); + } // If the whole debt is paid, adjust borrower's collateral balance if( collateral_freed.valid() ) @@ -1382,7 +1413,13 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if ( maint_time >= HARDFORK_CORE_460_TIME && bitasset.is_prediction_market ) return false; - if( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + + // Only check for black swan here if BDSM is not individual settlement + if( bdsm_type::individual_settlement_to_fund != bdsm + && bdsm_type::individual_settlement_to_order != bdsm + && check_for_blackswan( mia, enable_black_swan, &bitasset ) ) return false; if( bitasset.is_prediction_market ) return false; @@ -1449,33 +1486,36 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option + // FIXME there can be no call order due to individual_settlement_to_order + auto has_call_order = [ before_core_hardfork_1270, + &call_collateral_itr,&call_collateral_end, + &call_price_itr,&call_price_end ]() + { + return before_core_hardfork_1270 ? ( call_price_itr != call_price_end ) + : ( call_collateral_itr != call_collateral_end ); + }; + while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators && limit_itr != limit_end - && ( ( !before_core_hardfork_1270 && call_collateral_itr != call_collateral_end ) - || ( before_core_hardfork_1270 && call_price_itr != call_price_end ) ) ) + && has_call_order() ) { bool filled_call = false; const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - if( ( !before_core_hardfork_1270 - && bitasset.current_maintenance_collateralization < call_order.collateralization() ) - || ( before_core_hardfork_1270 - && after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) ) + bool feed_protected = before_core_hardfork_1270 ? + ( after_hardfork_436 + && bitasset.current_feed.settlement_price > ~call_order.call_price ) + : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); + if( feed_protected ) return margin_called; const limit_order_object& limit_order = *limit_itr; price match_price = limit_order.sell_price; // There was a check `match_price.validate();` here, which is removed now because it always passes - price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); - // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the - // excess being kept by the asset issuer as a margin call fee. In what follows, we use - // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, - // of course, to determine what the limit order receives. Note margin_call_pays_ratio() returns - // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit. // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606 if( before_core_hardfork_606 && match_price > ~call_order.call_price ) @@ -1483,6 +1523,13 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa margin_called = true; + price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); + // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the + // excess being kept by the asset issuer as a margin call fee. In what follows, we use + // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, + // of course, to determine what the limit order receives. Note margin_call_pays_ratio() returns + // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit. + // Although we checked for black swan above, we do one more check to ensure the call order can // pay the amount of collateral which we intend to take from it (including margin call fee). // TODO refactor code for better performance and readability, perhaps extract the new logic to a new @@ -1625,6 +1672,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } // while call_itr != call_end + // Check margin calls against force settlements if( after_core_hardfork_2481 && !bitasset.has_settlement() ) { @@ -1633,7 +1681,18 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( called_some ) margin_called = true; // At last, check for blackswan // TODO perhaps improve performance by passing in iterators - check_for_blackswan( mia, enable_black_swan, &bitasset ); + if( bdsm_type::individual_settlement_to_fund == bdsm + || bdsm_type::individual_settlement_to_order == bdsm ) + { + // Run multiple times, each time one call order gets settled + // TODO perhaps improve performance by settling multiple call orders inside in one call + while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) + { + // do nothing + } + } + else + check_for_blackswan( mia, enable_black_swan, &bitasset ); } return margin_called; @@ -1652,6 +1711,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); auto settle_end = settlement_index.upper_bound( bitasset.asset_id ); + // FIXME there can be no debt position due to individual_settlement_to_order const auto& call_collateral_index = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id ); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 68be70bd6a..47261962fe 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -212,6 +212,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); + // FIXME there can be no debt position due to individual_settlement_to_order if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price { const auto& call_price_index = get_index_type().indices().get(); @@ -295,8 +296,22 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s edump((enable_black_swan)); FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - if( after_core_hardfork_2481 ) + + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + if( bdsm_type::individual_settlement_to_fund == bdsm ) + { + individually_settle_to_fund( bitasset, *call_ptr ); + } + else if( bdsm_type::individual_settlement_to_order == bdsm ) + { + individually_settle_to_order( mia, bitasset, *call_ptr ); + } + // Global settlement or no settlement, but we should not be here if BDSM is no_settlement + else if( after_core_hardfork_2481 ) { + if( bdsm_type::no_settlement == bdsm ) // this should not happen, be defensive here + wlog( "Internal error: BDSM is no_settlement but undercollateralization occurred" ); // After hf_2481, when a global settlement occurs, // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and // they are closed at the same price, @@ -341,6 +356,7 @@ static optional get_derived_current_feed_price( const database& db, const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( bdsm_type::no_settlement == bdsm ) { + // FIXME there can be no debt position due to individual_settlement_to_order const auto& call_collateral_index = db.get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_itr = call_collateral_index.lower_bound( call_min ); @@ -576,6 +592,7 @@ void database::clear_expired_force_settlements() else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset settlement_price = settlement_fill_price; + // FIXME there can be no debt position due to individual_settlement_to_order auto& call_index = get_index_type().indices().get(); asset settled = mia_object.amount(mia.force_settled_volume); // Match against the least collateralized short until the settlement is finished or we reach max settlements diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 6bfa9afc9c..6e72a87a19 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -395,6 +395,10 @@ namespace graphene { namespace chain { const price& settle_price, const IndexType& call_index, bool check_margin_calls = false ); + void individually_settle_to_fund( const asset_bitasset_data_object& bitasset, + const call_order_object& call_order ); + void individually_settle_to_order( const asset_object& mia, const asset_bitasset_data_object& bitasset, + const call_order_object& call_order ); /// Match force settlements with margin calls /// @param bitasset the asset that to be checked /// @return true if matched at least one margin call order @@ -556,16 +560,21 @@ namespace graphene { namespace chain { * @param fill_price the price the transaction executed at * @param is_maker TRUE if the buyer is the maker, FALSE if the buyer is the taker * @param margin_fee Margin call fees paid in collateral asset + * @param reduce_current_supply Whether to reduce current supply of the asset. Usually it is true. + * When globally settleing or individually settling it is false. * @returns TRUE if the order was completely filled */ bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker, const asset& margin_fee ); + const price& fill_price, const bool is_maker, const asset& margin_fee, + bool reduce_current_supply = true ); /// Overload provides compatible default value for margin_fee: (margin_fee.asset_id == pays.asset_id) bool 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, + bool reduce_current_supply = true ) { - return fill_call_order( order, pays, receives, fill_price, is_maker, asset(0, pays.asset_id) ); + return fill_call_order( order, pays, receives, fill_price, is_maker, asset(0, pays.asset_id), + reduce_current_supply ); } bool fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives, From 57b837a4404d89f01a7416fdf19f88aa18608b43 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 16 Aug 2021 20:12:21 +0000 Subject: [PATCH 09/99] Implement BDSM::individual_settlement_to_order --- libraries/chain/db_getter.cpp | 10 + libraries/chain/db_market.cpp | 201 ++++++++++++++---- libraries/chain/db_update.cpp | 8 +- .../chain/include/graphene/chain/database.hpp | 20 +- .../include/graphene/chain/market_object.hpp | 8 + 5 files changed, 196 insertions(+), 51 deletions(-) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 98414cb8a8..584c0ecfc9 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace graphene { namespace chain { @@ -146,4 +147,13 @@ const witness_schedule_object& database::get_witness_schedule_object()const return *_p_witness_schedule_obj; } +const limit_order_object* database::find_bad_debt_settlement_order( const asset_id_type& a )const +{ + const auto& limit_index = get_index_type().indices().get(); + auto itr = limit_index.lower_bound( std::make_tuple( true, a ) ); + if( itr != limit_index.end() && itr->receive_asset_id() == a ) + return &(*itr); + return nullptr; +} + } } diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 01024ed9d4..a5b924695b 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -150,19 +150,36 @@ void database::globally_settle_asset_impl( const asset_object& mia, "Internal error: unable to close margin call ${o}", ("o", order) ); } - // TODO move individual settlement fund and order to the GS fund - // TODO update BDSM to GS + // Move the (individual) bad-debt settlement order to the GS fund + const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); + if( limit_ptr != nullptr ) + { + collateral_gathered.amount += limit_ptr->for_sale; + remove( *limit_ptr ); + } + + // Move individual settlement fund to the GS fund + collateral_gathered.amount += bitasset.individual_settlement_fund; modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ + obj.options.extensions.value.bad_debt_settlement_method.reset(); // Update BDSM to GS + obj.individual_settlement_debt = 0; + obj.individual_settlement_fund = 0; obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; obj.settlement_fund = collateral_gathered.amount; }); } FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } -void database::individually_settle_to_fund( const asset_bitasset_data_object& bitasset, - const call_order_object& order ) +void database::individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& order ) { + FC_ASSERT( bitasset.asset_id == order.debt_type(), "Internal error: asset type mismatch" ); + + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + FC_ASSERT( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm, + "Internal error: Invalid BDSM" ); + auto order_debt = order.get_debt(); auto order_collateral = order.get_collateral(); auto fund_receives_price = (~order.collateralization()) / bitasset.get_margin_call_pays_ratio(); @@ -172,23 +189,45 @@ void database::individually_settle_to_fund( const asset_bitasset_data_object& bi auto margin_call_fee = order_collateral - fund_receives; - modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){ - obj.individual_settlement_debt += order.debt; - obj.individual_settlement_fund += fund_receives.amount; - }); + if( bdsm_type::individual_settlement_to_fund == bdsm ) // 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 + { + const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); + if( limit_ptr != nullptr ) + { + modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { + obj.for_sale += fund_receives.amount; + obj.sell_price.base.amount = obj.for_sale; + obj.sell_price.quote.amount += order.debt; + } ); + } + else + { + create< limit_order_object >( [&order,&fund_receives]( limit_order_object& obj ) { + obj.expiration = time_point_sec::maximum(); + obj.seller = GRAPHENE_NULL_ACCOUNT; + obj.for_sale = fund_receives.amount; + obj.sell_price = fund_receives / order.get_debt(); + obj.is_settled_debt = true; + } ); + } + } // call order is maker FC_ASSERT( fill_call_order( order, order.get_collateral(), order_debt, fund_receives_price, true, margin_call_fee, false ), "Internal error: unable to close margin call ${o}", ("o", order) ); - update_bitasset_current_feed( bitasset, true ); - -} + // Update current feed if needed + if( bdsm_type::individual_settlement_to_fund == bdsm ) + update_bitasset_current_feed( bitasset, true ); -void database::individually_settle_to_order( const asset_object& mia, const asset_bitasset_data_object& bitasset, - const call_order_object& call_order ) -{ } void database::revive_bitasset( const asset_object& bitasset ) @@ -740,77 +779,157 @@ static database::match_result_type get_match_result( bool taker_filled, bool mak * 2 - maker was filled * 3 - both were filled */ -database::match_result_type database::match( const limit_order_object& usd, const limit_order_object& core, +database::match_result_type database::match( const limit_order_object& taker, const limit_order_object& maker, const price& match_price ) { - FC_ASSERT( usd.sell_price.quote.asset_id == core.sell_price.base.asset_id ); - FC_ASSERT( usd.sell_price.base.asset_id == core.sell_price.quote.asset_id ); - FC_ASSERT( usd.for_sale > 0 && core.for_sale > 0 ); + FC_ASSERT( taker.sell_price.quote.asset_id == maker.sell_price.base.asset_id ); + FC_ASSERT( taker.sell_price.base.asset_id == maker.sell_price.quote.asset_id ); + FC_ASSERT( taker.for_sale > 0 && maker.for_sale > 0 ); - auto usd_for_sale = usd.amount_for_sale(); - auto core_for_sale = core.amount_for_sale(); + return maker.is_settled_debt ? match_limit_settled_debt( taker, maker, match_price ) + : match_limit_normal_limit( taker, maker, match_price ); +} - asset usd_pays, usd_receives, core_pays, core_receives; +database::match_result_type database::match_limit_normal_limit( const limit_order_object& taker, + const limit_order_object& maker, const price& match_price ) +{ + FC_ASSERT( !maker.is_settled_debt, "Internal error: maker is settled debt" ); + + auto taker_for_sale = taker.amount_for_sale(); + auto maker_for_sale = maker.amount_for_sale(); + + asset taker_pays; + asset taker_receives; + asset maker_pays; + asset maker_receives; auto maint_time = get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding bool cull_taker = false; - if( usd_for_sale <= core_for_sale * match_price ) // rounding down here should be fine + if( taker_for_sale <= maker_for_sale * match_price ) // rounding down here should be fine { - usd_receives = usd_for_sale * match_price; // round down, in favor of bigger order + taker_receives = taker_for_sale * match_price; // round down, in favor of bigger 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 - if( usd_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) + if( taker_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) return match_result_type::only_taker_filled; if( before_core_hardfork_342 ) - core_receives = usd_for_sale; + maker_receives = taker_for_sale; else { - // The remaining amount in order `usd` would be too small, + // The remaining amount in order `taker` would be too small, // 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. - core_receives = usd_receives.multiply_and_round_up( match_price ); + maker_receives = taker_receives.multiply_and_round_up( match_price ); cull_taker = true; } } else { - //This line once read: assert( core_for_sale < usd_for_sale * match_price ); + //This line once read: assert( maker_for_sale < taker_for_sale * match_price ); //This assert is not always true -- see trade_amount_equals_zero in operation_tests.cpp - //Although usd_for_sale is greater than core_for_sale * match_price, core_for_sale == usd_for_sale * match_price + //Although taker_for_sale is greater than maker_for_sale * match_price, + // maker_for_sale == taker_for_sale * match_price //Removing the assert seems to be safe -- apparently no asset is created or destroyed. // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. - core_receives = core_for_sale * match_price; // round down, in favor of bigger order + maker_receives = maker_for_sale * match_price; // round down, in favor of bigger order if( before_core_hardfork_342 ) - usd_receives = core_for_sale; + taker_receives = maker_for_sale; else - // The remaining amount in order `core` would be too small, + // The remaining amount in order `maker` would be too small, // so the order will be culled in fill_limit_order() below - usd_receives = core_receives.multiply_and_round_up( match_price ); + taker_receives = maker_receives.multiply_and_round_up( match_price ); } - core_pays = usd_receives; - usd_pays = core_receives; + maker_pays = taker_receives; + taker_pays = maker_receives; if( before_core_hardfork_342 ) - FC_ASSERT( usd_pays == usd.amount_for_sale() || - core_pays == core.amount_for_sale() ); + FC_ASSERT( taker_pays == taker.amount_for_sale() || + maker_pays == maker.amount_for_sale() ); // the first param of match() is taker - bool taker_filled = fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ); + bool taker_filled = fill_limit_order( taker, taker_pays, taker_receives, cull_taker, match_price, false ); // the second param of match() is maker - bool maker_filled = fill_limit_order( core, core_pays, core_receives, true, match_price, true ); + bool maker_filled = fill_limit_order( maker, maker_pays, maker_receives, true, match_price, true ); match_result_type result = get_match_result( taker_filled, maker_filled ); FC_ASSERT( result != match_result_type::none_filled ); return result; } +// When matching a limit order against settled debt, the maker actually behaviors like a call order +database::match_result_type database::match_limit_settled_debt( const limit_order_object& taker, + const limit_order_object& maker, const price& match_price ) +{ + FC_ASSERT( maker.is_settled_debt, "Internal error: maker is not settled debt" ); + + bool cull_taker = false; + bool maker_filled = false; + + auto usd_for_sale = taker.amount_for_sale(); + auto usd_to_buy = maker.sell_price.quote; + + 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 + + // 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 + if( order_receives.amount == 0 ) + return match_result_type::only_taker_filled; + + // The remaining amount in the limit order could be too small, + // so we should cull the order in fill_limit_order() below. + // If the order would receive 0 even at `match_price`, 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 ); + cull_taker = true; + } + else + { // fill maker "call order" + call_receives = usd_to_buy; + order_receives = maker.amount_for_sale(); + maker_filled = true; + } + + // seller, pays, receives, ... + bool taker_filled = fill_limit_order( taker, call_receives, order_receives, cull_taker, match_price, false ); + + // Reduce current supply + const asset_dynamic_data_object& mia_ddo = call_receives.asset_id(*this).dynamic_asset_data_id(*this); + modify( mia_ddo, [&call_receives]( asset_dynamic_data_object& ao ){ + ao.current_supply -= call_receives.amount; + }); + + // Push fill_order vitual operation + // id, seller, pays, receives, ... + push_applied_operation( fill_order_operation( maker.id, maker.seller, order_receives, call_receives, + asset(0, call_receives.asset_id), match_price, true ) ); + + // Update the maker order + if( maker_filled ) + remove( maker ); + else + { + modify( maker, [&order_receives,&call_receives]( limit_order_object& obj ) { + obj.for_sale -= order_receives.amount; + obj.sell_price.base.amount = obj.for_sale; + obj.sell_price.quote.amount -= call_receives.amount; + }); + } + + match_result_type result = get_match_result( taker_filled, maker_filled ); + return result; +} + database::match_result_type database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, @@ -839,9 +958,9 @@ database::match_result_type database::match( const limit_order_object& bid, cons if( order_receives.amount == 0 ) return match_result_type::only_taker_filled; - // The remaining amount in the limit order would be too small, + // The remaining amount in the limit order could be too small, // 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, + // If the order would receive 0 even at `match_price`, 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 ); cull_taker = true; diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 47261962fe..cb0de76f32 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -299,13 +299,9 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s using bdsm_type = bitasset_options::bad_debt_settlement_type; const auto bdsm = bitasset.get_bad_debt_settlement_method(); - if( bdsm_type::individual_settlement_to_fund == bdsm ) + if( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm ) { - individually_settle_to_fund( bitasset, *call_ptr ); - } - else if( bdsm_type::individual_settlement_to_order == bdsm ) - { - individually_settle_to_order( mia, bitasset, *call_ptr ); + individually_settle( bitasset, *call_ptr ); } // Global settlement or no settlement, but we should not be here if BDSM is no_settlement else if( after_core_hardfork_2481 ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 6e72a87a19..4cbde09366 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -296,7 +296,13 @@ namespace graphene { namespace chain { rejected_predicate_map* rejected_authorities = nullptr )const; uint32_t last_non_undoable_block_num() const; + + /// Find the limit order which is the bad-debt settlement fund of the specified asset + /// @param a ID of the asset + /// @return nullptr if not found, pointer to the limit order if found + const limit_order_object* find_bad_debt_settlement_order( const asset_id_type& a )const; //////////////////// db_init.cpp //////////////////// + ///@{ void initialize_evaluators(); /// Reset the object graph in-memory @@ -310,6 +316,7 @@ namespace graphene { namespace chain { = std::make_unique>(); } + ///@} //////////////////// db_balance.cpp //////////////////// /** @@ -395,10 +402,11 @@ namespace graphene { namespace chain { const price& settle_price, const IndexType& call_index, bool check_margin_calls = false ); - void individually_settle_to_fund( const asset_bitasset_data_object& bitasset, - const call_order_object& call_order ); - void individually_settle_to_order( const asset_object& mia, const asset_bitasset_data_object& bitasset, - const call_order_object& call_order ); + /// Individually settle the @p call_order. Called when the call order is undercollateralized. + /// See @ref protocol::bitasset_options::bad_debt_settlement_type for more info. + /// @param bitasset the bitasset object + /// @param call_order the call order + void individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& call_order ); /// Match force settlements with margin calls /// @param bitasset the asset that to be checked /// @return true if matched at least one margin call order @@ -471,6 +479,10 @@ namespace graphene { namespace chain { }; match_result_type match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price ); + match_result_type match_limit_normal_limit( const limit_order_object& taker, const limit_order_object& maker, + const price& trade_price ); + match_result_type match_limit_settled_debt( const limit_order_object& taker, const limit_order_object& maker, + const price& trade_price ); /*** * @brief Match limit order as taker to a call order as maker * @param taker the order that is removing liquidity from the book diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 62876889f6..779fca5210 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -72,6 +72,7 @@ struct by_price; struct by_expiration; struct by_account; struct by_account_price; +struct by_is_settled_debt; typedef multi_index_container< limit_order_object, indexed_by< @@ -89,6 +90,13 @@ typedef multi_index_container< >, composite_key_compare< std::greater, std::less > >, + ordered_unique< tag, + composite_key< limit_order_object, + member< limit_order_object, bool, &limit_order_object::is_settled_debt >, + const_mem_fun< limit_order_object, asset_id_type, &limit_order_object::receive_asset_id >, + member< object, object_id_type, &object::id> + > + >, // index used by APIs ordered_unique< tag, composite_key< limit_order_object, From 6d73d008c37174a1fc8be806b7ac93d8ef174399 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 16 Aug 2021 22:08:44 +0000 Subject: [PATCH 10/99] Fix scenarios that no debt position exists --- libraries/chain/asset_evaluator.cpp | 18 ++++++++++-------- libraries/chain/db_market.cpp | 9 +++++---- libraries/chain/db_update.cpp | 22 ++++++++++++++-------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 2656959678..0f2a7c0283 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1004,18 +1004,20 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle const asset_bitasset_data_object& _bitasset_data = asset_to_settle->bitasset_data(d); // if there is a settlement for this asset, then no further global settle may be taken - FC_ASSERT( !_bitasset_data.has_settlement(), "This asset has settlement, cannot global settle again" ); + FC_ASSERT( !_bitasset_data.has_settlement(), + "This asset has been globally settled, cannot globally settle again" ); - // FIXME due to individual_settlement_to_order, there can be no debt position + // Note: there can be no debt position due to individual settlements, processed below const auto& idx = d.get_index_type().indices().get(); - FC_ASSERT( !idx.empty(), "Internal error: no debt position found" ); auto itr = idx.lower_bound( price::min( _bitasset_data.options.short_backing_asset, op.asset_to_settle ) ); - FC_ASSERT( itr != idx.end() && itr->debt_type() == op.asset_to_settle, "Internal error: no debt position found" ); - const call_order_object& least_collateralized_short = *itr; - FC_ASSERT( least_collateralized_short.get_debt() * op.settle_price <= least_collateralized_short.get_collateral(), - "Cannot force settle at supplied price: least collateralized short lacks " + if( itr != idx.end() && itr->debt_type() == op.asset_to_settle ) + { + const call_order_object& least_collateralized_short = *itr; + FC_ASSERT( ( least_collateralized_short.get_debt() * op.settle_price ) + <= least_collateralized_short.get_collateral(), + "Cannot globally settle at supplied price: least collateralized short lacks " "sufficient collateral to settle." ); - + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index a5b924695b..e87e300195 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -68,6 +68,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett } else { + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements globally_settle_asset_impl( mia, settlement_price, get_index_type().indices().get(), check_margin_calls ); @@ -623,7 +624,7 @@ bool database::apply_order(const limit_order_object& new_order_object) if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) @@ -727,7 +728,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle bool finished = false; // whether the new order is gone // check if there are margin calls - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, new_settlement.balance.asset_id ); while( !finished ) @@ -1569,6 +1570,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements const auto& call_collateral_index = call_index.indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); @@ -1605,7 +1607,6 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option - // FIXME there can be no call order due to individual_settlement_to_order auto has_call_order = [ before_core_hardfork_1270, &call_collateral_itr,&call_collateral_end, &call_price_itr,&call_price_end ]() @@ -1830,7 +1831,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); auto settle_end = settlement_index.upper_bound( bitasset.asset_id ); - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id ); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index cb0de76f32..7014b6325d 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -212,7 +212,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); - // FIXME there can be no debt position due to individual_settlement_to_order if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price { const auto& call_price_index = get_index_type().indices().get(); @@ -223,6 +222,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s } else // after core-1270 hard fork, check with collateralization { + // Note: it is safe to check here even if there is no call order due to individual bad debt settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_itr = call_collateral_index.lower_bound( call_min ); if( call_itr == call_collateral_index.end() ) // no call order @@ -352,7 +352,7 @@ static optional get_derived_current_feed_price( const database& db, const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( bdsm_type::no_settlement == bdsm ) { - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: it is safe to check here even if there is no call order due to individual bad debt settlements const auto& call_collateral_index = db.get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_itr = call_collateral_index.lower_bound( call_min ); @@ -549,7 +549,8 @@ void database::clear_expired_force_settlements() continue; } if( max_settlement_volume.asset_id != current_asset ) - max_settlement_volume = mia_object.amount(mia.max_force_settlement_volume(mia_object.dynamic_data(*this).current_supply)); + max_settlement_volume = mia_object.amount( mia.max_force_settlement_volume( + mia_object.dynamic_data(*this).current_supply ) ); // When current_asset_finished is true, this would be the 2nd time processing the same order. // In this case, we move to the next asset. if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) @@ -588,16 +589,21 @@ void database::clear_expired_force_settlements() else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset settlement_price = settlement_fill_price; - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: there can be no debt position due to individual settlements, processed below auto& call_index = get_index_type().indices().get(); asset settled = mia_object.amount(mia.force_settled_volume); // Match against the least collateralized short until the settlement is finished or we reach max settlements while( settled < max_settlement_volume && find_object(order_id) ) { - auto itr = call_index.lower_bound(boost::make_tuple(price::min(mia_object.bitasset_data(*this).options.short_backing_asset, - mia_object.get_id()))); - // There should always be a call order, since asset exists! - assert(itr != call_index.end() && itr->debt_type() == mia_object.get_id()); + auto itr = call_index.lower_bound( price::min( mia.options.short_backing_asset, mia.asset_id ) ); + // Note: there can be no debt position due to individual settlements + if( itr == call_index.end() || itr->debt_type() != mia.asset_id ) // no debt position + { + wlog( "No debt position found when processing force settlement ${o}", ("o",order) ); + cancel_settle_order( order ); + break; + } + asset max_settlement = max_settlement_volume - settled; if( order.balance.amount == 0 ) From 3834127a744c062f682c5018341798ddc6f747cb Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 16 Aug 2021 23:21:01 +0000 Subject: [PATCH 11/99] Implement BDSM update --- libraries/chain/asset_evaluator.cpp | 54 ++++++++++++++----- .../graphene/chain/asset_evaluator.hpp | 3 ++ .../include/graphene/chain/asset_object.hpp | 5 +- .../include/graphene/protocol/asset_ops.hpp | 8 +++ 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 0f2a7c0283..646dff27fc 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -640,7 +640,8 @@ void check_children_of_bitasset(const database& d, const asset_update_bitasset_o { const auto& child = bitasset_data.asset_id(d); FC_ASSERT( child.get_id() != op.new_options.short_backing_asset, - "A BitAsset would be invalidated by changing this backing asset ('A' backed by 'B' backed by 'A')." ); + "A BitAsset would be invalidated by changing this backing asset " + "('A' backed by 'B' backed by 'A')." ); FC_ASSERT( child.issuer != GRAPHENE_COMMITTEE_ACCOUNT, "A blockchain-controlled market asset would be invalidated by changing this backing asset." ); @@ -702,10 +703,32 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita || ( old_mssr.valid() && *old_mssr != *new_mssr ) ); FC_ASSERT( !mssr_changed, "No permission to update MSSR" ); } + // check if BDSM will change + const auto old_bdsm = current_bitasset_data.get_bad_debt_settlement_method(); + const auto new_bdsm = op.new_options.get_bad_debt_settlement_method(); + if( old_bdsm != new_bdsm ) + { + FC_ASSERT( asset_obj.can_owner_update_bdsm(), "No permission to update BDSM" ); + FC_ASSERT( !current_bitasset_data.has_settlement(), + "Unable to update BDSM when the asset has been globally settled" ); + + // Note: it is probably OK to allow BDSM update, be conservative here so far + using bdsm_type = bitasset_options::bad_debt_settlement_type; + if( bdsm_type::individual_settlement_to_fund == old_bdsm ) + FC_ASSERT( !current_bitasset_data.has_individual_settlement(), + "Unable to update BDSM when the individual bad debt settlement pool is not empty" ); + else if( bdsm_type::individual_settlement_to_order == old_bdsm ) + FC_ASSERT( nullptr == d.find_bad_debt_settlement_order( op.asset_to_update ), + "Unable to update BDSM when there exists a bad debt settlement order" ); + + // Since we do not allow updating in some cases (above), only check no_settlement here + if( bdsm_type::no_settlement == old_bdsm || bdsm_type::no_settlement == new_bdsm ) + update_feeds_due_to_bdsm_change = true; + } + // 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 ); + bool after_hf_core_922_931 = ( next_maint_time > HARDFORK_CORE_922_931_TIME ); // Are we changing the backing asset? if( op.new_options.short_backing_asset != current_bitasset_data.options.short_backing_asset ) @@ -766,7 +789,8 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita if ( new_backing_asset.is_market_issued() ) { asset_id_type backing_backing_asset_id = new_backing_asset.bitasset_data(d).options.short_backing_asset; - FC_ASSERT( (backing_backing_asset_id == asset_id_type() || !backing_backing_asset_id(d).is_market_issued()), + FC_ASSERT( (backing_backing_asset_id == asset_id_type() + || !backing_backing_asset_id(d).is_market_issued()), "A BitAsset cannot be backed by a BitAsset that itself is backed by a BitAsset."); } } @@ -805,7 +829,8 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita */ static bool update_bitasset_object_options( const asset_update_bitasset_operation& op, database& db, - asset_bitasset_data_object& bdo, const asset_object& asset_to_update ) + asset_bitasset_data_object& bdo, const asset_object& asset_to_update, + bool update_feeds_due_to_bdsm_change ) { const fc::time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; bool after_hf_core_868_890 = ( next_maint_time > HARDFORK_CORE_868_890_TIME ); @@ -892,10 +917,13 @@ static bool update_bitasset_object_options( } bool feed_actually_changed = false; - if( should_update_feeds ) + if( should_update_feeds || update_feeds_due_to_bdsm_change ) { const auto old_feed = bdo.current_feed; - db.update_bitasset_current_feed( bdo ); + if( should_update_feeds ) + db.update_bitasset_current_feed( bdo ); + else // to update feeds due to bdsm change + db.update_bitasset_current_feed( bdo, true ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); @@ -912,25 +940,25 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse try { auto& db_conn = db(); - const auto& asset_being_updated = (*asset_to_update); bool to_check_call_orders = false; db_conn.modify( *bitasset_to_update, - [&op, &asset_being_updated, &to_check_call_orders, &db_conn]( asset_bitasset_data_object& bdo ) + [&op, &to_check_call_orders, &db_conn, this]( asset_bitasset_data_object& bdo ) { - to_check_call_orders = update_bitasset_object_options( op, db_conn, bdo, asset_being_updated ); + to_check_call_orders = update_bitasset_object_options( op, db_conn, bdo, *asset_to_update, + update_feeds_due_to_bdsm_change ); }); if( to_check_call_orders ) // Process margin calls, allow black swan, not for a new limit order - db_conn.check_call_orders( asset_being_updated, true, false, bitasset_to_update ); + db_conn.check_call_orders( *asset_to_update, true, false, bitasset_to_update ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_evaluator::operation_type& o) +void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_operation& o) { try { database& d = db(); @@ -954,7 +982,7 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_evaluator::operation_type& o) +void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_operation& o) { try { database& d = db(); const auto head_time = d.head_block_time(); diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index d765185a42..5726dff1cd 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -101,8 +101,11 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_update_bitasset_operation& o ); void_result do_apply( const asset_update_bitasset_operation& o ); + private: const asset_bitasset_data_object* bitasset_to_update = nullptr; const asset_object* asset_to_update = nullptr; + + bool update_feeds_due_to_bdsm_change = false; }; class asset_update_feed_producers_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index b757018a85..4c7b13a161 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -332,10 +332,7 @@ namespace graphene { namespace chain { /// Get the effective bad debt settlement method of this bitasset bitasset_options::bad_debt_settlement_type get_bad_debt_settlement_method() const { - using bdsm_type = bitasset_options::bad_debt_settlement_type; - if( !options.extensions.value.bad_debt_settlement_method.valid() ) - return bdsm_type::global_settlement; - return static_cast( *options.extensions.value.bad_debt_settlement_method ); + return options.get_bad_debt_settlement_method(); } /// Get margin call order price (MCOP) of this bitasset diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 00611c0d88..ccde864aca 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -171,6 +171,14 @@ namespace graphene { namespace protocol { /// Perform internal consistency checks. /// @throws fc::exception if any check fails void validate()const; + + /// Get the effective bad debt settlement method + bad_debt_settlement_type get_bad_debt_settlement_method() const + { + if( !extensions.value.bad_debt_settlement_method.valid() ) + return bad_debt_settlement_type::global_settlement; + return static_cast( *extensions.value.bad_debt_settlement_method ); + } }; From 1a7721215626ee2714c7cc9f009ee73e5ca1813d Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 10:56:32 +0000 Subject: [PATCH 12/99] Fix code smells --- libraries/chain/asset_object.cpp | 4 +- libraries/chain/db_update.cpp | 2 +- .../include/graphene/chain/asset_object.hpp | 75 ++++++++++--------- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 669381f633..b896c871a3 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -136,12 +136,12 @@ void asset_bitasset_data_object::refresh_cache() { current_maintenance_collateralization = median_feed.maintenance_collateralization(); if( median_feed.initial_collateral_ratio > median_feed.maintenance_collateral_ratio ) // if ICR is above MCR - current_initial_collateralization = median_feed.calculate_initial_collateralization(); + current_initial_collateralization = median_feed.get_initial_collateralization(); else // if ICR is not above MCR current_initial_collateralization = current_maintenance_collateralization; } -price price_feed_with_icr::calculate_initial_collateralization()const +price price_feed_with_icr::get_initial_collateralization()const { if( settlement_price.is_null() ) return price(); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 7014b6325d..66e64879c0 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -661,7 +661,7 @@ void database::update_expired_feeds() const asset_bitasset_data_object& b = *itr; ++itr; // not always process begin() because old code skipped updating some assets before hf 615 // update feeds, check margin calls - if( after_hardfork_615 || b.feed_is_expired_before_hardfork_615( head_time ) ) + if( after_hardfork_615 || b.feed_is_expired_before_hf_615( head_time ) ) { auto old_median_feed = b.current_feed; const asset_object& asset_obj = b.asset_id( *this ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 4c7b13a161..3002633b1f 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -40,8 +40,6 @@ namespace graphene { namespace chain { class asset_bitasset_data_object; - class database; - using namespace graphene::db; /** * @brief tracks the asset information that changes frequently @@ -92,27 +90,28 @@ namespace graphene { namespace chain { /// @return true if this is a share asset of a liquidity pool; false otherwise. bool is_liquidity_pool_share_asset()const { return for_liquidity_pool.valid(); } /// @return true if users may request force-settlement of this market-issued asset; false otherwise - bool can_force_settle()const { return !(options.flags & disable_force_settle); } + bool can_force_settle()const { return (0 == (options.flags & disable_force_settle)); } /// @return true if the issuer of this market-issued asset may globally settle the asset; false otherwise - bool can_global_settle()const { return options.issuer_permissions & global_settle; } + bool can_global_settle()const { return (0 != (options.issuer_permissions & global_settle)); } /// @return true if this asset charges a fee for the issuer on market operations; false otherwise - bool charges_market_fees()const { return options.flags & charge_market_fee; } + bool charges_market_fees()const { return (0 != (options.flags & charge_market_fee)); } /// @return true if this asset may only be transferred to/from the issuer or market orders - bool is_transfer_restricted()const { return options.flags & transfer_restricted; } - bool can_override()const { return options.flags & override_authority; } - bool allow_confidential()const { return !(options.flags & asset_issuer_permission_flags::disable_confidential); } + bool is_transfer_restricted()const { return (0 != (options.flags & transfer_restricted)); } + bool can_override()const { return (0 != (options.flags & override_authority)); } + bool allow_confidential()const + { return (0 == (options.flags & asset_issuer_permission_flags::disable_confidential)); } /// @return true if max supply of the asset can be updated - bool can_update_max_supply()const { return !(options.flags & lock_max_supply); } + bool can_update_max_supply()const { return (0 == (options.flags & lock_max_supply)); } /// @return true if can create new supply for the asset - bool can_create_new_supply()const { return !(options.flags & disable_new_supply); } + bool can_create_new_supply()const { return (0 == (options.flags & disable_new_supply)); } /// @return true if the asset owner can update MCR directly - bool can_owner_update_mcr()const { return !(options.issuer_permissions & disable_mcr_update); } + bool can_owner_update_mcr()const { return (0 == (options.issuer_permissions & disable_mcr_update)); } /// @return true if the asset owner can update ICR directly - bool can_owner_update_icr()const { return !(options.issuer_permissions & disable_icr_update); } + bool can_owner_update_icr()const { return (0 == (options.issuer_permissions & disable_icr_update)); } /// @return true if the asset owner can update MSSR directly - bool can_owner_update_mssr()const { return !(options.issuer_permissions & disable_mssr_update); } + bool can_owner_update_mssr()const { return (0 == (options.issuer_permissions & disable_mssr_update)); } /// @return true if the asset owner can change bad debt settlement method - bool can_owner_update_bdsm()const { return !(options.issuer_permissions & disable_bdsm_update); } + bool can_owner_update_bdsm()const { return (0 == (options.issuer_permissions & disable_bdsm_update)); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } @@ -158,8 +157,9 @@ namespace graphene { namespace chain { // UIAs may not be prediction markets, have force settlement, or global settlements if( !is_market_issued() ) { - FC_ASSERT(!(options.flags & disable_force_settle || options.flags & global_settle)); - FC_ASSERT(!(options.issuer_permissions & disable_force_settle || options.issuer_permissions & global_settle)); + FC_ASSERT(0 == (options.flags & disable_force_settle) && 0 == (options.flags & global_settle)); + FC_ASSERT(0 == (options.issuer_permissions & disable_force_settle) + && 0 == (options.issuer_permissions & global_settle)); } } @@ -244,7 +244,7 @@ namespace graphene { namespace chain { /// The result will be used to check new debt positions and position updates. /// Calculation: ~settlement_price * initial_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM - price calculate_initial_collateralization()const; + price get_initial_collateralization()const; }; /** @@ -294,7 +294,7 @@ namespace graphene { namespace chain { /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply share_type max_force_settlement_volume(share_type current_supply)const; - /// return true if the bitasset has been globally settled, false otherwise + /// @return true if the bitasset has been globally settled, false otherwise bool has_settlement()const { return !settlement_price.is_null(); } /** @@ -319,7 +319,7 @@ namespace graphene { namespace chain { share_type individual_settlement_fund; ///@} - /// return true if the individual bad debt settlement pool is not empty, false otherwise + /// @return true if the individual bad debt settlement pool is not empty, false otherwise bool has_individual_settlement()const { return ( individual_settlement_debt != 0 ); } /// Get the price of the individual bad debt settlement pool @@ -353,13 +353,13 @@ namespace graphene { namespace chain { return current_feed.margin_call_pays_ratio( options.extensions.value.margin_call_fee_ratio ); } - /// Track whether core_exchange_rate in corresponding asset_object has updated + /// Track whether core_exchange_rate in corresponding @ref asset_object has updated bool asset_cer_updated = false; /// Track whether core exchange rate in current feed has updated bool feed_cer_updated = false; - /// Whether need to update core_exchange_rate in asset_object + /// Whether need to update core_exchange_rate in @ref asset_object bool need_to_update_cer() const { return ( ( feed_cer_updated || asset_cer_updated ) && !current_feed.core_exchange_rate.is_null() ); @@ -369,13 +369,16 @@ namespace graphene { namespace chain { time_point_sec feed_expiration_time()const { uint32_t current_feed_seconds = current_feed_publication_time.sec_since_epoch(); - if( std::numeric_limits::max() - current_feed_seconds <= options.feed_lifetime_sec ) + if( (std::numeric_limits::max() - current_feed_seconds) <= options.feed_lifetime_sec ) return time_point_sec::maximum(); else return current_feed_publication_time + options.feed_lifetime_sec; } - bool feed_is_expired_before_hardfork_615(time_point_sec current_time)const + /// The old and buggy implementation of @ref feed_is_expired before the No. 615 hardfork. + /// See https://github.com/cryptonomex/graphene/issues/615 + bool feed_is_expired_before_hf_615(time_point_sec current_time)const { return feed_expiration_time() >= current_time; } + /// @return whether @ref current_feed has expired bool feed_is_expired(time_point_sec current_time)const { return feed_expiration_time() <= current_time; } @@ -400,10 +403,10 @@ namespace graphene { namespace chain { void refresh_cache(); }; - // key extractor for short backing asset - struct bitasset_short_backing_asset_extractor + /// Key extractor for short backing asset + struct bitasset_backing_asst_extractor { - typedef asset_id_type result_type; + using result_type = asset_id_type; result_type operator() (const asset_bitasset_data_object& obj) const { return obj.options.short_backing_asset; @@ -414,28 +417,30 @@ namespace graphene { namespace chain { struct by_feed_expiration; struct by_cer_update; - typedef multi_index_container< + using bitasset_data_multi_index_type = multi_index_container< asset_bitasset_data_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, - ordered_non_unique< tag, bitasset_short_backing_asset_extractor >, + ordered_non_unique< tag, bitasset_backing_asst_extractor >, ordered_unique< tag, composite_key< asset_bitasset_data_object, - const_mem_fun< asset_bitasset_data_object, time_point_sec, &asset_bitasset_data_object::feed_expiration_time >, + const_mem_fun< asset_bitasset_data_object, time_point_sec, + &asset_bitasset_data_object::feed_expiration_time >, member< asset_bitasset_data_object, asset_id_type, &asset_bitasset_data_object::asset_id > > >, ordered_non_unique< tag, - const_mem_fun< asset_bitasset_data_object, bool, &asset_bitasset_data_object::need_to_update_cer > + const_mem_fun< asset_bitasset_data_object, bool, + &asset_bitasset_data_object::need_to_update_cer > > > - > asset_bitasset_data_object_multi_index_type; - typedef generic_index asset_bitasset_data_index; + >; + using asset_bitasset_data_index = generic_index< asset_bitasset_data_object, bitasset_data_multi_index_type >; struct by_symbol; struct by_type; struct by_issuer; - typedef multi_index_container< + using asset_object_multi_index_type = multi_index_container< asset_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, @@ -453,8 +458,8 @@ namespace graphene { namespace chain { > > > - > asset_object_multi_index_type; - typedef generic_index asset_index; + >; + using asset_index = generic_index< asset_object, asset_object_multi_index_type >; } } // graphene::chain From ccf9e3f8594a9661b37353a2f305106b31f0e8c3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 19:02:39 +0000 Subject: [PATCH 13/99] Fix more code smells --- libraries/chain/asset_evaluator.cpp | 8 ++- libraries/chain/asset_object.cpp | 8 +-- libraries/chain/db_market.cpp | 4 +- libraries/chain/db_update.cpp | 52 ++++++++++--------- .../graphene/chain/asset_evaluator.hpp | 3 +- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 646dff27fc..226eefb078 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -982,13 +982,11 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_operation& o) +void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_operation& o) const { try { database& d = db(); - const auto head_time = d.head_block_time(); - const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; const asset_bitasset_data_object& bitasset_to_update = asset_to_update->bitasset_data(d); - d.modify( bitasset_to_update, [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { + d.modify( bitasset_to_update, [&o](asset_bitasset_data_object& a) { //This is tricky because I have a set of publishers coming in, but a map of publisher to feed is stored. //I need to update the map such that the keys match the new publishers, but not munge the old price feeds from //publishers who are being kept. @@ -1340,7 +1338,7 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope auto old_feed = bad.current_feed; // Store medians for this asset - d.modify( bad , [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { + d.modify( bad , [&o,&head_time](asset_bitasset_data_object& a) { a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed, o.extensions.value.initial_collateral_ratio ) ); }); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index b896c871a3..0d843d98f6 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -32,9 +32,9 @@ using namespace graphene::chain; share_type asset_bitasset_data_object::max_force_settlement_volume(share_type current_supply) const { - if( options.maximum_force_settlement_volume == 0 ) + if( 0 == options.maximum_force_settlement_volume ) return 0; - if( options.maximum_force_settlement_volume == GRAPHENE_100_PERCENT ) + if( GRAPHENE_100_PERCENT == options.maximum_force_settlement_volume ) return current_supply + force_settled_volume; fc::uint128_t volume = current_supply.value; @@ -76,7 +76,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin return; } - if( effective_feeds.size() == 1 ) + if( 1u == effective_feeds.size() ) { if( median_feed.core_exchange_rate != effective_feeds.front().get().core_exchange_rate ) feed_cer_updated = true; @@ -99,7 +99,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin // *** Begin Median Calculations *** price_feed_with_icr tmp_median_feed; - const auto median_itr = effective_feeds.begin() + effective_feeds.size() / 2; + const auto median_itr = effective_feeds.begin() + ( effective_feeds.size() / 2 ); #define CALCULATE_MEDIAN_VALUE(r, data, field_name) \ std::nth_element( effective_feeds.begin(), median_itr, effective_feeds.end(), \ [](const price_feed_with_icr& a, const price_feed_with_icr& b) { \ diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index e87e300195..296ae4ea3f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -808,7 +808,7 @@ database::match_result_type database::match_limit_normal_limit( const limit_orde bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding bool cull_taker = false; - if( taker_for_sale <= maker_for_sale * match_price ) // rounding down here should be fine + if( taker_for_sale <= ( maker_for_sale * match_price ) ) // rounding down here should be fine { taker_receives = taker_for_sale * match_price; // round down, in favor of bigger order @@ -831,7 +831,7 @@ database::match_result_type database::match_limit_normal_limit( const limit_orde } else { - //This line once read: assert( maker_for_sale < taker_for_sale * match_price ); + //This line once read: assert( maker_for_sale < taker_for_sale * match_price ); // check //This assert is not always true -- see trade_amount_equals_zero in operation_tests.cpp //Although taker_for_sale is greater than maker_for_sale * match_price, // maker_for_sale == taker_for_sale * match_price diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 66e64879c0..235dd2377a 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -595,9 +595,9 @@ void database::clear_expired_force_settlements() // Match against the least collateralized short until the settlement is finished or we reach max settlements while( settled < max_settlement_volume && find_object(order_id) ) { - auto itr = call_index.lower_bound( price::min( mia.options.short_backing_asset, mia.asset_id ) ); + auto call_itr = call_index.lower_bound( price::min( mia.options.short_backing_asset, mia.asset_id ) ); // Note: there can be no debt position due to individual settlements - if( itr == call_index.end() || itr->debt_type() != mia.asset_id ) // no debt position + if( call_itr == call_index.end() || call_itr->debt_type() != mia.asset_id ) // no debt position { wlog( "No debt position found when processing force settlement ${o}", ("o",order) ); cancel_settle_order( order ); @@ -613,7 +613,8 @@ void database::clear_expired_force_settlements() break; } try { - asset new_settled = match(order, *itr, settlement_price, mia, max_settlement, settlement_fill_price); + asset new_settled = match( order, *call_itr, settlement_price, mia, + max_settlement, settlement_fill_price ); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { if( find_object( order_id ) ) // the settle order hasn't been cancelled @@ -622,12 +623,12 @@ void database::clear_expired_force_settlements() } settled += new_settled; // before hard fork core-342, `new_settled > 0` is always true, we'll have: - // * call order is completely filled (thus itr will change in next loop), or + // * call order is completely filled (thus call_itr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). // // after hard fork core-342, if new_settled > 0, we'll have: - // * call order is completely filled (thus itr will change in next loop), or + // * call order is completely filled (thus call_itr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. @@ -660,32 +661,33 @@ void database::update_expired_feeds() { const asset_bitasset_data_object& b = *itr; ++itr; // not always process begin() because old code skipped updating some assets before hf 615 + // update feeds, check margin calls - if( after_hardfork_615 || b.feed_is_expired_before_hf_615( head_time ) ) + if( !( after_hardfork_615 || b.feed_is_expired_before_hf_615( head_time ) ) ) + continue; + + auto old_median_feed = b.current_feed; + const asset_object& asset_obj = b.asset_id( *this ); + update_bitasset_current_feed( b ); + if( !b.current_feed.settlement_price.is_null() + && !b.current_feed.margin_call_params_equal( old_median_feed ) ) + { + check_call_orders( asset_obj, true, false, &b, true ); + } + // update CER + if( b.need_to_update_cer() ) { - auto old_median_feed = b.current_feed; - const asset_object& asset_obj = b.asset_id( *this ); - update_bitasset_current_feed( b ); - if( !b.current_feed.settlement_price.is_null() - && !b.current_feed.margin_call_params_equal( old_median_feed ) ) + modify( b, []( asset_bitasset_data_object& abdo ) { - check_call_orders( asset_obj, true, false, &b, true ); - } - // update CER - if( b.need_to_update_cer() ) + abdo.asset_cer_updated = false; + abdo.feed_cer_updated = false; + }); + if( asset_obj.options.core_exchange_rate != b.current_feed.core_exchange_rate ) { - modify( b, []( asset_bitasset_data_object& abdo ) + modify( asset_obj, [&b]( asset_object& ao ) { - abdo.asset_cer_updated = false; - abdo.feed_cer_updated = false; + ao.options.core_exchange_rate = b.current_feed.core_exchange_rate; }); - if( asset_obj.options.core_exchange_rate != b.current_feed.core_exchange_rate ) - { - modify( asset_obj, [&b]( asset_object& ao ) - { - ao.options.core_exchange_rate = b.current_feed.core_exchange_rate; - }); - } } } } // for each asset whose feed is expired diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 5726dff1cd..84645f831f 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -114,7 +114,7 @@ namespace graphene { namespace chain { using operation_type = asset_update_feed_producers_operation; void_result do_evaluate( const operation_type& o ); - void_result do_apply( const operation_type& o ); + void_result do_apply( const operation_type& o ) const; const asset_object* asset_to_update = nullptr; }; @@ -161,6 +161,7 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_publish_feed_operation& o ); void_result do_apply( const asset_publish_feed_operation& o ); + private: const asset_object* asset_ptr = nullptr; const asset_bitasset_data_object* bitasset_ptr = nullptr; }; From 61005500bb9ca7f54d31fcd26808b4fe7b552878 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 19:58:07 +0000 Subject: [PATCH 14/99] Refactor asset_settle_evaluator::do_apply --- libraries/chain/asset_evaluator.cpp | 164 ++++++++++++---------------- 1 file changed, 71 insertions(+), 93 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 226eefb078..c00ae2500f 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1108,74 +1108,22 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) { try { database& d = db(); - const auto head_time = d.head_block_time(); + const auto& head_time = d.head_block_time(); + const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time; const auto& bitasset = *bitasset_ptr; extendable_operation_result result; - // Process individual bad debt settlement pool first - asset to_settle = op.amount; - asset individual_settled( 0, bitasset.options.short_backing_asset ); - const asset_object* backing_asset_ptr = nullptr; - const asset_dynamic_data_object* mia_dyn = nullptr; - if( bitasset.has_individual_settlement() ) - { - asset pays; - if( to_settle.amount < bitasset.individual_settlement_debt ) - { - auto settlement_price = bitasset.get_individual_settlement_price(); - individual_settled = to_settle * settlement_price; // round down, in favor of settlement fund - FC_ASSERT( individual_settled.amount > 0, "Settle amount is too small to receive anything due to rounding" ); - pays = individual_settled.multiply_and_round_up( settlement_price ); - } - else - { - pays = bitasset.individual_settlement_debt; - individual_settled = bitasset.individual_settlement_fund; - } - - to_settle -= pays; - d.adjust_balance( op.account, -pays ); - d.modify( bitasset, [&pays,&individual_settled]( asset_bitasset_data_object& obj ){ - obj.individual_settlement_debt -= pays.amount; - obj.individual_settlement_fund -= individual_settled.amount; - }); - mia_dyn = &asset_to_settle->dynamic_asset_data_id(d); - d.modify( *mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ - obj.current_supply -= pays.amount; - }); - backing_asset_ptr = &bitasset.options.short_backing_asset(d); - auto issuer_fees = d.pay_market_fees( fee_paying_account, *backing_asset_ptr, individual_settled, false ); - individual_settled -= issuer_fees; - - if( individual_settled.amount > 0 ) - d.adjust_balance( op.account, individual_settled ); - - // Update current_feed if needed - const auto bdsm = bitasset.get_bad_debt_settlement_method(); - if( bitasset_options::bad_debt_settlement_type::individual_settlement_to_fund == bdsm ) - d.update_bitasset_current_feed( bitasset, true ); - - result.value.paid = vector({ pays }); - result.value.received = vector({ individual_settled }); - result.value.fees = vector({ issuer_fees }); - - if( 0 == to_settle.amount ) - return result; - } - - // Then process global settlement fund or others - auto maint_time = d.get_dynamic_global_properties().next_maintenance_time; + // Process global settlement fund if( bitasset.has_settlement() ) { - if( !mia_dyn ) - mia_dyn = &asset_to_settle->dynamic_asset_data_id(d); + const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); - asset settled_amount = ( to_settle.amount == mia_dyn->current_supply ) + asset settled_amount = ( op.amount.amount == mia_dyn.current_supply ) ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset ) - : to_settle * bitasset.settlement_price; // round down, favors global settlement fund - if( to_settle.amount != mia_dyn->current_supply ) + : op.amount * bitasset.settlement_price; // round down, favors global settlement fund + if( op.amount.amount != mia_dyn.current_supply ) { // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund, @@ -1184,15 +1132,13 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( 0 == settled_amount.amount && !bitasset.is_prediction_market ) { - if( maint_time > HARDFORK_CORE_184_TIME && 0 == individual_settled.amount ) + if( maint_time > HARDFORK_CORE_184_TIME ) FC_THROW( "Settle amount is too small to receive anything due to rounding" ); - else if( maint_time > HARDFORK_CORE_184_TIME ) // and individual_settled.amount > 0 - return result; // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur } - asset pays = to_settle; - if( to_settle.amount != mia_dyn->current_supply + asset pays = op.amount; + if( op.amount.amount != mia_dyn.current_supply && settled_amount.amount != 0 && maint_time > HARDFORK_CORE_342_TIME ) { @@ -1215,9 +1161,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: // performance loss. Needs testing. if( head_time >= HARDFORK_CORE_1780_TIME ) { - if( !backing_asset_ptr ) - backing_asset_ptr = &bitasset.options.short_backing_asset(d); - issuer_fees = d.pay_market_fees( fee_paying_account, *backing_asset_ptr, settled_amount, false ); + issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); settled_amount -= issuer_fees; } @@ -1225,43 +1169,77 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: d.adjust_balance( op.account, settled_amount ); } - d.modify( *mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ + d.modify( mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ obj.current_supply -= pays.amount; }); - if( to_settle.amount == op.amount.amount ) // it means there was no individual settlement - { - result.value.paid = vector({ pays }); - result.value.received = vector({ settled_amount }); - result.value.fees = vector({ issuer_fees }); - } - else - { - result.value.paid->push_back( pays ); - result.value.received->push_back( settled_amount ); - result.value.fees->push_back( issuer_fees ); - } + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); + return result; } - else + + // Process individual bad debt settlement pool + asset to_settle = op.amount; + if( bitasset.has_individual_settlement() ) { - d.adjust_balance( op.account, -to_settle ); - const auto& settle = d.create( - [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { - s.owner = op.account; - s.balance = to_settle; - s.settlement_date = head_time + bitasset.options.force_settlement_delay_sec; - }); - auto id = settle.id; - if( HARDFORK_CORE_2481_PASSED( maint_time ) ) + asset pays( bitasset.individual_settlement_debt, bitasset.asset_id ); + asset settled_amount( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + if( to_settle.amount < bitasset.individual_settlement_debt ) { - d.apply_force_settlement( settle, bitasset ); + auto settlement_price = bitasset.get_individual_settlement_price(); + settled_amount = to_settle * settlement_price; // round down, in favor of settlement fund + FC_ASSERT( settled_amount.amount > 0, "Settle amount is too small to receive anything due to rounding" ); + pays = settled_amount.multiply_and_round_up( settlement_price ); } - result.value.new_objects = flat_set({ id }); + d.adjust_balance( op.account, -pays ); + d.modify( bitasset, [&pays,&settled_amount]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt -= pays.amount; + obj.individual_settlement_fund -= settled_amount.amount; + }); + d.modify( asset_to_settle->dynamic_asset_data_id(d), [&pays]( asset_dynamic_data_object& obj ){ + obj.current_supply -= pays.amount; + }); + auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); + settled_amount -= issuer_fees; - return result; + if( settled_amount.amount > 0 ) + d.adjust_balance( op.account, settled_amount ); + + // Update current_feed since fund price changed + d.update_bitasset_current_feed( bitasset, true ); + + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); + + // If the amount to settle is too small, we return + if( bitasset.has_individual_settlement() ) + return result; + + to_settle -= pays; } + + // Process the rest + d.adjust_balance( op.account, -to_settle ); + const auto& settle = d.create( + [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { + s.owner = op.account; + s.balance = to_settle; + s.settlement_date = head_time + bitasset.options.force_settlement_delay_sec; + }); + + result.value.new_objects = flat_set({ settle.id }); + + if( HARDFORK_CORE_2481_PASSED( maint_time ) ) + { + d.apply_force_settlement( settle, bitasset ); + } + + return result; + } FC_CAPTURE_AND_RETHROW( (op) ) } void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_operation& o) From 7aa02cb78f6fa6ab5eff6d3440cc41e38ee54fcd Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 20:56:20 +0000 Subject: [PATCH 15/99] Disallow BDSM on PM --- libraries/chain/asset_evaluator.cpp | 28 ++++++++++++++++---------- libraries/protocol/asset_ops.cpp | 31 ++++++++++++++++------------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index c00ae2500f..94e26d25a3 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -70,7 +70,7 @@ namespace detail { if ( !HARDFORK_BSIP_48_75_PASSED( block_time ) ) { // new issuer permissions should not be set until activation of BSIP_48_75 - FC_ASSERT( !(options.issuer_permissions & ~ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK), + FC_ASSERT( 0 == (options.issuer_permissions & (uint16_t)(~ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK)), "New asset issuer permission bits should not be set before HARDFORK_BSIP_48_75_TIME" ); // Note: no check for flags here because we didn't check in the past } @@ -463,8 +463,9 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( dyn_data.current_supply != 0 ) { // new issuer_permissions must be subset of old issuer permissions - FC_ASSERT(!(o.new_options.get_enabled_issuer_permissions_mask() & ~enabled_issuer_permissions_mask), - "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero."); + FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() + & (uint16_t)(~enabled_issuer_permissions_mask) ), + "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero."); // precision can not be changed FC_ASSERT( !o.extensions.value.new_precision.valid(), "Cannot update precision if current supply is non-zero" ); @@ -488,12 +489,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) // Note: if an invalid bit was set, it can be unset regardless of the permissions uint16_t check_bits = ( a.is_market_issued() ? VALID_FLAGS_MASK : UIA_VALID_FLAGS_MASK ); - FC_ASSERT( !((o.new_options.flags ^ a.options.flags) & check_bits & ~enabled_issuer_permissions_mask), + FC_ASSERT( 0 == ( (o.new_options.flags ^ a.options.flags) & check_bits + & (uint16_t)(~enabled_issuer_permissions_mask) ), "Flag change is forbidden by issuer permissions" ); } else { - FC_ASSERT( !((o.new_options.flags ^ a.options.flags) & ~a.options.issuer_permissions), + FC_ASSERT( 0 == ( (o.new_options.flags ^ a.options.flags) & (uint16_t)(~a.options.issuer_permissions) ), "Flag change is forbidden by issuer permissions" ); } @@ -544,7 +546,7 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) database& d = db(); // If we are now disabling force settlements, cancel all open force settlement orders - if( (o.new_options.flags & disable_force_settle) && asset_to_update->can_force_settle() ) + if( 0 != (o.new_options.flags & disable_force_settle) && asset_to_update->can_force_settle() ) { const auto& idx = d.get_index_type().indices().get(); // Funky iteration code because we're removing objects as we go. We have to re-initialize itr every loop instead @@ -675,6 +677,10 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( !current_bitasset_data.has_settlement(), "Cannot update a bitasset after a global settlement has executed" ); + if( current_bitasset_data.is_prediction_market ) + FC_ASSERT( !op.new_options.extensions.value.bad_debt_settlement_method.valid(), + "Can not set bad_debt_settlement_method for Prediction Markets" ); + // TODO simplify code below when made sure operator==(optional,optional) works if( !asset_obj.can_owner_update_mcr() ) { @@ -855,7 +861,7 @@ static bool update_bitasset_object_options( { backing_asset_changed = true; should_update_feeds = true; - if( asset_to_update.options.flags & ( witness_fed_asset | committee_fed_asset ) ) + if( 0 != ( asset_to_update.options.flags & ( witness_fed_asset | committee_fed_asset ) ) ) is_witness_or_committee_fed = true; } @@ -968,8 +974,8 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat const asset_object& a = o.asset_to_update(d); FC_ASSERT(a.is_market_issued(), "Cannot update feed producers on a non-BitAsset."); - FC_ASSERT(!(a.options.flags & committee_fed_asset), "Cannot set feed producers on a committee-fed asset."); - FC_ASSERT(!(a.options.flags & witness_fed_asset), "Cannot set feed producers on a witness-fed asset."); + FC_ASSERT(0 == (a.options.flags & committee_fed_asset), "Cannot set feed producers on a committee-fed asset."); + FC_ASSERT(0 == (a.options.flags & witness_fed_asset), "Cannot set feed producers on a witness-fed asset."); FC_ASSERT( a.issuer == o.issuer, "Only asset issuer can update feed producers of an asset" ); @@ -1282,12 +1288,12 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ } //Verify that the publisher is authoritative to publish a feed - if( base.options.flags & witness_fed_asset ) + if( 0 != ( base.options.flags & witness_fed_asset ) ) { FC_ASSERT( d.get(GRAPHENE_WITNESS_ACCOUNT).active.account_auths.count(o.publisher) > 0, "Only active witnesses are allowed to publish price feeds for this asset" ); } - else if( base.options.flags & committee_fed_asset ) + else if( 0 != ( base.options.flags & committee_fed_asset ) ) { FC_ASSERT( d.get(GRAPHENE_COMMITTEE_ACCOUNT).active.account_auths.count(o.publisher) > 0, "Only active committee members are allowed to publish price feeds for this asset" ); diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 648c71e715..93c07fe685 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -112,13 +112,16 @@ void asset_create_operation::validate()const FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( is_valid_symbol(symbol) ); common_options.validate(); - if( common_options.issuer_permissions - & (disable_force_settle|global_settle|disable_mcr_update|disable_icr_update|disable_mssr_update) ) + if( 0 != ( common_options.issuer_permissions + & (disable_force_settle|global_settle + |disable_mcr_update|disable_icr_update|disable_mssr_update|disable_bdsm_update) ) ) FC_ASSERT( bitasset_opts.valid() ); if( is_prediction_market ) { FC_ASSERT( bitasset_opts.valid(), "Cannot have a User-Issued Asset implement a prediction market." ); - FC_ASSERT( common_options.issuer_permissions & global_settle ); + FC_ASSERT( 0 != (common_options.issuer_permissions & global_settle) ); + FC_ASSERT( !bitasset_opts->extensions.value.bad_debt_settlement_method.valid(), + "Can not set bad_debt_settlement_method for Prediction Markets" ); } if( bitasset_opts ) bitasset_opts->validate(); @@ -279,9 +282,9 @@ void asset_options::validate()const FC_ASSERT( max_market_fee >= 0 && max_market_fee <= GRAPHENE_MAX_SHARE_SUPPLY ); // There must be no high bits in permissions whose meaning is not known. - FC_ASSERT( !(issuer_permissions & ~ASSET_ISSUER_PERMISSION_MASK) ); + FC_ASSERT( 0 == (issuer_permissions & (uint16_t)(~ASSET_ISSUER_PERMISSION_MASK)) ); // The permission-only bits can not be set in flag - FC_ASSERT( !(flags & global_settle), + FC_ASSERT( 0 == (flags & global_settle), "Can not set global_settle flag, it is for issuer permission only" ); // the witness_fed and committee_fed flags cannot be set simultaneously @@ -291,7 +294,7 @@ void asset_options::validate()const core_exchange_rate.quote.asset_id.instance.value == 0 ); if(!whitelist_authorities.empty() || !blacklist_authorities.empty()) - FC_ASSERT( flags & white_list ); + FC_ASSERT( 0 != (flags & white_list) ); for( auto item : whitelist_markets ) { FC_ASSERT( blacklist_markets.find(item) == blacklist_markets.end() ); @@ -306,20 +309,20 @@ void asset_options::validate()const void asset_options::validate_flags( bool is_market_issued )const { - FC_ASSERT( !(flags & ~ASSET_ISSUER_PERMISSION_MASK), + FC_ASSERT( 0 == (flags & (uint16_t)(~ASSET_ISSUER_PERMISSION_MASK)), "Can not set an unknown bit in flags" ); // Note: global_settle is checked in validate(), so do not check again here - FC_ASSERT( !(flags & disable_mcr_update), + FC_ASSERT( 0 == (flags & disable_mcr_update), "Can not set disable_mcr_update flag, it is for issuer permission only" ); - FC_ASSERT( !(flags & disable_icr_update), + FC_ASSERT( 0 == (flags & disable_icr_update), "Can not set disable_icr_update flag, it is for issuer permission only" ); - FC_ASSERT( !(flags & disable_mssr_update), - "Can not set disable_mssr_update flag, it is for issuer permission only" ); - FC_ASSERT( !(flags & disable_bdsm_update), + FC_ASSERT( 0 == (flags & disable_mssr_update), "Can not set disable_mssr_update flag, it is for issuer permission only" ); + FC_ASSERT( 0 == (flags & disable_bdsm_update), + "Can not set disable_bdsm_update flag, it is for issuer permission only" ); if( !is_market_issued ) { - FC_ASSERT( !(flags & ~UIA_ASSET_ISSUER_PERMISSION_MASK), + FC_ASSERT( 0 == (flags & (uint16_t)(~UIA_ASSET_ISSUER_PERMISSION_MASK)), "Can not set a flag for bitassets only to UIA" ); } } @@ -327,7 +330,7 @@ void asset_options::validate_flags( bool is_market_issued )const uint16_t asset_options::get_enabled_issuer_permissions_mask() const { return ( (issuer_permissions & ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK) - | (~issuer_permissions & ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK) ); + | ((uint16_t)(~issuer_permissions) & ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK) ); } void asset_claim_fees_operation::validate()const { From e998ddd5b2fe143aa2663377eeb2b5f5bec6340a Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 21:59:37 +0000 Subject: [PATCH 16/99] Refactor asset_settle_evaluator::do_apply --- libraries/chain/asset_evaluator.cpp | 193 +++++++++++++++------------- 1 file changed, 107 insertions(+), 86 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 94e26d25a3..31b90747ed 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1111,124 +1111,145 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) -{ try { - database& d = db(); +static extendable_operation_result pay_settle_from_gs_fund( database& d, + const asset_settle_evaluator::operation_type& op, + const account_object* fee_paying_account, + const asset_object& asset_to_settle, + const asset_bitasset_data_object& bitasset ) +{ const auto& head_time = d.head_block_time(); const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time; - const auto& bitasset = *bitasset_ptr; + const auto& mia_dyn = asset_to_settle.dynamic_asset_data_id(d); - extendable_operation_result result; + asset settled_amount = ( op.amount.amount == mia_dyn.current_supply ) + ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset ) + : op.amount * bitasset.settlement_price; // round down, favors global settlement fund + if( op.amount.amount != mia_dyn.current_supply ) + { + // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero + FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund, + "Internal error: amount in the global settlement fund is not sufficient to pay the settlement" ); + } - // Process global settlement fund - if( bitasset.has_settlement() ) + if( 0 == settled_amount.amount && !bitasset.is_prediction_market && maint_time > HARDFORK_CORE_184_TIME ) + FC_THROW( "Settle amount is too small to receive anything due to rounding" ); + // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur + + asset pays = op.amount; + if( op.amount.amount != mia_dyn.current_supply + && settled_amount.amount != 0 + && maint_time > HARDFORK_CORE_342_TIME ) { - const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); + pays = settled_amount.multiply_and_round_up( bitasset.settlement_price ); + } - asset settled_amount = ( op.amount.amount == mia_dyn.current_supply ) - ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset ) - : op.amount * bitasset.settlement_price; // round down, favors global settlement fund - if( op.amount.amount != mia_dyn.current_supply ) - { - // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero - FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund, - "Internal error: amount in the global settlement fund is not sufficient to pay the settlement" ); - } + d.adjust_balance( op.account, -pays ); - if( 0 == settled_amount.amount && !bitasset.is_prediction_market ) - { - if( maint_time > HARDFORK_CORE_184_TIME ) - FC_THROW( "Settle amount is too small to receive anything due to rounding" ); - // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur - } + asset issuer_fees( 0, bitasset.options.short_backing_asset ); + if( settled_amount.amount > 0 ) + { + d.modify( bitasset, [&settled_amount]( asset_bitasset_data_object& obj ){ + obj.settlement_fund -= settled_amount.amount; + }); - asset pays = op.amount; - if( op.amount.amount != mia_dyn.current_supply - && settled_amount.amount != 0 - && maint_time > HARDFORK_CORE_342_TIME ) + // The account who settles pays market fees to the issuer of the collateral asset after HF core-1780 + // + // 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_time >= HARDFORK_CORE_1780_TIME ) { - pays = settled_amount.multiply_and_round_up( bitasset.settlement_price ); + issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); + settled_amount -= issuer_fees; } - d.adjust_balance( op.account, -pays ); - - asset issuer_fees( 0, bitasset.options.short_backing_asset ); if( settled_amount.amount > 0 ) - { - d.modify( bitasset, [&settled_amount]( asset_bitasset_data_object& obj ){ - obj.settlement_fund -= settled_amount.amount; - }); + d.adjust_balance( op.account, settled_amount ); + } - // The account who settles pays market fees to the issuer of the collateral asset after HF core-1780 - // - // 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_time >= HARDFORK_CORE_1780_TIME ) - { - issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); - settled_amount -= issuer_fees; - } + d.modify( mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ + obj.current_supply -= pays.amount; + }); - if( settled_amount.amount > 0 ) - d.adjust_balance( op.account, settled_amount ); - } + extendable_operation_result result; - d.modify( mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ - obj.current_supply -= pays.amount; - }); + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); - result.value.paid = vector({ pays }); - result.value.received = vector({ settled_amount }); - result.value.fees = vector({ issuer_fees }); + return result; +} - return result; +static extendable_operation_result pay_settle_from_individual_pool( database& d, + const asset_settle_evaluator::operation_type& op, + const account_object* fee_paying_account, + const asset_object& asset_to_settle, + const asset_bitasset_data_object& bitasset ) +{ + asset pays( bitasset.individual_settlement_debt, bitasset.asset_id ); + asset settled_amount( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + if( op.amount.amount < bitasset.individual_settlement_debt ) + { + auto settlement_price = bitasset.get_individual_settlement_price(); + settled_amount = op.amount * settlement_price; // round down, in favor of settlement fund + FC_ASSERT( settled_amount.amount > 0, "Settle amount is too small to receive anything due to rounding" ); + pays = settled_amount.multiply_and_round_up( settlement_price ); } - // Process individual bad debt settlement pool - asset to_settle = op.amount; - if( bitasset.has_individual_settlement() ) - { - asset pays( bitasset.individual_settlement_debt, bitasset.asset_id ); - asset settled_amount( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); - if( to_settle.amount < bitasset.individual_settlement_debt ) - { - auto settlement_price = bitasset.get_individual_settlement_price(); - settled_amount = to_settle * settlement_price; // round down, in favor of settlement fund - FC_ASSERT( settled_amount.amount > 0, "Settle amount is too small to receive anything due to rounding" ); - pays = settled_amount.multiply_and_round_up( settlement_price ); - } + d.adjust_balance( op.account, -pays ); + d.modify( bitasset, [&pays,&settled_amount]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt -= pays.amount; + obj.individual_settlement_fund -= settled_amount.amount; + }); + d.modify( asset_to_settle.dynamic_asset_data_id(d), [&pays]( asset_dynamic_data_object& obj ){ + obj.current_supply -= pays.amount; + }); + auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); + settled_amount -= issuer_fees; - d.adjust_balance( op.account, -pays ); - d.modify( bitasset, [&pays,&settled_amount]( asset_bitasset_data_object& obj ){ - obj.individual_settlement_debt -= pays.amount; - obj.individual_settlement_fund -= settled_amount.amount; - }); - d.modify( asset_to_settle->dynamic_asset_data_id(d), [&pays]( asset_dynamic_data_object& obj ){ - obj.current_supply -= pays.amount; - }); - auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); - settled_amount -= issuer_fees; + if( settled_amount.amount > 0 ) + d.adjust_balance( op.account, settled_amount ); - if( settled_amount.amount > 0 ) - d.adjust_balance( op.account, settled_amount ); + // Update current_feed since fund price changed + d.update_bitasset_current_feed( bitasset, true ); + + extendable_operation_result result; - // Update current_feed since fund price changed - d.update_bitasset_current_feed( bitasset, true ); + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); - result.value.paid = vector({ pays }); - result.value.received = vector({ settled_amount }); - result.value.fees = vector({ issuer_fees }); + return result; +} + +operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) +{ try { + database& d = db(); + + const auto& bitasset = *bitasset_ptr; + + // Process global settlement fund + if( bitasset.has_settlement() ) + return pay_settle_from_gs_fund( d, op, fee_paying_account, *asset_to_settle, bitasset ); + + // Process individual bad debt settlement pool + extendable_operation_result result; + asset to_settle = op.amount; + if( bitasset.has_individual_settlement() ) + { + result = pay_settle_from_individual_pool( d, op, fee_paying_account, *asset_to_settle, bitasset ); // If the amount to settle is too small, we return if( bitasset.has_individual_settlement() ) return result; - to_settle -= pays; + to_settle -= result.value.paid->front(); } // Process the rest + const auto& head_time = d.head_block_time(); + const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time; d.adjust_balance( op.account, -to_settle ); const auto& settle = d.create( [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { From 67dd8a11447b76a63b8661a73bfba9cfbdb3d492 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 22:50:50 +0000 Subject: [PATCH 17/99] Add database::find_least_collateralized_short() --- libraries/chain/db_getter.cpp | 36 ++++++++++++++ libraries/chain/db_update.cpp | 49 +++++-------------- .../chain/include/graphene/chain/database.hpp | 8 +++ 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 584c0ecfc9..987fafafb2 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -24,6 +24,8 @@ #include +#include + #include #include #include @@ -156,4 +158,38 @@ const limit_order_object* database::find_bad_debt_settlement_order( const asset_ return nullptr; } +const call_order_object* database::find_least_collateralized_short( const asset_bitasset_data_object& bitasset, + bool force_by_collateral_index )const +{ + bool find_by_collateral = true; + if( !force_by_collateral_index ) + // core-1270 hard fork : call price caching issue + find_by_collateral = ( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME ); + + const call_order_object* call_ptr = nullptr; // place holder + + auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); + + if( !find_by_collateral ) // before core-1270 hard fork, check with call_price + { + const auto& call_price_index = get_index_type().indices().get(); + auto call_itr = call_price_index.lower_bound( call_min ); + if( call_itr != call_price_index.end() ) // no call order + call_ptr = &(*call_itr); + } + else // after core-1270 hard fork, check with collateralization + { + // Note: it is safe to check here even if there is no call order due to individual bad debt settlements + const auto& call_collateral_index = get_index_type().indices().get(); + auto call_itr = call_collateral_index.lower_bound( call_min ); + if( call_itr != call_collateral_index.end() ) // no call order + call_ptr = &(*call_itr); + } + if( nullptr == call_ptr ) + return nullptr; + if( call_ptr->debt_type() != bitasset.asset_id ) // no call order + return nullptr; + return call_ptr; +} + } } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 235dd2377a..4291f0e685 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -208,28 +208,9 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s return false; } - const call_order_object* call_ptr = nullptr; // place holder for the call order with least collateral ratio - - auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); - - if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price - { - const auto& call_price_index = get_index_type().indices().get(); - auto call_itr = call_price_index.lower_bound( call_min ); - if( call_itr == call_price_index.end() ) // no call order - return false; - call_ptr = &(*call_itr); - } - else // after core-1270 hard fork, check with collateralization - { - // Note: it is safe to check here even if there is no call order due to individual bad debt settlements - const auto& call_collateral_index = get_index_type().indices().get(); - auto call_itr = call_collateral_index.lower_bound( call_min ); - if( call_itr == call_collateral_index.end() ) // no call order - return false; - call_ptr = &(*call_itr); - } - if( call_ptr->debt_type() != debt_asset_id ) // no call order + // Find the call order with the least collateral ratio + const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false ); + if( nullptr == call_ptr ) // no call order return false; price highest = settle_price; @@ -352,14 +333,12 @@ static optional get_derived_current_feed_price( const database& db, const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( bdsm_type::no_settlement == bdsm ) { - // Note: it is safe to check here even if there is no call order due to individual bad debt settlements - const auto& call_collateral_index = db.get_index_type().indices().get(); - auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); - auto call_itr = call_collateral_index.lower_bound( call_min ); - if( call_itr != call_collateral_index.end() && call_itr->debt_type() == bitasset.asset_id ) + // Find the call order with the least collateral ratio + const call_order_object* call_ptr = db.find_least_collateralized_short( bitasset, true ); + if( nullptr != call_ptr ) { - // GS if : call_itr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() ) - auto least_collateral = call_itr->collateralization(); + // GS if : call_ptr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() ) + auto least_collateral = call_ptr->collateralization(); auto lowest_callable_feed_price = (~least_collateral) / ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, bitasset.current_feed.maximum_short_squeeze_ratio ); result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); @@ -589,15 +568,13 @@ void database::clear_expired_force_settlements() else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset settlement_price = settlement_fill_price; - // Note: there can be no debt position due to individual settlements, processed below - auto& call_index = get_index_type().indices().get(); asset settled = mia_object.amount(mia.force_settled_volume); // Match against the least collateralized short until the settlement is finished or we reach max settlements while( settled < max_settlement_volume && find_object(order_id) ) { - auto call_itr = call_index.lower_bound( price::min( mia.options.short_backing_asset, mia.asset_id ) ); + auto call_ptr = find_least_collateralized_short( mia, true ); // Note: there can be no debt position due to individual settlements - if( call_itr == call_index.end() || call_itr->debt_type() != mia.asset_id ) // no debt position + if( nullptr == call_ptr ) // no debt position { wlog( "No debt position found when processing force settlement ${o}", ("o",order) ); cancel_settle_order( order ); @@ -613,7 +590,7 @@ void database::clear_expired_force_settlements() break; } try { - asset new_settled = match( order, *call_itr, settlement_price, mia, + asset new_settled = match( order, *call_ptr, settlement_price, mia, max_settlement, settlement_fill_price ); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { @@ -623,12 +600,12 @@ void database::clear_expired_force_settlements() } settled += new_settled; // before hard fork core-342, `new_settled > 0` is always true, we'll have: - // * call order is completely filled (thus call_itr will change in next loop), or + // * call order is completely filled (thus call_ptr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). // // after hard fork core-342, if new_settled > 0, we'll have: - // * call order is completely filled (thus call_itr will change in next loop), or + // * call order is completely filled (thus call_ptr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 4cbde09366..4466de2b96 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -301,6 +301,14 @@ namespace graphene { namespace chain { /// @param a ID of the asset /// @return nullptr if not found, pointer to the limit order if found const limit_order_object* find_bad_debt_settlement_order( const asset_id_type& a )const; + + /// Find the call order with the least collateral ratio + /// @param bitasset The bitasset object + /// @param force_by_collateral_index Whether to forcefully search via the by_collateral index + /// @return nullptr if not found, pointer to the call order if found + const call_order_object* find_least_collateralized_short( const asset_bitasset_data_object& bitasset, + bool force_by_collateral_index )const; + //////////////////// db_init.cpp //////////////////// ///@{ From d1b6bda1f1d67af9aad91444db69770e42ca2a77 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 23:29:55 +0000 Subject: [PATCH 18/99] Update macos-11.0 to macos-11 for Github Actions --- .github/workflows/build-and-test.mac.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.mac.yml b/.github/workflows/build-and-test.mac.yml index ed3874107a..6557de8603 100644 --- a/.github/workflows/build-and-test.mac.yml +++ b/.github/workflows/build-and-test.mac.yml @@ -8,7 +8,7 @@ jobs: name: Build and test in macOS strategy: matrix: - os: [macos-10.15, macos-11.0] + os: [macos-10.15, macos-11] runs-on: ${{ matrix.os }} steps: - name: Install dependencies From 0684f3f9b825d0c5b68344a126a6f7a541e5fb9e Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Aug 2021 19:23:49 +0000 Subject: [PATCH 19/99] Fix a code smell, rename variables for readability --- libraries/chain/db_update.cpp | 54 ++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 4291f0e685..1a9152df7a 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -210,7 +210,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // Find the call order with the least collateral ratio const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false ); - if( nullptr == call_ptr ) // no call order + if( !call_ptr ) // no call order return false; price highest = settle_price; @@ -335,7 +335,7 @@ static optional get_derived_current_feed_price( const database& db, { // Find the call order with the least collateral ratio const call_order_object* call_ptr = db.find_least_collateralized_short( bitasset, true ); - if( nullptr != call_ptr ) + if( call_ptr ) { // GS if : call_ptr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() ) auto least_collateral = call_ptr->collateralization(); @@ -478,9 +478,9 @@ void database::clear_expired_force_settlements() itr = settlement_index.lower_bound(current_asset) ) { ++count; - const force_settlement_object& order = *itr; - auto order_id = order.id; - current_asset = order.settlement_asset_id(); + const force_settlement_object& settle_order = *itr; + auto settle_order_id = settle_order.id; + current_asset = settle_order.settlement_asset_id(); const asset_object& mia_object = get(current_asset); const asset_bitasset_data_object& mia = mia_object.bitasset_data(*this); @@ -495,18 +495,18 @@ void database::clear_expired_force_settlements() if( mia.has_settlement() ) { ilog( "Canceling a force settlement because of black swan" ); - cancel_settle_order( order ); + cancel_settle_order( settle_order ); continue; } // Has this order not reached its settlement date? - if( order.settlement_date > head_time ) + if( settle_order.settlement_date > head_time ) { if( next_asset() ) { if( extra_dump ) { - ilog( "next_asset() returned true when order.settlement_date > head_block_time()" ); + ilog( "next_asset() returned true when settle_order.settlement_date > head_block_time()" ); } continue; } @@ -517,14 +517,14 @@ void database::clear_expired_force_settlements() { ilog("Canceling a force settlement in ${asset} because settlement price is null", ("asset", mia_object.symbol)); - cancel_settle_order(order); + cancel_settle_order(settle_order); continue; } if( GRAPHENE_100_PERCENT == mia.options.force_settlement_offset_percent ) // settle something for nothing { ilog( "Canceling a force settlement in ${asset} because settlement offset is 100%", ("asset", mia_object.symbol)); - cancel_settle_order(order); + cancel_settle_order(settle_order); continue; } if( max_settlement_volume.asset_id != current_asset ) @@ -557,12 +557,12 @@ void database::clear_expired_force_settlements() if( before_core_hardfork_342 ) { - auto& pays = order.balance; - auto receives = (order.balance * mia.current_feed.settlement_price); + auto& pays = settle_order.balance; + auto receives = (settle_order.balance * mia.current_feed.settlement_price); receives.amount = static_cast( fc::uint128_t(receives.amount.value) * (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / GRAPHENE_100_PERCENT ); - assert(receives <= order.balance * mia.current_feed.settlement_price); + assert(receives <= settle_order.balance * mia.current_feed.settlement_price); settlement_price = pays / receives; } else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset @@ -570,50 +570,52 @@ void database::clear_expired_force_settlements() asset settled = mia_object.amount(mia.force_settled_volume); // Match against the least collateralized short until the settlement is finished or we reach max settlements - while( settled < max_settlement_volume && find_object(order_id) ) + while( settled < max_settlement_volume && find_object(settle_order_id) ) { - auto call_ptr = find_least_collateralized_short( mia, true ); + const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); // Note: there can be no debt position due to individual settlements - if( nullptr == call_ptr ) // no debt position + if( !call_ptr ) // no debt position { - wlog( "No debt position found when processing force settlement ${o}", ("o",order) ); - cancel_settle_order( order ); + wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); + cancel_settle_order( settle_order ); break; } asset max_settlement = max_settlement_volume - settled; - if( order.balance.amount == 0 ) + if( settle_order.balance.amount == 0 ) { wlog( "0 settlement detected" ); - cancel_settle_order( order ); + cancel_settle_order( settle_order ); break; } try { - asset new_settled = match( order, *call_ptr, settlement_price, mia, + asset new_settled = match( settle_order, *call_ptr, settlement_price, mia, max_settlement, settlement_fill_price ); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { - if( find_object( order_id ) ) // the settle order hasn't been cancelled + if( find_object( settle_order_id ) ) // the settle order hasn't been cancelled current_asset_finished = true; break; } settled += new_settled; // before hard fork core-342, `new_settled > 0` is always true, we'll have: // * call order is completely filled (thus call_ptr will change in next loop), or - // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or + // * settle order is completely filled (thus find_object(settle_order_id) will be false so will + // break out), or // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). // // after hard fork core-342, if new_settled > 0, we'll have: // * call order is completely filled (thus call_ptr will change in next loop), or - // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or + // * settle order is completely filled (thus find_object(settle_order_id) will be false so will + // break out), or // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. } catch ( const black_swan_exception& e ) { wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}", - ("o", order)("e", e.to_detail_string()) ); - cancel_settle_order( order ); + ("o", settle_order)("e", e.to_detail_string()) ); + cancel_settle_order( settle_order ); break; } } From d9180f05ba6a7602491c09eee0522731d674cccf Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Aug 2021 21:43:31 +0000 Subject: [PATCH 20/99] Refactor database::clear_expired_force_settlements to reduce the number of levels of nested if, for, or while statements --- libraries/chain/db_update.cpp | 321 ++++++++++++++++------------------ 1 file changed, 152 insertions(+), 169 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 1a9152df7a..771118f127 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -176,7 +176,7 @@ void database::clear_expired_proposals() /** * let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral) - * let SP = current median feed's Settlement Price + * let SP = current median feed's Settlement Price * let LC = the least collateralized call order's swan price (debt/collateral) * * If there is no valid price feed or no bids then there is no black swan. @@ -311,7 +311,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s else globally_settle_asset(mia, ~least_collateral ); return true; - } + } return false; } @@ -429,11 +429,6 @@ void database::clear_expired_orders() void database::clear_expired_force_settlements() { try { // Process expired force settlement orders - auto head_time = head_block_time(); - auto maint_time = get_dynamic_global_properties().next_maintenance_time; - - bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing - bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding // TODO Possible performance optimization. Looping through all assets is not ideal. // - One idea is to check time first, if any expired settlement found, check asset. @@ -441,191 +436,179 @@ void database::clear_expired_force_settlements() // skip due to volume limit. // - Instead, maintain some data e.g. (whether_force_settle_volome_meets, first_settle_time) // in bitasset_data object and index by them, then we could process here faster. - auto& settlement_index = get_index_type().indices().get(); - if( !settlement_index.empty() ) - { - asset_id_type current_asset = settlement_index.begin()->settlement_asset_id(); - asset max_settlement_volume; - price settlement_fill_price; - price settlement_price; - bool current_asset_finished = false; - bool extra_dump = false; - - auto next_asset = [¤t_asset, ¤t_asset_finished, &settlement_index, &extra_dump] { - auto bound = settlement_index.upper_bound(current_asset); - if( bound == settlement_index.end() ) - { - if( extra_dump ) - { - ilog( "next_asset() returning false" ); - } - return false; - } - if( extra_dump ) - { - ilog( "next_asset returning true, bound is ${b}", ("b", *bound) ); - } - current_asset = bound->settlement_asset_id(); - current_asset_finished = false; - return true; - }; + const auto& settlement_index = get_index_type().indices().get(); + if( settlement_index.empty() ) + return; - uint32_t count = 0; + const auto& head_time = head_block_time(); + const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; - // At each iteration, we either consume the current order and remove it, or we move to the next asset - for( auto itr = settlement_index.lower_bound(current_asset); - itr != settlement_index.end(); - itr = settlement_index.lower_bound(current_asset) ) - { - ++count; - const force_settlement_object& settle_order = *itr; - auto settle_order_id = settle_order.id; - current_asset = settle_order.settlement_asset_id(); - const asset_object& mia_object = get(current_asset); - const asset_bitasset_data_object& mia = mia_object.bitasset_data(*this); + const bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing + const bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding - extra_dump = ((count >= 1000) && (count <= 1020)); + asset_id_type current_asset = settlement_index.begin()->settlement_asset_id(); + const asset_object* mia_object_ptr = &get(current_asset); + const asset_bitasset_data_object* mia_ptr = &mia_object_ptr->bitasset_data(*this); - if( extra_dump ) - { - wlog( "clear_expired_orders() dumping extra data for iteration ${c}", ("c", count) ); - ilog( "head_block_num is ${hb} current_asset is ${a}", ("hb", head_block_num())("a", current_asset) ); - } + asset max_settlement_volume; + price settlement_fill_price; + price settlement_price; + bool current_asset_finished = false; - if( mia.has_settlement() ) - { - ilog( "Canceling a force settlement because of black swan" ); - cancel_settle_order( settle_order ); - continue; - } + auto next_asset = [¤t_asset, &mia_object_ptr, &mia_ptr, ¤t_asset_finished, &settlement_index, this] { + const auto bound = settlement_index.upper_bound(current_asset); + if( bound == settlement_index.end() ) + return false; + current_asset = bound->settlement_asset_id(); + mia_object_ptr = &get(current_asset); + mia_ptr = &mia_object_ptr->bitasset_data(*this); + current_asset_finished = false; + return true; + }; + + // At each iteration, we either consume the current order and remove it, or we move to the next asset + for( auto itr = settlement_index.lower_bound(current_asset); + itr != settlement_index.end(); + itr = settlement_index.lower_bound(current_asset) ) + { + const force_settlement_object& settle_order = *itr; + auto settle_order_id = settle_order.id; - // Has this order not reached its settlement date? - if( settle_order.settlement_date > head_time ) - { - if( next_asset() ) - { - if( extra_dump ) - { - ilog( "next_asset() returned true when settle_order.settlement_date > head_block_time()" ); - } - continue; - } - break; - } - // Can we still settle in this asset? - if( mia.current_feed.settlement_price.is_null() ) - { - ilog("Canceling a force settlement in ${asset} because settlement price is null", - ("asset", mia_object.symbol)); - cancel_settle_order(settle_order); + if( current_asset != settle_order.settlement_asset_id() ) + { + current_asset = settle_order.settlement_asset_id(); + mia_object_ptr = &get(current_asset); + mia_ptr = &mia_object_ptr->bitasset_data(*this); + // Note: we did not reset current_asset_finished to false here, it is OK, + // because current_asset should not have changed if current_asset_finished is true + } + const asset_object& mia_object = *mia_object_ptr; + const asset_bitasset_data_object& mia = *mia_ptr; + + if( mia.has_settlement() ) + { + ilog( "Canceling a force settlement because of black swan" ); + cancel_settle_order( settle_order ); + continue; + } + + // Has this order not reached its settlement date? + if( settle_order.settlement_date > head_time ) + { + if( next_asset() ) continue; - } - if( GRAPHENE_100_PERCENT == mia.options.force_settlement_offset_percent ) // settle something for nothing - { - ilog( "Canceling a force settlement in ${asset} because settlement offset is 100%", - ("asset", mia_object.symbol)); - cancel_settle_order(settle_order); + break; + } + // Can we still settle in this asset? + if( mia.current_feed.settlement_price.is_null() ) + { + ilog("Canceling a force settlement in ${asset} because settlement price is null", + ("asset", mia_object.symbol)); + cancel_settle_order(settle_order); + continue; + } + if( GRAPHENE_100_PERCENT == mia.options.force_settlement_offset_percent ) // settle something for nothing + { + ilog( "Canceling a force settlement in ${asset} because settlement offset is 100%", + ("asset", mia_object.symbol)); + cancel_settle_order(settle_order); + continue; + } + // Note: although current supply would decrease during filling the settle orders, + // we always calculate with the initial value + if( max_settlement_volume.asset_id != current_asset ) + max_settlement_volume = mia_object.amount( mia.max_force_settlement_volume( + mia_object.dynamic_data(*this).current_supply ) ); + // When current_asset_finished is true, this would be the 2nd time processing the same order. + // In this case, we move to the next asset. + if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) + { + /* + ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}", + ("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null()) + ("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume)); // for debug + */ + if( next_asset() ) continue; - } - if( max_settlement_volume.asset_id != current_asset ) - max_settlement_volume = mia_object.amount( mia.max_force_settlement_volume( - mia_object.dynamic_data(*this).current_supply ) ); - // When current_asset_finished is true, this would be the 2nd time processing the same order. - // In this case, we move to the next asset. - if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) - { - /* - ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}", - ("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null()) - ("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume)); - */ - if( next_asset() ) - { - if( extra_dump ) - { - ilog( "next_asset() returned true when mia.force_settled_volume >= max_settlement_volume.amount" ); - } - continue; - } - break; - } + break; + } + + if( settlement_fill_price.base.asset_id != current_asset ) // only calculate once per asset + settlement_fill_price = mia.current_feed.settlement_price + / ratio_type( GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent, + GRAPHENE_100_PERCENT ); - if( settlement_fill_price.base.asset_id != current_asset ) // only calculate once per asset - settlement_fill_price = mia.current_feed.settlement_price - / ratio_type( GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent, - GRAPHENE_100_PERCENT ); + if( before_core_hardfork_342 ) + { + auto& pays = settle_order.balance; + auto receives = (settle_order.balance * mia.current_feed.settlement_price); + receives.amount = static_cast( ( fc::uint128_t(receives.amount.value) * + (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) ) / + GRAPHENE_100_PERCENT ); + assert(receives <= settle_order.balance * mia.current_feed.settlement_price); + settlement_price = pays / receives; + } + else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset + settlement_price = settlement_fill_price; - if( before_core_hardfork_342 ) + asset settled = mia_object.amount(mia.force_settled_volume); + // Match against the least collateralized short until the settlement is finished or we reach max settlements + while( settled < max_settlement_volume && find_object(settle_order_id) ) + { + const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); + // Note: there can be no debt position due to individual settlements + if( !call_ptr ) // no debt position { - auto& pays = settle_order.balance; - auto receives = (settle_order.balance * mia.current_feed.settlement_price); - receives.amount = static_cast( fc::uint128_t(receives.amount.value) * - (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / - GRAPHENE_100_PERCENT ); - assert(receives <= settle_order.balance * mia.current_feed.settlement_price); - settlement_price = pays / receives; + wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); + cancel_settle_order( settle_order ); + break; } - else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset - settlement_price = settlement_fill_price; - asset settled = mia_object.amount(mia.force_settled_volume); - // Match against the least collateralized short until the settlement is finished or we reach max settlements - while( settled < max_settlement_volume && find_object(settle_order_id) ) + if( 0 == settle_order.balance.amount ) { - const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); - // Note: there can be no debt position due to individual settlements - if( !call_ptr ) // no debt position - { - wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); - cancel_settle_order( settle_order ); - break; - } + wlog( "0 settlement detected" ); + cancel_settle_order( settle_order ); + break; + } + try { asset max_settlement = max_settlement_volume - settled; - if( settle_order.balance.amount == 0 ) + asset new_settled = match( settle_order, *call_ptr, settlement_price, mia, + max_settlement, settlement_fill_price ); + if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { - wlog( "0 settlement detected" ); - cancel_settle_order( settle_order ); - break; - } - try { - asset new_settled = match( settle_order, *call_ptr, settlement_price, mia, - max_settlement, settlement_fill_price ); - if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order - { - if( find_object( settle_order_id ) ) // the settle order hasn't been cancelled - current_asset_finished = true; - break; - } - settled += new_settled; - // before hard fork core-342, `new_settled > 0` is always true, we'll have: - // * call order is completely filled (thus call_ptr will change in next loop), or - // * settle order is completely filled (thus find_object(settle_order_id) will be false so will - // break out), or - // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). - // - // after hard fork core-342, if new_settled > 0, we'll have: - // * call order is completely filled (thus call_ptr will change in next loop), or - // * settle order is completely filled (thus find_object(settle_order_id) will be false so will - // break out), or - // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, - // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. - } - catch ( const black_swan_exception& e ) { - wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}", - ("o", settle_order)("e", e.to_detail_string()) ); - cancel_settle_order( settle_order ); + // current asset is finished when the settle order hasn't been cancelled + current_asset_finished = ( nullptr != find_object( settle_order_id ) ); break; } + settled += new_settled; + // before hard fork core-342, `new_settled > 0` is always true, we'll have: + // * call order is completely filled (thus call_ptr will change in next loop), or + // * settle order is completely filled (thus find_object(settle_order_id) will be false so will + // break out), or + // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). + // + // after hard fork core-342, if new_settled > 0, we'll have: + // * call order is completely filled (thus call_ptr will change in next loop), or + // * settle order is completely filled (thus find_object(settle_order_id) will be false so will + // break out), or + // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, + // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. } - if( mia.force_settled_volume != settled.amount ) - { - modify(mia, [&settled](asset_bitasset_data_object& b) { - b.force_settled_volume = settled.amount; - }); + catch ( const black_swan_exception& e ) { + wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}", + ("o", settle_order)("e", e.to_detail_string()) ); + cancel_settle_order( settle_order ); + break; } } + if( mia.force_settled_volume != settled.amount ) + { + modify(mia, [&settled](asset_bitasset_data_object& b) { + b.force_settled_volume = settled.amount; + }); + } } } FC_CAPTURE_AND_RETHROW() } From 183b80f9d33d81c9ddf695526e7e2030e1c8877d Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Aug 2021 22:32:33 +0000 Subject: [PATCH 21/99] Update coding style, fix code smells --- libraries/chain/asset_evaluator.cpp | 2 +- libraries/chain/db_getter.cpp | 2 +- libraries/chain/db_market.cpp | 29 ++++++++++++++--------------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 31b90747ed..024d78c88d 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -724,7 +724,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( !current_bitasset_data.has_individual_settlement(), "Unable to update BDSM when the individual bad debt settlement pool is not empty" ); else if( bdsm_type::individual_settlement_to_order == old_bdsm ) - FC_ASSERT( nullptr == d.find_bad_debt_settlement_order( op.asset_to_update ), + FC_ASSERT( !d.find_bad_debt_settlement_order( op.asset_to_update ), "Unable to update BDSM when there exists a bad debt settlement order" ); // Since we do not allow updating in some cases (above), only check no_settlement here diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 987fafafb2..63bd436453 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -185,7 +185,7 @@ const call_order_object* database::find_least_collateralized_short( const asset_ if( call_itr != call_collateral_index.end() ) // no call order call_ptr = &(*call_itr); } - if( nullptr == call_ptr ) + if( !call_ptr ) return nullptr; if( call_ptr->debt_type() != bitasset.asset_id ) // no call order return nullptr; diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 296ae4ea3f..d62dc871c2 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -153,7 +153,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, // Move the (individual) bad-debt settlement order to the GS fund const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); - if( limit_ptr != nullptr ) + if( limit_ptr ) { collateral_gathered.amount += limit_ptr->for_sale; remove( *limit_ptr ); @@ -200,7 +200,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, else // settle to order { const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); - if( limit_ptr != nullptr ) + if( limit_ptr ) { modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { obj.for_sale += fund_receives.amount; @@ -360,8 +360,8 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ // if there is any CORE fee to deduct, redirect it to referral program if( core_cancel_fee.amount > 0 ) { - seller_acc_stats = &order.seller( *this ).statistics( *this ); - modify( *seller_acc_stats, [&]( account_statistics_object& obj ) { + seller_acc_stats = &get_account_stats_by_owner( order.seller ); + modify( *seller_acc_stats, [&core_cancel_fee, this]( account_statistics_object& obj ) { obj.pay_fee( core_cancel_fee.amount, get_global_properties().parameters.cashback_vesting_threshold ); } ); deferred_fee -= core_cancel_fee.amount; @@ -382,7 +382,7 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ share_type cancel_fee_amount = static_cast(fee128); // cancel_fee should be positive, pay it to asset's accumulated_fees fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); - modify( *fee_asset_dyn_data, [&](asset_dynamic_data_object& addo) { + modify( *fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) { addo.accumulated_fees += cancel_fee_amount; }); // cancel_fee should be no more than deferred_paid_fee @@ -397,9 +397,9 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ auto refunded = order.amount_for_sale(); if( refunded.asset_id == asset_id_type() ) { - if( seller_acc_stats == nullptr ) - seller_acc_stats = &order.seller( *this ).statistics( *this ); - modify( *seller_acc_stats, [&]( account_statistics_object& obj ) { + if( !seller_acc_stats ) + seller_acc_stats = &get_account_stats_by_owner( order.seller ); + modify( *seller_acc_stats, [&refunded]( account_statistics_object& obj ) { obj.total_core_in_orders -= refunded.amount; }); } @@ -418,7 +418,7 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ { adjust_balance(order.seller, deferred_paid_fee); // be here, must have: fee_asset != CORE - if( fee_asset_dyn_data == nullptr ) + if( !fee_asset_dyn_data ) fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); modify( *fee_asset_dyn_data, [&](asset_dynamic_data_object& addo) { addo.fee_pool += deferred_fee; @@ -498,7 +498,7 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord check_call_orders(receive_asset); // the other side, same as above const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); - if( updated_order_object == nullptr ) + if( !updated_order_object ) return true; if( head_block_time() <= HARDFORK_555_TIME ) return false; @@ -694,7 +694,7 @@ bool database::apply_order(const limit_order_object& new_order_object) } const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); - if( updated_order_object == nullptr ) + if( !updated_order_object ) return true; // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() @@ -1106,8 +1106,7 @@ asset database::match_impl( const force_settlement_object& settle, { call_pays.amount = call.collateral; match_price = call_debt / call_collateral; - fill_price = ( margin_call_pays_ratio != nullptr ) ? ( match_price / (*margin_call_pays_ratio) ) - : match_price; + fill_price = margin_call_pays_ratio ? ( match_price / (*margin_call_pays_ratio) ) : match_price; } settle_receives = call_receives.multiply_and_round_up( fill_price ); } @@ -1175,7 +1174,7 @@ asset database::match_impl( const force_settlement_object& settle, // price changed, update call_receives // round up to mitigate rounding issues (hf core-342) call_receives = call_pays.multiply_and_round_up( match_price ); // round up // update fill price and settle_receives - if( margin_call_pays_ratio != nullptr ) // check to be defensive + if( margin_call_pays_ratio ) // check to be defensive { fill_price = match_price / (*margin_call_pays_ratio); settle_receives = call_receives * fill_price; // round down here, in favor of call order @@ -1961,7 +1960,7 @@ asset database::pay_market_fees(const account_object* seller, const asset_object asset reward = recv_asset.amount(0); auto is_rewards_allowed = [&recv_asset, seller]() { - if (seller == nullptr) + if ( !seller ) return false; const auto &white_list = recv_asset.options.extensions.value.whitelist_market_fee_sharing; return ( !white_list || (*white_list).empty() From f5c0fbfa4066c2e04b43ecf5cb570134c3107850 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Aug 2021 22:51:30 +0000 Subject: [PATCH 22/99] Remove code that was commented out --- libraries/chain/db_update.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 771118f127..023ccec9e9 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -523,11 +523,6 @@ void database::clear_expired_force_settlements() // In this case, we move to the next asset. if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) { - /* - ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}", - ("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null()) - ("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume)); // for debug - */ if( next_asset() ) continue; break; From f58a0cb0e50243091788bb037938df608a9d3b6c Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 19 Aug 2021 17:01:29 +0000 Subject: [PATCH 23/99] Add a comment --- libraries/chain/db_update.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 023ccec9e9..5a73827b57 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -436,6 +436,7 @@ void database::clear_expired_force_settlements() // skip due to volume limit. // - Instead, maintain some data e.g. (whether_force_settle_volome_meets, first_settle_time) // in bitasset_data object and index by them, then we could process here faster. + // Note: due to rounding, even when settled < max_volume, it is still possible that we have to skip const auto& settlement_index = get_index_type().indices().get(); if( settlement_index.empty() ) return; From 5dd32b25b63c53bd04ade5fbc3b39584dd570ec7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 19 Aug 2021 17:01:45 +0000 Subject: [PATCH 24/99] Add GS price tests --- tests/tests/market_tests.cpp | 101 +++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 5d227fdd13..92e73dbcff 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1448,6 +1448,107 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test_after_hf_core_2481) } FC_LOG_AND_RETHROW() } +/*** + * Tests GS price + */ +BOOST_AUTO_TEST_CASE(gs_price_test) +{ try { + // Proceeds to a desired hard fork time + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + if( hf2481 ) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 800% collateral, call price is 40/1.75 CORE/USD = 160/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(40000)); + call_order_id_type call2_id = call2.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 40000, call2.collateral.value ); + BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // No margin call at this moment + + // This order is right at of the first debt position + limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(2000), core.amount(30000))->id; + + BOOST_CHECK_EQUAL( 2000, sell_mid(db).for_sale.value ); + + BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to a value so that mssp is equal to call's collateralization + current_feed.settlement_price = bitusd.amount( 11 ) / core.amount(150); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 11/150, mssp = (11/150)*(10/11) = 1/15 + + if( !hf2481 ) + { + // GS occurs + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + // sell order did not change + BOOST_CHECK_EQUAL( 2000, sell_mid(db).for_sale.value ); + } + else + { + // GS does not occur, call got filled + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + + // sell order got half-filled + BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value ); + + // call2 did not change + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + } + + // generate a block to include operations above + BOOST_TEST_MESSAGE( "Generating a new block" ); + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(gs_price_test_after_hf2481) +{ + hf2481 = true; + INVOKE(gs_price_test); +} + /*** * Tests a scenario about rounding errors related to margin call fee */ From b1db7e6fa50f7f52eb6d70f3fa3ce3d8330dc305 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 01:57:32 +0000 Subject: [PATCH 25/99] Undo unneeded changes in bsip48_75_tests wrt bdsm --- tests/tests/bsip48_75_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 6721fd0837..14d5fe1d51 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK & ~disable_bdsm_update; + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set @@ -992,7 +992,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK & ~disable_bdsm_update; + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set From 5518907970d8fd67914bc9ca50ad147a642f5e64 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 04:01:27 +0000 Subject: [PATCH 26/99] Disallow non-UIA issuer permission bits on UIA --- libraries/chain/asset_evaluator.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 024d78c88d..9b4d53416c 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -433,6 +433,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) detail::check_asset_update_extensions_hf_bsip_48_75( now, o.extensions.value ); bool hf_bsip_48_75_passed = ( HARDFORK_BSIP_48_75_PASSED( now ) ); + bool hf_core_2467_passed = ( HARDFORK_CORE_2467_PASSED( next_maint_time ) ); const asset_object& a = o.asset_to_update(d); auto a_copy = a; @@ -446,6 +447,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } + // Unable to set non-UIA issuer permission bits on UIA + if( hf_core_2467_passed && !a.is_market_issued() ) + { + FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ), + "Unable to set non-UIA issuer permission bits on UIA" ); + } + uint16_t enabled_issuer_permissions_mask = a.options.get_enabled_issuer_permissions_mask(); if( hf_bsip_48_75_passed && a.is_market_issued() ) { @@ -463,7 +471,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( dyn_data.current_supply != 0 ) { // new issuer_permissions must be subset of old issuer permissions - FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() + if( hf_core_2467_passed && !a.is_market_issued() ) // for UIA, ignore non-UIA bits + FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask() + & (uint16_t)(~enabled_issuer_permissions_mask) ) & UIA_ASSET_ISSUER_PERMISSION_MASK ), + "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero, " + "unless to unset non-UIA issuer permission bits for UIA."); + else + FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() & (uint16_t)(~enabled_issuer_permissions_mask) ), "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero."); // precision can not be changed From 7b7eabec2780ab4d3b6397f4a1804463bcbbf38d Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 04:03:07 +0000 Subject: [PATCH 27/99] Update logic about bdsm hf time in bsip48_75_tests --- tests/tests/bsip48_75_tests.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 14d5fe1d51..ee97c25211 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -1063,7 +1063,11 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // advance to bsip48/75 hard fork generate_blocks( HARDFORK_BSIP_48_75_TIME ); if( hf2467 ) - generate_blocks( HARDFORK_CORE_2467_TIME ); + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } set_expiration( db, trx ); // take a look at flags of UIA From 23b78d35032541d3dc613a40053c539300160749 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 06:17:08 +0000 Subject: [PATCH 28/99] Disallow disable_bdsm_update permission bit on PM --- libraries/chain/asset_evaluator.cpp | 44 ++++++++++++++++------------- libraries/protocol/asset_ops.cpp | 1 + 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9b4d53416c..553f5f15c4 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -447,24 +447,29 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } - // Unable to set non-UIA issuer permission bits on UIA - if( hf_core_2467_passed && !a.is_market_issued() ) + if( a.is_market_issued() ) + bitasset_data = &a.bitasset_data(d); + + if( hf_core_2467_passed ) { - FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ), - "Unable to set non-UIA issuer permission bits on UIA" ); + // Unable to set non-UIA issuer permission bits on UIA + if( !a.is_market_issued() ) + FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ), + "Unable to set non-UIA issuer permission bits on UIA" ); + // Unable to set disable_bdsm_update issuer permission bit on PM + else if( bitasset_data->is_prediction_market ) + FC_ASSERT( 0 == ( o.new_options.issuer_permissions & disable_bdsm_update ), + "Unable to set disable_bdsm_update issuer permission bit on PM" ); + // else do nothing } uint16_t enabled_issuer_permissions_mask = a.options.get_enabled_issuer_permissions_mask(); - if( hf_bsip_48_75_passed && a.is_market_issued() ) + if( hf_bsip_48_75_passed && a.is_market_issued() && bitasset_data->is_prediction_market ) { - bitasset_data = &a.bitasset_data(d); - if( bitasset_data->is_prediction_market ) - { - // Note: if the global_settle permission was unset, it should be corrected - FC_ASSERT( a_copy.can_global_settle(), - "The global_settle permission should be enabled for prediction markets" ); - enabled_issuer_permissions_mask |= global_settle; - } + // Note: if the global_settle permission was unset, it should be corrected + FC_ASSERT( a_copy.can_global_settle(), + "The global_settle permission should be enabled for prediction markets" ); + enabled_issuer_permissions_mask |= global_settle; } const auto& dyn_data = a.dynamic_asset_data_id(d); @@ -474,8 +479,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( hf_core_2467_passed && !a.is_market_issued() ) // for UIA, ignore non-UIA bits FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask() & (uint16_t)(~enabled_issuer_permissions_mask) ) & UIA_ASSET_ISSUER_PERMISSION_MASK ), - "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero, " - "unless to unset non-UIA issuer permission bits for UIA."); + "Cannot reinstate previously revoked issuer permissions on a UIA if current supply is non-zero, " + "unless to unset non-UIA issuer permission bits."); + else if( hf_core_2467_passed && bitasset_data->is_prediction_market ) // for PM, ignore disable_bdsm_update + FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask() + & (uint16_t)(~enabled_issuer_permissions_mask) ) & (uint16_t)(~disable_bdsm_update) ), + "Cannot reinstate previously revoked issuer permissions on a PM if current supply is non-zero, " + "unless to unset the disable_bdsm_update issuer permission bit."); else FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() & (uint16_t)(~enabled_issuer_permissions_mask) ), @@ -527,11 +537,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) "Specified a new precision but it does not change" ); if( a.is_market_issued() ) - { - if( !bitasset_data ) - bitasset_data = &asset_to_update->bitasset_data(d); FC_ASSERT( !bitasset_data->is_prediction_market, "Can not update precision of a prediction market" ); - } // If any other asset is backed by this asset, this asset's precision can't be updated const auto& idx = d.get_index_type() diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 93c07fe685..a723338827 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -120,6 +120,7 @@ void asset_create_operation::validate()const { FC_ASSERT( bitasset_opts.valid(), "Cannot have a User-Issued Asset implement a prediction market." ); FC_ASSERT( 0 != (common_options.issuer_permissions & global_settle) ); + FC_ASSERT( 0 == (common_options.issuer_permissions & disable_bdsm_update) ); FC_ASSERT( !bitasset_opts->extensions.value.bad_debt_settlement_method.valid(), "Can not set bad_debt_settlement_method for Prediction Markets" ); } From 370080079e55b457fa88dad53ae98f4226a1e0a3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 06:17:43 +0000 Subject: [PATCH 29/99] Add some tests for BDSM --- tests/tests/bdsm_tests.cpp | 667 +++++++++++++++++++++++++++++++++++++ 1 file changed, 667 insertions(+) create mode 100644 tests/tests/bdsm_tests.cpp diff --git a/tests/tests/bdsm_tests.cpp b/tests/tests/bdsm_tests.cpp new file mode 100644 index 0000000000..dd7288d591 --- /dev/null +++ b/tests/tests/bdsm_tests.cpp @@ -0,0 +1,667 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( bdsm_tests, database_fixture ) + +/// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork +BOOST_AUTO_TEST_CASE( hardfork_protection_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update; + uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + + uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; + + vector ops; + + // Testing asset_create_operation + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = bitflag; + acop.common_options.issuer_permissions = old_bitmask; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 3; + + trx.operations.clear(); + trx.operations.push_back( acop ); + + { + auto& op = trx.operations.front().get(); + + // Unable to set new permission bit + op.common_options.issuer_permissions = new_bitmask; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.common_options.issuer_permissions = old_bitmask; + + // Unable to set new extensions in bitasset options + op.bitasset_opts->extensions.value.bad_debt_settlement_method = 0; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.bitasset_opts->extensions.value.bad_debt_settlement_method = {}; + + acop = op; + } + + // Able to create asset without new data + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& samcoin = db.get(ptx.operation_results[0].get()); + asset_id_type samcoin_id = samcoin.id; + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 ); + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 ); + + // Able to propose the good operation + propose( acop ); + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.market_fee_percent = 200; + + // Unable to set new permission bit + op.new_options.issuer_permissions = new_bitmask; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.issuer_permissions = old_bitmask; + + auop = op; + } + + // Able to update asset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 ); + + // Able to propose the good operation + propose( auop ); + + // Testing asset_update_bitasset_operation + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = samcoin_id; + aubop.new_options = samcoin_id(db).bitasset_data(db).options; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.minimum_feeds = 1; + + // Unable to set new extensions + op.new_options.extensions.value.bad_debt_settlement_method = 1; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.extensions.value.bad_debt_settlement_method = {}; + + aubop = op; + } + + // Able to update bitasset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 ); + + // Able to propose the good operation + propose( aubop ); + + // Unable to propose the invalid operations + for( const operation& op : ops ) + BOOST_CHECK_THROW( propose( op ), fc::exception ); + + // Check what we have now + idump( (samcoin) ); + idump( (samcoin.bitasset_data(db)) ); + + generate_block(); + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Now able to propose the operations that was invalid + for( const operation& op : ops ) + propose( op ); + + generate_block(); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests scenarios about setting non-UIA issuer permission bits on an UIA +BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update; + uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; + + uint16_t uiaflag = uiamask & ~disable_new_supply; // Allow creating new supply + + vector ops; + + asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), uiaflag ).id; + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + auop.new_options.issuer_permissions = old_bitmask & ~global_settle & ~disable_force_settle; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + // Able to update asset with non-UIA issuer permission bits + PUSH_TX(db, trx, ~0); + + // Able to propose too + propose( auop ); + + // Issue some coin + issue_uia( sam_id, asset( 1, samcoin_id ) ); + + // Unable to unset the non-UIA "disable" issuer permission bits + auto perms = samcoin_id(db).options.issuer_permissions; + + auop.new_options.issuer_permissions = perms & ~disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = perms & ~disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = perms & ~disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Still able to propose + auop.new_options.issuer_permissions = new_bitmask; + propose( auop ); + + // But no longer able to update directly + auop.new_options.issuer_permissions = uiamask | witness_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | committee_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unset the non-UIA bits in issuer permissions, should succeed + auop.new_options.issuer_permissions = uiamask; + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin_id(db).options.issuer_permissions, uiamask ); + + // Burn all supply + reserve_asset( sam_id, asset( 1, samcoin_id ) ); + + BOOST_CHECK_EQUAL( samcoin_id(db).dynamic_asset_data_id(db).current_supply.value, 0 ); + + // Still unable to set the non-UIA bits in issuer permissions + auop.new_options.issuer_permissions = uiamask | witness_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | committee_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + generate_block(); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests what kind of assets can have BDSM-related flags / issuer permissions / extensions +BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // Unable to create a PM with the disable_bdsm_update bit in flags + BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bdsm_update ), fc::exception ); + + // Unable to create a MPA with the disable_bdsm_update bit in flags + BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bdsm_update ), fc::exception ); + + // Unable to create a UIA with the disable_bdsm_update bit in flags + BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bdsm_update ), fc::exception ); + + // create a PM with a zero market_fee_percent + const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); + asset_id_type pm_id = pm.id; + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + // create a UIA with a zero market_fee_percent + const asset_object& uia = create_user_issued_asset( "TESTUIA", sam_id(db), charge_market_fee ); + asset_id_type uia_id = uia.id; + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + + // Unable to set disable_bdsm_update bit in flags for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.flags |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bdsm_update bit in flags for MPA + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + auop.new_options.flags |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bdsm_update bit in flags for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.flags |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bdsm_update bit in issuer_permissions for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.issuer_permissions |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to set disable_bdsm_update bit in issuer_permissions for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.issuer_permissions |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to create a UIA with disable_bdsm_update permission bit + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bdsm_update; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create UIA without disable_bdsm_update permission bit + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Unable to create a PM with disable_bdsm_update permission bit + acop.symbol = "SAMPM"; + acop.precision = asset_id_type()(db).precision; + acop.is_prediction_market = true; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bdsm_update; + acop.bitasset_opts = bitasset_options(); + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Unable to create a PM with BDSM in extensions + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; + acop.bitasset_opts->extensions.value.bad_debt_settlement_method = 0; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create PM with no disable_bdsm_update permission bit nor BDSM in extensions + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; + acop.bitasset_opts->extensions.value.bad_debt_settlement_method.reset(); + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Unable to update PM to set BDSM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = pm_id; + aubop.new_options = pm_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Able to propose + propose( aubop ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests whether asset owner has permission to update bdsm +BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa_id(db).can_owner_update_bdsm() ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + + // disable owner's permission to update bdsm + auop.new_options.issuer_permissions |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + + // check that owner can not update bdsm + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.bad_debt_settlement_method.reset(); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + + // enable owner's permission to update bdsm + auop.new_options.issuer_permissions &= ~disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( mpa_id(db).can_owner_update_bdsm() ); + + // check that owner can update bdsm + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method, 1u ); + + // check bdsm' valid range + aubop.new_options.extensions.value.bad_debt_settlement_method = 4; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + + // Sam borrow some + borrow( sam, asset(1000, mpa_id), asset(2000) ); + + // disable owner's permission to update bdsm + auop.new_options.issuer_permissions |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + + // check that owner can not update bdsm + aubop.new_options.extensions.value.bad_debt_settlement_method = 0; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.bad_debt_settlement_method.reset(); + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + + // able to update other params that still has permission E.G. force_settlement_delay_sec + aubop.new_options.force_settlement_delay_sec += 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, + aubop.new_options.force_settlement_delay_sec ); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method, 1u ); + + // unable to enable the permission to update bdsm + auop.new_options.issuer_permissions &= ~disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() From 7baa8f5d46843a70c006301c3894c4d2c3e202fd Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 07:18:30 +0000 Subject: [PATCH 30/99] Fix asset_settle when force settlement is disabled after paid from individual settlement fund, cancel the rest --- libraries/chain/asset_evaluator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 553f5f15c4..c10690670c 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1260,8 +1260,8 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: { result = pay_settle_from_individual_pool( d, op, fee_paying_account, *asset_to_settle, bitasset ); - // If the amount to settle is too small, we return - if( bitasset.has_individual_settlement() ) + // If the amount to settle is too small, or force settlement is disabled, we return + if( bitasset.has_individual_settlement() || !asset_to_settle->can_force_settle() ) return result; to_settle -= result.value.paid->front(); From 23902a6b8dc07d0c125aa81dc643e6b35b039108 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 17:54:03 +0000 Subject: [PATCH 31/99] Add a comment --- libraries/chain/db_update.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 5a73827b57..76115f98a9 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -260,6 +260,10 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // * if there is no force settlement, we check here with margin call fee in consideration. auto least_collateral = call_ptr->collateralization(); + // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, + // if the matching limit order is smaller, due to rounding, it is still possible that the + // call order's collateralization would increase and become higher than ~highest after matched. + // However, for simplicity, we only compare the prices here. bool gs = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); if( gs ) { From 8bbe40826f70bdbf509271de6c433df87b26a768 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 19:50:30 +0000 Subject: [PATCH 32/99] Fix uncapping of settlement price --- libraries/chain/db_market.cpp | 1 + libraries/chain/db_update.cpp | 36 ++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index d62dc871c2..f2d9eac6d4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -164,6 +164,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ obj.options.extensions.value.bad_debt_settlement_method.reset(); // Update BDSM to GS + obj.current_feed = obj.median_feed; // reset current feed price if was capped obj.individual_settlement_debt = 0; obj.individual_settlement_fund = 0; obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 76115f98a9..7d5b85c759 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -350,19 +350,17 @@ static optional get_derived_current_feed_price( const database& db, else // there is no call order of this bitasset result = bitasset.median_feed.settlement_price; } - else if( bdsm_type::individual_settlement_to_fund == bdsm ) + else if( bdsm_type::individual_settlement_to_fund == bdsm && bitasset.individual_settlement_debt > 0 ) { - if( bitasset.individual_settlement_debt <= 0 ) - result = bitasset.median_feed.settlement_price; - else - { - // Now bitasset.individual_settlement_debt > 0 - price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) - / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); - auto lowest_callable_feed_price = fund_price * bitasset.get_margin_call_order_ratio(); - result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); - } + // Check whether to cap + price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) + / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + auto lowest_callable_feed_price = fund_price * bitasset.get_margin_call_order_ratio(); + result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); } + else // should not cap + result = bitasset.median_feed.settlement_price; + // Check whether it's necessary to update if( result.valid() && (*result) == bitasset.current_feed.settlement_price ) result.reset(); @@ -378,10 +376,18 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b if( skip_median_update ) { if( bdsm_type::no_settlement != bdsm && bdsm_type::individual_settlement_to_fund != bdsm ) - return; - new_current_feed_price = get_derived_current_feed_price( *this, bitasset ); - if( !new_current_feed_price.valid() ) - return; + { + // it's possible that current_feed was capped thus we still need to update it + if( bitasset.current_feed.settlement_price == bitasset.median_feed.settlement_price ) + return; + new_current_feed_price = bitasset.median_feed.settlement_price; + } + else + { + new_current_feed_price = get_derived_current_feed_price( *this, bitasset ); + if( !new_current_feed_price.valid() ) + return; + } } // We need to update the database From 151ba844de8b4ef110844f90a3c90ce69cb05886 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 20:35:04 +0000 Subject: [PATCH 33/99] Slightly refactor clear_expired_force_settlements --- libraries/chain/db_update.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 7d5b85c759..a259d6cea7 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -561,18 +561,18 @@ void database::clear_expired_force_settlements() // Match against the least collateralized short until the settlement is finished or we reach max settlements while( settled < max_settlement_volume && find_object(settle_order_id) ) { - const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); - // Note: there can be no debt position due to individual settlements - if( !call_ptr ) // no debt position + if( 0 == settle_order.balance.amount ) { - wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); + wlog( "0 settlement detected" ); cancel_settle_order( settle_order ); break; } - if( 0 == settle_order.balance.amount ) + const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); + // Note: there can be no debt position due to individual settlements + if( !call_ptr ) // no debt position { - wlog( "0 settlement detected" ); + wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); cancel_settle_order( settle_order ); break; } From 8e9f3e4f1922fba43dbd8e6ec07fe76f242df5d1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 26 Aug 2021 07:02:40 +0000 Subject: [PATCH 34/99] Fix individual_settlement_to_fund black swan check --- libraries/chain/db_update.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index a259d6cea7..d71b18d486 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -213,13 +213,21 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( !call_ptr ) // no call order return false; + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + price highest = settle_price; - if( !before_core_hardfork_1270 ) - // due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + // * If BDSM is individual_settlement_to_fund, check with median_feed to decide whether to settle. + // * If BDSM is no_settlement, check with current_feed to NOT trigger global settlement. + // * If BDSM is global_settlement or individual_settlement_to_order, median_feed == current_feed. + if( bdsm_type::individual_settlement_to_fund == bdsm ) + highest = bitasset.median_feed.max_short_squeeze_price(); + else if( !before_core_hardfork_1270 ) 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(); + // else do nothing const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); @@ -264,8 +272,8 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // if the matching limit order is smaller, due to rounding, it is still possible that the // call order's collateralization would increase and become higher than ~highest after matched. // However, for simplicity, we only compare the prices here. - bool gs = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); - if( gs ) + bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); + if( is_blackswan ) { wdump( (*call_ptr) ); elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" @@ -282,8 +290,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm ) { individually_settle( bitasset, *call_ptr ); From d0351225eadb1195e2246adc70fefc03aeac4f69 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 26 Aug 2021 07:47:49 +0000 Subject: [PATCH 35/99] Rename BDSM to BSRM Rename "bad-debt settlement method" to "black swan response method" --- libraries/chain/asset_evaluator.cpp | 66 ++++----- libraries/chain/db_getter.cpp | 4 +- libraries/chain/db_market.cpp | 50 +++---- libraries/chain/db_update.cpp | 38 ++--- libraries/chain/hardfork.d/CORE_2467.hf | 2 +- .../graphene/chain/asset_evaluator.hpp | 2 +- .../include/graphene/chain/asset_object.hpp | 16 +- .../chain/include/graphene/chain/database.hpp | 6 +- .../include/graphene/chain/market_object.hpp | 2 +- libraries/chain/market_evaluator.cpp | 8 +- libraries/protocol/asset_ops.cpp | 20 +-- .../include/graphene/protocol/asset_ops.hpp | 22 +-- .../include/graphene/protocol/types.hpp | 10 +- tests/tests/bsip48_75_tests.cpp | 6 +- .../tests/{bdsm_tests.cpp => bsrm_tests.cpp} | 140 +++++++++--------- 15 files changed, 196 insertions(+), 196 deletions(-) rename tests/tests/{bdsm_tests.cpp => bsrm_tests.cpp} (84%) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index c10690670c..fd7e9d5f04 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -150,7 +150,7 @@ namespace detail { if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) ) { // new issuer permissions should not be set until activation of the hardfork - FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_bdsm_update), + FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_bsrm_update), "New asset issuer permission bits should not be set before Hardfork core-2467" ); } } @@ -160,8 +160,8 @@ namespace detail { // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) ) { - FC_ASSERT( !options.extensions.value.bad_debt_settlement_method.valid(), - "A BitAsset's bad debt settlement method cannot be set before Hardfork core-2467" ); + FC_ASSERT( !options.extensions.value.black_swan_response_method.valid(), + "A BitAsset's black swan response method cannot be set before Hardfork core-2467" ); } } @@ -456,10 +456,10 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( !a.is_market_issued() ) FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ), "Unable to set non-UIA issuer permission bits on UIA" ); - // Unable to set disable_bdsm_update issuer permission bit on PM + // Unable to set disable_bsrm_update issuer permission bit on PM else if( bitasset_data->is_prediction_market ) - FC_ASSERT( 0 == ( o.new_options.issuer_permissions & disable_bdsm_update ), - "Unable to set disable_bdsm_update issuer permission bit on PM" ); + FC_ASSERT( 0 == ( o.new_options.issuer_permissions & disable_bsrm_update ), + "Unable to set disable_bsrm_update issuer permission bit on PM" ); // else do nothing } @@ -481,11 +481,11 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) & (uint16_t)(~enabled_issuer_permissions_mask) ) & UIA_ASSET_ISSUER_PERMISSION_MASK ), "Cannot reinstate previously revoked issuer permissions on a UIA if current supply is non-zero, " "unless to unset non-UIA issuer permission bits."); - else if( hf_core_2467_passed && bitasset_data->is_prediction_market ) // for PM, ignore disable_bdsm_update + else if( hf_core_2467_passed && bitasset_data->is_prediction_market ) // for PM, ignore disable_bsrm_update FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask() - & (uint16_t)(~enabled_issuer_permissions_mask) ) & (uint16_t)(~disable_bdsm_update) ), + & (uint16_t)(~enabled_issuer_permissions_mask) ) & (uint16_t)(~disable_bsrm_update) ), "Cannot reinstate previously revoked issuer permissions on a PM if current supply is non-zero, " - "unless to unset the disable_bdsm_update issuer permission bit."); + "unless to unset the disable_bsrm_update issuer permission bit."); else FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() & (uint16_t)(~enabled_issuer_permissions_mask) ), @@ -698,8 +698,8 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita "Cannot update a bitasset after a global settlement has executed" ); if( current_bitasset_data.is_prediction_market ) - FC_ASSERT( !op.new_options.extensions.value.bad_debt_settlement_method.valid(), - "Can not set bad_debt_settlement_method for Prediction Markets" ); + FC_ASSERT( !op.new_options.extensions.value.black_swan_response_method.valid(), + "Can not set black_swan_response_method for Prediction Markets" ); // TODO simplify code below when made sure operator==(optional,optional) works if( !asset_obj.can_owner_update_mcr() ) @@ -729,27 +729,27 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita || ( old_mssr.valid() && *old_mssr != *new_mssr ) ); FC_ASSERT( !mssr_changed, "No permission to update MSSR" ); } - // check if BDSM will change - const auto old_bdsm = current_bitasset_data.get_bad_debt_settlement_method(); - const auto new_bdsm = op.new_options.get_bad_debt_settlement_method(); - if( old_bdsm != new_bdsm ) + // check if BSRM will change + const auto old_bsrm = current_bitasset_data.get_black_swan_response_method(); + const auto new_bsrm = op.new_options.get_black_swan_response_method(); + if( old_bsrm != new_bsrm ) { - FC_ASSERT( asset_obj.can_owner_update_bdsm(), "No permission to update BDSM" ); + FC_ASSERT( asset_obj.can_owner_update_bsrm(), "No permission to update BSRM" ); FC_ASSERT( !current_bitasset_data.has_settlement(), - "Unable to update BDSM when the asset has been globally settled" ); + "Unable to update BSRM when the asset has been globally settled" ); - // Note: it is probably OK to allow BDSM update, be conservative here so far - using bdsm_type = bitasset_options::bad_debt_settlement_type; - if( bdsm_type::individual_settlement_to_fund == old_bdsm ) + // Note: it is probably OK to allow BSRM update, be conservative here so far + using bsrm_type = bitasset_options::black_swan_response_type; + if( bsrm_type::individual_settlement_to_fund == old_bsrm ) FC_ASSERT( !current_bitasset_data.has_individual_settlement(), - "Unable to update BDSM when the individual bad debt settlement pool is not empty" ); - else if( bdsm_type::individual_settlement_to_order == old_bdsm ) - FC_ASSERT( !d.find_bad_debt_settlement_order( op.asset_to_update ), - "Unable to update BDSM when there exists a bad debt settlement order" ); + "Unable to update BSRM when the individual settlement pool is not empty" ); + else if( bsrm_type::individual_settlement_to_order == old_bsrm ) + FC_ASSERT( !d.find_individual_settlemnt_order( op.asset_to_update ), + "Unable to update BSRM when there exists an individual settlement order" ); // Since we do not allow updating in some cases (above), only check no_settlement here - if( bdsm_type::no_settlement == old_bdsm || bdsm_type::no_settlement == new_bdsm ) - update_feeds_due_to_bdsm_change = true; + if( bsrm_type::no_settlement == old_bsrm || bsrm_type::no_settlement == new_bsrm ) + update_feeds_due_to_bsrm_change = true; } @@ -856,7 +856,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita static bool update_bitasset_object_options( const asset_update_bitasset_operation& op, database& db, asset_bitasset_data_object& bdo, const asset_object& asset_to_update, - bool update_feeds_due_to_bdsm_change ) + bool update_feeds_due_to_bsrm_change ) { const fc::time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; bool after_hf_core_868_890 = ( next_maint_time > HARDFORK_CORE_868_890_TIME ); @@ -943,12 +943,12 @@ static bool update_bitasset_object_options( } bool feed_actually_changed = false; - if( should_update_feeds || update_feeds_due_to_bdsm_change ) + if( should_update_feeds || update_feeds_due_to_bsrm_change ) { const auto old_feed = bdo.current_feed; if( should_update_feeds ) db.update_bitasset_current_feed( bdo ); - else // to update feeds due to bdsm change + else // to update feeds due to bsrm change db.update_bitasset_current_feed( bdo, true ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 @@ -972,7 +972,7 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse [&op, &to_check_call_orders, &db_conn, this]( asset_bitasset_data_object& bdo ) { to_check_call_orders = update_bitasset_object_options( op, db_conn, bdo, *asset_to_update, - update_feeds_due_to_bdsm_change ); + update_feeds_due_to_bsrm_change ); }); if( to_check_call_orders ) @@ -1091,7 +1091,7 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.has_settlement() || bitasset.has_individual_settlement(), "Either the asset need to have the force_settle flag enabled, or it need to be globally settled, " - "or the individual bad debt settlement pool is not empty" ); + "or the individual settlement pool is not empty" ); if( bitasset.is_prediction_market ) { @@ -1111,7 +1111,7 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op { FC_THROW_EXCEPTION( insufficient_feeds, "Cannot force settle with no price feed if the asset is not globally settled and the " - "individual bad debt settlement pool is not empty" ); + "individual settlement pool is not empty" ); } } @@ -1253,7 +1253,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( bitasset.has_settlement() ) return pay_settle_from_gs_fund( d, op, fee_paying_account, *asset_to_settle, bitasset ); - // Process individual bad debt settlement pool + // Process individual settlement pool extendable_operation_result result; asset to_settle = op.amount; if( bitasset.has_individual_settlement() ) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 63bd436453..ed4fc84ac6 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -149,7 +149,7 @@ const witness_schedule_object& database::get_witness_schedule_object()const return *_p_witness_schedule_obj; } -const limit_order_object* database::find_bad_debt_settlement_order( const asset_id_type& a )const +const limit_order_object* database::find_individual_settlemnt_order( const asset_id_type& a )const { const auto& limit_index = get_index_type().indices().get(); auto itr = limit_index.lower_bound( std::make_tuple( true, a ) ); @@ -179,7 +179,7 @@ const call_order_object* database::find_least_collateralized_short( const asset_ } else // after core-1270 hard fork, check with collateralization { - // Note: it is safe to check here even if there is no call order due to individual bad debt settlements + // Note: it is safe to check here even if there is no call order due to individual settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_itr = call_collateral_index.lower_bound( call_min ); if( call_itr != call_collateral_index.end() ) // no call order diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f2d9eac6d4..e783e048a6 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -68,7 +68,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett } else { - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements globally_settle_asset_impl( mia, settlement_price, get_index_type().indices().get(), check_margin_calls ); @@ -151,8 +151,8 @@ void database::globally_settle_asset_impl( const asset_object& mia, "Internal error: unable to close margin call ${o}", ("o", order) ); } - // Move the (individual) bad-debt settlement order to the GS fund - const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); + // Move the individual settlement order to the GS fund + const limit_order_object* limit_ptr = find_individual_settlemnt_order( bitasset.asset_id ); if( limit_ptr ) { collateral_gathered.amount += limit_ptr->for_sale; @@ -163,7 +163,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, collateral_gathered.amount += bitasset.individual_settlement_fund; modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ - obj.options.extensions.value.bad_debt_settlement_method.reset(); // Update BDSM to GS + obj.options.extensions.value.black_swan_response_method.reset(); // Update BSRM to GS obj.current_feed = obj.median_feed; // reset current feed price if was capped obj.individual_settlement_debt = 0; obj.individual_settlement_fund = 0; @@ -177,10 +177,10 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, { FC_ASSERT( bitasset.asset_id == order.debt_type(), "Internal error: asset type mismatch" ); - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); - FC_ASSERT( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm, - "Internal error: Invalid BDSM" ); + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + FC_ASSERT( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm, + "Internal error: Invalid BSRM" ); auto order_debt = order.get_debt(); auto order_collateral = order.get_collateral(); @@ -191,7 +191,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, auto margin_call_fee = order_collateral - fund_receives; - if( bdsm_type::individual_settlement_to_fund == bdsm ) // settle to fund + 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; @@ -200,7 +200,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } else // settle to order { - const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); + const limit_order_object* limit_ptr = find_individual_settlemnt_order( bitasset.asset_id ); if( limit_ptr ) { modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { @@ -227,7 +227,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, "Internal error: unable to close margin call ${o}", ("o", order) ); // Update current feed if needed - if( bdsm_type::individual_settlement_to_fund == bdsm ) + if( bsrm_type::individual_settlement_to_fund == bsrm ) update_bitasset_current_feed( bitasset, true ); } @@ -625,7 +625,7 @@ bool database::apply_order(const limit_order_object& new_order_object) if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) @@ -729,7 +729,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle bool finished = false; // whether the new order is gone // check if there are margin calls - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, new_settlement.balance.asset_id ); while( !finished ) @@ -991,7 +991,7 @@ database::match_result_type database::match( const limit_order_object& bid, cons bool maker_filled = fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee ); // Update current_feed after filled call order if needed - if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() ) update_bitasset_current_feed( bitasset, true ); // Note: result can be none_filled when call order has target_collateral_ratio option set. @@ -1225,7 +1225,7 @@ asset database::match_impl( const force_settlement_object& settle, fill_settle_order( settle, settle_pays, settle_receives, fill_price, !settle_is_taker, !is_margin_call ); // Update current_feed after filled call order if needed - if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() ) update_bitasset_current_feed( bitasset, true ); if( cull_settle_order ) @@ -1533,12 +1533,12 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if ( maint_time >= HARDFORK_CORE_460_TIME && bitasset.is_prediction_market ) return false; - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); - // Only check for black swan here if BDSM is not individual settlement - if( bdsm_type::individual_settlement_to_fund != bdsm - && bdsm_type::individual_settlement_to_order != bdsm + // Only check for black swan here if BSRM is not individual settlement + if( bsrm_type::individual_settlement_to_fund != bsrm + && bsrm_type::individual_settlement_to_order != bsrm && check_for_blackswan( mia, enable_black_swan, &bitasset ) ) return false; @@ -1570,7 +1570,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_index = call_index.indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); @@ -1775,7 +1775,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); // Update current_feed after filled call order if needed - if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() ) update_bitasset_current_feed( bitasset, true ); if( !before_core_hardfork_1270 ) @@ -1801,8 +1801,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( called_some ) margin_called = true; // At last, check for blackswan // TODO perhaps improve performance by passing in iterators - if( bdsm_type::individual_settlement_to_fund == bdsm - || bdsm_type::individual_settlement_to_order == bdsm ) + if( bsrm_type::individual_settlement_to_fund == bsrm + || bsrm_type::individual_settlement_to_order == bsrm ) { // Run multiple times, each time one call order gets settled // TODO perhaps improve performance by settling multiple call orders inside in one call @@ -1831,7 +1831,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); auto settle_end = settlement_index.upper_bound( bitasset.asset_id ); - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id ); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index d71b18d486..d9924039f9 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -213,15 +213,15 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( !call_ptr ) // no call order return false; - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); price highest = settle_price; // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here - // * If BDSM is individual_settlement_to_fund, check with median_feed to decide whether to settle. - // * If BDSM is no_settlement, check with current_feed to NOT trigger global settlement. - // * If BDSM is global_settlement or individual_settlement_to_order, median_feed == current_feed. - if( bdsm_type::individual_settlement_to_fund == bdsm ) + // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. + // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. + // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. + if( bsrm_type::individual_settlement_to_fund == bsrm ) highest = bitasset.median_feed.max_short_squeeze_price(); else if( !before_core_hardfork_1270 ) highest = bitasset.current_feed.max_short_squeeze_price(); @@ -290,15 +290,15 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - if( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm ) + if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) { individually_settle( bitasset, *call_ptr ); } - // Global settlement or no settlement, but we should not be here if BDSM is no_settlement + // Global settlement or no settlement, but we should not be here if BSRM is no_settlement else if( after_core_hardfork_2481 ) { - if( bdsm_type::no_settlement == bdsm ) // this should not happen, be defensive here - wlog( "Internal error: BDSM is no_settlement but undercollateralization occurred" ); + if( bsrm_type::no_settlement == bsrm ) // this should not happen, be defensive here + wlog( "Internal error: BSRM is no_settlement but undercollateralization occurred" ); // After hf_2481, when a global settlement occurs, // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and // they are closed at the same price, @@ -339,9 +339,9 @@ static optional get_derived_current_feed_price( const database& db, return bitasset.median_feed.settlement_price; } - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); - if( bdsm_type::no_settlement == bdsm ) + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + if( bsrm_type::no_settlement == bsrm ) { // Find the call order with the least collateral ratio const call_order_object* call_ptr = db.find_least_collateralized_short( bitasset, true ); @@ -356,7 +356,7 @@ static optional get_derived_current_feed_price( const database& db, else // there is no call order of this bitasset result = bitasset.median_feed.settlement_price; } - else if( bdsm_type::individual_settlement_to_fund == bdsm && bitasset.individual_settlement_debt > 0 ) + else if( bsrm_type::individual_settlement_to_fund == bsrm && bitasset.individual_settlement_debt > 0 ) { // Check whether to cap price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) @@ -377,11 +377,11 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b { // For better performance, if nothing to update, we return optional new_current_feed_price; - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); if( skip_median_update ) { - if( bdsm_type::no_settlement != bdsm && bdsm_type::individual_settlement_to_fund != bdsm ) + if( bsrm_type::no_settlement != bsrm && bsrm_type::individual_settlement_to_fund != bsrm ) { // it's possible that current_feed was capped thus we still need to update it if( bitasset.current_feed.settlement_price == bitasset.median_feed.settlement_price ) @@ -397,7 +397,7 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b } // We need to update the database - modify( bitasset, [this, skip_median_update, &new_current_feed_price, &bdsm] + modify( bitasset, [this, skip_median_update, &new_current_feed_price, &bsrm] ( asset_bitasset_data_object& abdo ) { if( !skip_median_update ) @@ -406,7 +406,7 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; abdo.update_median_feeds( head_time, maint_time ); abdo.current_feed = abdo.median_feed; - if( bdsm_type::no_settlement == bdsm || bdsm_type::individual_settlement_to_fund == bdsm ) + if( bsrm_type::no_settlement == bsrm || bsrm_type::individual_settlement_to_fund == bsrm ) new_current_feed_price = get_derived_current_feed_price( *this, abdo ); } if( new_current_feed_price.valid() ) diff --git a/libraries/chain/hardfork.d/CORE_2467.hf b/libraries/chain/hardfork.d/CORE_2467.hf index 8d575bb5d0..43fc67ab14 100644 --- a/libraries/chain/hardfork.d/CORE_2467.hf +++ b/libraries/chain/hardfork.d/CORE_2467.hf @@ -1,4 +1,4 @@ -// bitshares-core issue #2467 Alternative bad debt settlement methods +// bitshares-core issue #2467 Alternative black swan response methods #ifndef HARDFORK_CORE_2467_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2467_TIME (fc::time_point_sec( 1893456000 )) diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 84645f831f..8b35585f02 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -105,7 +105,7 @@ namespace graphene { namespace chain { const asset_bitasset_data_object* bitasset_to_update = nullptr; const asset_object* asset_to_update = nullptr; - bool update_feeds_due_to_bdsm_change = false; + bool update_feeds_due_to_bsrm_change = false; }; class asset_update_feed_producers_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 3002633b1f..a9481604ea 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -110,8 +110,8 @@ namespace graphene { namespace chain { bool can_owner_update_icr()const { return (0 == (options.issuer_permissions & disable_icr_update)); } /// @return true if the asset owner can update MSSR directly bool can_owner_update_mssr()const { return (0 == (options.issuer_permissions & disable_mssr_update)); } - /// @return true if the asset owner can change bad debt settlement method - bool can_owner_update_bdsm()const { return (0 == (options.issuer_permissions & disable_bdsm_update)); } + /// @return true if the asset owner can change black swan response method + bool can_owner_update_bsrm()const { return (0 == (options.issuer_permissions & disable_bsrm_update)); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } @@ -309,7 +309,7 @@ namespace graphene { namespace chain { share_type settlement_fund; ///@} - /// The individual bad debt settlement pool. + /// 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. ///@{ @@ -319,20 +319,20 @@ namespace graphene { namespace chain { share_type individual_settlement_fund; ///@} - /// @return true if the individual bad debt settlement pool is not empty, false otherwise + /// @return true if the individual settlement pool is not empty, false otherwise bool has_individual_settlement()const { return ( individual_settlement_debt != 0 ); } - /// Get the price of the individual bad debt settlement pool + /// Get the price of the individual settlement pool price get_individual_settlement_price() const { return asset( individual_settlement_debt, asset_id ) / asset( individual_settlement_fund, options.short_backing_asset ); } - /// Get the effective bad debt settlement method of this bitasset - bitasset_options::bad_debt_settlement_type get_bad_debt_settlement_method() const + /// Get the effective black swan response method of this bitasset + bitasset_options::black_swan_response_type get_black_swan_response_method() const { - return options.get_bad_debt_settlement_method(); + return options.get_black_swan_response_method(); } /// Get margin call order price (MCOP) of this bitasset diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 4466de2b96..63fa00fb2f 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -297,10 +297,10 @@ namespace graphene { namespace chain { uint32_t last_non_undoable_block_num() const; - /// Find the limit order which is the bad-debt settlement fund of the specified asset + /// Find the limit order which is the individual settlement fund of the specified asset /// @param a ID of the asset /// @return nullptr if not found, pointer to the limit order if found - const limit_order_object* find_bad_debt_settlement_order( const asset_id_type& a )const; + const limit_order_object* find_individual_settlemnt_order( const asset_id_type& a )const; /// Find the call order with the least collateral ratio /// @param bitasset The bitasset object @@ -411,7 +411,7 @@ namespace graphene { namespace chain { const IndexType& call_index, bool check_margin_calls = false ); /// Individually settle the @p call_order. Called when the call order is undercollateralized. - /// See @ref protocol::bitasset_options::bad_debt_settlement_type for more info. + /// See @ref protocol::bitasset_options::black_swan_response_type for more info. /// @param bitasset the bitasset object /// @param call_order the call order void individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& call_order ); diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 779fca5210..3f62130a50 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -53,7 +53,7 @@ class limit_order_object : public abstract_object price sell_price; share_type deferred_fee; ///< fee converted to CORE asset deferred_paid_fee; ///< originally paid fee - bool is_settled_debt = false; ///< Whether this order is a bad-debt settlement fund + bool is_settled_debt = false; ///< Whether this order is an individual settlement fund pair get_market()const { diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index bbcd11cf87..e534af7f7a 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -305,8 +305,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope d.remove( *call_obj ); // Update current_feed if needed - const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); - if( bitasset_options::bad_debt_settlement_type::no_settlement == bdsm ) + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) d.update_bitasset_current_feed( *_bitasset_data, true ); return call_order_id; @@ -413,8 +413,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } } // Update current_feed if needed - const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); - if( bitasset_options::bad_debt_settlement_type::no_settlement == bdsm ) + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) d.update_bitasset_current_feed( *_bitasset_data, true ); } diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index a723338827..264f8f0649 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -114,15 +114,15 @@ void asset_create_operation::validate()const common_options.validate(); if( 0 != ( common_options.issuer_permissions & (disable_force_settle|global_settle - |disable_mcr_update|disable_icr_update|disable_mssr_update|disable_bdsm_update) ) ) + |disable_mcr_update|disable_icr_update|disable_mssr_update|disable_bsrm_update) ) ) FC_ASSERT( bitasset_opts.valid() ); if( is_prediction_market ) { FC_ASSERT( bitasset_opts.valid(), "Cannot have a User-Issued Asset implement a prediction market." ); FC_ASSERT( 0 != (common_options.issuer_permissions & global_settle) ); - FC_ASSERT( 0 == (common_options.issuer_permissions & disable_bdsm_update) ); - FC_ASSERT( !bitasset_opts->extensions.value.bad_debt_settlement_method.valid(), - "Can not set bad_debt_settlement_method for Prediction Markets" ); + FC_ASSERT( 0 == (common_options.issuer_permissions & disable_bsrm_update) ); + FC_ASSERT( !bitasset_opts->extensions.value.black_swan_response_method.valid(), + "Can not set black_swan_response_method for Prediction Markets" ); } if( bitasset_opts ) bitasset_opts->validate(); @@ -262,11 +262,11 @@ void bitasset_options::validate() const if( extensions.value.force_settle_fee_percent.valid() ) FC_ASSERT( *extensions.value.force_settle_fee_percent <= GRAPHENE_100_PERCENT ); - if( extensions.value.bad_debt_settlement_method.valid() ) + if( extensions.value.black_swan_response_method.valid() ) { - auto bdsm_count = static_cast( bad_debt_settlement_type::BDSM_TYPE_COUNT ); - FC_ASSERT( *extensions.value.bad_debt_settlement_method < bdsm_count, - "bad_debt_settlement_method should be less than ${c}", ("c",bdsm_count) ); + auto bsrm_count = static_cast( black_swan_response_type::BSRM_TYPE_COUNT ); + FC_ASSERT( *extensions.value.black_swan_response_method < bsrm_count, + "black_swan_response_method should be less than ${c}", ("c",bsrm_count) ); } } @@ -319,8 +319,8 @@ void asset_options::validate_flags( bool is_market_issued )const "Can not set disable_icr_update flag, it is for issuer permission only" ); FC_ASSERT( 0 == (flags & disable_mssr_update), "Can not set disable_mssr_update flag, it is for issuer permission only" ); - FC_ASSERT( 0 == (flags & disable_bdsm_update), - "Can not set disable_bdsm_update flag, it is for issuer permission only" ); + FC_ASSERT( 0 == (flags & disable_bsrm_update), + "Can not set disable_bsrm_update flag, it is for issuer permission only" ); if( !is_market_issued ) { FC_ASSERT( 0 == (flags & (uint16_t)(~UIA_ASSET_ISSUER_PERMISSION_MASK)), diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index ccde864aca..cb14713a68 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -108,8 +108,8 @@ namespace graphene { namespace protocol { */ struct bitasset_options { - /// Defines what will happen when bad debt appears - enum class bad_debt_settlement_type + /// Defines how to response to black swan events + enum class black_swan_response_type { /// All debt positions are closed, all or some collateral is moved to a global-settlement fund. /// Debt asset holders can claim collateral via force-settlement. @@ -128,8 +128,8 @@ namespace graphene { namespace protocol { /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining /// debt positions could be margin called at a worse price. individual_settlement_to_order = 3, - /// Total number of available bad debt settlement methods - BDSM_TYPE_COUNT = 4 + /// Total number of available black swan response methods + BSRM_TYPE_COUNT = 4 }; struct ext @@ -145,7 +145,7 @@ namespace graphene { namespace protocol { fc::optional margin_call_fee_ratio; // BSIP 74 fc::optional force_settle_fee_percent; // BSIP-87 // https://github.com/bitshares/bitshares-core/issues/2467 - fc::optional bad_debt_settlement_method; + fc::optional black_swan_response_method; }; /// Time before a price feed expires @@ -172,12 +172,12 @@ namespace graphene { namespace protocol { /// @throws fc::exception if any check fails void validate()const; - /// Get the effective bad debt settlement method - bad_debt_settlement_type get_bad_debt_settlement_method() const + /// Get the effective black swan response method + black_swan_response_type get_black_swan_response_method() const { - if( !extensions.value.bad_debt_settlement_method.valid() ) - return bad_debt_settlement_type::global_settlement; - return static_cast( *extensions.value.bad_debt_settlement_method ); + if( !extensions.value.black_swan_response_method.valid() ) + return black_swan_response_type::global_settlement; + return static_cast( *extensions.value.black_swan_response_method ); } }; @@ -635,7 +635,7 @@ FC_REFLECT( graphene::protocol::bitasset_options::ext, (maximum_short_squeeze_ratio) (margin_call_fee_ratio) (force_settle_fee_percent) - (bad_debt_settlement_method) + (black_swan_response_method) ) FC_REFLECT( graphene::protocol::bitasset_options, diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index b98c0eccdf..675de8e350 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -180,7 +180,7 @@ enum asset_issuer_permission_flags { disable_mcr_update = 0x800, ///< the bitasset owner can not update MCR, permisison only disable_icr_update = 0x1000, ///< the bitasset owner can not update ICR, permisison only disable_mssr_update = 0x2000, ///< the bitasset owner can not update MSSR, permisison only - disable_bdsm_update = 0x4000 ///< the bitasset owner can not update BDSM, permission only + disable_bsrm_update = 0x4000 ///< the bitasset owner can not update BSRM, permission only ///@} ///@} }; @@ -201,7 +201,7 @@ const static uint16_t ASSET_ISSUER_PERMISSION_MASK = | disable_mcr_update | disable_icr_update | disable_mssr_update - | disable_bdsm_update; + | disable_bsrm_update; // The "enable" bits for non-UIA assets const static uint16_t ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK = charge_market_fee @@ -220,7 +220,7 @@ const static uint16_t ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK = | disable_mcr_update | disable_icr_update | disable_mssr_update - | disable_bdsm_update; + | disable_bsrm_update; // The bits that can be used in asset issuer permissions for UIA assets const static uint16_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee @@ -246,7 +246,7 @@ const static uint16_t PERMISSION_ONLY_MASK = | disable_mcr_update | disable_icr_update | disable_mssr_update - | disable_bdsm_update; + | disable_bsrm_update; // The bits that can be used in flags for non-UIA assets const static uint16_t VALID_FLAGS_MASK = ASSET_ISSUER_PERMISSION_MASK & ~PERMISSION_ONLY_MASK; // the bits that can be used in flags for UIA assets @@ -364,7 +364,7 @@ FC_REFLECT_ENUM(graphene::protocol::asset_issuer_permission_flags, (disable_mcr_update) (disable_icr_update) (disable_mssr_update) - (disable_bdsm_update) + (disable_bsrm_update) ) namespace fc { namespace raw { diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index ee97c25211..6861b9e233 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -1164,7 +1164,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) acop2.symbol = "NEWSAMBIT"; // With all possible bits in permissions set to 1 acop2.common_options.issuer_permissions = hf2467 ? ASSET_ISSUER_PERMISSION_MASK - : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update ); + : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update ); for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { acop2.common_options.flags = valid_bitflag | bit; @@ -1191,9 +1191,9 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_CHECK( !newsambit_id(db).can_owner_update_mcr() ); BOOST_CHECK( !newsambit_id(db).can_owner_update_mssr() ); if( hf2467 ) - BOOST_CHECK( !newsambit_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( !newsambit_id(db).can_owner_update_bsrm() ); else - BOOST_CHECK( newsambit_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( newsambit_id(db).can_owner_update_bsrm() ); // Able to propose too propose( acop2 ); diff --git a/tests/tests/bdsm_tests.cpp b/tests/tests/bsrm_tests.cpp similarity index 84% rename from tests/tests/bdsm_tests.cpp rename to tests/tests/bsrm_tests.cpp index dd7288d591..4f3353afee 100644 --- a/tests/tests/bdsm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -34,7 +34,7 @@ using namespace graphene::chain; using namespace graphene::chain::test; -BOOST_FIXTURE_TEST_SUITE( bdsm_tests, database_fixture ) +BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) /// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork BOOST_AUTO_TEST_CASE( hardfork_protection_test ) @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update; + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; @@ -84,10 +84,10 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) op.common_options.issuer_permissions = old_bitmask; // Unable to set new extensions in bitasset options - op.bitasset_opts->extensions.value.bad_debt_settlement_method = 0; + op.bitasset_opts->extensions.value.black_swan_response_method = 0; BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); ops.push_back( op ); - op.bitasset_opts->extensions.value.bad_debt_settlement_method = {}; + op.bitasset_opts->extensions.value.black_swan_response_method = {}; acop = op; } @@ -147,10 +147,10 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) op.new_options.minimum_feeds = 1; // Unable to set new extensions - op.new_options.extensions.value.bad_debt_settlement_method = 1; + op.new_options.extensions.value.black_swan_response_method = 1; BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); ops.push_back( op ); - op.new_options.extensions.value.bad_debt_settlement_method = {}; + op.new_options.extensions.value.black_swan_response_method = {}; aubop = op; } @@ -205,7 +205,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update; + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; @@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - auop.new_options.issuer_permissions = uiamask | disable_bdsm_update; + auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); @@ -338,7 +338,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - auop.new_options.issuer_permissions = uiamask | disable_bdsm_update; + auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); @@ -350,7 +350,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) } } -/// Tests what kind of assets can have BDSM-related flags / issuer permissions / extensions +/// Tests what kind of assets can have BSRM-related flags / issuer permissions / extensions BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) { try { @@ -367,14 +367,14 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - // Unable to create a PM with the disable_bdsm_update bit in flags - BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bdsm_update ), fc::exception ); + // Unable to create a PM with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bsrm_update ), fc::exception ); - // Unable to create a MPA with the disable_bdsm_update bit in flags - BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bdsm_update ), fc::exception ); + // Unable to create a MPA with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bsrm_update ), fc::exception ); - // Unable to create a UIA with the disable_bdsm_update bit in flags - BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bdsm_update ), fc::exception ); + // Unable to create a UIA with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bsrm_update ), fc::exception ); // create a PM with a zero market_fee_percent const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); @@ -392,57 +392,57 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) asset_update_operation auop; auop.issuer = sam_id; - // Unable to set disable_bdsm_update bit in flags for PM + // Unable to set disable_bsrm_update bit in flags for PM auop.asset_to_update = pm_id; auop.new_options = pm_id(db).options; - auop.new_options.flags |= disable_bdsm_update; + auop.new_options.flags |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // Unable to propose either BOOST_CHECK_THROW( propose( auop ), fc::exception ); - // Unable to set disable_bdsm_update bit in flags for MPA + // Unable to set disable_bsrm_update bit in flags for MPA auop.asset_to_update = mpa_id; auop.new_options = mpa_id(db).options; - auop.new_options.flags |= disable_bdsm_update; + auop.new_options.flags |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // Unable to propose either BOOST_CHECK_THROW( propose( auop ), fc::exception ); - // Unable to set disable_bdsm_update bit in flags for UIA + // Unable to set disable_bsrm_update bit in flags for UIA auop.asset_to_update = uia_id; auop.new_options = uia_id(db).options; - auop.new_options.flags |= disable_bdsm_update; + auop.new_options.flags |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // Unable to propose either BOOST_CHECK_THROW( propose( auop ), fc::exception ); - // Unable to set disable_bdsm_update bit in issuer_permissions for PM + // Unable to set disable_bsrm_update bit in issuer_permissions for PM auop.asset_to_update = pm_id; auop.new_options = pm_id(db).options; - auop.new_options.issuer_permissions |= disable_bdsm_update; + auop.new_options.issuer_permissions |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // But able to propose propose( auop ); - // Unable to set disable_bdsm_update bit in issuer_permissions for UIA + // Unable to set disable_bsrm_update bit in issuer_permissions for UIA auop.asset_to_update = uia_id; auop.new_options = uia_id(db).options; - auop.new_options.issuer_permissions |= disable_bdsm_update; + auop.new_options.issuer_permissions |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // But able to propose propose( auop ); - // Unable to create a UIA with disable_bdsm_update permission bit + // Unable to create a UIA with disable_bsrm_update permission bit asset_create_operation acop; acop.issuer = sam_id; acop.symbol = "SAMCOIN"; @@ -451,7 +451,7 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; acop.common_options.market_fee_percent = 100; acop.common_options.flags = charge_market_fee; - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bdsm_update; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( acop ); @@ -460,17 +460,17 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) // Unable to propose either BOOST_CHECK_THROW( propose( acop ), fc::exception ); - // Able to create UIA without disable_bdsm_update permission bit + // Able to create UIA without disable_bsrm_update permission bit acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; trx.operations.clear(); trx.operations.push_back( acop ); PUSH_TX(db, trx, ~0); - // Unable to create a PM with disable_bdsm_update permission bit + // Unable to create a PM with disable_bsrm_update permission bit acop.symbol = "SAMPM"; acop.precision = asset_id_type()(db).precision; acop.is_prediction_market = true; - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bdsm_update; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bsrm_update; acop.bitasset_opts = bitasset_options(); trx.operations.clear(); @@ -480,9 +480,9 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) // Unable to propose either BOOST_CHECK_THROW( propose( acop ), fc::exception ); - // Unable to create a PM with BDSM in extensions + // Unable to create a PM with BSRM in extensions acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; - acop.bitasset_opts->extensions.value.bad_debt_settlement_method = 0; + acop.bitasset_opts->extensions.value.black_swan_response_method = 0; trx.operations.clear(); trx.operations.push_back( acop ); @@ -491,19 +491,19 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) // Unable to propose either BOOST_CHECK_THROW( propose( acop ), fc::exception ); - // Able to create PM with no disable_bdsm_update permission bit nor BDSM in extensions + // Able to create PM with no disable_bsrm_update permission bit nor BSRM in extensions acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; - acop.bitasset_opts->extensions.value.bad_debt_settlement_method.reset(); + acop.bitasset_opts->extensions.value.black_swan_response_method.reset(); trx.operations.clear(); trx.operations.push_back( acop ); PUSH_TX(db, trx, ~0); - // Unable to update PM to set BDSM + // Unable to update PM to set BSRM asset_update_bitasset_operation aubop; aubop.issuer = sam_id; aubop.asset_to_update = pm_id; aubop.new_options = pm_id(db).bitasset_data(db).options; - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + aubop.new_options.extensions.value.black_swan_response_method = 1; trx.operations.clear(); trx.operations.push_back( aubop ); @@ -520,8 +520,8 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) } } -/// Tests whether asset owner has permission to update bdsm -BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) +/// Tests whether asset owner has permission to update bsrm +BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) { try { @@ -541,9 +541,9 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); asset_id_type mpa_id = mpa.id; - BOOST_CHECK( mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -564,76 +564,76 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) auop.asset_to_update = mpa_id; auop.new_options = mpa_id(db).options; - // disable owner's permission to update bdsm - auop.new_options.issuer_permissions |= disable_bdsm_update; + // disable owner's permission to update bsrm + auop.new_options.issuer_permissions |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - // check that owner can not update bdsm + // check that owner can not update bsrm asset_update_bitasset_operation aubop; aubop.issuer = sam_id; aubop.asset_to_update = mpa_id; aubop.new_options = mpa_id(db).bitasset_data(db).options; - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + aubop.new_options.extensions.value.black_swan_response_method = 1; trx.operations.clear(); trx.operations.push_back( aubop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.bad_debt_settlement_method.reset(); + aubop.new_options.extensions.value.black_swan_response_method.reset(); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - // enable owner's permission to update bdsm - auop.new_options.issuer_permissions &= ~disable_bdsm_update; + // enable owner's permission to update bsrm + auop.new_options.issuer_permissions &= ~disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK( mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); - // check that owner can update bdsm - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + // check that owner can update bsrm + aubop.new_options.extensions.value.black_swan_response_method = 1; trx.operations.clear(); trx.operations.push_back( aubop ); PUSH_TX(db, trx, ~0); - BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method, 1u ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); - // check bdsm' valid range - aubop.new_options.extensions.value.bad_debt_settlement_method = 4; + // check bsrm' valid range + aubop.new_options.extensions.value.black_swan_response_method = 4; trx.operations.clear(); trx.operations.push_back( aubop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + aubop.new_options.extensions.value.black_swan_response_method = 1; // Sam borrow some borrow( sam, asset(1000, mpa_id), asset(2000) ); - // disable owner's permission to update bdsm - auop.new_options.issuer_permissions |= disable_bdsm_update; + // disable owner's permission to update bsrm + auop.new_options.issuer_permissions |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - // check that owner can not update bdsm - aubop.new_options.extensions.value.bad_debt_settlement_method = 0; + // check that owner can not update bsrm + aubop.new_options.extensions.value.black_swan_response_method = 0; trx.operations.clear(); trx.operations.push_back( aubop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.bad_debt_settlement_method.reset(); + aubop.new_options.extensions.value.black_swan_response_method.reset(); trx.operations.clear(); trx.operations.push_back( aubop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + aubop.new_options.extensions.value.black_swan_response_method = 1; // able to update other params that still has permission E.G. force_settlement_delay_sec aubop.new_options.force_settlement_delay_sec += 1; @@ -644,17 +644,17 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, aubop.new_options.force_settlement_delay_sec ); - BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method, 1u ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); - // unable to enable the permission to update bdsm - auop.new_options.issuer_permissions &= ~disable_bdsm_update; + // unable to enable the permission to update bsrm + auop.new_options.issuer_permissions &= ~disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); generate_block(); From fe6575801cd8b7856e8aaf040bdd61761383f8d3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 27 Aug 2021 00:53:15 +0000 Subject: [PATCH 36/99] Fix apply_order and apply_force_settlement by calculating with new current_feed after filled a call order --- libraries/chain/db_market.cpp | 33 ++++++++++++++++++- .../include/graphene/chain/asset_object.hpp | 4 +++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index e783e048a6..0cebe2d8ca 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -628,6 +628,11 @@ bool database::apply_order(const limit_order_object& new_order_object) // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( recv_asset_id, sell_asset_id ); + // Note: when BSRM is no_settlement, current_feed can change after filled a call order, + // so we recalculate inside the loop + using bsrm_type = bitasset_options::black_swan_response_type; + bool update_call_price = ( sell_abd->get_black_swan_response_method() == bsrm_type::no_settlement + && sell_abd->is_current_feed_price_capped() ); while( !finished ) { // hard fork core-343 and core-625 took place at same time, @@ -650,6 +655,12 @@ bool database::apply_order(const limit_order_object& new_order_object) if( match_result_type::only_taker_filled == match_result || match_result_type::both_filled == match_result ) finished = true; + else if( update_call_price ) + { + call_match_price = ~sell_abd->get_margin_call_order_price(); + call_pays_price = ~sell_abd->current_feed.max_short_squeeze_price(); + update_call_price = sell_abd->is_current_feed_price_capped(); + } } } else if( !finished ) // and before core-1270 hard fork @@ -726,6 +737,12 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // differ from call_match_price if there is a Margin Call Fee. price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + // Note: when BSRM is no_settlement, current_feed can change after filled a call order, + // so we recalculate inside the loop + using bsrm_type = bitasset_options::black_swan_response_type; + bool update_call_price = ( bitasset.get_black_swan_response_method() == bsrm_type::no_settlement + && bitasset.is_current_feed_price_capped() ); + bool finished = false; // whether the new order is gone // check if there are margin calls @@ -756,6 +773,13 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Check whether the new order is gone finished = ( nullptr == find_object( new_obj_id ) ); + + if( !finished && update_call_price ) + { + call_match_price = bitasset.get_margin_call_order_price(); + call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + update_call_price = bitasset.is_current_feed_price_capped(); + } } } @@ -1615,6 +1639,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa : ( call_collateral_itr != call_collateral_end ); }; + using bsrm_type = bitasset_options::black_swan_response_type; + bool update_current_feed = ( bsrm_type::no_settlement == bitasset.get_black_swan_response_method() + && bitasset.is_current_feed_price_capped() ); + while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators && limit_itr != limit_end @@ -1775,8 +1803,11 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); // Update current_feed after filled call order if needed - if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() ) + if( update_current_feed ) + { update_bitasset_current_feed( bitasset, true ); + update_current_feed = bitasset.is_current_feed_price_capped(); + } if( !before_core_hardfork_1270 ) call_collateral_itr = call_collateral_index.lower_bound( call_min ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index a9481604ea..5f6ef1cbbb 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -276,6 +276,10 @@ namespace graphene { namespace chain { /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; + /// @return whether @ref median_feed and @ref current_feed is different + bool is_current_feed_price_capped()const + { return ( median_feed.settlement_price != current_feed.settlement_price ); } + /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin /// call territory. /// This value is derived from @ref current_feed for better performance and should be kept consistent. From 3e42d21ff31ccf02205285aa9ca2750b2c90a4df Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 27 Aug 2021 01:22:35 +0000 Subject: [PATCH 37/99] Add tests about BSRM no_settlement --- tests/tests/bsrm_tests.cpp | 418 +++++++++++++++++++++++++++++++++++++ 1 file changed, 418 insertions(+) diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_tests.cpp index 4f3353afee..9bb6b12aee 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -545,6 +545,9 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + using bsrm_type = bitasset_options::black_swan_response_type; + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -603,6 +606,7 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); // check bsrm' valid range aubop.new_options.extensions.value.black_swan_response_method = 4; @@ -664,4 +668,418 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) } } +/// Tests margin calls when BSRM is no_settlement +BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(10,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 1000 * (2000/1250) * 1.9 = 3040 + // 1000 * (22/10) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(1000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower is unable to adjust debt position if it's still undercollateralized + // 1000 * (2000/1250) * 1.25 = 2000 + // 1000 * (22/10) * 1.25 = 2750 + BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); + // borrower adjust debt position to right at MSSR + borrow( borrower, asset(0, mpa_id), asset(750) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Sam update MSSR and MCFR + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; + aubop.new_options.extensions.value.margin_call_fee_ratio = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Transfer funds to seller + transfer( borrower, seller, asset(1000,mpa_id) ); + transfer( borrower2, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(1000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells some, due to MCFR, this order won't be filled + const limit_order_object* sell_high = create_sell_order( seller, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_high ); + BOOST_CHECK_EQUAL( sell_high->for_sale.value, 100 ); + + // seller sells some again, this order will be filled + const limit_order_object* sell_low = create_sell_order( seller, asset(111,mpa_id), asset(210) ); + BOOST_REQUIRE( !sell_low ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2789 ); // 3000 - 100 - 111 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(18670) ) ); // 13:10 * (1000-111):(2100-111*210/100) + // 13:10 * 889:1867 + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells more + sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) ); + BOOST_REQUIRE( !sell_low ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1789 ); // 3000 - 100 - 111 - 1000 + // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300) + // 232 + 1866 + 305 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(24450) ) ); // 13:10 * (1000-111):(2750-111*275/100) + // 13:10 * 889:2445 + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2445 ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells more + sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) ); + BOOST_REQUIRE( sell_low ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 789 ); // 3000 - 100 - 111 - 1000 - 1000 + // 2403 + round_up(889*(24450/11557)*(1299/1000)) + // 2403 + 2444 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4847); + + BOOST_CHECK_EQUAL( sell_low->for_sale.value, 111 ); + + // check + BOOST_CHECK( !mpa.bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK( !db.find(call_id) ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests force settlements when BSRM is no_settlement +BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(10,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 1000 * (2000/1250) * 1.9 = 3040 + // 1000 * (22/10) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(1000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower is unable to adjust debt position if it's still undercollateralized + // 1000 * (2000/1250) * 1.25 = 2000 + // 1000 * (22/10) * 1.25 = 2750 + BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); + // borrower adjust debt position to right at MSSR + borrow( borrower, asset(0, mpa_id), asset(750) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Sam update MSSR and MCFR + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; + aubop.new_options.extensions.value.margin_call_fee_ratio = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Transfer funds to seller + transfer( borrower, seller, asset(1000,mpa_id) ); + transfer( borrower2, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(1000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells some, due to MCFR, this order won't be filled + const limit_order_object* sell_high = create_sell_order( seller, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_high ); + BOOST_CHECK_EQUAL( sell_high->for_sale.value, 100 ); + + // seller settles some + auto result = force_settle( seller, asset(111,mpa_id) ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2789 ); // 3000 - 100 - 111 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) + + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_CHECK( !db.find(settle_id) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(18670) ) ); // 13:10 * (1000-111):(2100-111*210/100) + // 13:10 * 889:1867 + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller settles some more + result = force_settle( seller, asset(1000,mpa_id) ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1789 ); // 3000 - 100 - 111 - 1000 + // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300) + // 232 + 1866 + 305 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); + + settle_id = *result.get().value.new_objects->begin(); + BOOST_CHECK( !db.find(settle_id) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(24450) ) ); // 13:10 * (1000-111):(2750-111*275/100) + // 13:10 * 889:2445 + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2445 ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller settles more + result = force_settle( seller, asset(1000,mpa_id) ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 789 ); // 3000 - 100 - 111 - 1000 - 1000 + // 2403 + round_up(889*(24450/11557)*(1299/1000)) + // 2403 + 2444 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4847); + + settle_id = *result.get().value.new_objects->begin(); + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 111 ); + + // check + BOOST_CHECK( !mpa.bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK( !db.find(call_id) ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 2d706d0d683e66eb6dc6663c0909cb5399c8563d Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 27 Aug 2021 01:52:01 +0000 Subject: [PATCH 38/99] Fix match_force_settlements by calculating with new current_feed after filled a call order --- libraries/chain/db_market.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 0cebe2d8ca..43be9aeca4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1639,9 +1639,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa : ( call_collateral_itr != call_collateral_end ); }; - using bsrm_type = bitasset_options::black_swan_response_type; - bool update_current_feed = ( bsrm_type::no_settlement == bitasset.get_black_swan_response_method() - && bitasset.is_current_feed_price_capped() ); + bool update_current_feed = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() ); while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators @@ -1880,6 +1878,12 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio(); + // Note: when BSRM is no_settlement, current_feed can change after filled a call order, + // so we recalculate inside the loop + using bsrm_type = bitasset_options::black_swan_response_type; + bool update_call_price = ( bitasset.get_black_swan_response_method() == bsrm_type::no_settlement + && bitasset.is_current_feed_price_capped() ); + bool margin_called = false; while( settle_itr != settle_end && call_itr != call_end ) { @@ -1906,6 +1910,13 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass if( !margin_called && result.amount > 0 ) margin_called = true; + if( update_call_price ) + { + call_match_price = bitasset.get_margin_call_order_price(); + call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + update_call_price = bitasset.is_current_feed_price_capped(); + } + settle_itr = settlement_index.lower_bound( bitasset.asset_id ); call_itr = call_collateral_index.lower_bound( call_min ); } From 4c5ac83f4eda1983e3d5f238393beb6c9fb1198c Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 02:16:58 +0000 Subject: [PATCH 39/99] Call check_call_orders() in apply_order() when feed price is updated and the new limit order is fully filled --- libraries/chain/db_market.cpp | 50 +++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 43be9aeca4..0f6777253a 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -609,8 +609,10 @@ bool database::apply_order(const limit_order_object& new_order_object) } bool finished = false; // whether the new order is gone + bool feed_price_updated = false; // whether current_feed.settlement_price has been updated if( to_check_call_orders ) { + auto call_min = price::min( recv_asset_id, sell_asset_id ); // check limit orders first, match the ones with better price in comparison to call orders while( !finished && limit_itr != limit_end && limit_itr->sell_price > call_match_price ) { @@ -627,12 +629,12 @@ bool database::apply_order(const limit_order_object& new_order_object) // check if there are margin calls // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_idx = get_index_type().indices().get(); - auto call_min = price::min( recv_asset_id, sell_asset_id ); // Note: when BSRM is no_settlement, current_feed can change after filled a call order, // so we recalculate inside the loop using bsrm_type = bitasset_options::black_swan_response_type; - bool update_call_price = ( sell_abd->get_black_swan_response_method() == bsrm_type::no_settlement - && sell_abd->is_current_feed_price_capped() ); + auto bsrm = sell_abd->get_black_swan_response_method(); + bool update_call_price = ( bsrm_type::no_settlement == bsrm && sell_abd->is_current_feed_price_capped() ); + auto old_current_feed_price = sell_abd->current_feed.settlement_price; while( !finished ) { // hard fork core-343 and core-625 took place at same time, @@ -660,14 +662,19 @@ bool database::apply_order(const limit_order_object& new_order_object) call_match_price = ~sell_abd->get_margin_call_order_price(); call_pays_price = ~sell_abd->current_feed.max_short_squeeze_price(); update_call_price = sell_abd->is_current_feed_price_capped(); + // Since current feed price (in debt/collateral) can only decrease after updated, if there still + // exists a call order in margin call territory, it would be on the top of the order book, + // so no need to check if the current limit (buy) order would match another limit (sell) order atm. + // On the other hand, the current limit order is on the top of the other side of the order book. } - } - } + } // while !finished + if( bsrm_type::no_settlement == bsrm && sell_abd->current_feed.settlement_price != old_current_feed_price ) + feed_price_updated = true; + } // if after core-1270 hf else if( !finished ) // and before core-1270 hard fork { // check if there are margin calls const auto& call_price_idx = get_index_type().indices().get(); - auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) { // assume hard fork core-343 and core-625 will take place at same time, @@ -691,9 +698,9 @@ bool database::apply_order(const limit_order_object& new_order_object) if( match_result_type::only_taker_filled == match_result || match_result_type::both_filled == match_result ) finished = true; - } - } - } + } // while !finished + } // if before core-1270 hf + } // if to check call // still need to check limit orders while( !finished && limit_itr != limit_end ) @@ -705,16 +712,25 @@ bool database::apply_order(const limit_order_object& new_order_object) != match_result_type::only_maker_filled ); } + bool limit_order_is_gone = true; const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); - if( !updated_order_object ) - return true; + if( updated_order_object ) + // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() + // being called by match() above + // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that + // was done too eagerly before, and + // this is the point it's deferred to. + limit_order_is_gone = maybe_cull_small_order( *this, *updated_order_object ); + + if( limit_order_is_gone && feed_price_updated ) + { + // If current_feed got updated, and the new limit order is gone, + // it is possible that other limit orders are able to get filled, + // so we need to call check_call_orders() + check_call_orders( sell_asset, true, false, sell_abd ); + } - // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() - // being called by match() above - // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that - // was done too eagerly before, and - // this is the point it's deferred to. - return maybe_cull_small_order( *this, *updated_order_object ); + return limit_order_is_gone; } void database::apply_force_settlement( const force_settlement_object& new_settlement, From 2244a21fd65b5d1b618345f13d3705f57d17c414 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 03:58:16 +0000 Subject: [PATCH 40/99] Call check_call_orders in apply_force_settlements check limit orders first when feed price is updated --- libraries/chain/asset_evaluator.cpp | 2 +- libraries/chain/db_market.cpp | 29 ++++++++++++------- .../chain/include/graphene/chain/database.hpp | 7 +++-- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index fd7e9d5f04..b35ac6441f 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1282,7 +1282,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( HARDFORK_CORE_2481_PASSED( maint_time ) ) { - d.apply_force_settlement( settle, bitasset ); + d.apply_force_settlement( settle, bitasset, *asset_to_settle ); } return result; diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 0f6777253a..d377eab96f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -734,7 +734,8 @@ bool database::apply_order(const limit_order_object& new_order_object) } void database::apply_force_settlement( const force_settlement_object& new_settlement, - const asset_bitasset_data_object& bitasset ) + const asset_bitasset_data_object& bitasset, + const asset_object& asset_obj ) { // Defensive checks auto maint_time = get_dynamic_global_properties().next_maintenance_time; @@ -756,8 +757,8 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Note: when BSRM is no_settlement, current_feed can change after filled a call order, // so we recalculate inside the loop using bsrm_type = bitasset_options::black_swan_response_type; - bool update_call_price = ( bitasset.get_black_swan_response_method() == bsrm_type::no_settlement - && bitasset.is_current_feed_price_capped() ); + auto bsrm = bitasset.get_black_swan_response_method(); + bool update_call_price = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() ); bool finished = false; // whether the new order is gone @@ -790,11 +791,17 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Check whether the new order is gone finished = ( nullptr == find_object( new_obj_id ) ); - if( !finished && update_call_price ) + if( update_call_price ) { - call_match_price = bitasset.get_margin_call_order_price(); - call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - update_call_price = bitasset.is_current_feed_price_capped(); + // when current_feed is updated, it is possible that there are limit orders able to get filled, + // so we need to call check_call_orders(), but skip matching call orders with force settlements + check_call_orders( asset_obj, true, false, &bitasset, false, true ); + if( !finished ) + { + call_match_price = bitasset.get_margin_call_order_price(); + call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + update_call_price = bitasset.is_current_feed_price_capped(); + } } } @@ -1550,11 +1557,13 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a * function that calls this with for_new_limit_order true.) * @param bitasset_ptr - an optional pointer to the bitasset_data object of the asset * @param mute_exceptions - whether to mute exceptions in a special case + * @param skip_matching_settle_orders - whether to skip matching call orders with force settlements * * @return true if a margin call was executed. */ bool database::check_call_orders( const asset_object& mia, bool enable_black_swan, bool for_new_limit_order, - const asset_bitasset_data_object* bitasset_ptr, bool mute_exceptions ) + const asset_bitasset_data_object* bitasset_ptr, + bool mute_exceptions, bool skip_matching_settle_orders ) { try { const auto& dyn_prop = get_dynamic_global_properties(); auto maint_time = dyn_prop.next_maintenance_time; @@ -1820,6 +1829,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( update_current_feed ) { update_bitasset_current_feed( bitasset, true ); + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); update_current_feed = bitasset.is_current_feed_price_capped(); } @@ -1837,9 +1847,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } // while call_itr != call_end - // Check margin calls against force settlements - if( after_core_hardfork_2481 && !bitasset.has_settlement() ) + if( !skip_matching_settle_orders && after_core_hardfork_2481 && !bitasset.has_settlement() ) { // Be here, there exists at least one margin call not processed bool called_some = match_force_settlements( bitasset ); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 63fa00fb2f..7fb1681fd2 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -460,12 +460,14 @@ namespace graphene { namespace chain { * @brief Process a new force-settlement request * @param new_settlement The new force-settlement request * @param bitasset The bitasset data object + * @param asset_obj The asset object * * Since the core-2481 hard fork, this function is called after a new force-settlement object is created * to check if there are margin calls to be matched instantly. */ void apply_force_settlement( const force_settlement_object& new_settlement, - const asset_bitasset_data_object& bitasset ); + const asset_bitasset_data_object& bitasset, + const asset_object& asset_obj ); /** * Matches the two orders, the first parameter is taker, the second is maker. @@ -603,7 +605,8 @@ namespace graphene { namespace chain { bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, bool for_new_limit_order = false, const asset_bitasset_data_object* bitasset_ptr = nullptr, - bool mute_exceptions = false ); + bool mute_exceptions = false, + bool skip_matching_settle_orders = false ); /// helpers to fill_order /// @{ From 565c73fe48bb06f73e3cb40bdbe378de4f930da3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 07:11:10 +0000 Subject: [PATCH 41/99] Fix tests about BSRM no_settlement when current_feed got updated, some existing limit orders may be filled --- tests/tests/bsrm_tests.cpp | 238 +++++++++++++++++++++++++++---------- 1 file changed, 172 insertions(+), 66 deletions(-) diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_tests.cpp index 9bb6b12aee..f525bc7ca5 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -668,8 +668,8 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) } } -/// Tests margin calls when BSRM is no_settlement -BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) +/// Tests margin calls when BSRM is no_settlement and call order is maker +BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) { try { @@ -679,7 +679,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); - ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)); + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); @@ -769,6 +769,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); // Sam update MSSR and MCFR + // note: borrower's position is undercollateralized again due to the mssr change asset_update_bitasset_operation aubop; aubop.issuer = sam_id; aubop.asset_to_update = mpa_id; @@ -787,10 +788,11 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - // Transfer funds to seller + // Transfer funds to sellers transfer( borrower, seller, asset(1000,mpa_id) ); transfer( borrower2, seller, asset(1000,mpa_id) ); - transfer( borrower3, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(500,mpa_id) ); + transfer( borrower3, seller2, asset(500,mpa_id) ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); @@ -799,16 +801,31 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - // seller sells some, due to MCFR, this order won't be filled - const limit_order_object* sell_high = create_sell_order( seller, asset(100,mpa_id), asset(210) ); + // seller2 sells some, due to MCFR, this order won't be filled + const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); BOOST_REQUIRE( sell_high ); - BOOST_CHECK_EQUAL( sell_high->for_sale.value, 100 ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - // seller sells some again, this order will be filled + // seller2 sells more, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // 500 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller sells some, this order will be filled const limit_order_object* sell_low = create_sell_order( seller, asset(111,mpa_id), asset(210) ); - BOOST_REQUIRE( !sell_low ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2789 ); // 3000 - 100 - 111 + BOOST_CHECK( !sell_low ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); @@ -826,22 +843,36 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) // seller sells more sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) ); - BOOST_REQUIRE( !sell_low ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1789 ); // 3000 - 100 - 111 - 1000 + BOOST_CHECK( !sell_low ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1389 ); // 2500 - 111 - 1000 // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300) // 232 + 1866 + 305 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); + // now feed price is 13:10 * (1000-111):(2750-111*275/100) + // = 13:10 * 889:2445 = 11557:24450 + + // seller2's sell_mid got filled too + BOOST_CHECK( !db.find( sell_mid_id ) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + // call pays round_down(210*1300/1299) = 210, fee = 0 + // now feed price is 13:10 * (889-100):(2445-210) + // = 13:10 * 789:2235 = 10257:22350 + + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); // check BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price - == price( asset(11557,mpa_id), asset(24450) ) ); // 13:10 * (1000-111):(2750-111*275/100) - // 13:10 * 889:2445 + == price( asset(10257,mpa_id), asset(22350) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK_EQUAL( call_id(db).debt.value, 889 ); - BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2445 ); + BOOST_CHECK_EQUAL( call_id(db).debt.value, 789 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2235 ); BOOST_CHECK( !db.find(call2_id) ); BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); @@ -849,23 +880,39 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) // seller sells more sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) ); BOOST_REQUIRE( sell_low ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 789 ); // 3000 - 100 - 111 - 1000 - 1000 - // 2403 + round_up(889*(24450/11557)*(1299/1000)) - // 2403 + 2444 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4847); + limit_order_id_type sell_low_id = sell_low->id; - BOOST_CHECK_EQUAL( sell_low->for_sale.value, 111 ); + auto final_check = [&] + { + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 211 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - // check - BOOST_CHECK( !mpa.bitasset_data(db).is_current_feed_price_capped() ); - BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000 + // 2403 + round_up(789*(22350/10257)*(1299/1000)) + // 2403 + 2234 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4637 ); - BOOST_CHECK( !db.find(call_id) ); - BOOST_CHECK( !db.find(call2_id) ); - BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); - BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // no change + + // check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK( !db.find(call_id) ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); } catch (fc::exception& e) { edump((e.to_detail_string())); @@ -873,8 +920,8 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) } } -/// Tests force settlements when BSRM is no_settlement -BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) +/// Tests force settlements when BSRM is no_settlement and call order is maker +BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) { try { @@ -884,7 +931,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); - ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)); + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); @@ -974,6 +1021,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); // Sam update MSSR and MCFR + // note: borrower's position is undercollateralized again due to the mssr change asset_update_bitasset_operation aubop; aubop.issuer = sam_id; aubop.asset_to_update = mpa_id; @@ -992,10 +1040,11 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - // Transfer funds to seller + // Transfer funds to sellers transfer( borrower, seller, asset(1000,mpa_id) ); transfer( borrower2, seller, asset(1000,mpa_id) ); - transfer( borrower3, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(500,mpa_id) ); + transfer( borrower3, seller2, asset(500,mpa_id) ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); @@ -1004,19 +1053,33 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - // seller sells some, due to MCFR, this order won't be filled - const limit_order_object* sell_high = create_sell_order( seller, asset(100,mpa_id), asset(210) ); + // seller2 sells some + const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); BOOST_REQUIRE( sell_high ); - BOOST_CHECK_EQUAL( sell_high->for_sale.value, 100 ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + + // seller2 sells more, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // 500 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // seller settles some auto result = force_settle( seller, asset(111,mpa_id) ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2789 ); // 3000 - 100 - 111 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) - force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( !db.find(settle_id) ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price @@ -1033,48 +1096,91 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) // seller settles some more result = force_settle( seller, asset(1000,mpa_id) ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1789 ); // 3000 - 100 - 111 - 1000 - // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300) - // 232 + 1866 + 305 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); - settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( !db.find(settle_id) ); + // call2 is filled by settle order + BOOST_CHECK( !db.find(call2_id) ); + // now feed price is 13:10 * 1000:2750 = 26:55 (>10/22) + // call order match price is 1300:1299 * 1000:2750 = 0.363916299 + // sell_mid's price is 100/210 = 0.047619048 + + // then seller2's sell_mid got filled by call + BOOST_CHECK( !db.find( sell_mid_id ) ); + + // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price + // call pays round_down(210*1300/1299) = 210, fee = 0 + // now feed price is 13:10 * (1000-100):(2750-210) + // = 13:10 * 900:2540 = 11700:25400 (>10/22) + // call order match price is 1300:1299 * 900:2540 = 0.32732629 + // sell_high's price is 100/275 = 0.363636364 + + // then sell_high got filled by call + BOOST_CHECK( !db.find( sell_high_id ) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price + // sell_high was selling 100 MPA for 275 CORE as maker, matched at its price + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 + // call pays round_down(275*1300/1299) = 275, fee = 0 + // now feed price is 13:10 * (1000-100-100):(2750-210-275) + // = 13:10 * 800:2265 = 10400:22650 = 208:453 (>10/22) + + // then the settle order got filled by call + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1389 ); // 2500 - 111 - 1000 + // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(22650/10400)*(1299/1000) + // 232 + 1866 + 314 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2412 ); + // now feed price is 13:10 * (800-111):(2265-111*(22650/10400)*(13/10)) + // = 13:10 * 689:1951 = 8957:19510 (>10/22) + // check BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price - == price( asset(11557,mpa_id), asset(24450) ) ); // 13:10 * (1000-111):(2750-111*275/100) - // 13:10 * 889:2445 + == price( asset(8957,mpa_id), asset(19510) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK_EQUAL( call_id(db).debt.value, 889 ); - BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2445 ); + BOOST_CHECK_EQUAL( call_id(db).debt.value, 689 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1951 ); BOOST_CHECK( !db.find(call2_id) ); BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); // seller settles more result = force_settle( seller, asset(1000,mpa_id) ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 789 ); // 3000 - 100 - 111 - 1000 - 1000 - // 2403 + round_up(889*(24450/11557)*(1299/1000)) - // 2403 + 2444 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4847); - settle_id = *result.get().value.new_objects->begin(); - BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 111 ); - // check - BOOST_CHECK( !mpa.bitasset_data(db).is_current_feed_price_capped() ); - BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + auto final_check = [&] + { + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 311 ); - BOOST_CHECK( !db.find(call_id) ); - BOOST_CHECK( !db.find(call2_id) ); - BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); - BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000 + // 2412 + round_up(689*(19510/8957)*(1299/1000)) + // 2412 + 1950 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4362 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // no change + + // check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK( !db.find(call_id) ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); } catch (fc::exception& e) { edump((e.to_detail_string())); From b8f788165b0bbaa33660be647bc9990ccc00fdc6 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 21:26:42 +0000 Subject: [PATCH 42/99] Fix check_call_orders() wrt no_settlement check limit orders again after matched a call order with a settle order --- libraries/chain/db_market.cpp | 421 ++++++++++++++++++---------------- 1 file changed, 221 insertions(+), 200 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index d377eab96f..f46ab033e0 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1666,210 +1666,231 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool update_current_feed = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() ); - while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance - // by passing in iterators - && limit_itr != limit_end - && has_call_order() ) + // If update_current_feed is true and we filled a call order with a settle order, + // we need to check limit orders again, in this case we loop multiple times. + // In other cases we only need to loop once. + while( true ) { - bool filled_call = false; - - const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); - - // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - bool feed_protected = before_core_hardfork_1270 ? - ( after_hardfork_436 - && bitasset.current_feed.settlement_price > ~call_order.call_price ) - : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); - if( feed_protected ) - return margin_called; - - const limit_order_object& limit_order = *limit_itr; - - price match_price = limit_order.sell_price; - // There was a check `match_price.validate();` here, which is removed now because it always passes - - // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606 - if( before_core_hardfork_606 && match_price > ~call_order.call_price ) - return margin_called; - - margin_called = true; - - price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); - // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the - // excess being kept by the asset issuer as a margin call fee. In what follows, we use - // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, - // of course, to determine what the limit order receives. Note margin_call_pays_ratio() returns - // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit. - - // Although we checked for black swan above, we do one more check to ensure the call order can - // pay the amount of collateral which we intend to take from it (including margin call fee). - // TODO refactor code for better performance and readability, perhaps extract the new logic to a new - // function and call it after hf_1270, hf_bsip74 or hf_2481. - auto usd_to_buy = call_order.get_debt(); - if( !after_core_hardfork_2481 && ( usd_to_buy * call_pays_price ) > call_order.get_collateral() ) - { - // Trigger black swan - elog( "black swan detected on asset ${symbol} (${id}) at block ${b}", - ("id",bitasset.asset_id)("symbol",mia.symbol)("b",head_num) ); - edump((enable_black_swan)); - FC_ASSERT( enable_black_swan ); - globally_settle_asset(mia, bitasset.current_feed.settlement_price ); - return true; - } - - if( !before_core_hardfork_1270 ) - { - usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price, + + while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance + // by passing in iterators + && limit_itr != limit_end + && has_call_order() ) + { + bool filled_call = false; + + const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); + + // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 + bool feed_protected = before_core_hardfork_1270 ? + ( after_hardfork_436 + && bitasset.current_feed.settlement_price > ~call_order.call_price ) + : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); + if( feed_protected ) + return margin_called; + + const limit_order_object& limit_order = *limit_itr; + + price match_price = limit_order.sell_price; + // There was a check `match_price.validate();` here, which is removed now because it always passes + + // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606 + if( before_core_hardfork_606 && match_price > ~call_order.call_price ) + return margin_called; + + margin_called = true; + + price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); + // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the + // excess being kept by the asset issuer as a margin call fee. In what follows, we use + // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, + // of course, to determine what the limit order receives. Note margin_call_pays_ratio() returns + // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit. + + // Although we checked for black swan above, we do one more check to ensure the call order can + // pay the amount of collateral which we intend to take from it (including margin call fee). + // TODO refactor code for better performance and readability, perhaps extract the new logic to a new + // function and call it after hf_1270, hf_bsip74 or hf_2481. + auto usd_to_buy = call_order.get_debt(); + if( !after_core_hardfork_2481 && ( usd_to_buy * call_pays_price ) > call_order.get_collateral() ) + { + // Trigger black swan + elog( "black swan detected on asset ${symbol} (${id}) at block ${b}", + ("id",bitasset.asset_id)("symbol",mia.symbol)("b",head_num) ); + edump((enable_black_swan)); + FC_ASSERT( enable_black_swan ); + globally_settle_asset(mia, bitasset.current_feed.settlement_price ); + return true; + } + + if( !before_core_hardfork_1270 ) + { + usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price, bitasset.current_feed.settlement_price, bitasset.current_feed.maintenance_collateral_ratio, bitasset.current_maintenance_collateralization ); - } - else if( !before_core_hardfork_834 ) - { - usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price, + } + else if( !before_core_hardfork_834 ) + { + usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price, bitasset.current_feed.settlement_price, bitasset.current_feed.maintenance_collateral_ratio ); - } - - asset usd_for_sale = limit_order.amount_for_sale(); - asset call_pays, call_receives, limit_pays, limit_receives; - if( usd_to_buy > usd_for_sale ) - { // fill order - limit_receives = usd_for_sale * match_price; // round down, in favor of call order - if( !after_core_hardfork_2481 ) - // TODO add tests about CR change - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) - - // 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): - // * after hard fork core-625, the limit order will be always a maker if entered this function; - // * before hard fork core-625, - // * when the limit order is a taker, it could be paying something for nothing only when - // the call order is smaller and is too small - // * when the limit order is a maker, it won't be paying something for nothing - - if( before_core_hardfork_342 ) - call_receives = usd_for_sale; - else - // The remaining amount in the limit order would be too small, - // 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 = limit_receives.multiply_and_round_up( match_price ); - - if( after_core_hardfork_2481 ) - { - call_pays = call_receives * call_pays_price; // calculate with updated call_receives - if( call_pays.amount >= call_order.collateral ) - break; - auto new_collateral = call_order.get_collateral() - call_pays; - auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math - if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease - break; - } - - filled_limit = true; - - } else { // fill call, could be partial fill due to TCR - call_receives = usd_to_buy; - - if( before_core_hardfork_342 ) - { - limit_receives = usd_to_buy * match_price; // round down, in favor of call order - call_pays = limit_receives; - } else { - call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. - // Note: Due to different rounding, this could potentialy be - // one satoshi more than the blackswan check above - if( call_pays.amount > call_order.collateral ) - { - if( after_core_hardfork_2481 ) - break; - if( mute_exceptions ) - call_pays.amount = call_order.collateral; - } - // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher - // than the old CR, so no additional check for potential blackswan here - - limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order - if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 - limit_receives.amount = call_order.collateral; - // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more - // debt asset, it means the call order could be receiving a bit too much less than its value. - // It is a sad thing for the call order, but it is the rule -- when a call order is margin called, - // it does not get more than it borrowed. - // On the other hand, if the call order is not being closed (due to TCR), - // it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it. - } - - filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) - - if( usd_to_buy == usd_for_sale ) - filled_limit = true; - else if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) - { - //NOTE: Multiple limit match problem (see issue 453, yes this happened) - if( before_hardfork_615 ) - _issue_453_affected_assets.insert( bitasset.asset_id ); - } - } - limit_pays = call_receives; - - // BSIP74: Margin call fee - FC_ASSERT(call_pays >= limit_receives); - const asset margin_call_fee = call_pays - limit_receives; - - if( filled_call && before_core_hardfork_343 ) - ++call_price_itr; - - // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker - fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); - - // Update current_feed after filled call order if needed - if( update_current_feed ) - { - update_bitasset_current_feed( bitasset, true ); - limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); - update_current_feed = bitasset.is_current_feed_price_capped(); - } - - if( !before_core_hardfork_1270 ) - call_collateral_itr = call_collateral_index.lower_bound( call_min ); - else if( !before_core_hardfork_343 ) - call_price_itr = call_price_index.lower_bound( call_min ); - - 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, 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; - - } // while call_itr != call_end - - // Check margin calls against force settlements - if( !skip_matching_settle_orders && after_core_hardfork_2481 && !bitasset.has_settlement() ) - { - // Be here, there exists at least one margin call not processed - bool called_some = match_force_settlements( bitasset ); - if( called_some ) - margin_called = true; - // At last, check for blackswan // TODO perhaps improve performance by passing in iterators - if( bsrm_type::individual_settlement_to_fund == bsrm - || bsrm_type::individual_settlement_to_order == bsrm ) + } + + asset usd_for_sale = limit_order.amount_for_sale(); + asset call_pays, call_receives, limit_pays, limit_receives; + if( usd_to_buy > usd_for_sale ) + { // fill order + limit_receives = usd_for_sale * match_price; // round down, in favor of call order + if( !after_core_hardfork_2481 ) + // TODO add tests about CR change + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + + // 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): + // * after hard fork core-625, the limit order will be always a maker if entered this function; + // * before hard fork core-625, + // * when the limit order is a taker, it could be paying something for nothing only when + // the call order is smaller and is too small + // * when the limit order is a maker, it won't be paying something for nothing + + if( before_core_hardfork_342 ) + call_receives = usd_for_sale; + else + // The remaining amount in the limit order would be too small, + // 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 = limit_receives.multiply_and_round_up( match_price ); + + if( after_core_hardfork_2481 ) + { + call_pays = call_receives * call_pays_price; // calculate with updated call_receives + if( call_pays.amount >= call_order.collateral ) + break; + auto new_collateral = call_order.get_collateral() - call_pays; + auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math + if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease + break; + } + + filled_limit = true; + + } else { // fill call, could be partial fill due to TCR + call_receives = usd_to_buy; + + if( before_core_hardfork_342 ) + { + limit_receives = usd_to_buy * match_price; // round down, in favor of call order + call_pays = limit_receives; + } else { + call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. + // Note: Due to different rounding, this could potentialy be + // one satoshi more than the blackswan check above + if( call_pays.amount > call_order.collateral ) + { + if( after_core_hardfork_2481 ) + break; + if( mute_exceptions ) + call_pays.amount = call_order.collateral; + } + // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher + // than the old CR, so no additional check for potential blackswan here + + limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order + if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 + limit_receives.amount = call_order.collateral; + // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more + // debt asset, it means the call order could be receiving a bit too much less than its value. + // It is a sad thing for the call order, but it is the rule + // -- when a call order is margin called, it does not get more than it borrowed. + // On the other hand, if the call order is not being closed (due to TCR), + // it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it. + } + + filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) + + if( usd_to_buy == usd_for_sale ) + filled_limit = true; + else if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) + { + //NOTE: Multiple limit match problem (see issue 453, yes this happened) + if( before_hardfork_615 ) + _issue_453_affected_assets.insert( bitasset.asset_id ); + } + } + limit_pays = call_receives; + + // BSIP74: Margin call fee + FC_ASSERT(call_pays >= limit_receives); + const asset margin_call_fee = call_pays - limit_receives; + + if( filled_call && before_core_hardfork_343 ) + ++call_price_itr; + + // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker + fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); + + // Update current_feed after filled call order if needed + if( update_current_feed ) + { + update_bitasset_current_feed( bitasset, true ); + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + update_current_feed = bitasset.is_current_feed_price_capped(); + } + + if( !before_core_hardfork_1270 ) + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + else if( !before_core_hardfork_343 ) + call_price_itr = call_price_index.lower_bound( call_min ); + + 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, 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; + + } // while call_itr != call_end + + // Check margin calls against force settlements + if( !skip_matching_settle_orders && after_core_hardfork_2481 && !bitasset.has_settlement() ) { - // Run multiple times, each time one call order gets settled - // TODO perhaps improve performance by settling multiple call orders inside in one call - while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) + // Be here, there exists at least one margin call not processed + bool called_some = match_force_settlements( bitasset ); + if( called_some ) + { + margin_called = true; + // if called some, it means the call order is updated or removed, + // in this case, if update_current_feed is true, + // it is possible that there are limit orders able to get filled, + // so we need to check again + if( update_current_feed ) + { + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + update_current_feed = bitasset.is_current_feed_price_capped(); + continue; + } + } + // At last, check for blackswan // TODO perhaps improve performance by passing in iterators + if( bsrm_type::individual_settlement_to_fund == bsrm + || bsrm_type::individual_settlement_to_order == bsrm ) { - // do nothing + // Run multiple times, each time one call order gets settled + // TODO perhaps improve performance by settling multiple call orders inside in one call + while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) + { + // do nothing + } } + else + check_for_blackswan( mia, enable_black_swan, &bitasset ); } - else - check_for_blackswan( mia, enable_black_swan, &bitasset ); - } + break; + } // while true - return margin_called; + return margin_called; } FC_CAPTURE_AND_RETHROW() } bool database::match_force_settlements( const asset_bitasset_data_object& bitasset ) @@ -1932,15 +1953,15 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price, &margin_call_pays_ratio ); - if( !margin_called && result.amount > 0 ) - margin_called = true; - - if( update_call_price ) + // if result.amount > 0, it means the call order got updated or removed + if( result.amount > 0 ) { - call_match_price = bitasset.get_margin_call_order_price(); - call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - update_call_price = bitasset.is_current_feed_price_capped(); + // if update_call_price is true, we need to check limit orders first, so we return + if( update_call_price ) + return true; + margin_called = true; } + // else : result.amount == 0, it means the settle order got canceled directly and the call order did not change settle_itr = settlement_index.lower_bound( bitasset.asset_id ); call_itr = call_collateral_index.lower_bound( call_min ); From b50b2f82c6290ae6a273a72b597d65bcdf500dd4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 22:18:24 +0000 Subject: [PATCH 43/99] Fix no_settlement tests about feed update --- tests/tests/bsrm_tests.cpp | 95 +++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_tests.cpp index f525bc7ca5..d156fcd12b 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -801,21 +801,27 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - // seller2 sells some, due to MCFR, this order won't be filled + // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + + // seller2 sells more, this order won't be filled in the beginning either const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); BOOST_REQUIRE( sell_high ); limit_order_id_type sell_high_id = sell_high->id; BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - // seller2 sells more, due to MCFR, this order won't be filled in the beginning, but will be filled later - const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); - BOOST_REQUIRE( sell_mid ); - limit_order_id_type sell_mid_id = sell_mid->id; - BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // 500 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); // 500 - 100 - 100 - 100 BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // seller sells some, this order will be filled @@ -824,7 +830,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // check @@ -851,28 +857,41 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); // now feed price is 13:10 * (1000-111):(2750-111*275/100) // = 13:10 * 889:2445 = 11557:24450 + // call order match price is 1300:1299 * 889:2445 = 0.363879089 + // sell_mid's price is 100/210 = 0.047619048 - // seller2's sell_mid got filled too + // sell_mid got filled too BOOST_CHECK( !db.find( sell_mid_id ) ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price - BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // call pays round_down(210*1300/1299) = 210, fee = 0 // now feed price is 13:10 * (889-100):(2445-210) // = 13:10 * 789:2235 = 10257:22350 + // call order match price is 1300:1299 * 789:2235 = 0.353291897 + // sell_high's price is 100/275 = 0.363636364 - BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + // sell_high got filled too + BOOST_CHECK( !db.find( sell_high_id ) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); + // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price + // sell_high was selling 100 MPA for 275 CORE as maker, matched at its price + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 + // call pays round_down(275*1300/1299) = 275, fee = 0 + // now feed price is 13:10 * (789-100):(2235-275) + // = 13:10 * 689:1960 = 8957:19600 (>10/22) + // call order match price is 1300:1299 * 689:1960 = 0.351801229 + // sell_highest's price is 100/285 = 0.350877193 // check BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price - == price( asset(10257,mpa_id), asset(22350) ) ); + == price( asset(8957,mpa_id), asset(19600) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK_EQUAL( call_id(db).debt.value, 789 ); - BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2235 ); + BOOST_CHECK_EQUAL( call_id(db).debt.value, 689 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1960 ); BOOST_CHECK( !db.find(call2_id) ); BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); @@ -884,16 +903,16 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) auto final_check = [&] { - BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 211 ); - BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 311 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000 - // 2403 + round_up(789*(22350/10257)*(1299/1000)) - // 2403 + 2234 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4637 ); + // 2403 + round_up(689*(19600/8957)*(1299/1000)) + // 2403 + 1959 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4362 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // no change // check BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); @@ -1053,21 +1072,27 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - // seller2 sells some + // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + + // seller2 sells more, this order won't be filled in the beginning either const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); BOOST_REQUIRE( sell_high ); limit_order_id_type sell_high_id = sell_high->id; BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - // seller2 sells more, due to MCFR, this order won't be filled in the beginning, but will be filled later - const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); - BOOST_REQUIRE( sell_mid ); - limit_order_id_type sell_mid_id = sell_mid->id; - BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // 500 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); // 500 - 100 - 100 - 100 BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // seller settles some @@ -1077,7 +1102,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // check @@ -1118,13 +1143,15 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) // then sell_high got filled by call BOOST_CHECK( !db.find( sell_high_id ) ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price // sell_high was selling 100 MPA for 275 CORE as maker, matched at its price BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 // call pays round_down(275*1300/1299) = 275, fee = 0 // now feed price is 13:10 * (1000-100-100):(2750-210-275) // = 13:10 * 800:2265 = 10400:22650 = 208:453 (>10/22) + // call order match price is 1300:1299 * 800:2265 = 0.353472785 + // sell_highest's price is 100/285 = 0.350877193 // then the settle order got filled by call BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1389 ); // 2500 - 111 - 1000 @@ -1133,6 +1160,8 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2412 ); // now feed price is 13:10 * (800-111):(2265-111*(22650/10400)*(13/10)) // = 13:10 * 689:1951 = 8957:19510 (>10/22) + // call order match price is 1300:1299 * 689:1951 = 0.353424094 + // sell_highest's price is 100/285 = 0.350877193 // check BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); @@ -1155,12 +1184,14 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) { BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 311 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000 // 2412 + round_up(689*(19510/8957)*(1299/1000)) // 2412 + 1950 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4362 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // no change // check From 3a7a6c0956c6e06fb02e62e1e3de2137f4e96928 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 01:40:31 +0000 Subject: [PATCH 44/99] Reduce number of params for db::match(limit,call) --- libraries/chain/db_market.cpp | 16 +++++++--------- .../chain/include/graphene/chain/database.hpp | 10 +--------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f46ab033e0..fe863d6448 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -647,9 +647,6 @@ bool database::apply_order(const limit_order_object& new_order_object) break; // hard fork core-338 and core-625 took place at same time, not checking HARDFORK_CORE_338_TIME here. const auto match_result = match( new_order_object, *call_itr, call_match_price, - sell_abd->current_feed.settlement_price, - sell_abd->current_feed.maintenance_collateral_ratio, - sell_abd->current_maintenance_collateralization, *sell_abd, call_pays_price ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. @@ -687,10 +684,7 @@ bool database::apply_order(const limit_order_object& new_order_object) break; // assume hard fork core-338 and core-625 will take place at same time, // not checking HARDFORK_CORE_338_TIME here. - const auto match_result = match( new_order_object, *call_itr, call_match_price, - sell_abd->current_feed.settlement_price, - sell_abd->current_feed.maintenance_collateral_ratio, - optional(), *sell_abd ); + const auto match_result = match( new_order_object, *call_itr, call_match_price, *sell_abd ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hard fork core-834), @@ -981,8 +975,6 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde database::match_result_type database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization, const asset_bitasset_data_object& bitasset, const price& call_pays_price ) { @@ -992,6 +984,12 @@ database::match_result_type database::match( const limit_order_object& bid, cons bool cull_taker = false; + const auto& feed_price = bitasset.current_feed.settlement_price; + const auto& maintenance_collateral_ratio = bitasset.current_feed.maintenance_collateral_ratio; + optional maintenance_collateralization; + if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME ) // call price caching issue + maintenance_collateralization = bitasset.current_maintenance_collateralization; + asset usd_for_sale = bid.amount_for_sale(); asset usd_to_buy = asset( ask.get_max_debt_to_cover( call_pays_price, feed_price, maintenance_collateral_ratio, maintenance_collateralization ), ask.debt_type() ); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 7fb1681fd2..2f3452ae90 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -498,9 +498,6 @@ namespace graphene { namespace chain { * @param taker the order that is removing liquidity from the book * @param maker the order that put liquidity on the book * @param trade_price the price the trade should execute at - * @param feed_price the price of the current feed - * @param maintenance_collateral_ratio the maintenance collateral ratio - * @param maintenance_collateralization the maintenance collateralization * @param bitasset the bitasset object corresponding to debt asset of the call order * @param call_pays_price price call order pays. Call order may pay more collateral * than limit order takes if call order subject to a margin call fee. @@ -508,18 +505,13 @@ namespace graphene { namespace chain { */ match_result_type match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization, const asset_bitasset_data_object& bitasset, const price& call_pays_price); /// If separate call_pays_price not provided, assume call pays at trade_price: match_result_type match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization, const asset_bitasset_data_object& bitasset) { - return match(taker, maker, trade_price, feed_price, maintenance_collateral_ratio, - maintenance_collateralization, bitasset, trade_price); + return match(taker, maker, trade_price, bitasset, trade_price); } ///@} From d6b3d43b8886bffebb752301c65fb4bcdfa5b74f Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 02:00:29 +0000 Subject: [PATCH 45/99] Reduce number of params for db::match(settle,call) --- libraries/chain/db_market.cpp | 38 +++++-------------- .../chain/include/graphene/chain/database.hpp | 12 ++---- 2 files changed, 13 insertions(+), 37 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index fe863d6448..a4ea0e34f7 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -778,8 +778,6 @@ void database::apply_force_settlement( const force_settlement_object& new_settle bitasset.current_maintenance_collateralization ), new_settlement.balance.asset_id ); - // Note: the call order should be able to pay at call_pays_price, - // thus no need to pass in margin_call_pays_ratio match( new_settlement, *call_itr, call_pays_price, bitasset, max_debt_to_cover, call_match_price, true ); // Check whether the new order is gone @@ -1051,11 +1049,9 @@ asset database::match( const force_settlement_object& settle, const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, - bool is_margin_call, - const ratio_type* margin_call_pays_ratio ) + bool is_margin_call ) { - return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, is_margin_call, true, - margin_call_pays_ratio ); + return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, is_margin_call, true ); } asset database::match( const call_order_object& call, @@ -1063,11 +1059,9 @@ asset database::match( const call_order_object& call, const price& match_price, const asset_bitasset_data_object& bitasset, const asset& max_settlement, - const price& fill_price, - const ratio_type* margin_call_pays_ratio ) + const price& fill_price ) { - return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, true, false, - margin_call_pays_ratio ); + return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, true, false ); } asset database::match_impl( const force_settlement_object& settle, @@ -1077,8 +1071,7 @@ asset database::match_impl( const force_settlement_object& settle, const asset& max_settlement, const price& p_fill_price, bool is_margin_call, - bool settle_is_taker, - const ratio_type* margin_call_pays_ratio ) + bool settle_is_taker ) { try { FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id ); FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0); @@ -1141,6 +1134,7 @@ asset database::match_impl( const force_settlement_object& settle, } // end : if after the core-184 hf and call_pays.amount == 0 else if( !before_core_hardfork_342 && call_pays.amount != 0 ) { + auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio(); // be here, the call order is not paying nothing, // but it is still possible that the settle order is paying more than minimum required due to rounding if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order @@ -1152,7 +1146,7 @@ asset database::match_impl( const force_settlement_object& settle, { call_pays.amount = call.collateral; match_price = call_debt / call_collateral; - fill_price = margin_call_pays_ratio ? ( match_price / (*margin_call_pays_ratio) ) : match_price; + fill_price = match_price / margin_call_pays_ratio; } settle_receives = call_receives.multiply_and_round_up( fill_price ); } @@ -1220,17 +1214,8 @@ asset database::match_impl( const force_settlement_object& settle, // price changed, update call_receives // round up to mitigate rounding issues (hf core-342) call_receives = call_pays.multiply_and_round_up( match_price ); // round up // update fill price and settle_receives - if( margin_call_pays_ratio ) // check to be defensive - { - fill_price = match_price / (*margin_call_pays_ratio); - settle_receives = call_receives * fill_price; // round down here, in favor of call order - } - else // normally this code won't be reached. be defensive here - { - wlog( "Unexpected: margin_call_pays_ratio == nullptr" ); - fill_price = match_price; - settle_receives = call_pays; - } + fill_price = match_price / margin_call_pays_ratio; + settle_receives = call_receives * fill_price; // round down here, in favor of call order } if( settle_receives.amount == 0 ) settle_receives.amount = 1; // reduce margin-call fee in this case. Note: here call_pays >= 1 @@ -1920,8 +1905,6 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // It is in debt/collateral . price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio(); - // Note: when BSRM is no_settlement, current_feed can change after filled a call order, // so we recalculate inside the loop using bsrm_type = bitasset_options::black_swan_response_type; @@ -1948,8 +1931,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // Note: if the call order's CR is too low, it is probably unable to fill at call_pays_price. // In this case, the call order pays at its CR, the settle order may receive less due to margin call fee. // It is processed inside the function. - auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price, - &margin_call_pays_ratio ); + auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price ); // if result.amount > 0, it means the call order got updated or removed if( result.amount > 0 ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 2f3452ae90..a6736096ca 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -429,7 +429,6 @@ namespace graphene { namespace chain { /// how much the settle order receives when the call order is being margin called /// @param is_margin_call whether the call order is being margin called /// @param settle_order_is_taker whether the settle_order is the taker - /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match_impl( const force_settlement_object& settle, const call_order_object& call, @@ -438,8 +437,7 @@ namespace graphene { namespace chain { const asset& max_settlement, const price& fill_price, bool is_margin_call = false, - bool settle_order_is_taker = true, - const ratio_type* margin_call_pays_ratio = nullptr ); + bool settle_order_is_taker = true ); public: @@ -525,7 +523,6 @@ namespace graphene { namespace chain { /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives when the call order is being margin called /// @param is_margin_call whether the call order is being margin called - /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match( const force_settlement_object& settle, const call_order_object& call, @@ -533,8 +530,7 @@ namespace graphene { namespace chain { const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, - bool is_margin_call = false, - const ratio_type* margin_call_pays_ratio = nullptr ); + bool is_margin_call = false ); /// Matches the two orders, the first parameter is taker, the second is maker. /// @param call the call order being margin called @@ -544,15 +540,13 @@ namespace graphene { namespace chain { /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives. - /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, const asset_bitasset_data_object& bitasset, const asset& max_settlement, - const price& fill_price, - const ratio_type* margin_call_pays_ratio = nullptr ); + const price& fill_price ); /** * @brief fills limit order From e7b12450af62e8d12a7a07c147fd3bf925cbc00b Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 19:48:34 +0000 Subject: [PATCH 46/99] Simplify code --- libraries/chain/asset_evaluator.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index b35ac6441f..9b184814f0 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -946,10 +946,8 @@ static bool update_bitasset_object_options( if( should_update_feeds || update_feeds_due_to_bsrm_change ) { const auto old_feed = bdo.current_feed; - if( should_update_feeds ) - db.update_bitasset_current_feed( bdo ); - else // to update feeds due to bsrm change - db.update_bitasset_current_feed( bdo, true ); + // skip recalculating median feed if it is not needed + db.update_bitasset_current_feed( bdo, !should_update_feeds ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); From 5415c03d412d35cc5984a328f667e01b45c47904 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 19:55:43 +0000 Subject: [PATCH 47/99] Move check_for_blackswan from db_update to market --- libraries/chain/db_market.cpp | 151 ++++++++++++++++++++++++++++++++++ libraries/chain/db_update.cpp | 151 ---------------------------------- 2 files changed, 151 insertions(+), 151 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index a4ea0e34f7..b9ec5029c1 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -47,6 +47,157 @@ namespace detail { } //detail +/** + * let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral) + * let SP = current median feed's Settlement Price + * let LC = the least collateralized call order's swan price (debt/collateral) + * + * If there is no valid price feed or no bids then there is no black swan. + * + * A black swan occurs if MAX(HB,SP) <= LC + */ +bool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan, + const asset_bitasset_data_object* bitasset_ptr ) +{ + if( !mia.is_market_issued() ) return false; + + const asset_bitasset_data_object& bitasset = bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this); + if( bitasset.has_settlement() ) return true; // already force settled + auto settle_price = bitasset.current_feed.settlement_price; + if( settle_price.is_null() ) return false; // no feed + + asset_id_type debt_asset_id = bitasset.asset_id; + + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls + + // After core-2481 hard fork, if there are force-settlements, match call orders with them first + if( after_core_hardfork_2481 ) + { + const auto& settlement_index = get_index_type().indices().get(); + auto lower_itr = settlement_index.lower_bound( debt_asset_id ); + if( lower_itr != settlement_index.end() && lower_itr->balance.asset_id == debt_asset_id ) + return false; + } + + // Find the call order with the least collateral ratio + const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false ); + if( !call_ptr ) // no call order + return false; + + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + + price highest = settle_price; + // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. + // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. + // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. + if( bsrm_type::individual_settlement_to_fund == bsrm ) + highest = bitasset.median_feed.max_short_squeeze_price(); + else if( !before_core_hardfork_1270 ) + highest = bitasset.current_feed.max_short_squeeze_price(); + else if( maint_time > HARDFORK_CORE_338_TIME ) + highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); + // else do nothing + + const limit_order_index& limit_index = get_index_type(); + const auto& limit_price_index = limit_index.indices().get(); + + // looking for limit orders selling the most USD for the least CORE + auto highest_possible_bid = price::max( debt_asset_id, bitasset.options.short_backing_asset ); + // stop when limit orders are selling too little USD for too much CORE + auto lowest_possible_bid = price::min( debt_asset_id, bitasset.options.short_backing_asset ); + + FC_ASSERT( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id ); + // NOTE limit_price_index is sorted from greatest to least + auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); + auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); + + if( limit_itr != limit_end ) + { + FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); + auto call_pays_price = limit_itr->sell_price; + if( after_core_hardfork_2481 ) + { + // due to margin call fee, we check with MCPP (margin call pays price) here + call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); + } + highest = std::max( call_pays_price, highest ); + } + + // The variable `highest` after hf_338: + // * if no limit order, it is expected to be the black swan price; if the call order with the least CR + // has CR below or equal to the black swan price, we trigger GS, + // * if there exists at least one limit order and the price is higher, we use the limit order's price, + // which means we will match the margin call orders with the limit order first. + // + // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered + // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got + // caught by an additional check in check_call_orders(). + // This bug is fixed in hf_2481. Actually, after hf_2481, + // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), + // * if there is no force settlement, we check here with margin call fee in consideration. + + auto least_collateral = call_ptr->collateralization(); + // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, + // if the matching limit order is smaller, due to rounding, it is still possible that the + // call order's collateralization would increase and become higher than ~highest after matched. + // However, for simplicity, we only compare the prices here. + bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); + if( is_blackswan ) + { + wdump( (*call_ptr) ); + elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" + " Least collateralized call: ${lc} ${~lc}\n" + // " Highest Bid: ${hb} ${~hb}\n" + " Settle Price: ${~sp} ${sp}\n" + " Max: ${~h} ${h}\n", + ("id",mia.id)("symbol",mia.symbol)("b",head_block_num()) + ("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real()) + // ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real()) + ("sp",settle_price.to_real())("~sp",(~settle_price).to_real()) + ("h",highest.to_real())("~h",(~highest).to_real()) ); + edump((enable_black_swan)); + FC_ASSERT( enable_black_swan, + "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); + + if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) + { + individually_settle( bitasset, *call_ptr ); + } + // Global settlement or no settlement, but we should not be here if BSRM is no_settlement + else if( after_core_hardfork_2481 ) + { + if( bsrm_type::no_settlement == bsrm ) // this should not happen, be defensive here + wlog( "Internal error: BSRM is no_settlement but undercollateralization occurred" ); + // After hf_2481, when a global settlement occurs, + // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and + // they are closed at the same price, + // * the debt positions with CR > MCR do not pay premium or margin call fee, and they are closed at a same + // price too. + // * The GS price would close the position with the least CR with no collateral left for the owner, + // but would close other positions with some collateral left (if any) for their owners. + // * Both the premium and the margin call fee paid by the margin calls go to the asset owner, none will go + // to the global settlement fund, because + // - if a part of the premium or fees goes to the global settlement fund, it means there would be a + // difference in settlement prices, so traders are incentivized to create new debt in the last minute + // then settle after GS to earn free money, + // - if no premium or fees goes to the global settlement fund, it means debt asset holders would only + // settle for less after GS, so they are incentivized to settle before GS which helps avoid GS. + globally_settle_asset(mia, ~least_collateral, true ); + } + else if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price ) + // global settle at feed price if possible + globally_settle_asset(mia, settle_price ); + else + globally_settle_asset(mia, ~least_collateral ); + return true; + } + return false; +} + /** * All margin positions are force closed at the swan price * Collateral received goes into a force-settlement fund diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index d9924039f9..c3ce21888f 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -174,157 +174,6 @@ void database::clear_expired_proposals() } } -/** - * let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral) - * let SP = current median feed's Settlement Price - * let LC = the least collateralized call order's swan price (debt/collateral) - * - * If there is no valid price feed or no bids then there is no black swan. - * - * A black swan occurs if MAX(HB,SP) <= LC - */ -bool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan, - const asset_bitasset_data_object* bitasset_ptr ) -{ - if( !mia.is_market_issued() ) return false; - - const asset_bitasset_data_object& bitasset = bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this); - if( bitasset.has_settlement() ) return true; // already force settled - auto settle_price = bitasset.current_feed.settlement_price; - if( settle_price.is_null() ) return false; // no feed - - asset_id_type debt_asset_id = bitasset.asset_id; - - auto maint_time = get_dynamic_global_properties().next_maintenance_time; - bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue - bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls - - // After core-2481 hard fork, if there are force-settlements, match call orders with them first - if( after_core_hardfork_2481 ) - { - const auto& settlement_index = get_index_type().indices().get(); - auto lower_itr = settlement_index.lower_bound( debt_asset_id ); - if( lower_itr != settlement_index.end() && lower_itr->balance.asset_id == debt_asset_id ) - return false; - } - - // Find the call order with the least collateral ratio - const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false ); - if( !call_ptr ) // no call order - return false; - - using bsrm_type = bitasset_options::black_swan_response_type; - const auto bsrm = bitasset.get_black_swan_response_method(); - - price highest = settle_price; - // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here - // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. - // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. - // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. - if( bsrm_type::individual_settlement_to_fund == bsrm ) - highest = bitasset.median_feed.max_short_squeeze_price(); - else if( !before_core_hardfork_1270 ) - highest = bitasset.current_feed.max_short_squeeze_price(); - else if( maint_time > HARDFORK_CORE_338_TIME ) - highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); - // else do nothing - - const limit_order_index& limit_index = get_index_type(); - const auto& limit_price_index = limit_index.indices().get(); - - // looking for limit orders selling the most USD for the least CORE - auto highest_possible_bid = price::max( debt_asset_id, bitasset.options.short_backing_asset ); - // stop when limit orders are selling too little USD for too much CORE - auto lowest_possible_bid = price::min( debt_asset_id, bitasset.options.short_backing_asset ); - - FC_ASSERT( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id ); - // NOTE limit_price_index is sorted from greatest to least - auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); - auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); - - if( limit_itr != limit_end ) - { - FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); - auto call_pays_price = limit_itr->sell_price; - if( after_core_hardfork_2481 ) - { - // due to margin call fee, we check with MCPP (margin call pays price) here - call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); - } - highest = std::max( call_pays_price, highest ); - } - - // The variable `highest` after hf_338: - // * if no limit order, it is expected to be the black swan price; if the call order with the least CR - // has CR below or equal to the black swan price, we trigger GS, - // * if there exists at least one limit order and the price is higher, we use the limit order's price, - // which means we will match the margin call orders with the limit order first. - // - // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered - // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got - // caught by an additional check in check_call_orders(). - // This bug is fixed in hf_2481. Actually, after hf_2481, - // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), - // * if there is no force settlement, we check here with margin call fee in consideration. - - auto least_collateral = call_ptr->collateralization(); - // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, - // if the matching limit order is smaller, due to rounding, it is still possible that the - // call order's collateralization would increase and become higher than ~highest after matched. - // However, for simplicity, we only compare the prices here. - bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); - if( is_blackswan ) - { - wdump( (*call_ptr) ); - elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" - " Least collateralized call: ${lc} ${~lc}\n" - // " Highest Bid: ${hb} ${~hb}\n" - " Settle Price: ${~sp} ${sp}\n" - " Max: ${~h} ${h}\n", - ("id",mia.id)("symbol",mia.symbol)("b",head_block_num()) - ("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real()) - // ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real()) - ("sp",settle_price.to_real())("~sp",(~settle_price).to_real()) - ("h",highest.to_real())("~h",(~highest).to_real()) ); - edump((enable_black_swan)); - FC_ASSERT( enable_black_swan, - "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - - if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) - { - individually_settle( bitasset, *call_ptr ); - } - // Global settlement or no settlement, but we should not be here if BSRM is no_settlement - else if( after_core_hardfork_2481 ) - { - if( bsrm_type::no_settlement == bsrm ) // this should not happen, be defensive here - wlog( "Internal error: BSRM is no_settlement but undercollateralization occurred" ); - // After hf_2481, when a global settlement occurs, - // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and - // they are closed at the same price, - // * the debt positions with CR > MCR do not pay premium or margin call fee, and they are closed at a same - // price too. - // * The GS price would close the position with the least CR with no collateral left for the owner, - // but would close other positions with some collateral left (if any) for their owners. - // * Both the premium and the margin call fee paid by the margin calls go to the asset owner, none will go - // to the global settlement fund, because - // - if a part of the premium or fees goes to the global settlement fund, it means there would be a - // difference in settlement prices, so traders are incentivized to create new debt in the last minute - // then settle after GS to earn free money, - // - if no premium or fees goes to the global settlement fund, it means debt asset holders would only - // settle for less after GS, so they are incentivized to settle before GS which helps avoid GS. - globally_settle_asset(mia, ~least_collateral, true ); - } - else if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price ) - // global settle at feed price if possible - globally_settle_asset(mia, settle_price ); - else - globally_settle_asset(mia, ~least_collateral ); - return true; - } - return false; -} - // Helper function to check whether we need to udpate current_feed.settlement_price. static optional get_derived_current_feed_price( const database& db, const asset_bitasset_data_object& bitasset ) From 221cd45956f2fd983eac5cdd83f396df810ffbfb Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 21:00:46 +0000 Subject: [PATCH 48/99] Update order of members in database.hpp --- libraries/chain/db_notify.cpp | 4 +- .../chain/include/graphene/chain/database.hpp | 270 +++++++++--------- 2 files changed, 142 insertions(+), 132 deletions(-) diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 627aff52d2..a2c2c87210 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -387,6 +387,7 @@ struct get_impacted_account_visitor } // namespace detail +// Declared in impacted.hpp void operation_get_impacted_accounts( const operation& op, flat_set& result, bool ignore_custom_op_required_auths ) { @@ -395,6 +396,7 @@ void operation_get_impacted_accounts( const operation& op, flat_set& result, bool ignore_custom_op_required_auths ) { @@ -402,7 +404,7 @@ void transaction_get_impacted_accs( const transaction& tx, flat_set& accounts, +static void get_relevant_accounts( const object* obj, flat_set& accounts, bool ignore_custom_op_required_auths ) { FC_ASSERT( obj != nullptr, "Internal error: get_relevant_accounts called with nullptr" ); // This should not happen if( obj->id.space() == protocol_ids ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index a6736096ca..fecb6e6acc 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -69,11 +69,10 @@ namespace graphene { namespace chain { */ class database : public db::object_database { - public: //////////////////// db_management.cpp //////////////////// - + public: database(); - ~database(); + virtual ~database(); enum validation_steps { @@ -82,8 +81,8 @@ namespace graphene { namespace chain { skip_transaction_signatures = 1 << 1, ///< used by non-witness nodes skip_transaction_dupe_check = 1 << 2, ///< used while reindexing skip_block_size_check = 1 << 4, ///< used when applying locally generated transactions - skip_tapos_check = 1 << 5, ///< used while reindexing -- note this skips expiration check as well - // skip_authority_check = 1 << 6, ///< removed because effectively identical to skip_transaction_signatures + skip_tapos_check = 1 << 5, ///< used while reindexing -- note this skips expiration check too + // skip_authority_check = 1 << 6, // removed, effectively identical to skip_transaction_signatures skip_merkle_check = 1 << 7, ///< used while reindexing skip_assert_evaluation = 1 << 8, ///< used while reindexing skip_undo_history_check = 1 << 9, ///< used while reindexing @@ -126,101 +125,6 @@ namespace graphene { namespace chain { void wipe(const fc::path& data_dir, bool include_blocks); void close(bool rewind = true); - //////////////////// db_block.cpp //////////////////// - - /** - * @return true if the block is in our fork DB or saved to disk as - * part of the official chain, otherwise return false - */ - bool is_known_block( const block_id_type& id )const; - bool is_known_transaction( const transaction_id_type& id )const; - block_id_type get_block_id_for_num( uint32_t block_num )const; - optional fetch_block_by_id( const block_id_type& id )const; - optional fetch_block_by_number( uint32_t num )const; - const signed_transaction& get_recent_transaction( const transaction_id_type& trx_id )const; - std::vector get_block_ids_on_fork(block_id_type head_of_fork) const; - - /** - * Calculate the percent of block production slots that were missed in the - * past 128 blocks, not including the current block. - */ - uint32_t witness_participation_rate()const; - - void add_checkpoints( const flat_map& checkpts ); - const flat_map get_checkpoints()const { return _checkpoints; } - bool before_last_checkpoint()const; - - bool push_block( const signed_block& b, uint32_t skip = skip_nothing ); - processed_transaction push_transaction( const precomputable_transaction& trx, uint32_t skip = skip_nothing ); - bool _push_block( const signed_block& b ); - processed_transaction _push_transaction( const precomputable_transaction& trx ); - - ///@throws fc::exception if the proposed transaction fails to apply. - processed_transaction push_proposal( const proposal_object& proposal ); - - signed_block generate_block( - const fc::time_point_sec when, - witness_id_type witness_id, - const fc::ecc::private_key& block_signing_private_key, - uint32_t skip - ); - signed_block _generate_block( - const fc::time_point_sec when, - witness_id_type witness_id, - const fc::ecc::private_key& block_signing_private_key - ); - - void pop_block(); - void clear_pending(); - - /** - * This method is used to track appied operations during the evaluation of a block, these - * operations should include any operation actually included in a transaction as well - * as any implied/virtual operations that resulted, such as filling an order. The - * applied operations is cleared after applying each block and calling the block - * observers which may want to index these operations. - * - * @return the op_id which can be used to set the result after it has finished being applied. - */ - uint32_t push_applied_operation( const operation& op ); - void set_applied_operation_result( uint32_t op_id, const operation_result& r ); - const vector >& get_applied_operations()const; - - string to_pretty_string( const asset& a )const; - - /** - * This signal is emitted after all operations and virtual operation for a - * block have been applied but before the get_applied_operations() are cleared. - * - * You may not yield from this callback because the blockchain is holding - * the write lock and may be in an "inconstant state" until after it is - * released. - */ - fc::signal applied_block; - - /** - * This signal is emitted any time a new transaction is added to the pending - * block state. - */ - fc::signal on_pending_transaction; - - /** - * Emitted After a block has been applied and committed. The callback - * should not yield and should execute quickly. - */ - fc::signal&, const flat_set&)> new_objects; - - /** - * Emitted After a block has been applied and committed. The callback - * should not yield and should execute quickly. - */ - fc::signal&, const flat_set&)> changed_objects; - - /** this signal is emitted any time an object is removed and contains a - * pointer to the last value of every object that was removed. - */ - fc::signal&, const vector&, const flat_set&)> removed_objects; - //////////////////// db_witness_schedule.cpp //////////////////// /** @@ -259,10 +163,19 @@ namespace graphene { namespace chain { */ uint32_t get_slot_at_time(fc::time_point_sec when)const; + /** + * Calculate the percent of block production slots that were missed in the + * past 128 blocks, not including the current block. + */ + uint32_t witness_participation_rate()const; + private: + uint32_t update_witness_missed_blocks( const signed_block& b ); + void update_witness_schedule(); //////////////////// db_getter.cpp //////////////////// + public: const chain_id_type& get_chain_id()const; const asset_object& get_core_asset()const; const asset_dynamic_data_object& get_core_dynamic_data()const; @@ -312,9 +225,10 @@ namespace graphene { namespace chain { //////////////////// db_init.cpp //////////////////// ///@{ - void initialize_evaluators(); /// Reset the object graph in-memory - void initialize_indexes(); + void initialize_indexes(); // Mark as public since it is used in tests + private: + void initialize_evaluators(); void init_genesis(const genesis_state_type& genesis_state = genesis_state_type()); template @@ -323,10 +237,11 @@ namespace graphene { namespace chain { _operation_evaluators[operation::tag::value] = std::make_unique>(); } - ///@} + //////////////////// db_balance.cpp //////////////////// + public: /** * @brief Retrieve a particular account's balance in a given asset * @param owner Account whose balance should be retrieved @@ -345,7 +260,7 @@ namespace graphene { namespace chain { void adjust_balance(account_id_type account, asset delta); void deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta); - /** + /** * @brief Retrieve a particular account's market fee vesting balance in a given asset * @param account_id Account whose balance should be retrieved * @param asset_id ID of the asset to get balance in @@ -374,11 +289,13 @@ namespace graphene { namespace chain { account_id_type req_owner, bool require_vesting ); - // helper to handle cashback rewards + /// helper to handle cashback rewards void deposit_cashback(const account_object& acct, share_type amount, bool require_vesting = true); - // helper to handle witness pay + /// helper to handle witness pay void deposit_witness_pay(const witness_object& wit, share_type amount); + string to_pretty_string( const asset& a )const; + //////////////////// db_debug.cpp //////////////////// void debug_dump(); @@ -405,6 +322,9 @@ namespace graphene { namespace chain { share_type collateral_from_fund, const price_feed& current_feed ); private: + void _cancel_bids_and_revive_mpa( const asset_object& bitasset, const asset_bitasset_data_object& bad ); + bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, + const asset_bitasset_data_object* bitasset_ptr = nullptr ); template void globally_settle_asset_impl( const asset_object& bitasset, const price& settle_price, @@ -439,7 +359,6 @@ namespace graphene { namespace chain { bool is_margin_call = false, bool settle_order_is_taker = true ); - public: /** * @brief Process a new limit order through the markets @@ -467,6 +386,12 @@ namespace graphene { namespace chain { const asset_bitasset_data_object& bitasset, const asset_object& asset_obj ); + bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, + bool for_new_limit_order = false, + const asset_bitasset_data_object* bitasset_ptr = nullptr, + bool mute_exceptions = false, + bool skip_matching_settle_orders = false ); + /** * Matches the two orders, the first parameter is taker, the second is maker. * @@ -485,6 +410,8 @@ namespace graphene { namespace chain { only_maker_filled = 2, both_filled = 3 }; + + private: match_result_type match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price ); match_result_type match_limit_normal_limit( const limit_order_object& taker, const limit_order_object& maker, @@ -588,16 +515,11 @@ namespace graphene { namespace chain { bool fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives, const price& fill_price, bool is_maker, bool pay_force_settle_fee = true ); - bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, - bool for_new_limit_order = false, - const asset_bitasset_data_object* bitasset_ptr = nullptr, - bool mute_exceptions = false, - bool skip_matching_settle_orders = false ); - /// helpers to fill_order /// @{ void pay_order( const account_object& receiver, const asset& receives, const asset& pays ); + public: /** * @brief Calculate the market fee that is to be taken * @param trade_asset the asset (passed in to avoid a lookup) @@ -612,6 +534,97 @@ namespace graphene { namespace chain { /// @} ///@} + //////////////////// db_block.cpp //////////////////// + + /** + * @return true if the block is in our fork DB or saved to disk as + * part of the official chain, otherwise return false + */ + bool is_known_block( const block_id_type& id )const; + bool is_known_transaction( const transaction_id_type& id )const; + block_id_type get_block_id_for_num( uint32_t block_num )const; + optional fetch_block_by_id( const block_id_type& id )const; + optional fetch_block_by_number( uint32_t num )const; + const signed_transaction& get_recent_transaction( const transaction_id_type& trx_id )const; + std::vector get_block_ids_on_fork(block_id_type head_of_fork) const; + + void add_checkpoints( const flat_map& checkpts ); + const flat_map get_checkpoints()const { return _checkpoints; } + bool before_last_checkpoint()const; + + bool push_block( const signed_block& b, uint32_t skip = skip_nothing ); + processed_transaction push_transaction( const precomputable_transaction& trx, uint32_t skip = skip_nothing ); + private: + bool _push_block( const signed_block& b ); + public: + // It is public because it is used in pending_transactions_restorer in db_with.hpp + processed_transaction _push_transaction( const precomputable_transaction& trx ); + ///@throws fc::exception if the proposed transaction fails to apply. + processed_transaction push_proposal( const proposal_object& proposal ); + + signed_block generate_block( + const fc::time_point_sec when, + witness_id_type witness_id, + const fc::ecc::private_key& block_signing_private_key, + uint32_t skip + ); + private: + signed_block _generate_block( + const fc::time_point_sec when, + witness_id_type witness_id, + const fc::ecc::private_key& block_signing_private_key + ); + + public: + void pop_block(); + void clear_pending(); + + /** + * This method is used to track appied operations during the evaluation of a block, these + * operations should include any operation actually included in a transaction as well + * as any implied/virtual operations that resulted, such as filling an order. The + * applied operations is cleared after applying each block and calling the block + * observers which may want to index these operations. + * + * @return the op_id which can be used to set the result after it has finished being applied. + */ + uint32_t push_applied_operation( const operation& op ); + void set_applied_operation_result( uint32_t op_id, const operation_result& r ); + const vector >& get_applied_operations()const; + + /** + * This signal is emitted after all operations and virtual operation for a + * block have been applied but before the get_applied_operations() are cleared. + * + * You may not yield from this callback because the blockchain is holding + * the write lock and may be in an "inconstant state" until after it is + * released. + */ + fc::signal applied_block; + + /** + * This signal is emitted any time a new transaction is added to the pending + * block state. + */ + fc::signal on_pending_transaction; + + /** + * Emitted After a block has been applied and committed. The callback + * should not yield and should execute quickly. + */ + fc::signal&, const flat_set&)> new_objects; + + /** + * Emitted After a block has been applied and committed. The callback + * should not yield and should execute quickly. + */ + fc::signal&, const flat_set&)> changed_objects; + + /** this signal is emitted any time an object is removed and contains a + * pointer to the last value of every object that was removed. + */ + fc::signal&, + const vector&, const flat_set&)> removed_objects; ///@{ /** @@ -629,9 +642,6 @@ namespace graphene { namespace chain { * @} */ - /// Enable or disable tracking of votes of standby witnesses and committee members - inline void enable_standby_votes_tracking(bool enable) { _track_standby_votes = enable; } - /** Precomputes digests, signatures and operation validations depending * on skip flags. "Expensive" computations may be done in a parallel * thread. @@ -651,16 +661,14 @@ namespace graphene { namespace chain { * precomputations applied */ fc::future precompute_parallel( const precomputable_transaction& trx )const; - private: + private: template void _precompute_parallel( const Trx* trx, const size_t count, const uint32_t skip )const; - protected: - //Mark pop_undo() as protected -- we do not want outside calling pop_undo(); it should call pop_block() instead + protected: + // Mark pop_undo() as protected -- we do not want outside calling pop_undo(), + // it should call pop_block() instead void pop_undo() { object_database::pop_undo(); } - void notify_applied_block( const signed_block& block ); - void notify_on_pending_transaction( const signed_transaction& tx ); - void notify_changed_objects(); private: optional _pending_tx_session; @@ -669,8 +677,6 @@ namespace graphene { namespace chain { template vector> sort_votable_objects(size_t count)const; - //////////////////// db_block.cpp //////////////////// - public: // these were formerly private, but they have a fairly well-defined API, so let's make them public void apply_block( const signed_block& next_block, uint32_t skip = skip_nothing ); @@ -680,8 +686,6 @@ namespace graphene { namespace chain { private: void _apply_block( const signed_block& next_block ); processed_transaction _apply_transaction( const signed_transaction& trx ); - void _cancel_bids_and_revive_mpa( const asset_object& bitasset, - const asset_bitasset_data_object& bad ); ///Steps involved in applying a new block ///@{ @@ -692,9 +696,12 @@ namespace graphene { namespace chain { void update_witnesses( fork_item& fork_entry )const; void create_block_summary(const signed_block& next_block); - //////////////////// db_witness_schedule.cpp //////////////////// + //////////////////// db_notify.cpp //////////////////// - uint32_t update_witness_missed_blocks( const signed_block& b ); + protected: + void notify_applied_block( const signed_block& block ); + void notify_on_pending_transaction( const signed_transaction& tx ); + void notify_changed_objects(); //////////////////// db_update.cpp //////////////////// public: @@ -717,8 +724,6 @@ namespace graphene { namespace chain { void update_maintenance_flag( bool new_maintenance_flag ); void update_withdraw_permissions(); void update_credit_offers_and_deals(); - bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, - const asset_bitasset_data_object* bitasset_ptr = nullptr ); void clear_expired_htlcs(); ///Steps performed only at maintenance intervals @@ -792,7 +797,7 @@ namespace graphene { namespace chain { bool _opened = false; // Counts nested proposal updates - uint32_t _push_proposal_nesting_depth = 0; + uint32_t _push_proposal_nesting_depth = 0; /// Tracks assets affected by bitshares-core issue #453 before hard fork #615 in one block flat_set _issue_453_affected_assets; @@ -806,6 +811,9 @@ namespace graphene { namespace chain { const chain_property_object* _p_chain_property_obj = nullptr; const witness_schedule_object* _p_witness_schedule_obj = nullptr; ///@} + public: + /// Enable or disable tracking of votes of standby witnesses and committee members + inline void enable_standby_votes_tracking(bool enable) { _track_standby_votes = enable; } }; } } From caa70b073127535e3fec154c5a2b8c03dc6ff965 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 31 Aug 2021 06:10:18 +0000 Subject: [PATCH 49/99] Add no-settlement taker tests --- tests/tests/bsrm_tests.cpp | 1122 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1122 insertions(+) diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_tests.cpp index d156fcd12b..d63bb059c5 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -1219,4 +1219,1126 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) } } +/// Tests margin calls and force settlements when BSRM is no_settlement and call order is taker +BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // Several passes, with different limit orders and/or settle orders + for( int i = 0; i <= 20; ++ i ) + { + idump( (i) ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; // 1.1% + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2750) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // 1000 * (22/10) * 1.9 = 4180 + const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(1000,mpa_id) ); + transfer( borrower2, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(500,mpa_id) ); + transfer( borrower3, seller2, asset(500,mpa_id) ); + + int64_t expected_seller_balance_mpa = 2500; + int64_t expected_seller2_balance_mpa = 500; + + // seller2 sells some + const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + expected_seller2_balance_mpa -= 100; + + // seller2 sells more + const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + expected_seller2_balance_mpa -= 100; + + // seller2 sells more, due to MCFR, this order won't be filled if no order is selling lower + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + expected_seller2_balance_mpa -= 100; + + // seller sells + const limit_order_object* sell_low = nullptr; + limit_order_id_type sell_low_id; + force_settlement_id_type settle_id; + force_settlement_id_type settle2_id; + if( 0 == i ) + { + // Nothing to do here + } + else if( 1 == i ) + { + sell_low = create_sell_order( seller, asset(111,mpa_id), asset(230) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 111 ); + expected_seller_balance_mpa -= 111; + } + else if( 2 == i ) + { + sell_low = create_sell_order( seller, asset(111,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 111 ); + expected_seller_balance_mpa -= 111; + } + else if( 3 == i ) + { + sell_low = create_sell_order( seller, asset(900,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 900 ); + expected_seller_balance_mpa -= 900; + } + else if( 4 == i ) + { + sell_low = create_sell_order( seller, asset(920,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 920 ); + expected_seller_balance_mpa -= 920; + } + else if( 5 == i ) + { + sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1000 ); + expected_seller_balance_mpa -= 1000; + } + else if( 6 == i ) + { + sell_low = create_sell_order( seller, asset(1050,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1050 ); + expected_seller_balance_mpa -= 1050; + } + else if( 7 == i ) + { + sell_low = create_sell_order( seller, asset(1800,mpa_id), asset(1870*2) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1800 ); + expected_seller_balance_mpa -= 1800; + } + else if( 8 == i ) + { + sell_low = create_sell_order( seller, asset(2000,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 2000 ); + expected_seller_balance_mpa -= 2000; + } + else if( 9 == i ) + { + auto result = force_settle( seller, asset(111,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 111; + } + else if( 10 == i ) + { + auto result = force_settle( seller, asset(990,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 990; + } + else if( 11 == i ) + { + auto result = force_settle( seller, asset(995,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 995; + } + else if( 12 == i ) + { + auto result = force_settle( seller, asset(1000,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 1000; + } + else if( 13 == i ) + { + auto result = force_settle( seller, asset(1050,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 1050; + } + else if( 14 == i ) + { + auto result = force_settle( seller, asset(1750,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 1750; + } + else if( 15 == i ) + { + auto result = force_settle( seller, asset(1800,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 1800; + } + else if( 16 == i ) + { + auto result = force_settle( seller, asset(2000,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 2000; + } + else if( 17 == i ) + { + auto result = force_settle( seller, asset(492,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + result = force_settle( seller, asset(503,mpa_id) ); + settle2_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle2_id) ); + expected_seller_balance_mpa -= 995; + } + else + { + BOOST_TEST_MESSAGE( "No more test cases so far" ); + break; + } + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), expected_seller_balance_mpa ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // publish a new feed so that borrower's and borrower2's debt positions become undercollateralized + f.settlement_price = price( asset(10,mpa_id), asset(22) ); + f.maximum_short_squeeze_ratio = 1300; + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check result + auto check_result = [&] + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), expected_seller_balance_mpa ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), expected_seller2_balance_mpa ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + //BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + + if( 0 == i ) // no order is filled + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_mid price = 100:210 = 0.476190476 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(13,mpa_id), asset(21) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 1 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 111:230 = 0.482608696 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 230 + // call2 pays round_down(230*1300/1289) = 231, margin call fee = 1 + // now feed price is 13:10 * (1000-111):(2100-231) + // = 13:10 * 889:1869 = 11557:18690 = 0.61835206 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 889:1869 = 0.479714554 + // sell_mid price = 100:210 = 0.476190476 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(18690) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1869 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 230 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 2 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 111:210 = 0.504545455 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 210 + // call2 pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-111):(2100-211) + // = 13:10 * 889:1889 = 11557:18890 = 0.611805188 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 889:1889 = 0.474635522 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid receives 210 + // call2 pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-111-100):(2100-211-211) + // = 13:10 * 789:1678 = 10257:16780 = 0.611263409 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 789:1678 = 0.474215212 + // sell_high price = 100:275 = 0.363636364 + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10257,mpa_id), asset(16780) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 789 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1678 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 210 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 3 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 900:1870 = 0.481283422 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 1870 + // call2 pays round_down(1870*1300/1289) = 1885, margin call fee = 15 + // now feed price is 13:10 * (1000-900):(2100-1885) + // = 13:10 * 100:215 = 130:215 = 0.604651163 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 100:215 = 0.469085464 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid receives 210 + // call2 pays round_up(210*1300/1289) = 212, margin call fee = 2 + // call2 is fully filled, freed collateral = 215 - 212 = 3 + BOOST_CHECK( !db.find( call2_id ) ); + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_high price = 100:275 = 0.363636364 + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130,mpa_id), asset(275) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 3 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 4 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 920:1870 = 0.49197861 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 1870 + // call2 pays round_down(1870*1300/1289) = 1885, margin call fee = 15 + // now feed price is 13:10 * (1000-920):(2100-1885) + // = 13:10 * 80:215 = 104:215 = 0.48372093 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 80:215 = 0.375268371 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is partially filled + // sell_mid pays 80, receives 80 * 210/100 = 168 + // call2 pays round_up(80*(210/100)*(1300/1289)) = 170, margin call fee = 2 + // call2 is fully filled, freed collateral = 215-170 = 45 + BOOST_CHECK( !db.find( call2_id ) ); + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 20, receives 20 * 210/100 = 42 + // call pays round_down(20*(210/100)*(1300/1289)) = 42, margin call fee = 0 + // now feed price is 13:10 * (1000-20):(2750-42) + // = 13:10 * 980:2708 = 1274:2708 = 0.470457903 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 980:2708 = 0.364978978 + // sell_high price = 100:275 = 0.363636364 + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1274,mpa_id), asset(2708) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 980 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2708 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 45 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 5 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 1000:1870 = 0.534759358 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 1870 + // call2 pays round_up(1870*1300/1289) = 1886, margin call fee = 16 + // call2 is fully filled, freed collateral = 2100-1886 = 214 + BOOST_CHECK( !db.find( call2_id ) ); + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1040,mpa_id), asset(2262) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 800 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 214 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 6 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 1050:1870 = 0.561497326 + // sell_low is partially filled + // sell_low pays 1000, receives round_up(1000 * 1870/1050) = 1781 + // call2 pays round_up(1000*(1870/1050)*(1300/1289)) = 1797, margin call fee = 16 + // call2 is fully filled, freed collateral = 2100-1797 = 303 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_low price = 1050:1870 = 0.561497326 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low pays 50, receives round_down(50*1870/1050) = 89 + // call pays round_down(50*(1870/1050)*(1300/1289)) = 89, margin call fee = 0 + // now feed price is 13:10 * (1000-50):(2750-89) + // = 13:10 * 950:2661 = 1235:2661 = 0.464111236 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 950:2661 = 0.360055265 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price from call is 13:10 * (1000-50-100):(2750-89-211) + // = 13:10 * 850:2450 = 1105:2450 = 0.451020408 (< 10:22 = 0.454545455) + // so feed price is 10:22 + // call match price is 1000:1289 * 10:22 = 0.352634177 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price from call is 13:10 * (1000-50-100-100):(2750-89-211-277) + // = 13:10 * 750:2173 = 975:2173 = 0.448688449 (< 10:22 = 0.454545455) + // so feed price is 10:22 + // call match price is 1000:1289 * 10:22 = 0.352634177 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 750 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2173 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 303 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 7 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 900:1870 = 0.481283422 + // sell_low is partially filled + // sell_low pays 1000, receives round_up(1000 * 1870/900) = 2078 + // call2 pays round_up(1000*(1870/900)*(1300/1289)) = 2096, margin call fee = 18 + // call2 is fully filled, freed collateral = 2100-2096 = 4 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_low price = 900:1870 = 0.481283422 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low pays 800, receives round_down(800*1870/900) = 1662 + // call pays round_down(800*(1870/900)*(1300/1289)) = 1676, margin call fee = 14 + // now call's debt is 1000-800=200, collateral is 2750-1676=1074 + // CR = 1074/200 / (22/10) = 2.44 > 1.85, out of margin call territory + // so feed price is 10:22 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 200 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1074 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 4 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870*2 ); // 2078+1662 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 8 == i ) + { + // sell_low would match call2 and call + + // sell_low pays 1000, receives round_up(1000 * 1870/2000) = 935 + // call2 pays round_up(1000*(1870/2000)*(1300/1289)) = 943, margin call fee = 8 + // call2 is fully filled, freed collateral = 2100-943 = 1157 + BOOST_CHECK( !db.find( call2_id ) ); + + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + + // sell_low pays 1000, receives round_up(1000 * 1870/2000) = 935 + // call pays round_up(1000*(1870/2000)*(1300/1289)) = 943, margin call fee = 8 + // call is fully filled, freed collateral = 2750-943 = 1807 + BOOST_CHECK( !db.find( call_id ) ); + + // feed price is 10:22 + + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 1157 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 + 1807 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 9 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(111*27069/13000) = 231 + // call2 pays round_down(111*2100/1000) = 233, margin call fee = 2 + // now feed price is 13:10 * (1000-111):(2100-233) + // = 13:10 * 889:1867 = 11557:18670 = 0.619014462 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 889:1867 = 0.480228442 + // sell_mid price = 100:210 = 0.476190476 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(18670) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 231 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 10 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(990*27069/13000) = 2061 + // call2 pays round_down(990*2100/1000) = 2079, margin call fee = 18 + // now feed price is 13:10 * (1000-990):(2100-2079) + // = 13:10 * 10:21 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 10:21 = 0.480254165 + // sell_mid price = 100:210 = 0.476190476 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(13,mpa_id), asset(21) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 10 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 21 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2061 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 11 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(995*27069/13000) = 2071 + // call2 pays round_down(995*2100/1000) = 2089, margin call fee = 18 + // now feed price is 13:10 * (1000-995):(2100-2089) + // = 13:10 * 5:11 = 13:22 = 0.590909091 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 5:11 = 0.45842443 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is partially filled + // sell_mid pays 5, receives round_up(5 * 21/10) = 11 + // call2 pays round_up(5*(21/10)*(1300/1289)) = 11, margin call fee = 0 + // call2 is fully filled, freed collateral = 11-11 = 0 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 95, receives round_down(95*210/100) = 199 + // call pays round_down(95*(210/100)*(1300/1289)) = 201, margin call fee = 2 + // now feed price from call is 13:10 * (1000-95):(2750-201) + // = 13:10 * 905:2549 = 11765:25490 = 0.46155355 (> 10:22 = 0.454545455) + // so feed price is 11765:25490 + // call match price is 1300:1289 * 905:2549 = 0.358071024 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price from call is 13:10 * (1000-95-100):(2750-201-277) + // = 13:10 * 805:2272 = 10465:22720 = 0.460607394 (> 10:22 = 0.454545455) + // so feed price is 10465:22720 + // call match price is 1300:1289 * 805:2272 = 0.357337001 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10465,mpa_id), asset(22720) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 805 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2272 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2071 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 11+199+275 + } + else if( 12 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1040,mpa_id), asset(2262) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 800 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2083 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 13 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call pays price = 1000:2750 + // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193, does not match + + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(50*2915718/1040000) = 140 + // call pays round_down(50*2262/800) = 141, margin call fee = 1 + // now feed price is 13:10 * (800-50):(2262-141) + // = 13:10 * 750:2121 = 975:2121 = 0.459688826 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 750:2121 = 0.35662438 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(975,mpa_id), asset(2121) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 750 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2121 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2223 ); // 2083 + 140 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 14 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call pays price = 1000:2750 + // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193, does not match + + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(750*2915718/1040000) = 2102 + // call pays round_down(750*2262/800) = 2120, margin call fee = 18 + // now feed price is 13:10 * (800-750):(2262-2120) + // = 13:10 * 50:142 = 65:142 = 0.457746479 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 50:142 = 0.355117517 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(65,mpa_id), asset(142) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 50 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 142 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4185 ); // 2083 + 2102 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 15 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call pays price = 1000:2750 + // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193, does not match + + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_up(800*2915718/1040000) = 2243 + // call pays 2262, margin call fee = 19 + // call is fully filled + BOOST_CHECK( !db.find( call_id ) ); + // now feed price is 10:22, no margin call + + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4326 ); // 2083 + 2243 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 16 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call pays price = 1000:2750 + // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193, does not match + + // call is fully filled + BOOST_CHECK( !db.find( call_id ) ); + // settle order receives round_up(800*2915718/1040000) = 2243 + // call pays 2262, margin call fee = 19 + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 200 ); + + // now feed price is 10:22, no margin call + + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4326 ); // 2083 + 2243 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 17 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(492*27069/13000) = 1024 + // call2 pays round_down(492*2100/1000) = 1033, margin call fee = 9 + // now feed price is 13:10 * (1000-492):(2100-1033) + // = 13:10 * 508:1067 = 6604:10670 = 0.618931584 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 508:1067 = 660400:1375363 = 0.480164146 + // sell_mid price = 100:210 = 0.476190476 does not match + // settle2 is fully filled + BOOST_CHECK( !db.find( settle2_id ) ); + // settle2 receives round_down(503*(1375363/660400)) = 1047 + // call2 pays round_down(503*1067/508) = 1056, margin call fee = 9 + // now feed price is 13:10 * (508-503):(1067-1056) + // = 13:10 * 5:11 = 13:22 = 0.590909091 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 5:11 = 0.45842443 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is partially filled + // sell_mid pays 5, receives round_up(5 * 21/10) = 11 + // call2 pays round_up(5*(21/10)*(1300/1289)) = 11, margin call fee = 0 + // call2 is fully filled, freed collateral = 11-11 = 0 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 95, receives round_down(95*210/100) = 199 + // call pays round_down(95*(210/100)*(1300/1289)) = 201, margin call fee = 2 + // now feed price from call is 13:10 * (1000-95):(2750-201) + // = 13:10 * 905:2549 = 11765:25490 = 0.46155355 (> 10:22 = 0.454545455) + // so feed price is 11765:25490 + // call match price is 1300:1289 * 905:2549 = 0.358071024 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price from call is 13:10 * (1000-95-100):(2750-201-277) + // = 13:10 * 805:2272 = 10465:22720 = 0.460607394 (> 10:22 = 0.454545455) + // so feed price is 10465:22720 + // call match price is 1300:1289 * 805:2272 = 0.357337001 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10465,mpa_id), asset(22720) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 805 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2272 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2071 ); // 1024+1047 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 11+199+275 + } + else + { + BOOST_FAIL( "to be fixed" ); + } + }; + + check_result(); + + // generate a block + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + // check again + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From b614a1a004ac53e81eb03650f340bfa2dd2445ba Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 00:24:32 +0000 Subject: [PATCH 50/99] Split bsrm_tests.cpp into multiple files --- tests/tests/bsrm_basic_tests.cpp | 671 ++++++++++++++++++ ...tests.cpp => bsrm_no_settlement_tests.cpp} | 632 ----------------- 2 files changed, 671 insertions(+), 632 deletions(-) create mode 100644 tests/tests/bsrm_basic_tests.cpp rename tests/tests/{bsrm_tests.cpp => bsrm_no_settlement_tests.cpp} (78%) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp new file mode 100644 index 0000000000..4257285d9a --- /dev/null +++ b/tests/tests/bsrm_basic_tests.cpp @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) + +/// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork +BOOST_AUTO_TEST_CASE( hardfork_protection_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; + uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + + uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; + + vector ops; + + // Testing asset_create_operation + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = bitflag; + acop.common_options.issuer_permissions = old_bitmask; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 3; + + trx.operations.clear(); + trx.operations.push_back( acop ); + + { + auto& op = trx.operations.front().get(); + + // Unable to set new permission bit + op.common_options.issuer_permissions = new_bitmask; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.common_options.issuer_permissions = old_bitmask; + + // Unable to set new extensions in bitasset options + op.bitasset_opts->extensions.value.black_swan_response_method = 0; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.bitasset_opts->extensions.value.black_swan_response_method = {}; + + acop = op; + } + + // Able to create asset without new data + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& samcoin = db.get(ptx.operation_results[0].get()); + asset_id_type samcoin_id = samcoin.id; + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 ); + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 ); + + // Able to propose the good operation + propose( acop ); + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.market_fee_percent = 200; + + // Unable to set new permission bit + op.new_options.issuer_permissions = new_bitmask; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.issuer_permissions = old_bitmask; + + auop = op; + } + + // Able to update asset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 ); + + // Able to propose the good operation + propose( auop ); + + // Testing asset_update_bitasset_operation + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = samcoin_id; + aubop.new_options = samcoin_id(db).bitasset_data(db).options; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.minimum_feeds = 1; + + // Unable to set new extensions + op.new_options.extensions.value.black_swan_response_method = 1; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.extensions.value.black_swan_response_method = {}; + + aubop = op; + } + + // Able to update bitasset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 ); + + // Able to propose the good operation + propose( aubop ); + + // Unable to propose the invalid operations + for( const operation& op : ops ) + BOOST_CHECK_THROW( propose( op ), fc::exception ); + + // Check what we have now + idump( (samcoin) ); + idump( (samcoin.bitasset_data(db)) ); + + generate_block(); + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Now able to propose the operations that was invalid + for( const operation& op : ops ) + propose( op ); + + generate_block(); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests scenarios about setting non-UIA issuer permission bits on an UIA +BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; + uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; + + uint16_t uiaflag = uiamask & ~disable_new_supply; // Allow creating new supply + + vector ops; + + asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), uiaflag ).id; + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + auop.new_options.issuer_permissions = old_bitmask & ~global_settle & ~disable_force_settle; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + // Able to update asset with non-UIA issuer permission bits + PUSH_TX(db, trx, ~0); + + // Able to propose too + propose( auop ); + + // Issue some coin + issue_uia( sam_id, asset( 1, samcoin_id ) ); + + // Unable to unset the non-UIA "disable" issuer permission bits + auto perms = samcoin_id(db).options.issuer_permissions; + + auop.new_options.issuer_permissions = perms & ~disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = perms & ~disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = perms & ~disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Still able to propose + auop.new_options.issuer_permissions = new_bitmask; + propose( auop ); + + // But no longer able to update directly + auop.new_options.issuer_permissions = uiamask | witness_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | committee_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unset the non-UIA bits in issuer permissions, should succeed + auop.new_options.issuer_permissions = uiamask; + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin_id(db).options.issuer_permissions, uiamask ); + + // Burn all supply + reserve_asset( sam_id, asset( 1, samcoin_id ) ); + + BOOST_CHECK_EQUAL( samcoin_id(db).dynamic_asset_data_id(db).current_supply.value, 0 ); + + // Still unable to set the non-UIA bits in issuer permissions + auop.new_options.issuer_permissions = uiamask | witness_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | committee_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + generate_block(); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests what kind of assets can have BSRM-related flags / issuer permissions / extensions +BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // Unable to create a PM with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bsrm_update ), fc::exception ); + + // Unable to create a MPA with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bsrm_update ), fc::exception ); + + // Unable to create a UIA with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bsrm_update ), fc::exception ); + + // create a PM with a zero market_fee_percent + const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); + asset_id_type pm_id = pm.id; + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + // create a UIA with a zero market_fee_percent + const asset_object& uia = create_user_issued_asset( "TESTUIA", sam_id(db), charge_market_fee ); + asset_id_type uia_id = uia.id; + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + + // Unable to set disable_bsrm_update bit in flags for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.flags |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bsrm_update bit in flags for MPA + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + auop.new_options.flags |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bsrm_update bit in flags for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.flags |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bsrm_update bit in issuer_permissions for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.issuer_permissions |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to set disable_bsrm_update bit in issuer_permissions for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.issuer_permissions |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to create a UIA with disable_bsrm_update permission bit + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bsrm_update; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create UIA without disable_bsrm_update permission bit + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Unable to create a PM with disable_bsrm_update permission bit + acop.symbol = "SAMPM"; + acop.precision = asset_id_type()(db).precision; + acop.is_prediction_market = true; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bsrm_update; + acop.bitasset_opts = bitasset_options(); + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Unable to create a PM with BSRM in extensions + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; + acop.bitasset_opts->extensions.value.black_swan_response_method = 0; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create PM with no disable_bsrm_update permission bit nor BSRM in extensions + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; + acop.bitasset_opts->extensions.value.black_swan_response_method.reset(); + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Unable to update PM to set BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = pm_id; + aubop.new_options = pm_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Able to propose + propose( aubop ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests whether asset owner has permission to update bsrm +BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + + using bsrm_type = bitasset_options::black_swan_response_type; + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + + // disable owner's permission to update bsrm + auop.new_options.issuer_permissions |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); + + // check that owner can not update bsrm + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + aubop.new_options.extensions.value.black_swan_response_method = 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.black_swan_response_method.reset(); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + + // enable owner's permission to update bsrm + auop.new_options.issuer_permissions &= ~disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); + + // check that owner can update bsrm + aubop.new_options.extensions.value.black_swan_response_method = 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // check bsrm' valid range + aubop.new_options.extensions.value.black_swan_response_method = 4; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.black_swan_response_method = 1; + + // Sam borrow some + borrow( sam, asset(1000, mpa_id), asset(2000) ); + + // disable owner's permission to update bsrm + auop.new_options.issuer_permissions |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); + + // check that owner can not update bsrm + aubop.new_options.extensions.value.black_swan_response_method = 0; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.black_swan_response_method.reset(); + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.black_swan_response_method = 1; + + // able to update other params that still has permission E.G. force_settlement_delay_sec + aubop.new_options.force_settlement_delay_sec += 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, + aubop.new_options.force_settlement_delay_sec ); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); + + // unable to enable the permission to update bsrm + auop.new_options.issuer_permissions &= ~disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp similarity index 78% rename from tests/tests/bsrm_tests.cpp rename to tests/tests/bsrm_no_settlement_tests.cpp index d63bb059c5..f9db05f030 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -36,638 +36,6 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) -/// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork -BOOST_AUTO_TEST_CASE( hardfork_protection_test ) -{ - try { - - // Proceeds to a recent hard fork - generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); - generate_block(); - set_expiration( db, trx ); - - ACTORS((sam)); - - auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; - fund( sam, asset(init_amount) ); - - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; - uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; - - uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; - - vector ops; - - // Testing asset_create_operation - asset_create_operation acop; - acop.issuer = sam_id; - acop.symbol = "SAMCOIN"; - acop.precision = 2; - acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); - acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; - acop.common_options.market_fee_percent = 100; - acop.common_options.flags = bitflag; - acop.common_options.issuer_permissions = old_bitmask; - acop.bitasset_opts = bitasset_options(); - acop.bitasset_opts->minimum_feeds = 3; - - trx.operations.clear(); - trx.operations.push_back( acop ); - - { - auto& op = trx.operations.front().get(); - - // Unable to set new permission bit - op.common_options.issuer_permissions = new_bitmask; - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - ops.push_back( op ); - op.common_options.issuer_permissions = old_bitmask; - - // Unable to set new extensions in bitasset options - op.bitasset_opts->extensions.value.black_swan_response_method = 0; - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - ops.push_back( op ); - op.bitasset_opts->extensions.value.black_swan_response_method = {}; - - acop = op; - } - - // Able to create asset without new data - processed_transaction ptx = PUSH_TX(db, trx, ~0); - const asset_object& samcoin = db.get(ptx.operation_results[0].get()); - asset_id_type samcoin_id = samcoin.id; - - BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 ); - BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 ); - - // Able to propose the good operation - propose( acop ); - - // Testing asset_update_operation - asset_update_operation auop; - auop.issuer = sam_id; - auop.asset_to_update = samcoin_id; - auop.new_options = samcoin_id(db).options; - - trx.operations.clear(); - trx.operations.push_back( auop ); - - { - auto& op = trx.operations.front().get(); - op.new_options.market_fee_percent = 200; - - // Unable to set new permission bit - op.new_options.issuer_permissions = new_bitmask; - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - ops.push_back( op ); - op.new_options.issuer_permissions = old_bitmask; - - auop = op; - } - - // Able to update asset without new data - PUSH_TX(db, trx, ~0); - - BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 ); - - // Able to propose the good operation - propose( auop ); - - // Testing asset_update_bitasset_operation - asset_update_bitasset_operation aubop; - aubop.issuer = sam_id; - aubop.asset_to_update = samcoin_id; - aubop.new_options = samcoin_id(db).bitasset_data(db).options; - - trx.operations.clear(); - trx.operations.push_back( aubop ); - - { - auto& op = trx.operations.front().get(); - op.new_options.minimum_feeds = 1; - - // Unable to set new extensions - op.new_options.extensions.value.black_swan_response_method = 1; - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - ops.push_back( op ); - op.new_options.extensions.value.black_swan_response_method = {}; - - aubop = op; - } - - // Able to update bitasset without new data - PUSH_TX(db, trx, ~0); - - BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 ); - - // Able to propose the good operation - propose( aubop ); - - // Unable to propose the invalid operations - for( const operation& op : ops ) - BOOST_CHECK_THROW( propose( op ), fc::exception ); - - // Check what we have now - idump( (samcoin) ); - idump( (samcoin.bitasset_data(db)) ); - - generate_block(); - - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - // Now able to propose the operations that was invalid - for( const operation& op : ops ) - propose( op ); - - generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -/// Tests scenarios about setting non-UIA issuer permission bits on an UIA -BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) -{ - try { - - // Proceeds to a recent hard fork - generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); - generate_block(); - set_expiration( db, trx ); - - ACTORS((sam)); - - auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; - fund( sam, asset(init_amount) ); - - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; - uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; - uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; - - uint16_t uiaflag = uiamask & ~disable_new_supply; // Allow creating new supply - - vector ops; - - asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), uiaflag ).id; - - // Testing asset_update_operation - asset_update_operation auop; - auop.issuer = sam_id; - auop.asset_to_update = samcoin_id; - auop.new_options = samcoin_id(db).options; - auop.new_options.issuer_permissions = old_bitmask & ~global_settle & ~disable_force_settle; - - trx.operations.clear(); - trx.operations.push_back( auop ); - - // Able to update asset with non-UIA issuer permission bits - PUSH_TX(db, trx, ~0); - - // Able to propose too - propose( auop ); - - // Issue some coin - issue_uia( sam_id, asset( 1, samcoin_id ) ); - - // Unable to unset the non-UIA "disable" issuer permission bits - auto perms = samcoin_id(db).options.issuer_permissions; - - auop.new_options.issuer_permissions = perms & ~disable_icr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = perms & ~disable_mcr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = perms & ~disable_mssr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - // Still able to propose - auop.new_options.issuer_permissions = new_bitmask; - propose( auop ); - - // But no longer able to update directly - auop.new_options.issuer_permissions = uiamask | witness_fed_asset; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | committee_fed_asset; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_icr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_mcr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_mssr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Unset the non-UIA bits in issuer permissions, should succeed - auop.new_options.issuer_permissions = uiamask; - trx.operations.clear(); - trx.operations.push_back( auop ); - - PUSH_TX(db, trx, ~0); - - BOOST_CHECK_EQUAL( samcoin_id(db).options.issuer_permissions, uiamask ); - - // Burn all supply - reserve_asset( sam_id, asset( 1, samcoin_id ) ); - - BOOST_CHECK_EQUAL( samcoin_id(db).dynamic_asset_data_id(db).current_supply.value, 0 ); - - // Still unable to set the non-UIA bits in issuer permissions - auop.new_options.issuer_permissions = uiamask | witness_fed_asset; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | committee_fed_asset; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_icr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_mcr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_mssr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -/// Tests what kind of assets can have BSRM-related flags / issuer permissions / extensions -BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) -{ - try { - - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - ACTORS((sam)(feeder)); - - auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; - fund( sam, asset(init_amount) ); - fund( feeder, asset(init_amount) ); - - // Unable to create a PM with the disable_bsrm_update bit in flags - BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bsrm_update ), fc::exception ); - - // Unable to create a MPA with the disable_bsrm_update bit in flags - BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bsrm_update ), fc::exception ); - - // Unable to create a UIA with the disable_bsrm_update bit in flags - BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bsrm_update ), fc::exception ); - - // create a PM with a zero market_fee_percent - const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); - asset_id_type pm_id = pm.id; - - // create a MPA with a zero market_fee_percent - const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); - asset_id_type mpa_id = mpa.id; - - // create a UIA with a zero market_fee_percent - const asset_object& uia = create_user_issued_asset( "TESTUIA", sam_id(db), charge_market_fee ); - asset_id_type uia_id = uia.id; - - // Prepare for asset update - asset_update_operation auop; - auop.issuer = sam_id; - - // Unable to set disable_bsrm_update bit in flags for PM - auop.asset_to_update = pm_id; - auop.new_options = pm_id(db).options; - auop.new_options.flags |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // Unable to propose either - BOOST_CHECK_THROW( propose( auop ), fc::exception ); - - // Unable to set disable_bsrm_update bit in flags for MPA - auop.asset_to_update = mpa_id; - auop.new_options = mpa_id(db).options; - auop.new_options.flags |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // Unable to propose either - BOOST_CHECK_THROW( propose( auop ), fc::exception ); - - // Unable to set disable_bsrm_update bit in flags for UIA - auop.asset_to_update = uia_id; - auop.new_options = uia_id(db).options; - auop.new_options.flags |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // Unable to propose either - BOOST_CHECK_THROW( propose( auop ), fc::exception ); - - // Unable to set disable_bsrm_update bit in issuer_permissions for PM - auop.asset_to_update = pm_id; - auop.new_options = pm_id(db).options; - auop.new_options.issuer_permissions |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // But able to propose - propose( auop ); - - // Unable to set disable_bsrm_update bit in issuer_permissions for UIA - auop.asset_to_update = uia_id; - auop.new_options = uia_id(db).options; - auop.new_options.issuer_permissions |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // But able to propose - propose( auop ); - - // Unable to create a UIA with disable_bsrm_update permission bit - asset_create_operation acop; - acop.issuer = sam_id; - acop.symbol = "SAMCOIN"; - acop.precision = 2; - acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); - acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; - acop.common_options.market_fee_percent = 100; - acop.common_options.flags = charge_market_fee; - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bsrm_update; - - trx.operations.clear(); - trx.operations.push_back( acop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Unable to propose either - BOOST_CHECK_THROW( propose( acop ), fc::exception ); - - // Able to create UIA without disable_bsrm_update permission bit - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; - trx.operations.clear(); - trx.operations.push_back( acop ); - PUSH_TX(db, trx, ~0); - - // Unable to create a PM with disable_bsrm_update permission bit - acop.symbol = "SAMPM"; - acop.precision = asset_id_type()(db).precision; - acop.is_prediction_market = true; - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bsrm_update; - acop.bitasset_opts = bitasset_options(); - - trx.operations.clear(); - trx.operations.push_back( acop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Unable to propose either - BOOST_CHECK_THROW( propose( acop ), fc::exception ); - - // Unable to create a PM with BSRM in extensions - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; - acop.bitasset_opts->extensions.value.black_swan_response_method = 0; - - trx.operations.clear(); - trx.operations.push_back( acop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Unable to propose either - BOOST_CHECK_THROW( propose( acop ), fc::exception ); - - // Able to create PM with no disable_bsrm_update permission bit nor BSRM in extensions - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; - acop.bitasset_opts->extensions.value.black_swan_response_method.reset(); - trx.operations.clear(); - trx.operations.push_back( acop ); - PUSH_TX(db, trx, ~0); - - // Unable to update PM to set BSRM - asset_update_bitasset_operation aubop; - aubop.issuer = sam_id; - aubop.asset_to_update = pm_id; - aubop.new_options = pm_id(db).bitasset_data(db).options; - aubop.new_options.extensions.value.black_swan_response_method = 1; - - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Able to propose - propose( aubop ); - - generate_block(); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -/// Tests whether asset owner has permission to update bsrm -BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) -{ - try { - - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - ACTORS((sam)(feeder)); - - auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; - fund( sam, asset(init_amount) ); - fund( feeder, asset(init_amount) ); - - // create a MPA with a zero market_fee_percent - const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); - asset_id_type mpa_id = mpa.id; - - BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); - - BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - - using bsrm_type = bitasset_options::black_swan_response_type; - BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); - - // add a price feed publisher and publish a feed - update_feed_producers( mpa_id, { feeder_id } ); - - price_feed f; - f.settlement_price = price( asset(1,mpa_id), asset(1) ); - f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); - f.maintenance_collateral_ratio = 1850; - f.maximum_short_squeeze_ratio = 1250; - - uint16_t feed_icr = 1900; - - publish_feed( mpa_id, feeder_id, f, feed_icr ); - - // Prepare for asset update - asset_update_operation auop; - auop.issuer = sam_id; - auop.asset_to_update = mpa_id; - auop.new_options = mpa_id(db).options; - - // disable owner's permission to update bsrm - auop.new_options.issuer_permissions |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - PUSH_TX(db, trx, ~0); - - BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - - // check that owner can not update bsrm - asset_update_bitasset_operation aubop; - aubop.issuer = sam_id; - aubop.asset_to_update = mpa_id; - aubop.new_options = mpa_id(db).bitasset_data(db).options; - - aubop.new_options.extensions.value.black_swan_response_method = 1; - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.black_swan_response_method.reset(); - - BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - - // enable owner's permission to update bsrm - auop.new_options.issuer_permissions &= ~disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - PUSH_TX(db, trx, ~0); - - BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); - - // check that owner can update bsrm - aubop.new_options.extensions.value.black_swan_response_method = 1; - trx.operations.clear(); - trx.operations.push_back( aubop ); - PUSH_TX(db, trx, ~0); - - BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - - BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); - BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); - - // check bsrm' valid range - aubop.new_options.extensions.value.black_swan_response_method = 4; - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.black_swan_response_method = 1; - - // Sam borrow some - borrow( sam, asset(1000, mpa_id), asset(2000) ); - - // disable owner's permission to update bsrm - auop.new_options.issuer_permissions |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - PUSH_TX(db, trx, ~0); - - BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - - // check that owner can not update bsrm - aubop.new_options.extensions.value.black_swan_response_method = 0; - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - aubop.new_options.extensions.value.black_swan_response_method.reset(); - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - aubop.new_options.extensions.value.black_swan_response_method = 1; - - // able to update other params that still has permission E.G. force_settlement_delay_sec - aubop.new_options.force_settlement_delay_sec += 1; - trx.operations.clear(); - trx.operations.push_back( aubop ); - PUSH_TX(db, trx, ~0); - - BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, - aubop.new_options.force_settlement_delay_sec ); - - BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - - BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); - - // unable to enable the permission to update bsrm - auop.new_options.issuer_permissions &= ~disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - - generate_block(); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - /// Tests margin calls when BSRM is no_settlement and call order is maker BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) { From 19c39fccff4e52063947cc27234340824db2f9eb Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 02:04:33 +0000 Subject: [PATCH 51/99] Fix rounding issue when matching limit with call and slightly refactor other code --- libraries/chain/db_market.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index b9ec5029c1..83fba4ad91 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1133,21 +1133,27 @@ database::match_result_type database::match( const limit_order_object& bid, cons bool cull_taker = false; + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls + const auto& feed_price = bitasset.current_feed.settlement_price; const auto& maintenance_collateral_ratio = bitasset.current_feed.maintenance_collateral_ratio; optional maintenance_collateralization; - if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME ) // call price caching issue + if( !before_core_hardfork_1270 ) maintenance_collateralization = bitasset.current_maintenance_collateralization; asset usd_for_sale = bid.amount_for_sale(); asset usd_to_buy = asset( ask.get_max_debt_to_cover( call_pays_price, feed_price, maintenance_collateral_ratio, maintenance_collateralization ), ask.debt_type() ); - asset call_pays, call_receives, order_pays, order_receives; + asset call_pays; + asset call_receives; + asset order_pays; + asset order_receives; if( usd_to_buy > usd_for_sale ) { // fill limit order order_receives = usd_for_sale * match_price; // round down here, in favor of call order - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) // 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 @@ -1159,6 +1165,11 @@ database::match_result_type database::match( const limit_order_object& bid, cons // If the order would receive 0 even at `match_price`, 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 ); + if( after_core_hardfork_2481 ) + call_pays = call_receives * call_pays_price; // calculate with updated call_receives + else + // TODO add tests about CR change + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) cull_taker = true; } else @@ -1876,9 +1887,6 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( usd_to_buy > usd_for_sale ) { // fill order limit_receives = usd_for_sale * match_price; // round down, in favor of call order - if( !after_core_hardfork_2481 ) - // TODO add tests about CR change - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) // 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): @@ -1897,7 +1905,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // so calling maybe_cull_small() will always cull it. call_receives = limit_receives.multiply_and_round_up( match_price ); - if( after_core_hardfork_2481 ) + if( !after_core_hardfork_2481 ) + // TODO add tests about CR change + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + else { call_pays = call_receives * call_pays_price; // calculate with updated call_receives if( call_pays.amount >= call_order.collateral ) From d8a96e059c7dabf21e1449c04b37c66ab8199d62 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 02:08:44 +0000 Subject: [PATCH 52/99] Add no-settlement tests about small limit taker --- tests/tests/bsrm_no_settlement_tests.cpp | 269 ++++++++++++++++++++++- 1 file changed, 268 insertions(+), 1 deletion(-) diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index f9db05f030..e88672a24f 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -226,7 +226,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) // now feed price is 13:10 * (1000-111):(2750-111*275/100) // = 13:10 * 889:2445 = 11557:24450 // call order match price is 1300:1299 * 889:2445 = 0.363879089 - // sell_mid's price is 100/210 = 0.047619048 + // sell_mid's price is 100/210 = 0.47619048 // sell_mid got filled too BOOST_CHECK( !db.find( sell_mid_id ) ); @@ -307,6 +307,273 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) } } +/// Tests margin calls when BSRM is no_settlement and call order is maker and taker limit order is too small to fill +BOOST_AUTO_TEST_CASE( no_settlement_maker_small_limit_taker_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 100000 * (2000/125000) * 1.9 = 3040 + // 100000 * (22/1000) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower is unable to adjust debt position if it's still undercollateralized + // 100000 * (2000/125000) * 1.25 = 2000 + // 100000 * (22/1000) * 1.25 = 2750 + BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); + // borrower adjust debt position to right at MSSR + borrow( borrower, asset(0, mpa_id), asset(750) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Sam update MSSR and MCFR + // note: borrower's position is undercollateralized again due to the mssr change + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; + aubop.new_options.extensions.value.margin_call_fee_ratio = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(50000,mpa_id) ); + transfer( borrower3, seller2, asset(50000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(10000,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled in the beginning either + const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 250000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller sells some, this order will be filled + const limit_order_object* sell_low = create_sell_order( seller, asset(11100,mpa_id), asset(210) ); + BOOST_CHECK( !sell_low ); + + // call2 pays price = 210/10000 + // call2 match price = (210/10000) * (1299/1300) = 27279/1300000 + // sell_low receives = round_down(11100 * 27279/1300000)) = 232 + // sell_low pays = round_up(232 * 1300000/27279) = 11057, the rest is cancelled + // call2 receives = 11057 + // call2 pays = round_down(11057 * 210/10000) = 232, margin call fee = 0 + // now feed price = 13:10 * (100000-11057):(2100-232) = 13:10 * 88943:1868 = 1156259:18680 + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 238943 ); // 250000 - 11057 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(1156259,mpa_id), asset(18680) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 88943 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1868 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells more + sell_low = create_sell_order( seller, asset(100000,mpa_id), asset(100) ); + + // call2 is fully filled + BOOST_CHECK( !db.find(call2_id) ); + // call2 pays 1868 + // call2 pays price = 1868/88943 + // call2 match price = (1868/88943) * (1299/1300) = 2426532/115625900 + // sell_low pays 88943 + // sell_low receives = round_up(88943 * 2426532/115625900)) = 1867 + // sell_low reminder = 100000-88943 = 11057 + + // sell_low is fully filled + BOOST_CHECK( !sell_low ); + // call pays price = 275/10000 + // call match price = (275/10000) * (1299/1300) = 357225/13000000 + // sell_low receives = round_down(11057 * 357225/13000000)) = 303 + // sell_low pays = round_up(303 * 13000000/357225) = 11027, the rest is cancelled + + auto final_check = [&] + { + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 138973 ); // 250000 - 11057 - 88943 - 11027 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2402 ); // 232 + 1867 + 303 + // call receives = 11027 + // call pays = round_down(11027 * 275/10000) = 303, margin call fee = 0 + // now feed price = 13:10 * (100000-11027):(2750-303) = 13:10 * 88973:2447 = 1156649:24470 + // call order match price is 1300:1299 * 88973:2447 = 36.38802348 + // sell_mid's price is 10000/210 = 47.619047619 + + // sell_mid got filled too + BOOST_CHECK( !db.find( sell_mid_id ) ); + + // sell_mid was selling 10000 MPA for 210 CORE as maker, matched at its price + // call pays round_down(210*1300/1299) = 210, fee = 0 + // call receives + // now feed price is 13:10 * (88973-10000):(2447-210) + // = 13:10 * 78973:2237 = 1026649:22370 + // call order match price is 1300:1299 * 78973:2237 = 35.330261612 + // sell_high's price is 10000/275 = 36.363636364 + + // sell_high got filled too + BOOST_CHECK( !db.find( sell_high_id ) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); + // sell_mid was selling 10000 MPA for 210 CORE as maker, matched at its price + // sell_high was selling 10000 MPA for 275 CORE as maker, matched at its price + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 + // call pays round_down(275*1300/1299) = 275, fee = 0 + // now feed price is 13:10 * (78973-10000):(2237-275) + // = 13:10 * 68973:1962 = 896649:19620 (>1000/22) + // call order match price is 1300:1299 * 68973:1962 = 35.181496941 + // sell_highest's price is 10000/285 = 35.087719298, does not match + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(896649,mpa_id), asset(19620) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 68973 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1962 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /// Tests force settlements when BSRM is no_settlement and call order is maker BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) { From 64cbfd0200fff47391f4d022fa8c450ab3e11568 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 12:57:19 +0000 Subject: [PATCH 53/99] Fix rounding issue when matching settle with call --- libraries/chain/db_market.cpp | 45 ++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 83fba4ad91..aa9206a594 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1331,6 +1331,7 @@ asset database::match_impl( const force_settlement_object& settle, // the call order is a margin call, implies hf core-2481 else if( settle_pays == max_settlement ) // the settle order is larger, but the call order has TCR { + // Note: here settle_pays == call_receives call_pays = call_receives.multiply_and_round_up( match_price ); // round up, in favor of settle order settle_receives = call_receives.multiply_and_round_up( fill_price ); // round up // Note: here we do NOT stabilize call_receives since it is done in get_max_debt_to_cover(), @@ -1338,7 +1339,27 @@ asset database::match_impl( const force_settlement_object& settle, } else // the call order is a margin call, and the settle order is smaller { - // it was correct to round down call_pays. + // It was correct to round down call_pays. However, it is not the final result. + // For margin calls, due to margin call fee, it is fairer to calculate with fill_price first + const auto& calculate = [&settle_receives,&settle_pays,&fill_price,&call_receives,&call_pays,&match_price] + { + settle_receives = settle_pays * fill_price; // round down here, in favor of call order + if( settle_receives.amount != 0 ) + { + // round up to mitigate rounding issues (hf core-342) + call_receives = settle_receives.multiply_and_round_up( fill_price ); + // round down + call_pays = call_receives * match_price; + } + }; + + calculate(); + if( settle_receives.amount == 0 ) + { + cancel_settle_order( settle ); + // If the settle order is canceled, we just return, since nothing else can be done + return asset( 0, call_debt.asset_id ); + } // check whether the call order can be filled at match_price bool cap_price = false; @@ -1346,25 +1367,18 @@ asset database::match_impl( const force_settlement_object& settle, cap_price = true; else { - // round up to mitigate rounding issues (hf core-342) - call_receives = call_pays.multiply_and_round_up( match_price ); - auto new_collateral = call_collateral - call_pays; auto new_debt = call_debt - call_receives; // the result is positive due to math if( ( new_collateral / new_debt ) < call.collateralization() ) // if CR would decrease cap_price = true; } - if( !cap_price ) // match_price is good - { - settle_receives = call_receives * fill_price; // round down - } - else // match_price is not good + if( cap_price ) // match_price is not good, update match price and fill price, then calculate again { match_price = call_debt / call_collateral; - call_pays = settle_pays * match_price; // round down here, in favor of call order - // price changed, check if it is something-for-nothing again - if( call_pays.amount == 0 ) + fill_price = match_price / margin_call_pays_ratio; + calculate(); + if( settle_receives.amount == 0 ) { // Note: when it is a margin call, max_settlement is max_debt_to_cover. // if need to cap price here, max_debt_to_cover should be equal to call_debt. @@ -1373,14 +1387,7 @@ asset database::match_impl( const force_settlement_object& settle, // If the settle order is canceled, we just return, since nothing else can be done return asset( 0, call_debt.asset_id ); } - // price changed, update call_receives // round up to mitigate rounding issues (hf core-342) - call_receives = call_pays.multiply_and_round_up( match_price ); // round up - // update fill price and settle_receives - fill_price = match_price / margin_call_pays_ratio; - settle_receives = call_receives * fill_price; // round down here, in favor of call order } - if( settle_receives.amount == 0 ) - settle_receives.amount = 1; // reduce margin-call fee in this case. Note: here call_pays >= 1 } // end : if is_margin_call, else ... // be here, we should have: call_pays <= call_collateral From a432884610df9d8ed57b4a4e40470043fbfab8f6 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 12:58:40 +0000 Subject: [PATCH 54/99] Add no-settlement tests about small settle taker --- tests/tests/bsrm_no_settlement_tests.cpp | 207 +++++++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index e88672a24f..39f7e08dc6 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -854,6 +854,213 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) } } +/// Tests force settlements when BSRM is no_settlement and call order is maker and settle order is too small to fill +BOOST_AUTO_TEST_CASE( no_settlement_maker_small_settle_taker_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 100000 * (2000/125000) * 1.9 = 3040 + // 100000 * (22/1000) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower is unable to adjust debt position if it's still undercollateralized + // 100000 * (2000/125000) * 1.25 = 2000 + // 100000 * (22/1000) * 1.25 = 2750 + BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); + // borrower adjust debt position to right at MSSR + borrow( borrower, asset(0, mpa_id), asset(750) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Sam update MSSR and MCFR + // note: borrower's position is undercollateralized again due to the mssr change + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; + aubop.new_options.extensions.value.margin_call_fee_ratio = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(50000,mpa_id) ); + transfer( borrower3, seller2, asset(50000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(10000,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled in the beginning either + const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 250000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + + auto final_check = [&] + { + BOOST_CHECK( !db.find(settle_id) ); + + // call2 pays price = 210/10000 + // call2 match price = (210/10000) * (1299/1300) = 27279/1300000 + // sell_low receives = round_down(11100 * 27279/1300000)) = 232 + // sell_low pays = round_up(232 * 1300000/27279) = 11057, the rest is cancelled + // call2 receives = 11057 + // call2 pays = round_down(11057 * 210/10000) = 232, margin call fee = 0 + // now feed price = 13:10 * (100000-11057):(2100-232) = 13:10 * 88943:1868 = 1156259:18680 + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 238943 ); // 250000 - 11057 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1156259,mpa_id), asset(18680) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 88943 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1868 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /// Tests margin calls and force settlements when BSRM is no_settlement and call order is taker BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) { try { From edd73826af0e49e0aaddf7b3178bf7337a53f724 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 13:58:45 +0000 Subject: [PATCH 55/99] Fix tests about matching call with settle --- tests/tests/force_settle_match_tests.cpp | 56 +++++++++++------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 97299f4c0a..db51ef4588 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -321,9 +321,9 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK( db.find( call_id ) != nullptr ); // check - share_type call_to_pay = expected_amount_to_settle * 11 / 100; // round down, favors call order - share_type call_to_cover = (call_to_pay * 100 + 10 ) / 11; // stabilize : 101 -> 100 - share_type call_to_settler = (call_to_cover * 107) / 1000; // round down, favors call order + share_type call_to_settler = expected_amount_to_settle * 107 / 1000; // round down, favors call order : 10 + share_type call_to_cover = (call_to_settler * 1000 + 106 ) / 107; // stabilize : 101 -> 94 + share_type call_to_pay = call_to_cover * 11 / 100; // round down, favors call order : 10, fee = 0 BOOST_CHECK_EQUAL( 100000 - call_to_cover.value, call.debt.value ); BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value ); idump( (call) ); @@ -332,7 +332,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99884, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100, the rest 1 be canceled + BOOST_CHECK_EQUAL( 99890, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94, the rest 7 be canceled int64_t expected_seller_core_balance = 1 + 1 + call_to_settler.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); @@ -352,9 +352,9 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK( db.find( call_id ) != nullptr ); // check - share_type call_to_pay2 = amount_to_settle2 * 11 / 100; // round down, favors call order - share_type call_to_cover2 = (call_to_pay2 * 100 + 10 ) / 11; // stabilize : 100 -> 100 (no change) - share_type call_to_settler2 = (call_to_cover2 * 107) / 1000; // round down, favors call order + share_type call_to_settler2 = amount_to_settle2 * 107 / 1000; // round down, favors call order : 10 + share_type call_to_cover2 = (call_to_settler2 * 1000 + 106 ) / 107; // stabilize : 100 -> 94 + share_type call_to_pay2 = call_to_cover2 * 11 / 100; // round down, favors call order : 10, fee = 0 BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value ); BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value ); idump( (call) ); @@ -363,7 +363,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99784, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 + BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94 expected_seller_core_balance += call_to_settler2.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); @@ -398,22 +398,18 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK( db.find( call_id ) != nullptr ); // check - share_type call_to_pay3 = amount_to_settle3 * 13 / 100; // round down, favors call order - share_type call_to_cover3 = (call_to_pay3 * 100 + 10 ) / 13; // stabilize : 9 -> 8 - share_type call_to_settler3 = (call_to_cover3 * 103) / 1000; // round down, favors call order + share_type call_to_settler3 = amount_to_settle3 * 103 / 1000; // round down, favors call order : 0 BOOST_CHECK_EQUAL( 0, call_to_settler3.value ); - call_to_settler3 = 1; // 0 -> 1 - BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value - call_to_cover3.value, - call.debt.value ); - BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value - call_to_pay3.value, - call.collateral.value ); + // the settle order will be cancelled + BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value ); idump( (call) ); // borrower's balance doesn't change BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99776, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 - 8 + BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94 expected_seller_core_balance += call_to_settler3.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); @@ -433,16 +429,14 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK( db.find( call_id ) != nullptr ); // no data change - BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value - call_to_cover3.value, - call.debt.value ); - BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value - call_to_pay3.value, - call.collateral.value ); + BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value ); idump( (call) ); // borrower's balance doesn't change BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99776, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 - 8 + BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94 // expected_seller_core_balance does not change BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); @@ -1156,14 +1150,15 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // call2 is still in margin call territory after matched with limit order, now it matches with settle orders // the settle orders are too small to fill call2 share_type call2_to_cover1 = 39000; // 40000 - 1000 - share_type call2_to_pay1 = call2_to_cover1 * call2_copy.collateral / call2_copy.debt; // round down + share_type settle_receives2 = call2_to_cover1 * call2_copy.collateral * 107 + / (call2_copy.debt * 110); // round down share_type call2_to_cover1_old = call2_to_cover1; // stabilize - call2_to_cover1 = (call2_to_pay1 * call2_copy.debt + call2_copy.collateral - 1) / call2_copy.collateral; + call2_to_cover1 = (settle_receives2 * call2_copy.debt * 110 + call2_copy.collateral * 107 - 1) + / ( call2_copy.collateral * 107 ); + share_type call2_to_pay1 = call2_to_cover1 * call2_copy.collateral / call2_copy.debt; // round down share_type settle_refund = call2_to_cover1_old - call2_to_cover1; - share_type settle_receives2 = call2_to_cover1 * call2_copy.collateral * 107 - / (call2_copy.debt * 110); // round down share_type margin_call_fee_settle_2 = call2_to_pay1 - settle_receives2; expected_margin_call_fees += margin_call_fee_settle_2; @@ -1176,14 +1171,15 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // call2 matches with the other settle order share_type call2_to_cover2 = 10000; - share_type call2_to_pay2 = call2_to_cover2 * call2_copy.collateral / call2_copy.debt; // round down + share_type settle2_receives2 = call2_to_cover2 * call2_copy.collateral * 107 + / (call2_copy.debt * 110); // round down share_type call2_to_cover2_old = call2_to_cover2; // stabilize - call2_to_cover2 = (call2_to_pay2 * call2_copy.debt + call2_copy.collateral - 1) / call2_copy.collateral; + call2_to_cover2 = (settle2_receives2 * call2_copy.debt * 110 + call2_copy.collateral * 107 - 1) + / ( call2_copy.collateral * 107 ); + share_type call2_to_pay2 = call2_to_cover2 * call2_copy.collateral / call2_copy.debt; // round down share_type settle2_refund = call2_to_cover2_old - call2_to_cover2; - share_type settle2_receives2 = call2_to_cover2 * call2_copy.collateral * 107 - / (call2_copy.debt * 110); // round down share_type margin_call_fee_settle2_2 = call2_to_pay2 - settle2_receives2; expected_margin_call_fees += margin_call_fee_settle2_2; From 0443f5468d51beeb4e4cd2177edc2a22343dfe3a Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 14:32:22 +0000 Subject: [PATCH 56/99] Add tests about updating BSRM after GS --- tests/tests/bsrm_basic_tests.cpp | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 4257285d9a..5082d836ad 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -668,4 +668,117 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) } } +/// Tests whether it is able to update BSRM after GS +BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + + // Create asset + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + using bsrm_type = bitasset_options::black_swan_response_type; + + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed to trigger GS" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + + // Sam tries to update BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + for( uint16_t i = 1; i <= 3; ++i ) + { + idump( (i) ); + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + } + + // recheck + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + + // publish a new feed to revive the MPA + ilog( "Publish a new feed to revive MPA" ); + f.settlement_price = price( asset(1000,mpa_id), asset(3) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check - revived + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + // Sam tries to update BSRM + for( uint8_t i = 1; i <= 3; ++i ) + { + idump( (i) ); + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); + + if( i != 3 ) + { + aubop.new_options.extensions.value.black_swan_response_method = 0; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + } + } + + ilog( "Generate a block" ); + generate_block(); + + // final check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 8f71eb26d171c01ce7ae882d31f141c8cad78f82 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 15:13:14 +0000 Subject: [PATCH 57/99] Add BSRM update tests for indvd-settlement to fund --- tests/common/database_fixture.cpp | 3 + tests/tests/bsrm_basic_tests.cpp | 144 ++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 2c04862c0f..8b65780aef 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -514,7 +514,10 @@ void database_fixture_base::verify_asset_supplies( const database& db ) { const auto& bad = asset_obj.bitasset_data(db); total_balances[bad.options.short_backing_asset] += bad.settlement_fund; + total_balances[bad.options.short_backing_asset] += bad.individual_settlement_fund; total_balances[bad.options.short_backing_asset] += dasset_obj.accumulated_collateral_fees; + if( !bad.has_settlement() ) // Note: if asset has been globally settled, do not check total debt + total_debts[bad.asset_id] += bad.individual_settlement_debt; } total_balances[asset_obj.id] += dasset_obj.confidential_supply.value; } diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 5082d836ad..79a01c67e8 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -781,4 +781,148 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) } } +/// Tests whether it is able to update BSRM after individual settlement to fund +BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed to trigger settlement" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + // Sam tries to update BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + for( uint16_t i = 0; i <= 3; ++i ) + { + if( static_cast(i) == bsrm_type::individual_settlement_to_fund ) + continue; + idump( (i) ); + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + } + + // recheck + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + // Settle debt + ilog( "Settle" ); + force_settle( borrower2, asset(100000,mpa_id) ); + + // recheck + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + + // Sam tries to update BSRM + for( uint8_t i = 0; i <= 3; ++i ) + { + if( static_cast(i) == bsrm_type::individual_settlement_to_fund ) + continue; + idump( (i) ); + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); + if( i != 3 ) + { + aubop.new_options.extensions.value.black_swan_response_method = bsrm_value; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + } + } + + ilog( "Generate a block" ); + generate_block(); + + // final check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 0e207048feece1ab8a1b17ef9ba2b02bc802ee09 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 16:09:33 +0000 Subject: [PATCH 58/99] Add BSRM update tests for indvd-settle to order --- tests/common/database_fixture.cpp | 9 +- tests/tests/bsrm_basic_tests.cpp | 179 +++++++++++++++++++++++++++++- 2 files changed, 182 insertions(+), 6 deletions(-) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 8b65780aef..9218a90a63 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -493,10 +493,17 @@ void database_fixture_base::verify_asset_supplies( const database& db ) for( const limit_order_object& o : db.get_index_type().indices() ) { asset for_sale = o.amount_for_sale(); - if( for_sale.asset_id == asset_id_type() ) core_in_orders += for_sale.amount; + if( for_sale.asset_id == asset_id_type() && !o.is_settled_debt ) + // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders + core_in_orders += for_sale.amount; total_balances[for_sale.asset_id] += for_sale.amount; total_balances[asset_id_type()] += o.deferred_fee; total_balances[o.deferred_paid_fee.asset_id] += o.deferred_paid_fee.amount; + if( o.is_settled_debt ) + { + total_debts[o.receive_asset_id()] += o.sell_price.quote.amount; + BOOST_CHECK_EQUAL( o.sell_price.base.amount.value, for_sale.amount.value ); + } } for( const call_order_object& o : db.get_index_type().indices() ) { diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 79a01c67e8..c8192553c6 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -692,6 +692,9 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) using bsrm_type = bitasset_options::black_swan_response_type; BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -717,7 +720,10 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) publish_feed( mpa_id, feeder_id, f, feed_icr ); // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); // Sam tries to update BSRM @@ -736,8 +742,10 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) } // recheck - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // publish a new feed to revive the MPA ilog( "Publish a new feed to revive MPA" ); @@ -746,6 +754,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) // check - revived BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 1; i <= 3; ++i ) @@ -772,8 +782,10 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) generate_block(); // final check - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); @@ -823,8 +835,9 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_fund ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -855,6 +868,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -880,14 +894,16 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) == bsrm_type::individual_settlement_to_fund ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // Settle debt ilog( "Settle" ); force_settle( borrower2, asset(100000,mpa_id) ); // recheck - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 0; i <= 3; ++i ) @@ -916,8 +932,161 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) generate_block(); // final check - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests whether it is able to update BSRM after individual settlement to order +BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed to trigger settlement" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + // Sam tries to update BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + for( uint16_t i = 0; i <= 3; ++i ) + { + if( static_cast(i) == bsrm_type::individual_settlement_to_order ) + continue; + idump( (i) ); + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + } + + // recheck + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( db.find_individual_settlemnt_order(mpa_id) ); + + // Fill the individual settlement order + ilog( "Buy into the individual settlement order" ); + const limit_order_object* sell_ptr = create_sell_order( borrower2, asset(100000,mpa_id), asset(1) ); + BOOST_CHECK( !sell_ptr ); + + // recheck + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + + // Sam tries to update BSRM + for( uint8_t i = 0; i <= 3; ++i ) + { + if( static_cast(i) == bsrm_type::individual_settlement_to_order ) + continue; + idump( (i) ); + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); + if( i != 2 ) + { + aubop.new_options.extensions.value.black_swan_response_method = bsrm_value; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + } + } + + ilog( "Generate a block" ); + generate_block(); + + // final check + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(2) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); From cdcc4d6549c254c61c281fa86a4dc3684420fe8f Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 16:10:31 +0000 Subject: [PATCH 59/99] Add comments --- libraries/chain/db_market.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index aa9206a594..3201646177 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -370,6 +370,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, obj.is_settled_debt = true; } ); } + // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders } // call order is maker @@ -1107,6 +1108,7 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde asset(0, call_receives.asset_id), match_price, true ) ); // 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 From d483ebbdc651a2069fda0b19db25780f810e15d0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 16:15:31 +0000 Subject: [PATCH 60/99] Rename a function --- libraries/chain/asset_evaluator.cpp | 2 +- libraries/chain/db_getter.cpp | 2 +- libraries/chain/db_market.cpp | 4 +-- .../chain/include/graphene/chain/database.hpp | 2 +- tests/tests/bsrm_basic_tests.cpp | 30 +++++++++---------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9b184814f0..f8c22536cb 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -744,7 +744,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( !current_bitasset_data.has_individual_settlement(), "Unable to update BSRM when the individual settlement pool is not empty" ); else if( bsrm_type::individual_settlement_to_order == old_bsrm ) - FC_ASSERT( !d.find_individual_settlemnt_order( op.asset_to_update ), + FC_ASSERT( !d.find_settled_debt_order( op.asset_to_update ), "Unable to update BSRM when there exists an individual settlement order" ); // Since we do not allow updating in some cases (above), only check no_settlement here diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index ed4fc84ac6..085b48f338 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -149,7 +149,7 @@ const witness_schedule_object& database::get_witness_schedule_object()const return *_p_witness_schedule_obj; } -const limit_order_object* database::find_individual_settlemnt_order( const asset_id_type& a )const +const limit_order_object* database::find_settled_debt_order( const asset_id_type& a )const { const auto& limit_index = get_index_type().indices().get(); auto itr = limit_index.lower_bound( std::make_tuple( true, a ) ); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 3201646177..12063c75b8 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -303,7 +303,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, } // Move the individual settlement order to the GS fund - const limit_order_object* limit_ptr = find_individual_settlemnt_order( bitasset.asset_id ); + const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id ); if( limit_ptr ) { collateral_gathered.amount += limit_ptr->for_sale; @@ -351,7 +351,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } else // settle to order { - const limit_order_object* limit_ptr = find_individual_settlemnt_order( bitasset.asset_id ); + const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id ); if( limit_ptr ) { modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index fecb6e6acc..9f8b438d95 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -213,7 +213,7 @@ namespace graphene { namespace chain { /// Find the limit order which is the individual settlement fund of the specified asset /// @param a ID of the asset /// @return nullptr if not found, pointer to the limit order if found - const limit_order_object* find_individual_settlemnt_order( const asset_id_type& a )const; + const limit_order_object* find_settled_debt_order( const asset_id_type& a )const; /// Find the call order with the least collateral ratio /// @param bitasset The bitasset object diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index c8192553c6..4b0a24b1e6 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -694,7 +694,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -723,7 +723,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); // Sam tries to update BSRM @@ -745,7 +745,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // publish a new feed to revive the MPA ilog( "Publish a new feed to revive MPA" ); @@ -755,7 +755,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) // check - revived BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 1; i <= 3; ++i ) @@ -785,7 +785,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); @@ -837,7 +837,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) == bsrm_type::individual_settlement_to_fund ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -868,7 +868,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -894,7 +894,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) == bsrm_type::individual_settlement_to_fund ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Settle debt ilog( "Settle" ); @@ -903,7 +903,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) // recheck BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 0; i <= 3; ++i ) @@ -935,7 +935,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); @@ -987,7 +987,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) == bsrm_type::individual_settlement_to_order ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -1018,7 +1018,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) // check BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -1044,7 +1044,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) == bsrm_type::individual_settlement_to_order ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); // Fill the individual settlement order ilog( "Buy into the individual settlement order" ); @@ -1054,7 +1054,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) // recheck BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 0; i <= 3; ++i ) @@ -1086,7 +1086,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(2) ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); From fa7e7d7f7dafb9d13a15c9c5f40c521411be8590 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 17:11:41 +0000 Subject: [PATCH 61/99] Add tests about updating BSRM from no_settlement to other types when exists an undercollateralized debt position --- tests/tests/bsrm_basic_tests.cpp | 148 +++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 4b0a24b1e6..cb062a196a 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -1094,4 +1094,152 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) } } +/// Tests scenarios: +/// updating BSRM from no_settlement to others when the least collateralized short is actually undercollateralized +BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Several passes, update BSRM from no_settlement to different values + for( uint8_t i = 0; i <= 3; ++i ) + { + if( i == bsrm_value ) + continue; + idump( (i) ); + + ACTORS((sam)(feeder)(borrower)(borrower2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::no_settlement ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed so that the least collateralized short is undercollateralized" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + // Sam tries to update BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + const auto& check_result = [&] + { + switch( static_cast(i) ) + { + case bsrm_type::global_settlement: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + break; + case bsrm_type::individual_settlement_to_fund: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + break; + case bsrm_type::individual_settlement_to_order: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + break; + default: + BOOST_FAIL( "This should not happen" ); + break; + } + }; + + check_result(); + + ilog( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 7b82a6c08b11f06d5282b59ec07bcf03f7cc108a Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 6 Sep 2021 17:57:09 +0000 Subject: [PATCH 62/99] Simplify code --- libraries/chain/db_market.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 12063c75b8..8fdaa5bb4a 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -686,7 +686,7 @@ bool database::apply_order(const limit_order_object& new_order_object) // We only need to check if the new order will match with others if it is at the front of the book const auto& limit_price_idx = get_index_type().indices().get(); - auto limit_itr = limit_price_idx.lower_bound( boost::make_tuple( new_order_object.sell_price, order_id ) ); + auto limit_itr = limit_price_idx.iterator_to( new_order_object ); if( limit_itr != limit_price_idx.begin() ) { --limit_itr; @@ -766,13 +766,14 @@ bool database::apply_order(const limit_order_object& new_order_object) { auto call_min = price::min( recv_asset_id, sell_asset_id ); // check limit orders first, match the ones with better price in comparison to call orders - while( !finished && limit_itr != limit_end && limit_itr->sell_price > call_match_price ) + auto limit_itr_after_call = limit_price_idx.lower_bound( call_match_price ); + while( !finished && limit_itr != limit_itr_after_call ) { - auto old_limit_itr = limit_itr; + const limit_order_object& matching_limit_order = *limit_itr; ++limit_itr; // match returns 2 when only the old order was fully filled. // In this case, we keep matching; otherwise, we stop. - finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) + finished = ( match( new_order_object, matching_limit_order, matching_limit_order.sell_price ) != match_result_type::only_maker_filled ); } @@ -851,10 +852,10 @@ bool database::apply_order(const limit_order_object& new_order_object) // still need to check limit orders while( !finished && limit_itr != limit_end ) { - auto old_limit_itr = limit_itr; + const limit_order_object& matching_limit_order = *limit_itr; ++limit_itr; // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. - finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) + finished = ( match( new_order_object, matching_limit_order, matching_limit_order.sell_price ) != match_result_type::only_maker_filled ); } @@ -1966,12 +1967,9 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( usd_to_buy == usd_for_sale ) filled_limit = true; - else if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) - { + else if( filled_limit && before_hardfork_615 ) //NOTE: Multiple limit match problem (see issue 453, yes this happened) - if( before_hardfork_615 ) - _issue_453_affected_assets.insert( bitasset.asset_id ); - } + _issue_453_affected_assets.insert( bitasset.asset_id ); } limit_pays = call_receives; From e1f5547380ec3433110fc0edfe83ec5a2ea51473 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 6 Sep 2021 20:43:41 +0000 Subject: [PATCH 63/99] Refactor call_order_update_evaluator by moving some assertions from do_apply() to do_evaluator() --- .../graphene/chain/market_evaluator.hpp | 16 ++-- libraries/chain/market_evaluator.cpp | 86 +++++++++++-------- 2 files changed, 60 insertions(+), 42 deletions(-) diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 80a0c25eab..5dec716aea 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -37,7 +37,7 @@ namespace graphene { namespace chain { class limit_order_create_evaluator : public evaluator { public: - typedef limit_order_create_operation operation_type; + using operation_type = limit_order_create_operation; void_result do_evaluate( const limit_order_create_operation& o ); object_id_type do_apply( const limit_order_create_operation& o ); @@ -51,6 +51,7 @@ namespace graphene { namespace chain { */ virtual void pay_fee() override; + private: share_type _deferred_fee = 0; asset _deferred_paid_fee; const limit_order_create_operation* _op = nullptr; @@ -62,25 +63,29 @@ namespace graphene { namespace chain { class limit_order_cancel_evaluator : public evaluator { public: - typedef limit_order_cancel_operation operation_type; + using operation_type = limit_order_cancel_operation; void_result do_evaluate( const limit_order_cancel_operation& o ); asset do_apply( const limit_order_cancel_operation& o ); + private: const limit_order_object* _order; }; class call_order_update_evaluator : public evaluator { public: - typedef call_order_update_operation operation_type; + using operation_type = call_order_update_operation; void_result do_evaluate( const call_order_update_operation& o ); object_id_type do_apply( const call_order_update_operation& o ); + private: bool _closing_order = false; const asset_object* _debt_asset = nullptr; - const call_order_object* _order = nullptr; + const call_order_object* call_ptr = nullptr; + share_type new_debt; + share_type new_collateral; const asset_bitasset_data_object* _bitasset_data = nullptr; const asset_dynamic_data_object* _dynamic_data_obj = nullptr; }; @@ -88,11 +93,12 @@ namespace graphene { namespace chain { class bid_collateral_evaluator : public evaluator { public: - typedef bid_collateral_operation operation_type; + using operation_type = bid_collateral_operation; void_result do_evaluate( const bid_collateral_operation& o ); void_result do_apply( const bid_collateral_operation& o ); + private: const asset_object* _debt_asset = nullptr; const asset_bitasset_data_object* _bitasset_data = nullptr; const collateral_bid_object* _bid = nullptr; diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index e534af7f7a..c8ebb33115 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -180,7 +180,7 @@ asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& void_result call_order_update_evaluator::do_evaluate(const call_order_update_operation& o) { try { - database& d = db(); + const database& d = db(); auto next_maintenance_time = d.get_dynamic_global_properties().next_maintenance_time; @@ -214,6 +214,30 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset, "Collateral asset type should be same as backing asset of debt asset" ); + auto& call_idx = d.get_index_type().indices().get(); + auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) ); + if( itr != call_idx.end() ) // updating or closing debt position + { + call_ptr = &(*itr); + new_collateral = call_ptr->collateral + o.delta_collateral.amount; + new_debt = call_ptr->debt + o.delta_debt.amount; + if( new_debt == 0 ) + { + FC_ASSERT( new_collateral == 0, "Should claim all collateral when closing debt position" ); + _closing_order = true; + } + else + { + FC_ASSERT( new_collateral > 0 && new_debt > 0, + "Both collateral and debt should be positive after updated a debt position if not to close it" ); + } + } + else // creating new debt position + { + FC_ASSERT( o.delta_collateral.amount > 0, "Delta collateral amount of new debt position should be positive" ); + FC_ASSERT( o.delta_debt.amount > 0, "Delta debt amount of new debt position should be positive" ); + } + if( _bitasset_data->is_prediction_market ) FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount, "Debt amount and collateral amount should be same when updating debt position in a prediction market" ); @@ -245,7 +269,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope d.adjust_balance( o.funding_account, o.delta_debt ); // Deduct the debt paid from the total supply of the debt asset. - d.modify(*_dynamic_data_obj, [&](asset_dynamic_data_object& dynamic_asset) { + d.modify(*_dynamic_data_obj, [&o](asset_dynamic_data_object& dynamic_asset) { dynamic_asset.current_supply += o.delta_debt.amount; }); } @@ -266,20 +290,14 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_1270 = ( next_maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue - auto& call_idx = d.get_index_type().indices().get(); - auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) ); - const call_order_object* call_obj = nullptr; call_order_id_type call_order_id; optional old_collateralization; optional old_debt; - if( itr == call_idx.end() ) // creating new debt position + if( !call_ptr ) // creating new debt position { - FC_ASSERT( o.delta_collateral.amount > 0, "Delta collateral amount of new debt position should be positive" ); - FC_ASSERT( o.delta_debt.amount > 0, "Delta debt amount of new debt position should be positive" ); - - call_obj = &d.create( [&o,this,before_core_hardfork_1270]( call_order_object& call ){ + call_ptr = &d.create( [&o,this,before_core_hardfork_1270]( call_order_object& call ){ call.borrower = o.funding_account; call.collateral = o.delta_collateral.amount; call.debt = o.delta_debt.amount; @@ -290,19 +308,15 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope call.call_price = price( asset( 1, o.delta_collateral.asset_id ), asset( 1, o.delta_debt.asset_id ) ); call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; }); - call_order_id = call_obj->id; + call_order_id = call_ptr->id; } else // updating existing debt position { - call_obj = &*itr; - auto new_collateral = call_obj->collateral + o.delta_collateral.amount; - auto new_debt = call_obj->debt + o.delta_debt.amount; - call_order_id = call_obj->id; + call_order_id = call_ptr->id; - if( new_debt == 0 ) + if( _closing_order ) { - FC_ASSERT( new_collateral == 0, "Should claim all collateral when closing debt position" ); - d.remove( *call_obj ); + d.remove( *call_ptr ); // Update current_feed if needed const auto bsrm = _bitasset_data->get_black_swan_response_method(); @@ -312,13 +326,10 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope return call_order_id; } - FC_ASSERT( new_collateral > 0 && new_debt > 0, - "Both collateral and debt should be positive after updated a debt position if not to close it" ); - - old_collateralization = call_obj->collateralization(); - old_debt = call_obj->debt; + old_collateralization = call_ptr->collateralization(); + old_debt = call_ptr->debt; - d.modify( *call_obj, [&o,new_debt,new_collateral,this,before_core_hardfork_1270]( call_order_object& call ){ + d.modify( *call_ptr, [&o,this,before_core_hardfork_1270]( call_order_object& call ){ call.collateral = new_collateral; call.debt = new_debt; if( before_core_hardfork_1270 ) // don't update call_price after core-1270 hard fork @@ -339,7 +350,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // force settlement order. if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) { - FC_ASSERT( call_obj->collateralization() >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), + // Note: this will throw even when increasing CR + FC_ASSERT( call_ptr->collateralization() >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), "Could not create a debt position which would trigger a blackswan event instantly" ); } // check to see if the order needs to be margin called now, but don't allow black swans and require there to be @@ -349,23 +361,23 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope if( d.check_call_orders( *_debt_asset, false, false, _bitasset_data ) ) // don't allow black swan, // not for new limit order { - call_obj = d.find(call_order_id); + call_ptr = d.find(call_order_id); // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. // after hard fork core-583: we want to allow increasing collateral // Note: increasing collateral won't get the call order itself matched (instantly margin called) // if there is at least a call order get matched but didn't cause a black swan event, // current order must have got matched. in this case, it's OK if it's totally filled. GRAPHENE_ASSERT( - !call_obj, + !call_ptr, call_order_update_unfilled_margin_call, "Updating call order would trigger a margin call that cannot be fully filled" ); } else { - call_obj = d.find(call_order_id); + call_ptr = d.find(call_order_id); // we know no black swan event has occurred - FC_ASSERT( call_obj, "no margin call was executed and yet the call object was deleted" ); + FC_ASSERT( call_ptr, "no margin call was executed and yet the call object was deleted" ); // this HF must remain as-is, as the assert inside the "if" was triggered during push_proposal() if( d.head_block_time() <= HARDFORK_CORE_583_TIME ) { @@ -374,11 +386,11 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // were no matching orders. In the latter case, we throw. GRAPHENE_ASSERT( // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here - ~call_obj->call_price < _bitasset_data->current_feed.settlement_price, + ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price, call_order_update_unfilled_margin_call, "Updating call order would trigger a margin call that cannot be fully filled", // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here - ("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price) + ("a", ~call_ptr->call_price )("b", _bitasset_data->current_feed.settlement_price) ); } else // after hard fork core-583, always allow call order to be updated if collateral ratio @@ -396,19 +408,19 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // The `current_initial_collateralization` variable has been initialized according to the logic, // so we directly use it here. bool check = ( !before_core_hardfork_1270 - && call_obj->collateralization() > _bitasset_data->current_initial_collateralization ) + && call_ptr->collateralization() > _bitasset_data->current_initial_collateralization ) || ( before_core_hardfork_1270 - && ~call_obj->call_price < _bitasset_data->current_feed.settlement_price ) - || ( old_collateralization.valid() && call_obj->debt <= *old_debt - && call_obj->collateralization() > *old_collateralization ); + && ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) + || ( old_collateralization.valid() && call_ptr->debt <= *old_debt + && call_ptr->collateralization() > *old_collateralization ); FC_ASSERT( check, "Can only increase collateral ratio without increasing debt when the debt position's " "collateral ratio is lower than required initial collateral ratio (ICR), " "if not to trigger a margin call that be fully filled immediately", ("old_debt", old_debt) - ("new_debt", call_obj->debt) + ("new_debt", call_ptr->debt) ("old_collateralization", old_collateralization) - ("new_collateralization", call_obj->collateralization() ) + ("new_collateralization", call_ptr->collateralization() ) ); } } From fb90153f068fdc071db1780bc65ec6001f3dfa88 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 6 Sep 2021 21:00:45 +0000 Subject: [PATCH 64/99] Allow closing debt position even if no price feed --- libraries/chain/market_evaluator.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index c8ebb33115..1b65fe5def 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -165,11 +165,12 @@ asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& auto quote_asset = _order->sell_price.quote.asset_id; auto refunded = _order->amount_for_sale(); - d.cancel_limit_order(*_order, false /* don't create a virtual op*/); + d.cancel_limit_order( *_order, false ); // don't create a virtual op if( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_606_TIME ) { - // Possible optimization: order can be called by canceling a limit order iff the canceled order was at the top of the book. + // Possible optimization: + // order can be called by canceling a limit order if the canceled order was at the top of the book. // Do I need to check calls in both assets? d.check_call_orders(base_asset(d)); d.check_call_orders(quote_asset(d)); @@ -209,7 +210,8 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope /// if there is a settlement for this asset, then no further margin positions may be taken and /// all existing margin positions should have been closed va database::globally_settle_asset - FC_ASSERT( !_bitasset_data->has_settlement(), "Cannot update debt position when the asset has been globally settled" ); + FC_ASSERT( !_bitasset_data->has_settlement(), + "Cannot update debt position when the asset has been globally settled" ); FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset, "Collateral asset type should be same as backing asset of debt asset" ); @@ -240,8 +242,10 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope if( _bitasset_data->is_prediction_market ) FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount, - "Debt amount and collateral amount should be same when updating debt position in a prediction market" ); - else if( _bitasset_data->current_feed.settlement_price.is_null() ) + "Debt amount and collateral amount should be same when updating debt position in a prediction " + "market" ); + else if( _bitasset_data->current_feed.settlement_price.is_null() + && !( HARDFORK_CORE_2467_PASSED( next_maintenance_time ) && _closing_order ) ) FC_THROW_EXCEPTION(insufficient_feeds, "Cannot borrow asset with no price feed."); // Since hard fork core-973, check asset authorization limitations From ea5ffb88fc1c916e7a40dd62c5106c93859d229f Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 6 Sep 2021 21:35:19 +0000 Subject: [PATCH 65/99] Add tests about closing debt position when no feed --- tests/tests/bsrm_basic_tests.cpp | 76 ++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index cb062a196a..0eed9937a3 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -668,6 +668,82 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) } } +/// Tests closing debt position when there is no sufficient price feeds +BOOST_AUTO_TEST_CASE( close_debt_position_when_no_feed ) +{ + try { + + // Advance to a time before core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + + // Create asset + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // update price feed publisher list so that there is no valid feed + update_feed_producers( mpa_id, { sam_id } ); + + // no sufficient price feeds + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + // Unable to close debt position + BOOST_CHECK_THROW( cover( borrower, asset(100000, mpa_id), asset(2000) ), fc::exception ); + BOOST_CHECK( db.find( call_id ) ); + + // Go beyond the hard fork time + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Still no sufficient price feeds + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + // The debt position is there + BOOST_CHECK( db.find( call_id ) ); + + // Able to close debt position + cover( borrower_id(db), asset(100000, mpa_id), asset(2000) ); + BOOST_CHECK( !db.find( call_id ) ); + + ilog( "Generate a block" ); + generate_block(); + + // final check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + BOOST_CHECK( !db.find( call_id ) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /// Tests whether it is able to update BSRM after GS BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) { From 0a843b5250a7360de4dde21950d3104cb7967733 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 00:25:01 +0000 Subject: [PATCH 66/99] Remove Ubuntu 16 from Github Actions workflows --- .github/workflows/build-and-test.ubuntu-debug.yml | 2 +- .github/workflows/build-and-test.ubuntu-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.ubuntu-debug.yml b/.github/workflows/build-and-test.ubuntu-debug.yml index 6689f61c4a..960902bf3b 100644 --- a/.github/workflows/build-and-test.ubuntu-debug.yml +++ b/.github/workflows/build-and-test.ubuntu-debug.yml @@ -8,7 +8,7 @@ jobs: name: Build and test in Debug mode strategy: matrix: - os: [ ubuntu-16.04, ubuntu-18.04, ubuntu-20.04 ] + os: [ ubuntu-18.04, ubuntu-20.04 ] runs-on: ${{ matrix.os }} services: elasticsearch: diff --git a/.github/workflows/build-and-test.ubuntu-release.yml b/.github/workflows/build-and-test.ubuntu-release.yml index 8f310d8d0a..69e25a21ae 100644 --- a/.github/workflows/build-and-test.ubuntu-release.yml +++ b/.github/workflows/build-and-test.ubuntu-release.yml @@ -8,7 +8,7 @@ jobs: name: Build and test in Release mode strategy: matrix: - os: [ ubuntu-16.04, ubuntu-18.04, ubuntu-20.04 ] + os: [ ubuntu-18.04, ubuntu-20.04 ] runs-on: ${{ matrix.os }} services: elasticsearch: From 29e36f043afd9953982e5bf79805fc4367dd1f3d Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 00:27:00 +0000 Subject: [PATCH 67/99] Update cache keys in Github Actions MinGW workflow --- .github/workflows/build-and-test.win.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-and-test.win.yml b/.github/workflows/build-and-test.win.yml index e1fad2984e..eeb942b79e 100644 --- a/.github/workflows/build-and-test.win.yml +++ b/.github/workflows/build-and-test.win.yml @@ -14,12 +14,15 @@ jobs: name: Build required 3rd-party libraries runs-on: ubuntu-latest steps: + # Get OS version to be used in cache key - see https://github.com/actions/cache/issues/543 + - run: | + echo "OS_VERSION=`lsb_release -sr`" >> $GITHUB_ENV - name: Load Cache id: cache-libs - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: libs - key: mingw64-libs-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }} + key: mingw64-libs-${{ env.OS_VERSION }}-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }} - name: Install dependencies if: steps.cache-libs.outputs.cache-hit != 'true' run: | @@ -113,11 +116,13 @@ jobs: - uses: actions/checkout@v2 with: submodules: recursive + - run: | + echo "OS_VERSION=`lsb_release -sr`" >> $GITHUB_ENV - name: Load external libraries - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: libs - key: mingw64-libs-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }} + key: mingw64-libs-${{ env.OS_VERSION }}-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }} - name: Configure run: | LIBS="`pwd`/libs" @@ -138,13 +143,13 @@ jobs: -D GRAPHENE_DISABLE_UNITY_BUILD=ON \ .. - name: Load Cache - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ccache - key: ccache-mingw64-${{ github.ref }}-${{ github.sha }} + key: ccache-mingw64-${{ env.OS_VERSION }}-${{ github.ref }}-${{ github.sha }} restore-keys: | - ccache-mingw64-${{ github.ref }}- - ccache-mingw64- + ccache-mingw64-${{ env.OS_VERSION }}-${{ github.ref }}- + ccache-mingw64-${{ env.OS_VERSION }}- - name: Build run: | export CCACHE_DIR="$GITHUB_WORKSPACE/ccache" From 30c64415dafa03f6cf32cbcecb31c37808f1e53b Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 14:04:57 +0000 Subject: [PATCH 68/99] Fix call_order_update issues about no_settlement * check call orders after updated/closed a debt position * always allow increasing CR if not to increasing debt * allow partial fill if triggers margin call and new CR > ICR --- libraries/chain/db_market.cpp | 2 +- libraries/chain/market_evaluator.cpp | 84 +++++++++++++++++++--------- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 8fdaa5bb4a..2cf41d4911 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -2033,7 +2033,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // TODO perhaps improve performance by settling multiple call orders inside in one call while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) { - // do nothing + margin_called = true; } } else diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 1b65fe5def..e657106ffc 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -325,7 +325,15 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // Update current_feed if needed const auto bsrm = _bitasset_data->get_black_swan_response_method(); if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) + { + auto old_feed_price = _bitasset_data->current_feed.settlement_price; d.update_bitasset_current_feed( *_bitasset_data, true ); + if( !_bitasset_data->current_feed.settlement_price.is_null() + && _bitasset_data->current_feed.settlement_price != old_feed_price ) + { + d.check_call_orders( *_debt_asset, true, false, _bitasset_data ); + } + } return call_order_id; } @@ -352,34 +360,65 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // * if there is no force settlement order, it would trigger a blackswan event instantly, // * if there is a force settlement order, they will match at the call order's CR, but it is not fair for the // force settlement order. + auto call_collateralization = call_ptr->collateralization(); + bool increasing_cr = ( old_collateralization.valid() && call_ptr->debt <= *old_debt + && call_collateralization > *old_collateralization ); if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) { - // Note: this will throw even when increasing CR - FC_ASSERT( call_ptr->collateralization() >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), - "Could not create a debt position which would trigger a blackswan event instantly" ); + // Note: if it is to increase CR and is not increasing debt amount, it is allowed, + // because it implies BSRM == no_settlement + FC_ASSERT( increasing_cr + || call_collateralization >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), + "Could not create a debt position which would trigger a blackswan event instantly, " + "unless it is to increase collateral ratio of an existing debt position and " + "is not increasing its debt amount" ); } + // Update current_feed if needed + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) + d.update_bitasset_current_feed( *_bitasset_data, true ); + // check to see if the order needs to be margin called now, but don't allow black swans and require there to be // limit orders available that could be used to fill the order. // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork, // the first call order may be unable to be updated if the second one is undercollateralized. - if( d.check_call_orders( *_debt_asset, false, false, _bitasset_data ) ) // don't allow black swan, - // not for new limit order + // Note: check call orders, don't allow black swan, not for new limit order + bool called_some = d.check_call_orders( *_debt_asset, false, false, _bitasset_data ); + call_ptr = d.find(call_order_id); + if( called_some ) { - call_ptr = d.find(call_order_id); // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. // after hard fork core-583: we want to allow increasing collateral // Note: increasing collateral won't get the call order itself matched (instantly margin called) // if there is at least a call order get matched but didn't cause a black swan event, // current order must have got matched. in this case, it's OK if it's totally filled. - GRAPHENE_ASSERT( - !call_ptr, - call_order_update_unfilled_margin_call, - "Updating call order would trigger a margin call that cannot be fully filled" - ); + // after hard fork core-2467: when BSRM is no_settlement, it is possible that other call orders are matched + // in check_call_orders, also possible that increasing CR will get the call order itself matched + if( !HARDFORK_CORE_2467_PASSED( next_maint_time ) ) // before core-2467 hf + { + GRAPHENE_ASSERT( !call_ptr, call_order_update_unfilled_margin_call, + "Updating call order would trigger a margin call that cannot be fully filled" ); + } + // after core-2467 hf + else + { + // if the call order is totally filled, it is OK, + // if it is increasing CR, it is always ok, no matter if it or another another call order is called, + // otherwise, the remaining call order's CR need to be > ICR + // TODO: perhaps it makes sense to allow more cases, e.g. + // - when a position has ICR > CR > MCR, allow the owner to sell some collateral to increase CR + // - allow owners to sell collateral at price < MSSP (need to update code elsewhere) + FC_ASSERT( !call_ptr || increasing_cr + || call_ptr->collateralization() > _bitasset_data->current_initial_collateralization, + "Could not create a debt position which would trigger a margin call instantly, " + "unless the debt position is fully filled, or it is to increase collateral ratio of " + "an existing debt position and is not increasing its debt amount, " + "or the remaining debt position's collateral ratio is above required " + "initial collateral ratio (ICR)" ); + } } else { - call_ptr = d.find(call_order_id); // we know no black swan event has occurred FC_ASSERT( call_ptr, "no margin call was executed and yet the call object was deleted" ); // this HF must remain as-is, as the assert inside the "if" was triggered during push_proposal() @@ -411,27 +450,22 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // after BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). // The `current_initial_collateralization` variable has been initialized according to the logic, // so we directly use it here. - bool check = ( !before_core_hardfork_1270 - && call_ptr->collateralization() > _bitasset_data->current_initial_collateralization ) - || ( before_core_hardfork_1270 - && ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) - || ( old_collateralization.valid() && call_ptr->debt <= *old_debt - && call_ptr->collateralization() > *old_collateralization ); + bool check = ( increasing_cr + || ( !before_core_hardfork_1270 + && call_collateralization > _bitasset_data->current_initial_collateralization ) + || ( before_core_hardfork_1270 + && ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) ); FC_ASSERT( check, "Can only increase collateral ratio without increasing debt when the debt position's " - "collateral ratio is lower than required initial collateral ratio (ICR), " - "if not to trigger a margin call that be fully filled immediately", + "collateral ratio is lower than or equal to required initial collateral ratio (ICR), " + "if not to trigger a margin call immediately", ("old_debt", old_debt) ("new_debt", call_ptr->debt) ("old_collateralization", old_collateralization) - ("new_collateralization", call_ptr->collateralization() ) + ("new_collateralization", call_collateralization) ); } } - // Update current_feed if needed - const auto bsrm = _bitasset_data->get_black_swan_response_method(); - if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) - d.update_bitasset_current_feed( *_bitasset_data, true ); } return call_order_id; From 9866bb0eae6341b723d644723b8adddda7e1642f Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 14:35:46 +0000 Subject: [PATCH 69/99] Fix no_settlement tests wrt call_order_update --- tests/tests/bsrm_no_settlement_tests.cpp | 32 +++++++++--------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index 39f7e08dc6..f864522da2 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -124,12 +124,10 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_REQUIRE( call3_ptr ); call_order_id_type call3_id = call3_ptr->id; - // borrower is unable to adjust debt position if it's still undercollateralized - // 1000 * (2000/1250) * 1.25 = 2000 - // 1000 * (22/10) * 1.25 = 2750 - BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); // borrower adjust debt position to right at MSSR - borrow( borrower, asset(0, mpa_id), asset(750) ); + // 1000 * (22/10) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CRid; - // borrower is unable to adjust debt position if it's still undercollateralized - // 100000 * (2000/125000) * 1.25 = 2000 - // 100000 * (22/1000) * 1.25 = 2750 - BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); // borrower adjust debt position to right at MSSR - borrow( borrower, asset(0, mpa_id), asset(750) ); + // 100000 * (22/1000) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CRid; - // borrower is unable to adjust debt position if it's still undercollateralized - // 1000 * (2000/1250) * 1.25 = 2000 - // 1000 * (22/10) * 1.25 = 2750 - BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); // borrower adjust debt position to right at MSSR - borrow( borrower, asset(0, mpa_id), asset(750) ); + // 1000 * (22/10) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CRid; - // borrower is unable to adjust debt position if it's still undercollateralized - // 100000 * (2000/125000) * 1.25 = 2000 - // 100000 * (22/1000) * 1.25 = 2750 - BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); // borrower adjust debt position to right at MSSR - borrow( borrower, asset(0, mpa_id), asset(750) ); + // 100000 * (22/1000) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CR Date: Tue, 7 Sep 2021 15:38:41 +0000 Subject: [PATCH 70/99] Fix tests about call_order_update partially fill --- tests/tests/force_settle_match_tests.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index db51ef4588..8cda2a54ae 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -640,11 +640,11 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // buy_low's price is too low that won't be matched BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); - // Can not reduce CR of a call order to trigger a margin call but not get fully filled - BOOST_CHECK_THROW( borrow( borrower_id(db), asset(10, usd_id), asset(0), 1700), fc::exception ); + // Can not reduce CR of a call order to trigger a margin call but not get fully filled and final CR <= ICR + BOOST_CHECK_THROW( borrow( borrower_id(db), asset(10000, usd_id), asset(160000), 1700), fc::exception ); - // Can not create a new call order that is partially called instantly - BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10, usd_id), asset(160), 1700), fc::exception ); + // Can not create a new call order that is partially called instantly if final CR <= ICR + BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10000, usd_id), asset(160000), 1700), fc::exception ); idump( (settle_id(db))(get_balance(seller, core)) ); @@ -661,6 +661,12 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // Can not create a new call order that would trigger a black swan event BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10000, usd_id), asset(10000) ), fc::exception ); + // Able to reduce CR of a call order to trigger a margin call if final CR is above ICR + borrow( borrower_id(db), asset(10, usd_id), asset(0), 1700 ); + + // Able to create a new call order that is partially called instantly if final CR is above ICR + borrow( borrower4_id(db), asset(10, usd_id), asset(160), 1700 ); + // generate a block generate_block(); From 186a4b6044cef955091c877cce0eee9862728f92 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 15:43:22 +0000 Subject: [PATCH 71/99] Fix code smells --- .../graphene/chain/market_evaluator.hpp | 6 +-- libraries/chain/market_evaluator.cpp | 48 +++++++++---------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 5dec716aea..8df92ff464 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -40,7 +40,7 @@ namespace graphene { namespace chain { using operation_type = limit_order_create_operation; void_result do_evaluate( const limit_order_create_operation& o ); - object_id_type do_apply( const limit_order_create_operation& o ); + object_id_type do_apply( const limit_order_create_operation& o ) const; /** override the default behavior defined by generic_evalautor */ @@ -66,7 +66,7 @@ namespace graphene { namespace chain { using operation_type = limit_order_cancel_operation; void_result do_evaluate( const limit_order_cancel_operation& o ); - asset do_apply( const limit_order_cancel_operation& o ); + asset do_apply( const limit_order_cancel_operation& o ) const; private: const limit_order_object* _order; @@ -96,7 +96,7 @@ namespace graphene { namespace chain { using operation_type = bid_collateral_operation; void_result do_evaluate( const bid_collateral_operation& o ); - void_result do_apply( const bid_collateral_operation& o ); + void_result do_apply( const bid_collateral_operation& o ) const; private: const asset_object* _debt_asset = nullptr; diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index e657106ffc..a90754f8c9 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -80,16 +80,12 @@ void limit_order_create_evaluator::convert_fee() { if( db().head_block_time() <= HARDFORK_CORE_604_TIME ) generic_evaluator::convert_fee(); - else - if( !trx_state->skip_fee ) - { - if( fee_asset->get_id() != asset_id_type() ) - { - db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { - d.fee_pool -= core_fee_paid; - }); - } - } + else if( !trx_state->skip_fee && fee_asset->get_id() != asset_id_type() ) + { + db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { + d.fee_pool -= core_fee_paid; + }); + } } void limit_order_create_evaluator::pay_fee() @@ -104,7 +100,7 @@ void limit_order_create_evaluator::pay_fee() } } -object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op) +object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op) const { try { if( op.amount_to_sell.asset_id == asset_id_type() ) { @@ -115,7 +111,7 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o db().adjust_balance(op.seller, -op.amount_to_sell); - const auto& new_order_object = db().create([&](limit_order_object& obj){ + const auto& new_order_object = db().create([this,&op](limit_order_object& obj){ obj.seller = _seller->id; obj.for_sale = op.amount_to_sell.amount; obj.sell_price = op.get_price(); @@ -140,7 +136,7 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o) { try { - database& d = db(); + const database& d = db(); _order = d.find( o.order ); @@ -157,7 +153,7 @@ void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_o return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o) +asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o) const { try { database& d = db(); @@ -202,7 +198,7 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount <= _debt_asset->options.max_supply, "Borrowing this quantity would exceed MAX_SUPPLY" ); } - + FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount >= 0, "This transaction would bring current supply below zero."); @@ -446,16 +442,16 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // be here, we know no margin call was executed, // so call_obj's collateral ratio should be set only by op // ------ - // Before BSIP77, CR of the new/updated position is required to be above MCR; - // after BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). + // Before BSIP77, CR of the new/updated position is required to be above MCR. + // After BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). // The `current_initial_collateralization` variable has been initialized according to the logic, // so we directly use it here. - bool check = ( increasing_cr - || ( !before_core_hardfork_1270 - && call_collateralization > _bitasset_data->current_initial_collateralization ) - || ( before_core_hardfork_1270 - && ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) ); - FC_ASSERT( check, + bool ok = increasing_cr; + if( !ok ) + ok = before_core_hardfork_1270 ? + ( ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) + : ( call_collateralization > _bitasset_data->current_initial_collateralization ); + FC_ASSERT( ok, "Can only increase collateral ratio without increasing debt when the debt position's " "collateral ratio is lower than or equal to required initial collateral ratio (ICR), " "if not to trigger a margin call immediately", @@ -473,7 +469,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation& o) { try { - database& d = db(); + const database& d = db(); FC_ASSERT( d.head_block_time() > HARDFORK_CORE_216_TIME, "Not yet!" ); @@ -527,7 +523,7 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o) +void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o) const { try { database& d = db(); @@ -538,7 +534,7 @@ void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o d.adjust_balance( o.bidder, -o.additional_collateral ); - _bid = &d.create([&]( collateral_bid_object& bid ) { + d.create([&o]( collateral_bid_object& bid ) { bid.bidder = o.bidder; bid.inv_swan_price = o.additional_collateral / o.debt_covered; }); From e1a6ac53d8add2d0f2aaa8de60143783cf53b778 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 17:15:56 +0000 Subject: [PATCH 72/99] Refactor call_order_update_evaluator::do_apply() to reduce the number of levels of nested if, for, or while statements --- libraries/chain/market_evaluator.cpp | 260 +++++++++++++-------------- 1 file changed, 130 insertions(+), 130 deletions(-) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index a90754f8c9..4714e5a99d 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -287,11 +287,31 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } } + if( _closing_order ) // closing the debt position + { + auto call_order_id = call_ptr->id; + + d.remove( *call_ptr ); + + // Update current_feed if needed + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) + { + auto old_feed_price = _bitasset_data->current_feed.settlement_price; + d.update_bitasset_current_feed( *_bitasset_data, true ); + if( !_bitasset_data->current_feed.settlement_price.is_null() + && _bitasset_data->current_feed.settlement_price != old_feed_price ) + { + d.check_call_orders( *_debt_asset, true, false, _bitasset_data ); + } + } + + return call_order_id; + } + const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_1270 = ( next_maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue - call_order_id_type call_order_id; - optional old_collateralization; optional old_debt; @@ -308,32 +328,9 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope call.call_price = price( asset( 1, o.delta_collateral.asset_id ), asset( 1, o.delta_debt.asset_id ) ); call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; }); - call_order_id = call_ptr->id; } else // updating existing debt position { - call_order_id = call_ptr->id; - - if( _closing_order ) - { - d.remove( *call_ptr ); - - // Update current_feed if needed - const auto bsrm = _bitasset_data->get_black_swan_response_method(); - if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) - { - auto old_feed_price = _bitasset_data->current_feed.settlement_price; - d.update_bitasset_current_feed( *_bitasset_data, true ); - if( !_bitasset_data->current_feed.settlement_price.is_null() - && _bitasset_data->current_feed.settlement_price != old_feed_price ) - { - d.check_call_orders( *_debt_asset, true, false, _bitasset_data ); - } - } - - return call_order_id; - } - old_collateralization = call_ptr->collateralization(); old_debt = call_ptr->debt; @@ -349,118 +346,121 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope }); } + call_order_id_type call_order_id = call_ptr->id; + + if( _bitasset_data->is_prediction_market ) + return call_order_id; + // then we must check for margin calls and other issues - if( !_bitasset_data->is_prediction_market ) + + // After hf core-2481, we do not allow new position's CR to be <= ~max_short_squeeze_price, because + // * if there is no force settlement order, it would trigger a blackswan event instantly, + // * if there is a force settlement order, they will match at the call order's CR, but it is not fair for the + // force settlement order. + auto call_collateralization = call_ptr->collateralization(); + bool increasing_cr = ( old_collateralization.valid() && call_ptr->debt <= *old_debt + && call_collateralization > *old_collateralization ); + if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) + { + // Note: if it is to increase CR and is not increasing debt amount, it is allowed, + // because it implies BSRM == no_settlement + FC_ASSERT( increasing_cr + || call_collateralization >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), + "Could not create a debt position which would trigger a blackswan event instantly, " + "unless it is to increase collateral ratio of an existing debt position and " + "is not increasing its debt amount" ); + } + // Update current_feed if needed + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) + d.update_bitasset_current_feed( *_bitasset_data, true ); + + // check to see if the order needs to be margin called now, but don't allow black swans and require there to be + // limit orders available that could be used to fill the order. + // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork, + // the first call order may be unable to be updated if the second one is undercollateralized. + // Note: check call orders, don't allow black swan, not for new limit order + bool called_some = d.check_call_orders( *_debt_asset, false, false, _bitasset_data ); + call_ptr = d.find(call_order_id); + if( called_some ) { - // After hf core-2481, we do not allow new position's CR to be <= ~max_short_squeeze_price, because - // * if there is no force settlement order, it would trigger a blackswan event instantly, - // * if there is a force settlement order, they will match at the call order's CR, but it is not fair for the - // force settlement order. - auto call_collateralization = call_ptr->collateralization(); - bool increasing_cr = ( old_collateralization.valid() && call_ptr->debt <= *old_debt - && call_collateralization > *old_collateralization ); - if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) + // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. + // after hard fork core-583: we want to allow increasing collateral + // Note: increasing collateral won't get the call order itself matched (instantly margin called) + // if there is at least a call order get matched but didn't cause a black swan event, + // current order must have got matched. in this case, it's OK if it's totally filled. + // after hard fork core-2467: when BSRM is no_settlement, it is possible that other call orders are matched + // in check_call_orders, also possible that increasing CR will get the call order itself matched + if( !HARDFORK_CORE_2467_PASSED( next_maint_time ) ) // before core-2467 hf { - // Note: if it is to increase CR and is not increasing debt amount, it is allowed, - // because it implies BSRM == no_settlement - FC_ASSERT( increasing_cr - || call_collateralization >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), - "Could not create a debt position which would trigger a blackswan event instantly, " - "unless it is to increase collateral ratio of an existing debt position and " - "is not increasing its debt amount" ); + GRAPHENE_ASSERT( !call_ptr, call_order_update_unfilled_margin_call, + "Updating call order would trigger a margin call that cannot be fully filled" ); } - // Update current_feed if needed - const auto bsrm = _bitasset_data->get_black_swan_response_method(); - if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) - d.update_bitasset_current_feed( *_bitasset_data, true ); - - // check to see if the order needs to be margin called now, but don't allow black swans and require there to be - // limit orders available that could be used to fill the order. - // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork, - // the first call order may be unable to be updated if the second one is undercollateralized. - // Note: check call orders, don't allow black swan, not for new limit order - bool called_some = d.check_call_orders( *_debt_asset, false, false, _bitasset_data ); - call_ptr = d.find(call_order_id); - if( called_some ) + // after core-2467 hf + else { - // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. - // after hard fork core-583: we want to allow increasing collateral - // Note: increasing collateral won't get the call order itself matched (instantly margin called) - // if there is at least a call order get matched but didn't cause a black swan event, - // current order must have got matched. in this case, it's OK if it's totally filled. - // after hard fork core-2467: when BSRM is no_settlement, it is possible that other call orders are matched - // in check_call_orders, also possible that increasing CR will get the call order itself matched - if( !HARDFORK_CORE_2467_PASSED( next_maint_time ) ) // before core-2467 hf - { - GRAPHENE_ASSERT( !call_ptr, call_order_update_unfilled_margin_call, - "Updating call order would trigger a margin call that cannot be fully filled" ); - } - // after core-2467 hf - else - { - // if the call order is totally filled, it is OK, - // if it is increasing CR, it is always ok, no matter if it or another another call order is called, - // otherwise, the remaining call order's CR need to be > ICR - // TODO: perhaps it makes sense to allow more cases, e.g. - // - when a position has ICR > CR > MCR, allow the owner to sell some collateral to increase CR - // - allow owners to sell collateral at price < MSSP (need to update code elsewhere) - FC_ASSERT( !call_ptr || increasing_cr - || call_ptr->collateralization() > _bitasset_data->current_initial_collateralization, - "Could not create a debt position which would trigger a margin call instantly, " - "unless the debt position is fully filled, or it is to increase collateral ratio of " - "an existing debt position and is not increasing its debt amount, " - "or the remaining debt position's collateral ratio is above required " - "initial collateral ratio (ICR)" ); - } + // if the call order is totally filled, it is OK, + // if it is increasing CR, it is always ok, no matter if it or another another call order is called, + // otherwise, the remaining call order's CR need to be > ICR + // TODO: perhaps it makes sense to allow more cases, e.g. + // - when a position has ICR > CR > MCR, allow the owner to sell some collateral to increase CR + // - allow owners to sell collateral at price < MSSP (need to update code elsewhere) + FC_ASSERT( !call_ptr || increasing_cr + || call_ptr->collateralization() > _bitasset_data->current_initial_collateralization, + "Could not create a debt position which would trigger a margin call instantly, " + "unless the debt position is fully filled, or it is to increase collateral ratio of " + "an existing debt position and is not increasing its debt amount, " + "or the remaining debt position's collateral ratio is above required " + "initial collateral ratio (ICR)" ); } - else + } + else + { + // we know no black swan event has occurred + FC_ASSERT( call_ptr, "no margin call was executed and yet the call object was deleted" ); + // this HF must remain as-is, as the assert inside the "if" was triggered during push_proposal() + if( d.head_block_time() <= HARDFORK_CORE_583_TIME ) { - // we know no black swan event has occurred - FC_ASSERT( call_ptr, "no margin call was executed and yet the call object was deleted" ); - // this HF must remain as-is, as the assert inside the "if" was triggered during push_proposal() - if( d.head_block_time() <= HARDFORK_CORE_583_TIME ) - { - // We didn't fill any call orders. This may be because we - // aren't in margin call territory, or it may be because there - // were no matching orders. In the latter case, we throw. - GRAPHENE_ASSERT( - // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here - ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price, - call_order_update_unfilled_margin_call, - "Updating call order would trigger a margin call that cannot be fully filled", - // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here - ("a", ~call_ptr->call_price )("b", _bitasset_data->current_feed.settlement_price) - ); - } - else // after hard fork core-583, always allow call order to be updated if collateral ratio - // is increased and debt is not increased - { - // We didn't fill any call orders. This may be because we - // aren't in margin call territory, or it may be because there - // were no matching orders. In the latter case, - // if collateral ratio is not increased or debt is increased, we throw. - // be here, we know no margin call was executed, - // so call_obj's collateral ratio should be set only by op - // ------ - // Before BSIP77, CR of the new/updated position is required to be above MCR. - // After BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). - // The `current_initial_collateralization` variable has been initialized according to the logic, - // so we directly use it here. - bool ok = increasing_cr; - if( !ok ) - ok = before_core_hardfork_1270 ? - ( ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) - : ( call_collateralization > _bitasset_data->current_initial_collateralization ); - FC_ASSERT( ok, - "Can only increase collateral ratio without increasing debt when the debt position's " - "collateral ratio is lower than or equal to required initial collateral ratio (ICR), " - "if not to trigger a margin call immediately", - ("old_debt", old_debt) - ("new_debt", call_ptr->debt) - ("old_collateralization", old_collateralization) - ("new_collateralization", call_collateralization) - ); - } + // We didn't fill any call orders. This may be because we + // aren't in margin call territory, or it may be because there + // were no matching orders. In the latter case, we throw. + GRAPHENE_ASSERT( + // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here + ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price, + call_order_update_unfilled_margin_call, + "Updating call order would trigger a margin call that cannot be fully filled", + // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here + ("a", ~call_ptr->call_price )("b", _bitasset_data->current_feed.settlement_price) + ); + } + else // after hard fork core-583, always allow call order to be updated if collateral ratio + // is increased and debt is not increased + { + // We didn't fill any call orders. This may be because we + // aren't in margin call territory, or it may be because there + // were no matching orders. In the latter case, + // if collateral ratio is not increased or debt is increased, we throw. + // be here, we know no margin call was executed, + // so call_obj's collateral ratio should be set only by op + // ------ + // Before BSIP77, CR of the new/updated position is required to be above MCR. + // After BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). + // The `current_initial_collateralization` variable has been initialized according to the logic, + // so we directly use it here. + bool ok = increasing_cr; + if( !ok ) + ok = before_core_hardfork_1270 ? + ( ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) + : ( call_collateralization > _bitasset_data->current_initial_collateralization ); + FC_ASSERT( ok, + "Can only increase collateral ratio without increasing debt when the debt position's " + "collateral ratio is lower than or equal to required initial collateral ratio (ICR), " + "if not to trigger a margin call immediately", + ("old_debt", old_debt) + ("new_debt", call_ptr->debt) + ("old_collateralization", old_collateralization) + ("new_collateralization", call_collateralization) + ); } } From c699e5304d83f39a6e2ca7394718505b92b6572b Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 18:23:26 +0000 Subject: [PATCH 73/99] Add call_order_update tests about no_settlement --- tests/tests/bsrm_no_settlement_tests.cpp | 478 ++++++++++++++++++++++- 1 file changed, 476 insertions(+), 2 deletions(-) diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index f864522da2..22780a8a2f 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -1334,8 +1334,6 @@ BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - //BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - if( 0 == i ) // no order is filled { // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) @@ -2175,4 +2173,480 @@ BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) } FC_CAPTURE_AND_RETHROW() } +/// Tests updating debt positions when BSRM is no_settlement +BOOST_AUTO_TEST_CASE( no_settlement_update_debt_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // Several passes + for( int i = 0; i <= 20; ++ i ) + { + idump( (i) ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 100000 * (2000/125000) * 1.9 = 3040 + // 100000 * (22/1000) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower adjust debt position to right at MSSR + // 100000 * (22/1000) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CRid; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled in the beginning either + const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // update debt position + BOOST_TEST_MESSAGE( "Update debt position"); + if( 0 == i ) + { + // borrower2 slightly update the position and is not filled + borrow( borrower2, asset(0, mpa_id), asset(1) ); // to 100000:2101 + } + else if( 1 == i ) + { + // borrower2 update the position and is partially filled + borrow( borrower2, asset(0, mpa_id), asset(100) ); // to 100000:2200 + } + else if( 2 == i ) + { + // borrower2 update the position to smaller so it will be fully filled + borrow( borrower2, asset(-90000, mpa_id), asset(-1880) ); // to 10000:220 + } + else if( 3 == i ) + { + // borrower2 update the position to smaller so it will be fully filled, + // and borrower's position is partially filled + borrow( borrower2, asset(-91000, mpa_id), asset(-1880) ); // to 9000:220 + } + else if( 4 == i ) + { + // borrower2 update the position so that its CR is higher than borrower's, + // and borrower's position is partially filled + borrow( borrower2, asset(0, mpa_id), asset(651) ); // to 100000:2751 + } + else if( 5 == i ) + { + // borrower2 close the position, so borrwer's position is partially filled + borrow( borrower2, asset(-100000, mpa_id), asset(-2100) ); // to 10000:220 + } + else if( 6 == i ) + { + // borrower close the position, no order is filled + borrow( borrower, asset(-100000, mpa_id), asset(-2750) ); + } + else + { + BOOST_TEST_MESSAGE( "No more test cases so far" ); + break; + } + + // check result + auto check_result = [&] + { + BOOST_TEST_MESSAGE( "Check result"); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + if( 0 == i ) // no order is filled + { + // now feed price is 13:10 * 100000:2101 = 130000:2101 = 61.875297477 (> 1000:22 = 45.454545455) + // call2 pays price = 100000:2101 + // match price = 100000:2101 * 1300:1289 = 48.002558167 + // sell_mid price = 10000:210 = 47.619047619 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130000,mpa_id), asset(2101) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2101 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2101 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 1 == i ) + { + // now feed price is 13:10 * 100000:2200 = 1300:22 = 59.090909091 (> 1000:22 = 45.454545455) + // call2 pays price = 100000:2200 + // match price = 100000:2200 * 1300:1289 = 45.84244305 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // sell_mid receives 210 + // call2 pays round_down(10000*(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (100000-10000):(2200-211) + // = 13:10 * 90000:1989 = 1170000:19890 = 58.823529412 (> 1000:22 = 45.454545455) + // call2 match price is 1300:1289 * 90000:1989 = 42.124625705 + // sell_high price = 10000:275 = 36.363636364 + + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1170000,mpa_id), asset(19890) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1989 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2200 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 2 == i ) + { + // now feed price is 13:10 * 10000:220 = 1300:22 = 59.090909091 (> 1000:22 = 45.454545455) + // call2 pays price = 10000:220 + // match price = 10000:220 * 1300:1289 = 45.84244305 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // sell_mid receives 210 + // call2 pays round_up(210*1300/1289) = 212, margin call fee = 2 + // call2 is filled + BOOST_REQUIRE( !db.find( call2_id ) ); + // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455) + // call match price is 1300:1289 * 100000:2750 = 36.67395444 + // sell_high price = 10000:275 = 36.363636364 + + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(13000,mpa_id), asset(275) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 212 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 3 == i ) + { + // now feed price is 13:10 * 9000:220 = 1170:22 = 53.181818182 (> 1000:22 = 45.454545455) + // call2 pays price = 9000:220 + // match price = 9000:220 * 1300:1289 = 41.258198745 + // sell_mid price = 10000:210 = 47.619047619 + // call2 is filled + BOOST_REQUIRE( !db.find( call2_id ) ); + // call2 receives 9000 + // sell_mid receives round_up(9000*210/10000) = 189 + // call2 pays round_up(9000*(210/10000)*(1300/1289)) = 191, margin call fee = 2 + + // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455) + // call match price is 1300:1289 * 100000:2750 = 36.67395444 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // call receives 1000 + // sell_mid receives round_down(1000*210/10000) = 21 + // call pays round_down(1000*(210/10000)*(1300/1289)) = 21, margin call fee = 0 + + // now feed price is 13:10 * (100000-1000):(2750-21) + // = 13:10 * 99000:2729 = 128700:2729 = 47.160131916 (> 1000:22 = 45.454545455) + // call pays price = 99000:2729 + // match price = 99000:2729 * 1300:1289 = 36.586603504 + // sell_high price = 10000:275 = 36.363636364 (does not match) + + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(128700,mpa_id), asset(2729) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 99000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2729 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 191 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // 189 + 21 + } + else if( 4 == i ) + { + // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455) + // call match price is 1300:1289 * 100000:2750 = 36.67395444 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // call receives 10000 + // sell_mid receives round_down(10000*210/10000) = 210 + // call pays round_down(10000*(210/10000)*(1300/1289)) = 211, margin call fee = 1 + // call is now (100000-10000):(2750-211) = 90000:2539 = 35.447026388 + // call2 is 100000:2751 = 36.35041803 + + // now feed price is 13:10 * 100000:2751 = 130000:2751 = 47.255543439 (> 1000:22 = 45.454545455) + // call2 pays price = 100000:2751 + // match price = 100000:2751 * 1300:1289 = 36.660623304 + // sell_high price = 10000:275 = 36.363636364 (does not match) + + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130000,mpa_id), asset(2751) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 90000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2539 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2751 ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2751 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 5 == i ) + { + BOOST_REQUIRE( !db.find( call2_id ) ); + // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455) + // call match price is 1300:1289 * 100000:2750 = 36.67395444 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // call receives 10000 + // sell_mid receives round_down(10000*210/10000) = 210 + // call pays round_down(10000*(210/10000)*(1300/1289)) = 211, margin call fee = 1 + // call is now (100000-10000):(2750-211) = 90000:2539 = 35.447026388 + + // now feed price is 13:10 * 90000:2539 = 117000:2539 = 46.081134305 (> 1000:22 = 45.454545455) + // call pays price = 90000:2539 + // match price = 90000:2539 * 1300:1289 = 35.749522347 + // sell_high price = 10000:275 = 36.363636364 + // sell_high is filled + BOOST_REQUIRE( !db.find( sell_high_id ) ); + // call receives 10000 + // sell_mid receives round_down(10000*275/10000) = 275 + // call pays round_down(10000*(275/10000)*(1300/1289)) = 277, margin call fee = 2 + // call is now (100000-20000):(2750-211-277) = 80000:2262 = 35.366931919 + // now feed price is 13:10 * 80000:2262 = 104000:2262 = 45.977011494 (> 1000:22 = 45.454545455) + // call pays price = 80000:2262 + // match price = 80000:2262 * 1300:1289 = 35.668744371 + // sell_highest price = 10000:285 = 35.087719298 (does not match) + + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(104000,mpa_id), asset(2262) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 80000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 + } + else if( 6 == i ) + { + BOOST_REQUIRE( !db.find( call_id ) ); + // now feed price is 13:10 * 100000:2100 = 130000:2100 = 61.904761905 (> 1000:22 = 45.454545455) + // call2 pays price = 100000:2100 + // match price = 100000:2100 * 1300:1289 = 48.025416528 + // sell_mid price = 10000:210 = 47.619047619 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130000,mpa_id), asset(2100) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else + { + BOOST_FAIL( "to be fixed" ); + } + }; + check_result(); + + // generate a block + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + // check again + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 6abdc1ef6a3d858f262773e1a2e7c9fabc2ac736 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 19:13:23 +0000 Subject: [PATCH 74/99] Add tests about manual GS for different BSRM types --- tests/tests/bsrm_basic_tests.cpp | 195 +++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 0eed9937a3..b947ec6899 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -1318,4 +1318,199 @@ BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) } FC_CAPTURE_AND_RETHROW() } +/// Tests scenarios: +/// manually trigger global settlement via asset_global_settle_operation on each BSRM type +BOOST_AUTO_TEST_CASE( manual_gs_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + using bsrm_type = bitasset_options::black_swan_response_type; + + // Several passes for each BSRM type + for( uint8_t i = 0; i <= 3; ++i ) + { + idump( (i) ); + + ACTORS((sam)(feeder)(borrower)(borrower2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = i; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed so that the least collateralized short is undercollateralized" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + const auto& check_result = [&] + { + switch( static_cast(i) ) + { + case bsrm_type::global_settlement: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + // can not globally settle again + BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception ); + break; + case bsrm_type::no_settlement: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::no_settlement ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + // can not globally settle at real price since the least collateralized short's CR is too low + BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception ); + break; + case bsrm_type::individual_settlement_to_fund: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + // MSSR = 1250, MCFR = 11, fee = round_down(2000 * 11 / 1250) = 17, fund = 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + break; + case bsrm_type::individual_settlement_to_order: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->amount_to_receive().amount.value, 100000 ); + break; + default: + BOOST_FAIL( "This should not happen" ); + break; + } + }; + + check_result(); + + ilog( "Generate a block" ); + generate_block(); + + check_result(); + + // globally settle + if( bsrm_type::no_settlement == static_cast(i) ) + force_global_settle( mpa_id(db), price( asset(1000,mpa_id), asset(18) ) ); + else if( bsrm_type::individual_settlement_to_fund == static_cast(i) + || bsrm_type::individual_settlement_to_order == static_cast(i) ) + force_global_settle( mpa_id(db), price( asset(1000,mpa_id), asset(22) ) ); + + // check + const auto& check_result2 = [&] + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + + switch( static_cast(i) ) + { + case bsrm_type::global_settlement: + break; + case bsrm_type::no_settlement: + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 3600 ); // 1800 * 2 + break; + case bsrm_type::individual_settlement_to_fund: + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 4183 ); // 1983 + 2200 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + break; + case bsrm_type::individual_settlement_to_order: + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 4183 ); // 1983 + 2200 + break; + default: + BOOST_FAIL( "This should not happen" ); + break; + } + }; + + check_result2(); + + ilog( "Generate a block" ); + generate_block(); + + check_result2(); + + // reset + db.pop_block(); + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From cb0069626cfe3c16ee5d11b1d2de49628b71a6e3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 19:33:41 +0000 Subject: [PATCH 75/99] Update comments --- libraries/chain/db_getter.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 085b48f338..8bdcfd8b30 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -174,7 +174,7 @@ const call_order_object* database::find_least_collateralized_short( const asset_ { const auto& call_price_index = get_index_type().indices().get(); auto call_itr = call_price_index.lower_bound( call_min ); - if( call_itr != call_price_index.end() ) // no call order + if( call_itr != call_price_index.end() ) // found a call order call_ptr = &(*call_itr); } else // after core-1270 hard fork, check with collateralization @@ -182,12 +182,12 @@ const call_order_object* database::find_least_collateralized_short( const asset_ // Note: it is safe to check here even if there is no call order due to individual settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_itr = call_collateral_index.lower_bound( call_min ); - if( call_itr != call_collateral_index.end() ) // no call order + if( call_itr != call_collateral_index.end() ) // found a call order call_ptr = &(*call_itr); } - if( !call_ptr ) + if( !call_ptr ) // not found return nullptr; - if( call_ptr->debt_type() != bitasset.asset_id ) // no call order + if( call_ptr->debt_type() != bitasset.asset_id ) // call order is of another asset return nullptr; return call_ptr; } From 1a8f51d77e74b2114e314ec2c0b5cf1aa2676f9a Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 20:01:45 +0000 Subject: [PATCH 76/99] Update manual GS tests for different BSRM types check capping and uncapping of current feed price --- tests/tests/bsrm_basic_tests.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index b947ec6899..71a7ce788a 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -1397,6 +1397,7 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) // check const auto& check_result = [&] { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); switch( static_cast(i) ) { case bsrm_type::global_settlement: @@ -1407,6 +1408,8 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); // can not globally settle again BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception ); break; @@ -1418,6 +1421,9 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1250,mpa_id), asset(20) ) ); // can not globally settle at real price since the least collateralized short's CR is too low BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception ); break; @@ -1433,6 +1439,11 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); // MSSR = 1250, MCFR = 11, fee = round_down(2000 * 11 / 1250) = 17, fund = 2000 - 17 = 1983 BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + // current feed = 100000:1983 * (1250-11):1000 + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(123900,mpa_id), asset(1983) ) ); break; case bsrm_type::individual_settlement_to_order: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() @@ -1442,6 +1453,8 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->for_sale.value, 1983 ); BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->amount_to_receive().amount.value, 100000 ); @@ -1476,6 +1489,9 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); switch( static_cast(i) ) { From c0c7c572c1e2cc5a08dffdcd18bf7aabc6394865 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 21:03:02 +0000 Subject: [PATCH 77/99] Slightly refactor asset_global_settle_evaluator by using database::find_least_collateralized_short() --- libraries/chain/asset_evaluator.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index f8c22536cb..a1fa87e2fe 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1057,17 +1057,16 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle FC_ASSERT( !_bitasset_data.has_settlement(), "This asset has been globally settled, cannot globally settle again" ); - // Note: there can be no debt position due to individual settlements, processed below - const auto& idx = d.get_index_type().indices().get(); - auto itr = idx.lower_bound( price::min( _bitasset_data.options.short_backing_asset, op.asset_to_settle ) ); - if( itr != idx.end() && itr->debt_type() == op.asset_to_settle ) + // Note: after core-2467 hard fork, there can be no debt position due to individual settlements, so we check here + const call_order_object* least_collateralized_short = d.find_least_collateralized_short( _bitasset_data, true ); + if( least_collateralized_short ) { - const call_order_object& least_collateralized_short = *itr; - FC_ASSERT( ( least_collateralized_short.get_debt() * op.settle_price ) - <= least_collateralized_short.get_collateral(), - "Cannot globally settle at supplied price: least collateralized short lacks " - "sufficient collateral to settle." ); + FC_ASSERT( ( least_collateralized_short->get_debt() * op.settle_price ) + <= least_collateralized_short->get_collateral(), + "Cannot globally settle at supplied price: least collateralized short lacks " + "sufficient collateral to settle." ); } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } From a070eeb079cd5bf433c25d508adab375c684ca14 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 12 Sep 2021 16:52:57 +0000 Subject: [PATCH 78/99] Add tests for individual_settlement_to_order --- tests/tests/bsrm_indvd_settlement_tests.cpp | 334 ++++++++++++++++++++ tests/tests/bsrm_no_settlement_tests.cpp | 1 - 2 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 tests/tests/bsrm_indvd_settlement_tests.cpp diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp new file mode 100644 index 0000000000..07669bfd17 --- /dev/null +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) + +/// Tests individual settlement to order +BOOST_AUTO_TEST_CASE( individual_settlement_to_order_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + fund( borrower4, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // undercollateralization price = 100000:3000 * 1250:1000 = 100000:1760 + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // undercollateralization price = 100000:4000 * 1250:1000 = 100000:2400 + const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); + BOOST_REQUIRE( call4_ptr ); + call_order_id_type call4_id = call4_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(100000,mpa_id) ); + transfer( borrower4, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 400000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + // order match price = 100000 / 1983 = 50.428643469 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // seller sells some + const limit_order_object* limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + // call2 is partially filled + // limit order gets round_down(10000*(1650/100000)*(1239/1000)) = 204 + // limit order pays round_up(204*(100000/1650)*(1000/1239)) = 9979 + // call2 gets 9979 + // call2 pays round_down(9979*(1650/100000)*(1250/1000)) = 205, margin call fee = 1 + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90021 ); // 100000 - 9979 + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1895 ); // 2100 - 205 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 390021 ); // 400000 - 9979 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 204 ); + + // publish a new feed so that 2 other debt positions are undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1800) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 + // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789 + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // call2: margin call fee deducted = round_down(1895*11/1250) = 16, + // fund receives 1895 - 16 = 1879 + // call3: margin call fee deducted = round_down(2200*11/1250) = 19, + // fund receives 2200 - 19 = 2181 + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // 1983 + 1879 + 2181 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // 100000 + 90021 + 100000 + // order match price = 290021 / 6043 = 47.992884329 + + // borrower buys at higher price + const limit_order_object* buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) ); + BOOST_CHECK( buy_high ); + limit_order_id_type buy_high_id = buy_high->id; + + // seller sells some, this will match buy_high, + // and when it matches call4, it will be cancelled since it is too small + limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + // buy_high is filled + BOOST_CHECK( !db.find( buy_high_id ) ); + + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // no change + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // no change + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389921 ); // 400000 - 9979 - 100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 214 ); // 204 + 10 + + // publish a new feed so that the settled debt order is in the front of the order book + f.settlement_price = price( asset(100000,mpa_id), asset(1600) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1600 * 1000:1250 = 100000:2000 = 50 + // call match price = 100000:1600 * 1000:1239 = 100000:1982.4 = 50.443906376 + + // borrower buys at higher price + buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) ); + BOOST_CHECK( buy_high ); + buy_high_id = buy_high->id; + + // seller sells some, this will match buy_high, + // and when it matches the settled debt, it will be cancelled since it is too small + limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + // buy_high is filled + BOOST_CHECK( !db.find( buy_high_id ) ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // no change + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // no change + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389821 ); // 400000 - 9979 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 224 ); // 204 + 10 + 10 + + // seller sells some + limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + // the settled debt is partially filled + // limit order receives = round_down(10000*6043/290021) = 208 + // settled debt receives = round_up(208*290021/6043) = 9983 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5835 ); // 6043 - 208 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 280038 ); // 290021 - 9983 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 379838 ); // 400000 - 9979 - 100 - 100 - 9983 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 432 ); // 204 + 10 + 10 + 208 + + // seller sells some + limit_ptr = create_sell_order( seller, asset(300000,mpa_id), asset(3000) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + auto final_check = [&] + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + + // the settled debt is fully filled + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + // limit order reminder = 300000 - 280038 = 19962 + // call4 is partially filled + // limit order gets round_down(19962*(1600/100000)*(1239/1000)) = 395 + // limit order pays round_up(395*(100000/1600)*(1000/1239)) = 19926 + // call4 gets 19926 + // call4 pays round_down(19926*(1600/100000)*(1250/1000)) = 398, margin call fee = 3 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79874 ); // 400000 - 9979 - 100 - 100 - 9983 + // - 280038 - 19926 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6662 ); // 204 + 10 + 10 + 208 + 5835 + 395 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 80074 ); // 100000 - 19926 + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2102 ); // 2500 - 398 + + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index 22780a8a2f..4487c9050b 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include From 5a5c0bc2e77d98dfbda8dd224607764eb6f9462d Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 15 Sep 2021 16:56:41 +0000 Subject: [PATCH 79/99] Add tests about cancelling settle order on no call --- tests/tests/bsrm_indvd_settlement_tests.cpp | 180 ++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index 07669bfd17..a1fea75c8e 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -331,4 +331,184 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_test ) } } +/// Tests a scenario that force settlements get cancelled on expiration when there is no debt position +/// due to individual settlement to order +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_debt_position ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 86400; + acop.bitasset_opts->force_settlement_delay_sec = 600; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + + acop.symbol = "SAMMPA2"; + acop.bitasset_opts->force_settlement_delay_sec = 60000; + trx.operations.clear(); + trx.operations.push_back( acop ); + ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa2 = db.get(ptx.operation_results[0].get()); + asset_id_type mpa2_id = mpa2.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + update_feed_producers( mpa2_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + price_feed f2; + f2.settlement_price = price( asset(100,mpa2_id), asset(1) ); + f2.core_exchange_rate = price( asset(100,mpa2_id), asset(1) ); + f2.maintenance_collateral_ratio = 1850; + f2.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa2_id, feeder_id, f2, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower, asset(100000, mpa2_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower, seller, asset(100000,mpa2_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + // order match price = 100000 / 1983 = 50.428643469 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + result = force_settle( seller, asset(11100,mpa2_id) ); + force_settlement_id_type settle2_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle2_id) ); + + BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the first settle order expire + generate_blocks( db.head_block_time() + fc::seconds(600) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); // no change + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + + // the first settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + // no change to the second settle order + BOOST_REQUIRE( db.find(settle2_id) ); + BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 7729a5169c3129d9f095a704b1a6e692f96a774c Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 15 Sep 2021 17:36:22 +0000 Subject: [PATCH 80/99] Add more tests about cancelling settle orders --- tests/tests/settle_tests.cpp | 474 +++++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 4bad2a299b..c900b35688 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -2004,6 +2004,480 @@ BOOST_AUTO_TEST_CASE( global_settle_ticker_test ) } } +/// Tests a scenario that force settlements get cancelled on the block when the asset is globally settled +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_gs ) +{ + try { + + // Advance to a desired hard fork time + // Note: this test doesn't apply after hf2481 + generate_blocks( HARDFORK_CORE_1780_TIME ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 800; + acop.bitasset_opts->force_settlement_delay_sec = 600; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa_id, feeder_id, f ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // Transfer funds to seller + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + // generate a block + generate_block(); + + // check again + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that the asset is globally settled + f.settlement_price = price( asset(100,mpa_id), asset(10) ); + publish_feed( mpa_id, feeder_id, f ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // generate a block + generate_block(); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + + // the settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests a scenario that force settlements get cancelled on expiration when there is no sufficient feed +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_feed ) +{ + try { + + // Advance to a desired hard fork time + if(hf2467) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + generate_blocks( HARDFORK_CORE_1780_TIME ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 400; + acop.bitasset_opts->force_settlement_delay_sec = 600; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa_id, feeder_id, f ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // Transfer funds to seller + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the price feed expire + generate_blocks( db.head_block_time() + fc::seconds(400) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the settle order expire + generate_blocks( db.head_block_time() + fc::seconds(200) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + // the settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests a scenario that force settlements get cancelled on expiration when the amount is zero +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_zero_amount ) +{ + try { + + // Advance to a desired hard fork time + if(hf2467) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + generate_blocks( HARDFORK_CORE_1780_TIME ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 800; + acop.bitasset_opts->force_settlement_delay_sec = 600; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa_id, feeder_id, f ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // Transfer funds to seller + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(0,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 0 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // generate a block + generate_block(); + + // check again + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 0 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the settle order expire + generate_blocks( db.head_block_time() + fc::seconds(600) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // the settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests a scenario that force settlements get cancelled on expiration when offset is 100% +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_100_percent_offset ) +{ + try { + + // Advance to a desired hard fork time + if(hf2467) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + generate_blocks( HARDFORK_CORE_1780_TIME ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 800; + acop.bitasset_opts->force_settlement_delay_sec = 600; + acop.bitasset_opts->force_settlement_offset_percent = GRAPHENE_100_PERCENT; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa_id, feeder_id, f ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // Transfer funds to seller + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // generate a block + generate_block(); + + // check again + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the settle order expire + generate_blocks( db.head_block_time() + fc::seconds(600) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // the settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_no_feed_after_hf_2467) +{ try { + hf2467 = true; + INVOKE(settle_order_cancel_due_to_no_feed); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_zero_amount_after_hf_2467) +{ try { + hf2467 = true; + INVOKE(settle_order_cancel_due_to_zero_amount); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_100_percent_offset_after_hf_2467) +{ try { + hf2467 = true; + INVOKE(settle_order_cancel_due_to_100_percent_offset); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(settle_rounding_test_after_hf_1270) { try { hf1270 = true; From 3170106441933a8dd70831cd09f5202e201c5791 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 16 Sep 2021 21:42:45 +0000 Subject: [PATCH 81/99] Fix check_call_orders() : check limit orders again after filled a call order with a settle order --- libraries/chain/db_market.cpp | 205 ++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 97 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 2cf41d4911..d0fde8e7a1 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -86,22 +86,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( !call_ptr ) // no call order return false; - using bsrm_type = bitasset_options::black_swan_response_type; - const auto bsrm = bitasset.get_black_swan_response_method(); - - price highest = settle_price; - // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here - // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. - // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. - // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. - if( bsrm_type::individual_settlement_to_fund == bsrm ) - highest = bitasset.median_feed.max_short_squeeze_price(); - else if( !before_core_hardfork_1270 ) - highest = bitasset.current_feed.max_short_squeeze_price(); - else if( maint_time > HARDFORK_CORE_338_TIME ) - highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); - // else do nothing - const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); @@ -115,39 +99,62 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); - if( limit_itr != limit_end ) + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + + // when BSRM is individual settlement, we loop multiple times + bool settled_some = false; + while( true ) { - FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); - auto call_pays_price = limit_itr->sell_price; - if( after_core_hardfork_2481 ) + settle_price = bitasset.current_feed.settlement_price; + price highest = settle_price; + // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. + // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. + // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. + if( bsrm_type::individual_settlement_to_fund == bsrm ) + highest = bitasset.median_feed.max_short_squeeze_price(); + else if( !before_core_hardfork_1270 ) + highest = bitasset.current_feed.max_short_squeeze_price(); + else if( maint_time > HARDFORK_CORE_338_TIME ) + highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); + // else do nothing + + if( limit_itr != limit_end ) { - // due to margin call fee, we check with MCPP (margin call pays price) here - call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); + FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); + auto call_pays_price = limit_itr->sell_price; + if( after_core_hardfork_2481 ) + { + // due to margin call fee, we check with MCPP (margin call pays price) here + call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); + } + highest = std::max( call_pays_price, highest ); } - highest = std::max( call_pays_price, highest ); - } - // The variable `highest` after hf_338: - // * if no limit order, it is expected to be the black swan price; if the call order with the least CR - // has CR below or equal to the black swan price, we trigger GS, - // * if there exists at least one limit order and the price is higher, we use the limit order's price, - // which means we will match the margin call orders with the limit order first. - // - // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered - // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got - // caught by an additional check in check_call_orders(). - // This bug is fixed in hf_2481. Actually, after hf_2481, - // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), - // * if there is no force settlement, we check here with margin call fee in consideration. - - auto least_collateral = call_ptr->collateralization(); - // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, - // if the matching limit order is smaller, due to rounding, it is still possible that the - // call order's collateralization would increase and become higher than ~highest after matched. - // However, for simplicity, we only compare the prices here. - bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); - if( is_blackswan ) - { + // The variable `highest` after hf_338: + // * if no limit order, it is expected to be the black swan price; if the call order with the least CR + // has CR below or equal to the black swan price, we trigger GS, + // * if there exists at least one limit order and the price is higher, we use the limit order's price, + // which means we will match the margin call orders with the limit order first. + // + // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered + // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got + // caught by an additional check in check_call_orders(). + // This bug is fixed in hf_2481. Actually, after hf_2481, + // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), + // * if there is no force settlement, we check here with margin call fee in consideration. + + auto least_collateral = call_ptr->collateralization(); + // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, + // if the matching limit order is smaller, due to rounding, it is still possible that the + // call order's collateralization would increase and become higher than ~highest after matched. + // However, for simplicity, we only compare the prices here. + bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) + : ( ~least_collateral >= highest ); + if( !is_blackswan ) + return settled_some; + wdump( (*call_ptr) ); elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" " Least collateralized call: ${lc} ${~lc}\n" @@ -166,6 +173,11 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) { individually_settle( bitasset, *call_ptr ); + call_ptr = find_least_collateralized_short( bitasset, true ); + if( !call_ptr ) // no call order + return true; + settled_some = true; + continue; } // Global settlement or no settlement, but we should not be here if BSRM is no_settlement else if( after_core_hardfork_2481 ) @@ -195,7 +207,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s globally_settle_asset(mia, ~least_collateral ); return true; } - return false; } /** @@ -609,6 +620,7 @@ bool maybe_cull_small_order( database& db, const limit_order_object& order ) return false; } +// Note: optimizations have been done in apply_order(...) bool database::apply_order_before_hardfork_625(const limit_order_object& new_order_object) { auto order_id = new_order_object.id; @@ -625,7 +637,7 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord const auto& limit_price_idx = get_index_type().indices().get(); - // TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to + // it should be possible to simply check the NEXT/PREV iterator after new_order_object to // determine whether or not this order has "changed the book" in a way that requires us to // check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs // constant time check. Potential optimization. @@ -644,8 +656,8 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord != match_result_type::only_maker_filled ); } - // TODO Possible optimization: only check calls if the new order completely filled some old order. - // Do I need to check both assets? + // Possible optimization: only check calls if the new order completely filled some old order. + // Do I need to check both assets? check_call_orders(sell_asset); // after the new limit order filled some orders on the book, // if a call order matches another order, the call order is taker check_call_orders(receive_asset); // the other side, same as above @@ -1821,12 +1833,26 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool update_current_feed = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() ); - // If update_current_feed is true and we filled a call order with a settle order, + const auto& settlement_index = get_index_type().indices().get(); + + // If we filled a call order with a settle order, // we need to check limit orders again, in this case we loop multiple times. // In other cases we only need to loop once. while( true ) { + // If BSRM is individual settlement, check for blackswan first + if( ( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) + // Note: only call when conditions above are true + && check_for_blackswan( mia, enable_black_swan, &bitasset ) ) // TODO perhaps improve performance + // by passing in iterators + { + margin_called = true; + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + if( bsrm_type::individual_settlement_to_fund == bsrm ) + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + } + // match call orders with limit orders while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators && limit_itr != limit_end @@ -2003,46 +2029,42 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) limit_itr = next_limit_itr; - } // while call_itr != call_end + } // while !blackswan and call_itr != call_end and limit_itr != limit_end + + // If already GSed, or there is no call order, we return. + // Note: if GSed, the iterators used in has_call_order() should have been invalidated + if( bitasset.has_settlement() || !has_call_order() ) + return margin_called; + + // If no need to process force settlements, we return + if( skip_matching_settle_orders || !after_core_hardfork_2481 ) + return margin_called; + + // If feed protected, we return + if( bitasset.current_maintenance_collateralization < call_collateral_itr->collateralization() ) + return margin_called; + + // If no force settlements, we return + auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); + if( settle_itr == settlement_index.end() || settle_itr->balance.asset_id != bitasset.asset_id ) + return margin_called; // Check margin calls against force settlements - if( !skip_matching_settle_orders && after_core_hardfork_2481 && !bitasset.has_settlement() ) + // Note: we always need to recheck limit orders after processed call-settle match, + // in case when the least collateralized short was undercollateralized. + if( match_force_settlements( bitasset ) ) { - // Be here, there exists at least one margin call not processed - bool called_some = match_force_settlements( bitasset ); - if( called_some ) - { - margin_called = true; - // if called some, it means the call order is updated or removed, - // in this case, if update_current_feed is true, - // it is possible that there are limit orders able to get filled, - // so we need to check again - if( update_current_feed ) - { - limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); - call_collateral_itr = call_collateral_index.lower_bound( call_min ); - update_current_feed = bitasset.is_current_feed_price_capped(); - continue; - } - } - // At last, check for blackswan // TODO perhaps improve performance by passing in iterators - if( bsrm_type::individual_settlement_to_fund == bsrm - || bsrm_type::individual_settlement_to_order == bsrm ) + margin_called = true; + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + if( update_current_feed ) { - // Run multiple times, each time one call order gets settled - // TODO perhaps improve performance by settling multiple call orders inside in one call - while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) - { - margin_called = true; - } + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + update_current_feed = bitasset.is_current_feed_price_capped(); } - else - check_for_blackswan( mia, enable_black_swan, &bitasset ); } - break; + // else : no more force settlements, or feed protected, both will be handled in the next loop } // while true - return margin_called; } FC_CAPTURE_AND_RETHROW() } bool database::match_force_settlements( const asset_bitasset_data_object& bitasset ) @@ -2074,13 +2096,6 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // It is in debt/collateral . price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - // Note: when BSRM is no_settlement, current_feed can change after filled a call order, - // so we recalculate inside the loop - using bsrm_type = bitasset_options::black_swan_response_type; - bool update_call_price = ( bitasset.get_black_swan_response_method() == bsrm_type::no_settlement - && bitasset.is_current_feed_price_capped() ); - - bool margin_called = false; while( settle_itr != settle_end && call_itr != call_end ) { const force_settlement_object& settle_order = *settle_itr; @@ -2088,7 +2103,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 if( bitasset.current_maintenance_collateralization < call_order.collateralization() ) - return margin_called; + return false; // TCR applies here asset max_debt_to_cover( call_order.get_max_debt_to_cover( call_pays_price, @@ -2103,19 +2118,15 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price ); // if result.amount > 0, it means the call order got updated or removed + // in this case, we need to check limit orders first, so we return if( result.amount > 0 ) - { - // if update_call_price is true, we need to check limit orders first, so we return - if( update_call_price ) - return true; - margin_called = true; - } + return true; // else : result.amount == 0, it means the settle order got canceled directly and the call order did not change settle_itr = settlement_index.lower_bound( bitasset.asset_id ); call_itr = call_collateral_index.lower_bound( call_min ); } - return margin_called; + return false; } void database::pay_order( const account_object& receiver, const asset& receives, const asset& pays ) From 2b38112e8e6c2fa69127356299ec3cc0430557c7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 16 Sep 2021 21:48:10 +0000 Subject: [PATCH 82/99] Add tests wrt matching call with settle then limit --- tests/tests/force_settle_match_tests.cpp | 155 +++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 8cda2a54ae..a0b030a2e9 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -1323,4 +1323,159 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) } FC_LOG_AND_RETHROW() } +/*** + * Match taker call orders with maker settle orders, + * then it is able to match taker call orders with maker limit orders again + */ +BOOST_AUTO_TEST_CASE(call_settle_limit_settle) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(seller2)(borrower)(borrower2)(borrower3)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id; + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3% + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/175 CORE/USD = 60/700, tcr 170% is lower than 175% + const call_order_object& call = *borrow( borrower, bitusd.amount(100000), asset(15000), 1700); + call_order_id_type call_id = call.id; + // create another position with 360% collateral, call price is 18/175 CORE/USD = 72/700, no tcr + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(18000) ); + call_order_id_type call2_id = call2.id; + // create yet another position with 800% collateral, call price is 40/175 CORE/USD = 160/700, no tcr + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(40000) ); + call_order_id_type call3_id = call3.id; + + transfer(borrower, seller, bitusd.amount(100000)); + transfer(borrower2, seller, bitusd.amount(100000)); + transfer(borrower3, seller2, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 100000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call2.debt.value ); + BOOST_CHECK_EQUAL( 18000, call2.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 40000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 200000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 100000, get_balance(seller2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 18000, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); + + // Create a sell order which will trigger a blackswan event if matched, price 100/16 + limit_order_id_type sell_swan = create_sell_order(seller2, bitusd.amount(10000), core.amount(1600) )->id; + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 ); + + // Create a force settlement, will be matched with several call orders later + auto result = force_settle( seller, bitusd.amount(200000) ); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_CHECK( db.find( settle_id ) != nullptr ); + BOOST_CHECK_EQUAL( 200000, settle_id(db).balance.amount.value ); + + // Check balances + BOOST_CHECK_EQUAL( 0, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 90000, get_balance(seller2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) ); + + // adjust price feed to get call and call2 (but not call3) into margin call territory + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(16); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 100/16, mssp = 1000/176, mcop = 100/16 * 100/107 = 625/107, mcpr = 110/107 + + const auto& check_result = [&] + { + // matching call with sell_swan would trigger a black swan event, so it's skipped + // so matching call with settle + // the settle order is bigger so call is fully filled + BOOST_CHECK( !db.find( call_id ) ); + // call pays 15000, gets 100000 + // settle receives round_up(15000 * 107 / 110) = 14591, margin call fee = 409 + + // now it is able to match call2 with sell_swan + // call2 is bigger, sell_swan is fully filled + BOOST_CHECK( !db.find( sell_swan ) ); + // sell_swan pays 10000, gets 1600 + // call2 pays round_down(1600 * 110 / 107) = 1644, margin call fee = 44 + + // now match call2 with settle + // the settle order is bigger so call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // call2 gets 90000, pays round_up(90000 * (16/100) * (11/10)) = 15840 + // settle receives round_up(90000 * (16/100) * (107/100)) = 15408, margin call fee = 432 + + // the settle order is not fully filled + BOOST_CHECK_EQUAL( 10000, settle_id(db).balance.amount.value ); + + // no change to call3 + BOOST_CHECK_EQUAL( 100000, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call3_id(db).collateral.value ); + + // blackswan event did not occur + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + + // check balances + BOOST_CHECK_EQUAL( 0, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 14591+15408, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 90000, get_balance(seller2_id, usd_id) ); + BOOST_CHECK_EQUAL( 1600, get_balance(seller2_id, core_id) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower_id, core_id) ); + BOOST_CHECK_EQUAL( init_balance - 1644 - 15840, get_balance(borrower2_id, core_id) ); + BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower3_id, core_id) ); + }; + + // check + check_result(); + + // generate a block + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + BOOST_TEST_MESSAGE( "Check again" ); + + // check + check_result(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 9b2bfffe2fd150222d79f5b13012f8f7fe78e431 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 17 Sep 2021 13:23:22 +0000 Subject: [PATCH 83/99] Remove an unused variable --- libraries/chain/include/graphene/chain/market_evaluator.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 8df92ff464..f9d94ddd5f 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -54,7 +54,6 @@ namespace graphene { namespace chain { private: share_type _deferred_fee = 0; asset _deferred_paid_fee; - const limit_order_create_operation* _op = nullptr; const account_object* _seller = nullptr; const asset_object* _sell_asset = nullptr; const asset_object* _receive_asset = nullptr; From c253f40e9ffbef06a5986f68cff5dca77c24c5d5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 17 Sep 2021 14:11:44 +0000 Subject: [PATCH 84/99] Check call orders after paid from individual fund --- libraries/chain/asset_evaluator.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index a1fa87e2fe..0082ab0a88 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1229,8 +1229,17 @@ static extendable_operation_result pay_settle_from_individual_pool( database& d, d.adjust_balance( op.account, settled_amount ); // Update current_feed since fund price changed + auto old_feed_price = bitasset.current_feed.settlement_price; d.update_bitasset_current_feed( bitasset, true ); + // When current_feed is updated, it is possible that there are limit orders able to get filled, + // so we need to call check_call_orders() + // Note: theoretically, if the fund is still not empty, its new CR should be >= old CR, + // in this case, calling check_call_orders() should not change anything. + // Note: there should be no existing force settlements + if( 0 == bitasset.individual_settlement_debt && old_feed_price != bitasset.current_feed.settlement_price ) + d.check_call_orders( asset_to_settle, true, false, &bitasset ); + extendable_operation_result result; result.value.paid = vector({ pays }); From d357023fbb8d399244400a8d0008f71e31636ded Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 18 Sep 2021 15:47:35 +0000 Subject: [PATCH 85/99] Add tests for individual settlement to order --- tests/tests/bsrm_indvd_settlement_tests.cpp | 308 +++++++++++++++++++- 1 file changed, 304 insertions(+), 4 deletions(-) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index a1fea75c8e..6baa2fc359 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -35,8 +35,308 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) -/// Tests individual settlement to order -BOOST_AUTO_TEST_CASE( individual_settlement_to_order_test ) +/// Tests individual settlement (to order or fund) : how call orders are being processed when price drops +BOOST_AUTO_TEST_CASE( individual_settlement_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // two passes, one for individual settlement to order, the other for individual settlement to fund + for( int i = 0; i < 2; ++ i ) + { + idump( (i) ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller)(seller2)(seller3)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + fund( borrower4, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = (i == 0) ? static_cast(bsrm_type::individual_settlement_to_order) + : static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + if( 0 == i ) + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + else if( 1 == i ) + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // 100000 / 2000 = 50 + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // 100000 / 2100 = 47.619047619 + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // 100000 / 2200 = 45.454545455 + // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760 + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // 100000 / 2500 = 40 + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 + const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); + BOOST_REQUIRE( call4_ptr ); + call_order_id_type call4_id = call4_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(100000,mpa_id) ); + transfer( borrower4, seller2, asset(50000,mpa_id) ); + transfer( borrower4, seller3, asset(50000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 300000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 50000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 50000 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 ); + + // seller sells some + const limit_order_object* sell_low = create_sell_order( seller, asset(10000,mpa_id), asset(190) ); + BOOST_REQUIRE( sell_low ); + limit_order_id_type sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 10000 ); + + // seller sells some + const limit_order_object* sell_mid = create_sell_order( seller, asset(100000,mpa_id), asset(2000) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100000 ); + + // seller sells some + const limit_order_object* sell_high = create_sell_order( seller, asset(100000,mpa_id), asset(2400) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + + // seller2 settles + auto result = force_settle( seller2, asset(50000,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find( settle_id ) ); + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 50000 ); + + // seller3 settles + result = force_settle( seller3, asset(10000,mpa_id) ); + force_settlement_id_type settle2_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find( settle2_id ) ); + BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 10000 ); + + // check + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // 300000 - 10000 - 100000 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 0 ); // 50000 - 50000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40000 ); // 50000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 ); + + // publish a new feed so that call, call2 and call3 are undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1800) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 + // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789 + + auto check_result = [&] + { + // sell_low price is 10000/190 = 52.631578947 + // call is matched with sell_low + // call pays price is (10000/190) * (1239/1250) + // sell_low is smaller thus fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low gets 190, pays 10000 + // call gets 10000, pays round_down(190 * 1250/1239) = 191, margin call fee = 1 + // call is now (100000-10000):(2000-191) = 90000:1809 = 49.751243781 (< 50) + + // sell_mid price is 100000/2000 = 50 + // call is matched with sell_mid + // call pays price is (100000/2000) * (1239/1250) + // call is smaller + // call gets 90000, pays round_up(90000 * (2000/100000) * (1250/1239)) = 1815 + // 1815 > 1809, unable to fill + + // call is matched with settle + // settle is smaller thus fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // unable to pay at MSSP + // call pays at its own price + // settle receives round_down(50000 * (1809/90000) * (1239/1250)) = 996 + // settle pays round_up(996 * (90000/1809) * (1250/1239)) = 49993, refund 7 + // call receives 49993 + // call pays round_down(49993 * 1809/90000) = 1004, margin call fee = 1004 - 996 = 8 + // call is now (90000-49993):(1809-1004) = 40007:805 = 49.698136646 (< 90000:1809) + + // call is matched with sell_mid again + // call gets 40007, pays round_up(40007 * (2000/100000) * (1250/1239)) = 807 + // 807 > 805, unable to fill + + // call is matched with settle2 + // settle2 is smaller thus fully filled + BOOST_CHECK( !db.find( settle2_id ) ); + // unable to pay at MSSP + // call pays at its own price + // settle2 receives round_down(10000 * (805/40007) * (1239/1250)) = 199 + // settle2 pays round_up(199 * (40007/805) * (1250/1239)) = 9978, refund 22 + // call receives 9978 + // call pays round_down(9978 * 805/40007) = 200, margin call fee = 200 - 199 = 1 + // call is now (40007-9978):(805-200) = 30029:605 = 49.634710744 (< 40007:805) + + // call is matched with sell_mid again + // call gets 30029, pays round_up(30029 * (2000/100000) * (1250/1239)) = 606 + // 606 > 605, unable to fill + + // no settle order + // call is individually settled + BOOST_CHECK( !db.find( call_id ) ); + // fund gets round_up(605 * 1239/1250) = 600, margin call fee = 605 - 600 = 5 + // fund debt = 30029 + + if( 0 == i ) // to order + { + // call2 is matched with sell_mid + // the size is the same, consider call2 as smaller + // call2 gets 100000, pays round_up(100000 * (2000/100000) * (1250/1239)) = 2018, margin call fee = 18 + // 2018 < 2100, able to fill + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( sell_mid_id ) ); + + // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + + // no settle order + // call3 is individually settled + BOOST_CHECK( !db.find( call3_id ) ); + // fund gets round_up(2200 * 1239/1250) = 2181, margin call fee = 2200 - 2181 = 19 + // fund debt += 100000 + + // call4 is not undercollateralized + BOOST_REQUIRE( db.find( call4_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 2781 ); // 600 + 2181 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 130029 ); // 30029 + 100000 + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2018 ); // refund 82 + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 7 ); // refund 7 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22 + BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 ); + } + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + +/// Tests individual settlement to order : settles when price drops, and how orders are being matched after settled +BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) { try { @@ -113,12 +413,12 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_test ) BOOST_REQUIRE( call2_ptr ); call_order_id_type call2_id = call2_ptr->id; - // undercollateralization price = 100000:3000 * 1250:1000 = 100000:1760 + // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760 const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); BOOST_REQUIRE( call3_ptr ); call_order_id_type call3_id = call3_ptr->id; - // undercollateralization price = 100000:4000 * 1250:1000 = 100000:2400 + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); BOOST_REQUIRE( call4_ptr ); call_order_id_type call4_id = call4_ptr->id; From 7f9e15b9581d74e4e06c0b433d8fd79992495eb3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 18 Sep 2021 18:58:41 +0000 Subject: [PATCH 86/99] Fix code smells --- libraries/chain/db_market.cpp | 7 ++++--- libraries/chain/db_notify.cpp | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index d0fde8e7a1..a8c4a8133e 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1159,8 +1159,9 @@ database::match_result_type database::match( const limit_order_object& bid, cons maintenance_collateralization = bitasset.current_maintenance_collateralization; asset usd_for_sale = bid.amount_for_sale(); - asset usd_to_buy = asset( ask.get_max_debt_to_cover( call_pays_price, feed_price, - maintenance_collateral_ratio, maintenance_collateralization ), ask.debt_type() ); + asset usd_to_buy( ask.get_max_debt_to_cover( call_pays_price, feed_price, maintenance_collateral_ratio, + maintenance_collateralization ), + ask.debt_type() ); asset call_pays; asset call_receives; @@ -2298,7 +2299,7 @@ asset database::pay_force_settle_fees(const asset_object& collecting_asset, cons 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 }; + asset settle_fee( value, collat_receives.asset_id ); // Deposit fee in asset's dynamic data object: if( value > 0) { diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index a2c2c87210..8f33f8a15d 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -391,8 +391,7 @@ struct get_impacted_account_visitor void operation_get_impacted_accounts( const operation& op, flat_set& result, bool ignore_custom_op_required_auths ) { - detail::get_impacted_account_visitor vtor = detail::get_impacted_account_visitor( result, - ignore_custom_op_required_auths ); + detail::get_impacted_account_visitor vtor( result, ignore_custom_op_required_auths ); op.visit( vtor ); } From 21c93b475930dfd2afeae0f8afd2e2a07b3cbdef Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 21 Sep 2021 21:42:46 +0000 Subject: [PATCH 87/99] Fix individual_settlement_to_fund Settle undercollateralized debt position when unable to match with a limit order --- libraries/chain/db_market.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index a8c4a8133e..354d00f6b2 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -129,7 +129,14 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // due to margin call fee, we check with MCPP (margin call pays price) here call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); } - highest = std::max( call_pays_price, highest ); + if( bsrm_type::individual_settlement_to_fund != bsrm ) + highest = std::max( call_pays_price, highest ); + // for individual_settlement_to_fund, if call_pays_price < current_feed.max_short_squeeze_price(), + // we don't match the least collateralized short with the limit order + // even if call_pays_price >= median_feed.max_short_squeeze_price() + else if( call_pays_price >= bitasset.current_feed.max_short_squeeze_price() ) + highest = call_pays_price; + // else highest is median_feed.max_short_squeeze_price() } // The variable `highest` after hf_338: From 9cecdb566366442f00edc1bd649d652aec1a5f02 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 21 Sep 2021 22:06:39 +0000 Subject: [PATCH 88/99] Add tests for individual settlement to fund --- tests/tests/bsrm_indvd_settlement_tests.cpp | 68 +++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index 6baa2fc359..fcea75d284 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -312,6 +312,74 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 7 ); // refund 7 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22 + BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 ); + } + else if( 1 == i ) // to fund + { + // sell_mid price is 100000/2000 = 50 + // call pays price is (100000/2000) * (1239:1250) = 49.56 + + // median feed is 100000/1800 = 55.555555556 + // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 + // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789 + + // fund collateral = 600 + // fund debt = 30029 + // current feed is capped at (30029:600) * (1239:1000) = 62.009885 + // call pays price is (30029:600) * (1239:1250) = 49.607908 + // call match price is 30029/600 = 50.048333333 (> sell_mid.price) + + // call2 will not match with sell_mid + // call2 is individually settled + BOOST_CHECK( !db.find( call2_id ) ); + // fund gets round_up(2100 * 1239/1250) = 2082, margin call fee = 2100 - 2082 = 18 + // fund debt += 100000 + + // fund collateral = 600 + 2082 = 2682 + // fund debt = 30029 + 100000 = 130029 + // current feed is capped at (130029:2682) * (1239:1000) = 60.069325503 + // call pays price is (130029:2682) * (1239:1250) = 48.055460403 + // call match price is 130029/2682 = 48.482102908 + + // call3 is matched with sell_mid + // the size is the same, consider call3 as smaller + // call3 gets 100000, pays round_up(100000 * (2000/100000) * (1250/1239)) = 2018, margin call fee = 18 + // 2018 < 2200, able to fill + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK( !db.find( sell_mid_id ) ); + + // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + + // call4 is not undercollateralized + BOOST_REQUIRE( db.find( call4_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 2682 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 130029 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130029*1239,mpa_id), asset(2682*1000) ) ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 7 ); // refund 7 From 9240fa74c5ba5fa2c9d454b4519edc35a3c11f01 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 22 Sep 2021 20:24:38 +0000 Subject: [PATCH 89/99] Refactor database::check_call_orders() and fix individual settlements: always check limit orders again after individually settled some debt positions --- libraries/chain/db_market.cpp | 267 +++++++++++++++++----------------- 1 file changed, 134 insertions(+), 133 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 354d00f6b2..2e5c878ddc 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1843,41 +1843,36 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const auto& settlement_index = get_index_type().indices().get(); - // If we filled a call order with a settle order, - // we need to check limit orders again, in this case we loop multiple times. - // In other cases we only need to loop once. - while( true ) + while( has_call_order() ) { - // If BSRM is individual settlement, check for blackswan first - if( ( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) - // Note: only call when conditions above are true - && check_for_blackswan( mia, enable_black_swan, &bitasset ) ) // TODO perhaps improve performance - // by passing in iterators + // check for blackswan first // TODO perhaps improve performance by passing in iterators + bool settled_some = check_for_blackswan( mia, enable_black_swan, &bitasset ); + if( bitasset.has_settlement() ) + return margin_called; + + if( settled_some ) // which implies that BSRM is individual settlement to fund or to order { - margin_called = true; call_collateral_itr = call_collateral_index.lower_bound( call_min ); + if( call_collateral_itr == call_collateral_end ) + return true; + margin_called = true; if( bsrm_type::individual_settlement_to_fund == bsrm ) limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); } - // match call orders with limit orders - while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance - // by passing in iterators - && limit_itr != limit_end - && has_call_order() ) - { - bool filled_call = false; - - const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); + // be here, there exists at least one call order + const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); - // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - bool feed_protected = before_core_hardfork_1270 ? - ( after_hardfork_436 - && bitasset.current_feed.settlement_price > ~call_order.call_price ) - : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); - if( feed_protected ) - return margin_called; + // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 + bool feed_protected = before_core_hardfork_1270 ? + ( after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) + : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); + if( feed_protected ) + return margin_called; + // match call orders with limit orders + if( limit_itr != limit_end ) + { const limit_order_object& limit_order = *limit_itr; price match_price = limit_order.sell_price; @@ -1928,130 +1923,136 @@ 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, limit_pays, limit_receives; - if( usd_to_buy > usd_for_sale ) - { // fill 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): - // * after hard fork core-625, the limit order will be always a maker if entered this function; - // * before hard fork core-625, - // * when the limit order is a taker, it could be paying something for nothing only when - // the call order is smaller and is too small - // * when the limit order is a maker, it won't be paying something for nothing - - if( before_core_hardfork_342 ) - call_receives = usd_for_sale; - else - // The remaining amount in the limit order would be too small, - // 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 = limit_receives.multiply_and_round_up( match_price ); - - if( !after_core_hardfork_2481 ) - // TODO add tests about CR change - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) - else - { - call_pays = call_receives * call_pays_price; // calculate with updated call_receives - if( call_pays.amount >= call_order.collateral ) - break; - auto new_collateral = call_order.get_collateral() - call_pays; - auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math - if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease - break; - } - filled_limit = true; + struct UndercollateralizationException {}; + try { // throws UndercollateralizationException if the call order is undercollateralized + + bool filled_call = false; + + if( usd_to_buy > usd_for_sale ) + { // fill 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): + // * after hard fork core-625, the limit order will be always a maker if entered this function; + // * before hard fork core-625, + // * when the limit order is a taker, it could be paying something for nothing only when + // the call order is smaller and is too small + // * when the limit order is a maker, it won't be paying something for nothing + + if( before_core_hardfork_342 ) + call_receives = usd_for_sale; + else + // The remaining amount in the limit order would be too small, + // 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 = limit_receives.multiply_and_round_up( match_price ); + + if( !after_core_hardfork_2481 ) + // TODO add tests about CR change + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + else + { + call_pays = call_receives * call_pays_price; // calculate with updated call_receives + if( call_pays.amount >= call_order.collateral ) + throw UndercollateralizationException(); + auto new_collateral = call_order.get_collateral() - call_pays; + auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math + if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease + throw UndercollateralizationException(); + } + + filled_limit = true; - } else { // fill call, could be partial fill due to TCR - call_receives = usd_to_buy; + } else { // fill call, could be partial fill due to TCR + call_receives = usd_to_buy; - if( before_core_hardfork_342 ) - { - limit_receives = usd_to_buy * match_price; // round down, in favor of call order - call_pays = limit_receives; - } else { - call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. - // Note: Due to different rounding, this could potentialy be - // one satoshi more than the blackswan check above - if( call_pays.amount > call_order.collateral ) + if( before_core_hardfork_342 ) { - if( after_core_hardfork_2481 ) - break; - if( mute_exceptions ) - call_pays.amount = call_order.collateral; + limit_receives = usd_to_buy * match_price; // round down, in favor of call order + call_pays = limit_receives; + } else { + call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. + // Note: Due to different rounding, this could potentialy be + // one satoshi more than the blackswan check above + if( call_pays.amount > call_order.collateral ) + { + if( after_core_hardfork_2481 ) + throw UndercollateralizationException(); + if( mute_exceptions ) + call_pays.amount = call_order.collateral; + } + // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher + // than the old CR, so no additional check for potential blackswan here + + limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, favors limit order + if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 + limit_receives.amount = call_order.collateral; + // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more + // debt asset, it means the call order could be receiving a bit too much less than its value. + // It is a sad thing for the call order, but it is the rule + // -- when a call order is margin called, it does not get more than it borrowed. + // On the other hand, if the call order is not being closed (due to TCR), + // it means get_max_debt_to_cover() did not return a perfect result, maybe we can improve it. } - // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher - // than the old CR, so no additional check for potential blackswan here - - limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order - if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 - limit_receives.amount = call_order.collateral; - // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more - // debt asset, it means the call order could be receiving a bit too much less than its value. - // It is a sad thing for the call order, but it is the rule - // -- when a call order is margin called, it does not get more than it borrowed. - // On the other hand, if the call order is not being closed (due to TCR), - // it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it. + + filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) + + if( usd_to_buy == usd_for_sale ) + filled_limit = true; + else if( filled_limit && before_hardfork_615 ) + //NOTE: Multiple limit match problem (see issue 453, yes this happened) + _issue_453_affected_assets.insert( bitasset.asset_id ); } + limit_pays = call_receives; - filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) + // BSIP74: Margin call fee + FC_ASSERT(call_pays >= limit_receives); + const asset margin_call_fee = call_pays - limit_receives; - if( usd_to_buy == usd_for_sale ) - filled_limit = true; - else if( filled_limit && before_hardfork_615 ) - //NOTE: Multiple limit match problem (see issue 453, yes this happened) - _issue_453_affected_assets.insert( bitasset.asset_id ); - } - limit_pays = call_receives; + if( filled_call && before_core_hardfork_343 ) + ++call_price_itr; - // BSIP74: Margin call fee - FC_ASSERT(call_pays >= limit_receives); - const asset margin_call_fee = call_pays - limit_receives; + // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker + fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); - if( filled_call && before_core_hardfork_343 ) - ++call_price_itr; + // Update current_feed after filled call order if needed + if( update_current_feed ) + { + update_bitasset_current_feed( bitasset, true ); + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + update_current_feed = bitasset.is_current_feed_price_capped(); + } - // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker - fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); + if( !before_core_hardfork_1270 ) + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + else if( !before_core_hardfork_343 ) + call_price_itr = call_price_index.lower_bound( call_min ); - // Update current_feed after filled call order if needed - if( update_current_feed ) - { - update_bitasset_current_feed( bitasset, true ); - limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); - update_current_feed = bitasset.is_current_feed_price_capped(); + 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, 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; + + continue; // check for blackswan again + + } catch( const UndercollateralizationException& ) { + // Nothing to do here } + } // if there is a matching limit order - if( !before_core_hardfork_1270 ) - call_collateral_itr = call_collateral_index.lower_bound( call_min ); - else if( !before_core_hardfork_343 ) - call_price_itr = call_price_index.lower_bound( call_min ); - - 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, 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; - - } // while !blackswan and call_itr != call_end and limit_itr != limit_end - - // If already GSed, or there is no call order, we return. - // Note: if GSed, the iterators used in has_call_order() should have been invalidated - if( bitasset.has_settlement() || !has_call_order() ) - return margin_called; + // be here, it is unable to fill a limit order due to undercollateralization (and there is a force settlement), + // or there is no matching limit order due to MSSR, or no limit order at all // If no need to process force settlements, we return + // Note: before core-2481/2467 hf, or BSRM is no_settlement and processing a new force settlement if( skip_matching_settle_orders || !after_core_hardfork_2481 ) return margin_called; - // If feed protected, we return - if( bitasset.current_maintenance_collateralization < call_collateral_itr->collateralization() ) - return margin_called; - // If no force settlements, we return auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); if( settle_itr == settlement_index.end() || settle_itr->balance.asset_id != bitasset.asset_id ) @@ -2071,8 +2072,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } } // else : no more force settlements, or feed protected, both will be handled in the next loop - } // while true - + } // while there exists a call order + return margin_called; } FC_CAPTURE_AND_RETHROW() } bool database::match_force_settlements( const asset_bitasset_data_object& bitasset ) From 8ec1e03e1fbb26fca2c8d2f1288af3e8264576f0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 22 Sep 2021 22:05:44 +0000 Subject: [PATCH 90/99] Add more tests about individual settlements --- tests/tests/bsrm_indvd_settlement_tests.cpp | 98 ++++++++++++++++++--- 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index fcea75d284..d8414567f4 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) set_expiration( db, trx ); - ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller)(seller2)(seller3)); + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(seller)(seller2)(seller3)(seller4)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); @@ -60,6 +60,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) fund( borrower2, asset(init_amount) ); fund( borrower3, asset(init_amount) ); fund( borrower4, asset(init_amount) ); + fund( borrower5, asset(init_amount) ); using bsrm_type = bitasset_options::black_swan_response_type; uint8_t bsrm_value = (i == 0) ? static_cast(bsrm_type::individual_settlement_to_order) @@ -108,6 +109,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); @@ -137,12 +139,19 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_REQUIRE( call4_ptr ); call_order_id_type call4_id = call4_ptr->id; + // 100000 / 2240 = 44.642857143 + // undercollateralization price = 100000:2240 * 1250:1000 = 100000:1792 + const call_order_object* call5_ptr = borrow( borrower5, asset(1000000, mpa_id), asset(22400) ); + BOOST_REQUIRE( call5_ptr ); + call_order_id_type call5_id = call5_ptr->id; + // Transfer funds to sellers transfer( borrower, seller, asset(100000,mpa_id) ); transfer( borrower2, seller, asset(100000,mpa_id) ); transfer( borrower3, seller, asset(100000,mpa_id) ); transfer( borrower4, seller2, asset(50000,mpa_id) ); transfer( borrower4, seller3, asset(50000,mpa_id) ); + transfer( borrower5, seller4, asset(1000000,mpa_id) ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); @@ -152,11 +161,14 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + BOOST_CHECK_EQUAL( call5_id(db).debt.value, 1000000 ); + BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 22400 ); BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 300000 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); @@ -164,6 +176,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 50000 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 1000000 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 0 ); // seller sells some const limit_order_object* sell_low = create_sell_order( seller, asset(10000,mpa_id), asset(190) ); @@ -177,6 +191,12 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) limit_order_id_type sell_mid_id = sell_mid->id; BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100000 ); + // seller4 sells some + const limit_order_object* sell_mid2 = create_sell_order( seller4, asset(20000,mpa_id), asset(439) ); + BOOST_REQUIRE( sell_mid2 ); + limit_order_id_type sell_mid2_id = sell_mid2->id; + BOOST_CHECK_EQUAL( sell_mid2_id(db).for_sale.value, 20000 ); + // seller sells some const limit_order_object* sell_high = create_sell_order( seller, asset(100000,mpa_id), asset(2400) ); BOOST_REQUIRE( sell_high ); @@ -204,6 +224,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + BOOST_CHECK_EQUAL( call5_id(db).debt.value, 1000000 ); + BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 22400 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // 300000 - 10000 - 100000 - 100000 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); @@ -211,8 +233,10 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40000 ); // 50000 - 10000 BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // 1000000 - 20000 + BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 0 ); - // publish a new feed so that call, call2 and call3 are undercollateralized + // publish a new feed so that call, call2, call3 and call5 are undercollateralized f.settlement_price = price( asset(100000,mpa_id), asset(1800) ); publish_feed( mpa_id, feeder_id, f, feed_icr ); // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 @@ -281,16 +305,32 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( !db.find( call2_id ) ); BOOST_CHECK( !db.find( sell_mid_id ) ); - // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) - BOOST_REQUIRE( db.find( sell_high_id ) ); - BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + // sell_mid2 price is 20000/439 = 45.55808656 + // call pays price is (20000/439) * (1239/1250) = 45.157175399 - // no settle order + // call3 is 100000/2200 = 45.454545455 (>45.157175399), so unable to fill // call3 is individually settled BOOST_CHECK( !db.find( call3_id ) ); // fund gets round_up(2200 * 1239/1250) = 2181, margin call fee = 2200 - 2181 = 19 // fund debt += 100000 + // call5 is 1000000/22400 = 44.642857143 (<45.157175399) + // call5 is matched with sell_mid2 + // sell_mid2 is smaller thus fully filled + BOOST_CHECK( !db.find( sell_mid2_id ) ); + // sell_mid2 gets 439, pays 20000 + // call5 gets 20000, pays round_down(439 * 1250/1239) = 442, margin call fee = 3 + // call5 is now (1000000-20000):(22400-442) = 980000:21958 = 44.63065853 (> MSSP 44.444444444) + + // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + + // call5 is individually settled + BOOST_CHECK( !db.find( call5_id ) ); + // fund gets round_up(21958 * 1239/1250) = 21765, margin call fee = 21958 - 21765 = 193 + // fund debt += 980000 + // call4 is not undercollateralized BOOST_REQUIRE( db.find( call4_id ) ); BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); @@ -299,18 +339,20 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); BOOST_REQUIRE( settled_debt ); - BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 2781 ); // 600 + 2181 - BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 130029 ); // 30029 + 100000 + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 24546 ); // 600 + 2181 + 21765 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 1110029 ); // 30029 + 100000 + 980000 BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2018 ); // refund 82 BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 @@ -318,6 +360,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22 BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 439 ); // 439 } else if( 1 == i ) // to fund { @@ -344,7 +388,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) // fund debt = 30029 + 100000 = 130029 // current feed is capped at (130029:2682) * (1239:1000) = 60.069325503 // call pays price is (130029:2682) * (1239:1250) = 48.055460403 - // call match price is 130029/2682 = 48.482102908 + // call match price is 130029/2682 = 48.482102908 (< sell_mid.price) // call3 is matched with sell_mid // the size is the same, consider call3 as smaller @@ -353,14 +397,36 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( !db.find( call3_id ) ); BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid2 price is 20000/439 = 45.55808656 + // call match price is 130029/2682 = 48.482102908 (> sell_mid2.price) + + // call5 will not match with sell_mid2 + // call5 is individually settled + BOOST_CHECK( !db.find( call5_id ) ); + // fund gets round_up(22400 * 1239/1250) = 22203, margin call fee = 22400 - 22203 = 197 + // fund debt += 1000000 + + // fund collateral = 600 + 2082 + 22203 = 24885 + // fund debt = 30029 + 100000 + 100000 = 1130029 + // current feed is capped at (1130029:24885) * (1239:1000) = 56.263047257 + // call pays price is (1130029:24885) * (1239:1250) = 45.010437806 + // call match price is 1130029/24885 = 45.410046213 (< sell_mid2.price) + + // sell_mid2 is matched with call4 + // sell_mid2 is smaller thus fully filled + BOOST_CHECK( !db.find( sell_mid2_id ) ); + // sell_mid2 gets 439, pays 20000 + // call4 gets 20000, pays round_down(439 * 1250/1239) = 442, margin call fee = 3 + // call4 is now (100000-20000):(2500-442) = 80000:2058 = 38.872691934 (< MSSP 44.444444444) + // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) BOOST_REQUIRE( db.find( sell_high_id ) ); BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); // call4 is not undercollateralized BOOST_REQUIRE( db.find( call4_id ) ); - BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); - BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 80000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2058 ); // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); @@ -369,16 +435,18 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); - BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 2682 ); - BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 130029 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 24885 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 1130029 ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price - == price( asset(130029*1239,mpa_id), asset(2682*1000) ) ); + == price( asset(1130029*1239,mpa_id), asset(24885*1000) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 @@ -386,6 +454,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22 BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 439 ); // 439 } }; From 63909453dcc6b44b57032048fad47c5d53dc2bab Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Sep 2021 20:22:23 +0000 Subject: [PATCH 91/99] Add disable_force_settle tests for indvd settle --- tests/tests/bsrm_indvd_settlement_tests.cpp | 259 ++++++++++++++++++++ 1 file changed, 259 insertions(+) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index d8414567f4..5ff96a93ed 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -473,6 +473,265 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) } FC_CAPTURE_AND_RETHROW() } +/// Tests individual settlement to fund : if disable_force_settle flag is set, +/// * able to settle if the fund is not empty, +/// * and settle order is cancelled when the fund becomes empty +BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // two passes, + // i == 0 : with valid feed, + // i == 1 : no feed + for( int i = 0; i < 2; ++ i ) + { + idump( (i) ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), charge_market_fee, + price(asset(1, asset_id_type(1)), asset(1)), + 2, 100 ).id; // fee 1% + issue_uia( borrower, asset(init_amount, samcoin_id) ); + issue_uia( borrower2, asset(init_amount, samcoin_id) ); + + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee | disable_force_settle; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 300; + acop.bitasset_opts->short_backing_asset = samcoin_id; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + acop.bitasset_opts->extensions.value.force_settle_fee_percent = 300; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1,samcoin_id) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000, samcoin_id) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2500, samcoin_id) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 0 ); + + // Unable to settle when the fund is empty and disable_force_settle is set + BOOST_CHECK_THROW( force_settle( seller, asset(1000,mpa_id) ), fc::exception ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650,samcoin_id) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(100000*1239,mpa_id), asset(1983*1000,samcoin_id) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + if( 1 == i ) // let the feed expire + { + generate_blocks( db.head_block_time() + fc::seconds(350) ); + set_expiration( db, trx ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + } + + // seller settles some : allowed when fund is not empty + auto result = force_settle( seller_id(db), asset(10000,mpa_id) ); + auto op_result = result.get().value; + // seller gets round_down(10000*1983/100000) = 198, market fee 1, finally gets 197 + // seller pays round_up(198*100000/1983) = 9985 + BOOST_CHECK( !op_result.new_objects.valid() ); // no delayed force settlement + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 9985, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 197, samcoin_id ) ); + BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 1, samcoin_id ) ); + // fund is now (100000-9985):(1983-198) = 90015:1785 + + // check + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + if( 0 == i ) + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(90015*1239,mpa_id), asset(1785*1000,samcoin_id) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + } + else if( 1 == i ) + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + } + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 190015 ); // 200000 - 9985 + BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 197 ); + + // seller settles more, more than debt in the fund + result = force_settle( seller_id(db), asset(150000,mpa_id) ); + op_result = result.get().value; + + auto check_result = [&] + { + // seller gets 99041 + // seller pays 1964 + BOOST_CHECK( !op_result.new_objects.valid() ); // no delayed force settlement + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 90015, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1768, samcoin_id ) ); + BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 17, samcoin_id ) ); + // fund is now empty + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + if( 0 == i ) + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + } + else if( 1 == i ) + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + } + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); // 200000 - 9985 - 90015 + BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 1965 ); // 197 + 1768 + + // Unable to settle when the fund is empty and disable_force_settle is set + BOOST_CHECK_THROW( force_settle( seller, asset(1000,mpa_id) ), fc::exception ); + + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + /// Tests individual settlement to order : settles when price drops, and how orders are being matched after settled BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) { From 4dbaa3043bf408beab07fe9b7b67a8596ddd4ba5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Sep 2021 22:41:29 +0000 Subject: [PATCH 92/99] Add taking tests for individual settlement to fund --- tests/tests/bsrm_indvd_settlement_tests.cpp | 265 ++++++++++++++++++++ 1 file changed, 265 insertions(+) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index 5ff96a93ed..53b7b352a6 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -732,6 +732,271 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_tes } FC_CAPTURE_AND_RETHROW() } +/// Tests individual settlement to fund : settles when price drops, and how taker orders would match after that +BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(seller)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + fund( borrower4, asset(init_amount) ); + fund( borrower5, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760 + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 + const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); + BOOST_REQUIRE( call4_ptr ); + call_order_id_type call4_id = call4_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller2, asset(100000,mpa_id) ); + transfer( borrower4, seller2, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(100000*1239,mpa_id), asset(1983*1000) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + // call pays price = 100000:1983 * 1239:1250 = 49.984871407 + // call match price = 100000:1983 = 50.428643469 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // borrower5 is unable to borrow if CR <= real ICR + // for median_feed: 1650 * 1.9 = 3135 + // for current_feed: 1983 * 1.9 / 1.239 = 3040.9 + BOOST_CHECK_THROW( borrow( borrower5, asset(100000, mpa_id), asset(3135) ), fc::exception ); + const call_order_object* call5_ptr = borrow( borrower5, asset(100000, mpa_id), asset(3136) ); + BOOST_REQUIRE( call5_ptr ); + call_order_id_type call5_id = call5_ptr->id; + + BOOST_CHECK_EQUAL( call5_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 3136 ); + + // seller sells some + const limit_order_object* limit_ptr = create_sell_order( seller, asset(80000,mpa_id), asset(100) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + // call2 is partially filled + // limit order gets round_down(80000*(1983/100000)) = 1586 + // limit order pays round_up(1586*(100000/1983)) = 79980 + // call2 gets 79980 + // call2 pays round_down(79980*(1983/100000)*(1250/1239)) = 1600, margin call fee = 14 + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); // 100000 - 79980 + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); // 2100 - 1600 + // 20020 / 500 = 40.04 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + // 100000 / 2200 = 45.454545455 + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 120020 ); // 200000 - 79980 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller sells more, this order is below MSSP so will not be matched right now + limit_ptr = create_sell_order( seller, asset(100000,mpa_id), asset(2000) ); + // the limit order is not filled + BOOST_REQUIRE( limit_ptr ); + limit_order_id_type limit_id = limit_ptr->id; + + BOOST_CHECK_EQUAL( limit_ptr->for_sale.value, 100000 ); + + // unable to settle too little amount + BOOST_CHECK_THROW( force_settle( seller2, asset(50,mpa_id) ), fc::exception ); + + // seller2 settles + auto result = force_settle( seller2, asset(150000,mpa_id) ); + auto op_result = result.get().value; + + auto check_result = [&] + { + // seller2 gets 1983 + // seller2 pays 100000 + BOOST_REQUIRE( op_result.new_objects.valid() ); // force settlement order created + force_settlement_id_type settle_id = *op_result.new_objects->begin(); + + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1983 ) ); + // fund is now empty + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + // call3 is the least collateralized short, matched with the limit order, both filled + BOOST_CHECK( !db.find(call3_id) ); + BOOST_CHECK( !db.find(limit_id) ); + // same size, consider call3 as smaller + // call3 match price 100000:2000 + // call3 gets 100000, pays round_up(2000 * 1250/1239) = 2018, margin call fee 18 + + // settle order is matched with call2 + // call2 is smaller + // call2 gets 20020, pays round_up(20020 * (1650/100000) * (1250/1000)) = 413 + // settle order gets round_up(20020 * (1650/100000) * (1239/1000)) = 410, margin call fee = 3 + + // settle order is matched with call4 + // settle order is smaller + BOOST_CHECK( !db.find(settle_id) ); + // settle order gets round_down((50000-20020) * (1650/100000) * (1239/1000)) = 612 + // settle order pays round_up(612 * (100000/1650) * (1000/1239)) = 29937 + // call4 gets 29937 + // call4 pays round_down(29937 * (1650/100000) * (1250/1000)) = 617, margin call fee = 5 + // call4 is now (100000-29937):(2500-617) = 70063:1883 + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 70063 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 1883 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 3586 ); // 1586 + 2000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 50043 ); // 200000 - 100000 - 20020 - 29937 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 3005 ); // 1983 + 410 + 612 + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2013 ); // refund some + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /// Tests individual settlement to order : settles when price drops, and how orders are being matched after settled BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) { From f9446a727b11824368a04fba411af30a72262aa8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Sep 2021 23:15:17 +0000 Subject: [PATCH 93/99] Add tests about force-settling when no feed --- tests/tests/settle_tests.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index c900b35688..b6e0ee735f 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -2230,6 +2230,11 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_feed ) BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + // unable to settle when no feed + BOOST_CHECK_THROW( force_settle( seller, asset(11100,mpa_id) ), fc::exception ); + + generate_block(); + } catch (fc::exception& e) { edump((e.to_detail_string())); throw; From 270a506496c04ea13e41e7ae385d000d2681e131 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Sep 2021 23:45:55 +0000 Subject: [PATCH 94/99] Fix a comment in a test case --- tests/tests/market_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 92e73dbcff..3cbfcb84bf 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1497,7 +1497,7 @@ BOOST_AUTO_TEST_CASE(gs_price_test) // No margin call at this moment - // This order is right at of the first debt position + // This order is right at MSSP of the first debt position limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(2000), core.amount(30000))->id; BOOST_CHECK_EQUAL( 2000, sell_mid(db).for_sale.value ); From 01bcfa5c93fd2fde3109bf7e0ad201a725b45ccf Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 27 Sep 2021 16:06:27 +0000 Subject: [PATCH 95/99] Update a comment --- libraries/chain/include/graphene/chain/asset_object.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 5f6ef1cbbb..55744113a9 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -276,7 +276,7 @@ namespace graphene { namespace chain { /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; - /// @return whether @ref median_feed and @ref current_feed is different + /// @return whether @ref current_feed is different from @ref median_feed bool is_current_feed_price_capped()const { return ( median_feed.settlement_price != current_feed.settlement_price ); } From d3da53f7cff507026b88f623f1b6e90ac93d21f1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 27 Sep 2021 16:30:49 +0000 Subject: [PATCH 96/99] Update comments --- .../protocol/include/graphene/protocol/asset_ops.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index cb14713a68..136728b431 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -108,7 +108,7 @@ namespace graphene { namespace protocol { */ struct bitasset_options { - /// Defines how to response to black swan events + /// Defines how a BitAsset would respond to black swan events enum class black_swan_response_type { /// All debt positions are closed, all or some collateral is moved to a global-settlement fund. @@ -118,15 +118,19 @@ namespace graphene { namespace protocol { /// No debt position is closed, and the derived settlement price is dynamically capped at the collateral /// ratio of the debt position with the least collateral ratio so that all debt positions are able to pay /// off their debt when being margin called or force-settled. + /// It is allowed to create new debt positions and update existing debt positions. /// Also known as "Global Settlement Protection". no_settlement = 1, /// Only the undercollateralized debt positions are closed and their collateral is moved to a fund which /// can be claimed via force-settlement. The derived settlement price is capped at the fund's collateral - /// ratio so that remaining debt positions will not be margin called or force-settled at a worse price. + /// ratio so that remaining debt positions will not be margin called or force-settled at a worse price + /// when the fund is not empty. + /// It is allowed to create new debt positions and update existing debt positions. individual_settlement_to_fund = 2, /// Only the undercollateralized debt positions are closed and their collateral is moved to a limit order /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining /// debt positions could be margin called at a worse price. + /// It is allowed to create new debt positions and update existing debt positions. individual_settlement_to_order = 3, /// Total number of available black swan response methods BSRM_TYPE_COUNT = 4 From c38ad74be67b336c2316f16a998ced69fba37401 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 29 Sep 2021 18:39:15 +0000 Subject: [PATCH 97/99] Slightly optimize check_for_blackswan() and etc --- libraries/chain/db_market.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 2e5c878ddc..045082562a 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -99,6 +99,17 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); + price call_pays_price; + if( limit_itr != limit_end ) + { + call_pays_price = limit_itr->sell_price; + if( after_core_hardfork_2481 ) + { + // due to margin call fee, we check with MCPP (margin call pays price) here + call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); + } + } + using bsrm_type = bitasset_options::black_swan_response_type; const auto bsrm = bitasset.get_black_swan_response_method(); @@ -123,12 +134,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( limit_itr != limit_end ) { FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); - auto call_pays_price = limit_itr->sell_price; - if( after_core_hardfork_2481 ) - { - // due to margin call fee, we check with MCPP (margin call pays price) here - call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); - } if( bsrm_type::individual_settlement_to_fund != bsrm ) highest = std::max( call_pays_price, highest ); // for individual_settlement_to_fund, if call_pays_price < current_feed.max_short_squeeze_price(), @@ -380,11 +385,11 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } else { - create< limit_order_object >( [&order,&fund_receives]( limit_order_object& obj ) { + create< limit_order_object >( [&order_debt,&fund_receives]( limit_order_object& obj ) { obj.expiration = time_point_sec::maximum(); obj.seller = GRAPHENE_NULL_ACCOUNT; obj.for_sale = fund_receives.amount; - obj.sell_price = fund_receives / order.get_debt(); + obj.sell_price = fund_receives / order_debt; obj.is_settled_debt = true; } ); } @@ -392,7 +397,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } // call order is maker - FC_ASSERT( fill_call_order( order, order.get_collateral(), order_debt, + FC_ASSERT( fill_call_order( order, order_collateral, order_debt, fund_receives_price, true, margin_call_fee, false ), "Internal error: unable to close margin call ${o}", ("o", order) ); @@ -783,7 +788,6 @@ bool database::apply_order(const limit_order_object& new_order_object) bool feed_price_updated = false; // whether current_feed.settlement_price has been updated if( to_check_call_orders ) { - auto call_min = price::min( recv_asset_id, sell_asset_id ); // check limit orders first, match the ones with better price in comparison to call orders auto limit_itr_after_call = limit_price_idx.lower_bound( call_match_price ); while( !finished && limit_itr != limit_itr_after_call ) @@ -796,6 +800,7 @@ bool database::apply_order(const limit_order_object& new_order_object) != match_result_type::only_maker_filled ); } + auto call_min = price::min( recv_asset_id, sell_asset_id ); if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls From 67d6624f4b7aa236683b0dc33ba6fd6e9457dd95 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 29 Sep 2021 21:24:39 +0000 Subject: [PATCH 98/99] Update comments --- libraries/chain/db_market.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 045082562a..877e8a6bb1 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1188,16 +1188,17 @@ database::match_result_type database::match( const limit_order_object& bid, cons if( order_receives.amount == 0 ) return match_result_type::only_taker_filled; - // The remaining amount in the limit order could be too small, - // so we should cull the order in fill_limit_order() below. - // If the order would receive 0 even at `match_price`, 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 ); if( after_core_hardfork_2481 ) call_pays = call_receives * call_pays_price; // calculate with updated call_receives else // TODO add tests about CR change call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + + // The remaining amount (if any) in the limit order would be too small, + // 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. cull_taker = true; } else From 5ba086c30bb7e9ded69b9cc5e59e0f25016d3831 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 29 Sep 2021 21:46:31 +0000 Subject: [PATCH 99/99] Add a comment --- libraries/chain/db_market.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 877e8a6bb1..c2b1c8649f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -2073,6 +2073,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa call_collateral_itr = call_collateral_index.lower_bound( call_min ); if( update_current_feed ) { + // Note: we do not call update_bitasset_current_feed() here, + // because it's called in match_impl() in match() in match_force_settlements() limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); update_current_feed = bitasset.is_current_feed_price_capped(); }