diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 926ae1146c..5e6460a089 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -708,10 +708,10 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op 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 + && ( 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."); - FC_ASSERT(d.get_balance(d.get(op.account), *asset_to_settle) >= op.amount); + FC_ASSERT( d.get_balance( op.account, op.amount.asset_id ) >= op.amount, "Insufficient balance" ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -735,8 +735,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: { if( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) FC_THROW( "Settle amount is too small to receive anything due to rounding" ); - else // TODO remove this warning after hard fork core-184 - wlog( "Something for nothing issue (#184, variant F) occurred at block #${block}", ("block",d.head_block_num()) ); + // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur } asset pays = op.amount; @@ -755,7 +754,19 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: 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( d.head_block_time() >= HARDFORK_CORE_1780_TIME ) + { + auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount ); + settled_amount -= issuer_fees; + } + + if( settled_amount.amount > 0 ) + d.adjust_balance( op.account, settled_amount ); } d.modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 8ed9490e44..18ad895fbb 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -916,14 +916,22 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a { try { bool filled = false; - auto issuer_fees = pay_market_fees( nullptr, get(receives.asset_id), receives); + const account_object* settle_owner_ptr = nullptr; + // The owner of the settle order pays market fees to the issuer of the collateral asset after HF core-1780 + // + // TODO Check whether the HF check can be removed after the HF. + // Note: even if logically it can be removed, perhaps the removal will lead to a small performance + // loss. Needs testing. + if( head_block_time() >= HARDFORK_CORE_1780_TIME ) + settle_owner_ptr = &settle.owner(*this); + + auto issuer_fees = pay_market_fees( settle_owner_ptr, get(receives.asset_id), receives ); if( pays < settle.balance ) { modify(settle, [&pays](force_settlement_object& s) { s.balance -= pays; }); - filled = false; } else { filled = true; } diff --git a/libraries/chain/hardfork.d/CORE_1780.hf b/libraries/chain/hardfork.d/CORE_1780.hf new file mode 100644 index 0000000000..8e1358b15b --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1780.hf @@ -0,0 +1,4 @@ +// Market fees of settle orders aren't shared to referral program +#ifndef HARDFORK_CORE_1780_TIME +#define HARDFORK_CORE_1780_TIME (fc::time_point_sec( 1600000000 ) ) // September 13, 2020 3:26:40 PM (GMT) +#endif diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp index d99c4644a2..ad0b533863 100644 --- a/tests/tests/market_fee_sharing_tests.cpp +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -436,8 +436,8 @@ BOOST_AUTO_TEST_CASE(create_actors) price price(asset(1, asset_id_type(1)), asset(1)); uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; - auto obj = jill_id(db); - const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); + const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, + price, 2, market_fee_percent ); const account_object alice = create_account("alice", izzyregistrar, izzyreferrer, 50/*0.5%*/); const account_object bob = create_account("bob", izzyregistrar, izzyreferrer, 50/*0.5%*/); diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 6310493ae5..51e4c24036 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -1501,6 +1501,354 @@ BOOST_AUTO_TEST_CASE( global_settle_rounding_test_after_hf_184 ) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( create_bitassets ) +{ + try { + + generate_blocks( HARDFORK_480_TIME ); // avoid being affected by the price feed bug + generate_block(); + set_expiration( db, trx ); + + ACTORS((paul)(rachelregistrar)(rachelreferrer)); + + upgrade_to_lifetime_member(rachelregistrar); + upgrade_to_lifetime_member(rachelreferrer); + + constexpr auto market_fee_percent = 50 * GRAPHENE_1_PERCENT; + constexpr auto biteur_reward_percent = 90 * GRAPHENE_1_PERCENT; + constexpr auto referrer_reward_percent = 10 * GRAPHENE_1_PERCENT; + + const auto& biteur = create_bitasset( "EURBIT", paul_id, market_fee_percent, charge_market_fee, 2 ); + asset_id_type biteur_id = biteur.id; + + const auto& bitusd = create_bitasset( "USDBIT", paul_id, market_fee_percent, charge_market_fee, 2, biteur_id ); + + const account_object rachel = create_account( "rachel", rachelregistrar, rachelreferrer, + referrer_reward_percent ); + + transfer( committee_account, rachelregistrar_id, asset( 10000000 ) ); + transfer( committee_account, rachelreferrer_id, asset( 10000000 ) ); + transfer( committee_account, rachel.get_id(), asset( 10000000) ); + transfer( committee_account, paul_id, asset( 10000000000 ) ); + + asset_update_operation op; + op.issuer = biteur.issuer; + op.asset_to_update = biteur_id; + op.new_options.issuer_permissions = charge_market_fee; + op.new_options.extensions.value.reward_percent = biteur_reward_percent; + op.new_options.flags = bitusd.options.flags | charge_market_fee; + op.new_options.core_exchange_rate = price( asset(20,biteur_id), asset(1,asset_id_type()) ); + op.new_options.market_fee_percent = market_fee_percent; + trx.operations.push_back(op); + sign(trx, paul_private_key); + PUSH_TX(db, trx); + generate_block(); + trx.clear(); + set_expiration( db, trx ); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( market_fee_of_settle_order_before_hardfork_1780 ) +{ + try { + INVOKE(create_bitassets); + + GET_ACTOR(paul); + GET_ACTOR(rachel); + GET_ACTOR(rachelregistrar); + GET_ACTOR(rachelreferrer); + + const asset_object &biteur = get_asset( "EURBIT" ); + asset_id_type biteur_id = biteur.id; + const asset_object &bitusd = get_asset( "USDBIT" ); + asset_id_type bitusd_id = bitusd.id; + + const auto& core = asset_id_type()(db); + + {// add a feed to asset bitusd + update_feed_producers( bitusd, {paul_id} ); + price_feed feed; + feed.settlement_price = price( bitusd.amount(100), biteur.amount(5) ); + feed.core_exchange_rate = price( bitusd.amount(100), asset(1) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( bitusd_id, paul_id, feed ); + } + + {// add a feed to asset biteur + update_feed_producers( biteur, {paul_id} ); + price_feed feed; + feed.settlement_price = price( biteur.amount(100), core.amount(5) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( biteur_id, paul_id, feed ); + } + + enable_fees(); + + // paul gets some bitusd and biteur + borrow( paul_id, biteur.amount(20000), core.amount(2000) ); + borrow( paul_id, bitusd.amount(10000), biteur.amount(1000) ); + + // and transfer some bitusd to rachel + constexpr auto rachel_bitusd_count = 1000; + transfer( paul_id, rachel_id, asset(rachel_bitusd_count, bitusd_id) ); + + force_settle( rachel, bitusd.amount(rachel_bitusd_count) ); + generate_block(); + generate_blocks( db.head_block_time() + fc::hours(24) ); + set_expiration( db, trx ); + + // Check results + int64_t biteur_balance = 0; + int64_t biteur_accumulated_fee = 0; + int64_t bitusd_accumulated_fee = 0; + { + // 1 biteur = 20 bitusd see publish_feed + const auto biteur_expected_result = rachel_bitusd_count/20; + const auto biteur_market_fee = biteur_expected_result / 2; // market fee percent = 50% + biteur_balance += biteur_expected_result - biteur_market_fee; + + BOOST_CHECK_EQUAL( get_balance(rachel, biteur), biteur_balance ); + BOOST_CHECK_EQUAL( get_balance(rachel, bitusd), 0 ); + + const auto rachelregistrar_reward = get_market_fee_reward( rachelregistrar, biteur ); + const auto rachelreferrer_reward = get_market_fee_reward( rachelreferrer, biteur ); + + BOOST_CHECK_EQUAL( rachelregistrar_reward, 0 ); + BOOST_CHECK_EQUAL( rachelreferrer_reward, 0 ); + + // market fee + biteur_accumulated_fee += biteur_market_fee; + bitusd_accumulated_fee += 0; // usd market fee percent 50%, but call orders don't pay + BOOST_CHECK_EQUAL( biteur.dynamic_data(db).accumulated_fees.value, biteur_accumulated_fee); + BOOST_CHECK_EQUAL( bitusd.dynamic_data(db).accumulated_fees.value, bitusd_accumulated_fee ); + } + + // Update the feed to asset bitusd to trigger a global settlement + { + price_feed feed; + feed.settlement_price = price( bitusd.amount(10), biteur.amount(5) ); + feed.core_exchange_rate = price( bitusd.amount(100), asset(1) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( bitusd_id, paul_id, feed ); + } + + // Transfer more bitusd to rachel + transfer( paul_id, rachel_id, asset(rachel_bitusd_count, bitusd_id) ); + // Instant settlement + force_settle( rachel, bitusd.amount(rachel_bitusd_count) ); + + // Check results + { + // 950 biteur = 9000 bitusd in settlement fund + const auto biteur_expected_result = rachel_bitusd_count * 950 / 9000; + const auto biteur_market_fee = 0; // market fee percent = 50%, but doesn't pay before hf + biteur_balance += biteur_expected_result - biteur_market_fee; + + BOOST_CHECK_EQUAL( get_balance(rachel, biteur), biteur_balance ); + BOOST_CHECK_EQUAL( get_balance(rachel, bitusd), 0 ); + + const auto rachelregistrar_reward = get_market_fee_reward( rachelregistrar, biteur ); + const auto rachelreferrer_reward = get_market_fee_reward( rachelreferrer, biteur ); + + BOOST_CHECK_EQUAL( rachelregistrar_reward, 0 ); + BOOST_CHECK_EQUAL( rachelreferrer_reward, 0 ); + + // No market fee for instant settlement before hf + biteur_accumulated_fee += 0; + bitusd_accumulated_fee += 0; + BOOST_CHECK_EQUAL( biteur.dynamic_data(db).accumulated_fees.value, biteur_accumulated_fee); + BOOST_CHECK_EQUAL( bitusd.dynamic_data(db).accumulated_fees.value, bitusd_accumulated_fee ); + } + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( market_fee_of_settle_order_after_hardfork_1780 ) +{ + try { + INVOKE(create_bitassets); + + generate_blocks( HARDFORK_CORE_1780_TIME ); + set_expiration( db, trx ); + + GET_ACTOR(paul); + GET_ACTOR(rachel); + GET_ACTOR(rachelregistrar); + GET_ACTOR(rachelreferrer); + + const asset_object &biteur = get_asset( "EURBIT" ); + asset_id_type biteur_id = biteur.id; + const asset_object &bitusd = get_asset( "USDBIT" ); + asset_id_type bitusd_id = bitusd.id; + + const auto& core = asset_id_type()(db); + + {// add a feed to asset bitusd + update_feed_producers( bitusd, {paul_id} ); + price_feed feed; + feed.settlement_price = price( bitusd.amount(100), biteur.amount(5) ); + feed.core_exchange_rate = price( bitusd.amount(100), asset(1) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( bitusd_id, paul_id, feed ); + } + + {// add a feed to asset biteur + update_feed_producers( biteur, {paul_id} ); + price_feed feed; + feed.settlement_price = price( biteur.amount(100), core.amount(5) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( biteur_id, paul_id, feed ); + } + + enable_fees(); + + // paul gets some bitusd and biteur + borrow( paul_id, biteur.amount(20000), core.amount(2000) ); + borrow( paul_id, bitusd.amount(10000), biteur.amount(1000) ); + + // and transfer some bitusd to rachel + constexpr auto rachel_bitusd_count = 1000; + transfer( paul_id, rachel_id, asset(rachel_bitusd_count, bitusd_id) ); + + force_settle( rachel, bitusd.amount(rachel_bitusd_count) ); + generate_block(); + generate_blocks( db.head_block_time() + fc::hours(24) ); + set_expiration( db, trx ); + + // Check results + int64_t biteur_balance = 0; + int64_t biteur_accumulated_fee = 0; + int64_t bitusd_accumulated_fee = 0; + { + // 1 biteur = 20 bitusd see publish_feed + const auto biteur_expected_result = rachel_bitusd_count/20; + const auto biteur_market_fee = biteur_expected_result / 2; // market fee percent = 50% + biteur_balance += biteur_expected_result - biteur_market_fee; + + BOOST_CHECK_EQUAL( get_balance(rachel, biteur), biteur_balance ); + BOOST_CHECK_EQUAL( get_balance(rachel, bitusd), 0 ); + + const auto rachelregistrar_reward = get_market_fee_reward( rachelregistrar, biteur ); + const auto rachelreferrer_reward = get_market_fee_reward( rachelreferrer, biteur ); + + const auto biteur_reward = biteur_market_fee * 9 / 10; // 90% + const auto referrer_reward = biteur_reward / 10; // 10% + const auto registrar_reward = biteur_reward - referrer_reward; + + BOOST_CHECK_EQUAL( rachelregistrar_reward, registrar_reward ); + BOOST_CHECK_EQUAL( rachelreferrer_reward, referrer_reward ); + + // market fee + biteur_accumulated_fee += biteur_market_fee - biteur_reward; + bitusd_accumulated_fee += 0; // usd market fee percent 50%, but call orders don't pay + BOOST_CHECK_EQUAL( biteur.dynamic_data(db).accumulated_fees.value, biteur_accumulated_fee); + BOOST_CHECK_EQUAL( bitusd.dynamic_data(db).accumulated_fees.value, bitusd_accumulated_fee ); + + } + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( market_fee_of_instant_settle_order_after_hardfork_1780 ) +{ + try { + INVOKE(create_bitassets); + + generate_blocks( HARDFORK_CORE_1780_TIME ); + set_expiration( db, trx ); + + GET_ACTOR(paul); + GET_ACTOR(rachel); + GET_ACTOR(rachelregistrar); + GET_ACTOR(rachelreferrer); + + const asset_object &biteur = get_asset( "EURBIT" ); + asset_id_type biteur_id = biteur.id; + const asset_object &bitusd = get_asset( "USDBIT" ); + asset_id_type bitusd_id = bitusd.id; + + const auto& core = asset_id_type()(db); + + {// add a feed to asset bitusd + update_feed_producers( bitusd, {paul_id} ); + price_feed feed; + feed.settlement_price = price( bitusd.amount(100), biteur.amount(5) ); + feed.core_exchange_rate = price( bitusd.amount(100), asset(1) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( bitusd_id, paul_id, feed ); + } + + {// add a feed to asset biteur + update_feed_producers( biteur, {paul_id} ); + price_feed feed; + feed.settlement_price = price( biteur.amount(100), core.amount(5) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( biteur_id, paul_id, feed ); + } + + enable_fees(); + + // paul gets some bitusd and biteur + borrow( paul_id, biteur.amount(20000), core.amount(2000) ); + borrow( paul_id, bitusd.amount(10000), biteur.amount(1000) ); + + // Update the feed to asset bitusd to trigger a global settlement + { + price_feed feed; + feed.settlement_price = price( bitusd.amount(10), biteur.amount(5) ); + feed.core_exchange_rate = price( bitusd.amount(100), asset(1) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 110 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( bitusd_id, paul_id, feed ); + } + + // Transfer some bitusd to rachel + constexpr auto rachel_bitusd_count = 1000; + transfer( paul_id, rachel_id, asset(rachel_bitusd_count, bitusd_id) ); + // Instant settlement + force_settle( rachel, bitusd.amount(rachel_bitusd_count) ); // instant settlement + + // Check results + int64_t biteur_balance = 0; + int64_t biteur_accumulated_fee = 0; + int64_t bitusd_accumulated_fee = 0; + { + // 1000 biteur = 10000 bitusd in settlement fund + const auto biteur_expected_result = rachel_bitusd_count/10; + const auto biteur_market_fee = biteur_expected_result / 2; // market fee percent = 50% + biteur_balance += biteur_expected_result - biteur_market_fee; + + BOOST_CHECK_EQUAL( get_balance(rachel, biteur), biteur_balance ); + BOOST_CHECK_EQUAL( get_balance(rachel, bitusd), 0 ); + + const auto rachelregistrar_reward = get_market_fee_reward( rachelregistrar, biteur ); + const auto rachelreferrer_reward = get_market_fee_reward( rachelreferrer, biteur ); + + const auto biteur_reward = biteur_market_fee * 9 / 10; // 90% + const auto referrer_reward = biteur_reward / 10; // 10% + const auto registrar_reward = biteur_reward - referrer_reward; + + BOOST_CHECK_EQUAL( rachelregistrar_reward, registrar_reward ); + BOOST_CHECK_EQUAL( rachelreferrer_reward, referrer_reward ); + + // market fee + biteur_accumulated_fee += biteur_market_fee - biteur_reward; + bitusd_accumulated_fee += 0; // usd market fee percent 50%, but call orders don't pay + BOOST_CHECK_EQUAL( biteur.dynamic_data(db).accumulated_fees.value, biteur_accumulated_fee); + BOOST_CHECK_EQUAL( bitusd.dynamic_data(db).accumulated_fees.value, bitusd_accumulated_fee ); + + } + + } FC_LOG_AND_RETHROW() +} + /** * Test case to reproduce https://github.com/bitshares/bitshares-core/issues/1883. * When there is only one fill_order object in the ticker rolling buffer, it should only be rolled out once.