From 0a789cf3f5915280d379415110a7aaf5a84dd3d5 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Tue, 5 May 2020 22:39:42 -0400 Subject: [PATCH 01/19] Improve error message in asset_claim_fees_evaluator --- libraries/chain/asset_evaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index b345e730db..011ef1b2fb 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -970,7 +970,7 @@ void_result asset_claim_fees_evaluator::do_evaluate( const asset_claim_fees_oper FC_ASSERT( o.amount_to_claim.amount <= ((container_asset->get_id() == o.amount_to_claim.asset_id) ? container_ddo->accumulated_fees : container_ddo->accumulated_collateral_fees), - "Attempt to claim more fees than have accumulated within asset ${a} (${id})", + "Attempt to claim more collateral fees than have accumulated within asset ${a} (${id})", ("a",container_asset->symbol)("id",container_asset->id)("ddo",*container_ddo) ); return void_result(); From a8d348d00f8138386b05cf0d1d495b7654b1406f Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Tue, 28 Apr 2020 20:08:20 -0400 Subject: [PATCH 02/19] BSIP74 and BSIP87: Test of collateral-denominated fees container --- tests/tests/bitasset_tests.cpp | 136 ++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 0c58af042d..f0da699a2b 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -75,7 +75,7 @@ void change_backing_asset(database_fixture& fixture, const fc::ecc::private_key& } catch (fc::exception& ex) { - BOOST_FAIL( "Exception thrown in chainge_backing_asset. Exception was: " + + BOOST_FAIL( "Exception thrown in change_backing_asset. Exception was: " + ex.to_string(fc::log_level(fc::log_level::all)) ); } } @@ -1403,4 +1403,138 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) } FC_LOG_AND_RETHROW() } + + /** + * Test the claiming of collateral asset fees before HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME. + * + * Test prohibitions against changing of the backing/collateral asset for a smart asset + * if any collateral asset fees are available to be claimed. + */ + BOOST_AUTO_TEST_CASE(change_backing_asset_prohibitions) { + try { + /** + * Initialize + */ + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((smartissuer)(feedproducer)); // Actors for smart asset + ACTORS((jill)(izzy)); // Actors for user-issued assets + ACTORS((alice)); // Actors who hold balances + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, market_fee_percent); + generate_block(); trx.clear(); set_expiration(db, trx); + const asset_object jillcoin = get_asset("JCOIN"); + + create_user_issued_asset("ICOIN", izzy, charge_market_fee, price, 2, market_fee_percent); + generate_block(); + const asset_object izzycoin = get_asset("ICOIN"); + + // Create the smart asset backed by JCOIN + const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + create_bitasset("SMARTBIT", smartissuer.id, smartbit_market_fee_percent, + charge_market_fee, 2, jillcoin.id); + + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); trx.clear(); set_expiration(db, trx); + const asset_object &smartbit = get_asset("SMARTBIT"); + const asset_bitasset_data_object& smartbit_bitasset_data = (*smartbit.bitasset_data_id)(db); + // Confirm that the asset is to be backed by JCOIN + BOOST_CHECK(smartbit_bitasset_data.options.short_backing_asset == jillcoin.id); + + // Fund balances of the actors + issue_uia(alice, jillcoin.amount(5000 * std::pow(10, jillcoin.precision))); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 5000 * std::pow(10, jillcoin.precision)); + BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit), 0); + + + /** + * Claim any amount of collateral asset fees. This should fail because claiming such fees are prohibited + * before the HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME. + */ + trx.clear(); + asset_claim_fees_operation claim_op; + claim_op.issuer = smartissuer.id; + claim_op.extensions.value.claim_from_asset_id = smartbit.id; + claim_op.amount_to_claim = jillcoin.amount(5 * std::pow(10, jillcoin.precision)); + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, smartissuer_private_key); + GRAPHENE_REQUIRE_THROW(PUSH_TX(db, trx), fc::exception); + + + /** + * Advance to when the collateral fee container is activated + */ + generate_blocks(HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME); + generate_block(); trx.clear(); set_expiration(db, trx); + + + /** + * Cause some collateral of JCOIN to be accumulated as collateral fee within the SMARTBIT asset type + */ + // HACK: Before BSIP74 or BSIP87 are introduced, it is not formally possible to accumulate collateral fees. + // Therefore, the accumulation for this test will be informally induced by direct manipulation of the database. + // More formal tests will be provided with the PR for either BSIP74 or BSIP87. + // IMPORTANT: The use of this hack requires that no additional blocks are subsequently generated! + asset accumulation_amount = jillcoin.amount(40 * std::pow(10, jillcoin.precision)); // JCOIN + db.adjust_balance(alice.id, -accumulation_amount); // Deduct 40 JCOIN from alice as a "collateral fee" + smartbit.accumulate_fee(db, accumulation_amount); // Add 40 JCOIN from alice as a "collateral fee" + + + /** + * Attempt to change the backing asset. This should fail because there are unclaimed collateral fees. + */ + trx.clear(); + asset_update_bitasset_operation change_backing_asset_op; + change_backing_asset_op.asset_to_update = smartbit.id; + change_backing_asset_op.issuer = smartissuer.id; + change_backing_asset_op.new_options.short_backing_asset = izzycoin.id; + trx.operations.push_back(change_backing_asset_op); + sign(trx, smartissuer_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Must claim collateral-denominated fees"); + + + /** + * Attempt to claim a negative amount of the collateral asset fees. + * This should fail because positive amounts are required. + */ + trx.clear(); + claim_op.amount_to_claim = jillcoin.amount(-9 * std::pow(10, jillcoin.precision)); + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, smartissuer_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "amount_to_claim.amount > 0"); + + + /** + * Claim all of the available collateral asset fees + */ + trx.clear(); + claim_op.amount_to_claim = accumulation_amount; + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, smartissuer_private_key); + PUSH_TX(db, trx); + + + /** + * Attempt to change the backing asset. + * This should succeed because there are no collateral asset fees are waiting to be claimed. + */ + trx.clear(); + trx.operations.push_back(change_backing_asset_op); + sign(trx, smartissuer_private_key); + PUSH_TX(db, trx); + + // Confirm the change to the backing asset + BOOST_CHECK(smartbit_bitasset_data.options.short_backing_asset == izzycoin.id); + + } FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() From a4b4a7795b5a9f50f6d12d9f975844376331ed98 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Fri, 24 Apr 2020 11:32:41 -0400 Subject: [PATCH 03/19] BSIP87: Test of force settlement fee with 3 actors --- tests/tests/settle_tests2.cpp | 315 ++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 tests/tests/settle_tests2.cpp diff --git a/tests/tests/settle_tests2.cpp b/tests/tests/settle_tests2.cpp new file mode 100644 index 0000000000..acd8bb0ff2 --- /dev/null +++ b/tests/tests/settle_tests2.cpp @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2020 Michel Santos, 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 + +#include + +#include +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +struct force_settle_database_fixture : database_fixture { + force_settle_database_fixture() + : database_fixture() { + } + + /** + * Create a smart asset + * @param name Asset name + * @param issuer Issuer ID + * @param force_settlement_offset_percent Force-settlement offset percent + * @param force_settlement_fee_percent Force-settlement fee percent (BSIP87) + * @return Asset object + */ + const asset_object &create_smart_asset( + const string &name, + account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, + uint16_t force_settlement_offset_percent /* = 100 */ /* 1% */, + uint16_t force_settlement_fee_percent /* = 100 */ /* 1% */ + ) { + try { + uint16_t market_fee_percent = 100; /*1%*/ + uint16_t flags = charge_market_fee; + uint16_t precision = 2; + asset_id_type backing_asset = {}; + share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + + asset_create_operation creator; + creator.issuer = issuer; + creator.fee = asset(); + creator.symbol = name; + creator.precision = precision; + + creator.common_options.max_supply = max_supply; + creator.common_options.market_fee_percent = market_fee_percent; + if (issuer == GRAPHENE_WITNESS_ACCOUNT) + flags |= witness_fed_asset; + creator.common_options.issuer_permissions = flags; + creator.common_options.flags = flags & ~global_settle; + creator.common_options.core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)); + + creator.common_options.extensions.value.force_settle_fee_percent = force_settlement_fee_percent; + + creator.bitasset_opts = bitasset_options(); + creator.bitasset_opts->force_settlement_offset_percent = force_settlement_offset_percent; + creator.bitasset_opts->short_backing_asset = backing_asset; + + trx.operations.push_back(std::move(creator)); + trx.validate(); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + return db.get(ptx.operation_results[0].get()); + } FC_CAPTURE_AND_RETHROW((name)(issuer)) + } +}; + + +/** + * Test the effects of the new force settlement fee from BSIP87 + */ +BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) + + /** + * Test when one holder of a smart asset force settles (FS) their holding when there are two debtors + * + * There are three primary actors: michael, paul, rachel + * + * 1. Asset owner creates the smart coin called bitUSD + * 2. The feed price is 20 satoshi bitUSD for 1 satoshi Core -> 0.2 bitUSD for 0.00001 Core = 20000 bitUSD for 1 Core + * 3. Michael borrows 0.06 bitUSD (6 satoshis of bitUSD) from the blockchain with a high amount of collateral + * 4. Paul borrows 1000 bitUSD (100000 satoshis of bitUSD) from the blockchain with a low amount of collateral + * 5. Paul gives Rachel 200 bitUSD + * 6. Rachel force-settles 20 bitUSD which should be collected from Paul's debt position + * because of its relatively lower collateral ratio + * + * The force-settlement by Rachel should account for both the force-settlement offset fee, + * and the new force settlement fee from BSIP87. + * + * Michael's debt and balances should be unaffected by the activities of Paul and Rachel + */ + BOOST_AUTO_TEST_CASE(force_settle_fee_1_test) { + try { + /////// + // Initialize the scenario + /////// + // Get around Graphene issue #615 feed expiration bug + generate_blocks(HARDFORK_615_TIME); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // Advance to the when the force-settlement fee activates + generate_blocks(HARDFORK_CORE_BSIP87_TIME); + generate_block(); + set_expiration(db, trx); + trx.clear(); + + // Create actors + ACTORS((assetowner)(feedproducer)(paul)(michael)(rachel)); + + // Fund actors + uint64_t initial_balance_core = 10000000; + transfer(committee_account, assetowner.id, asset(initial_balance_core)); + transfer(committee_account, feedproducer.id, asset(initial_balance_core)); + transfer(committee_account, michael_id, asset(initial_balance_core)); + transfer(committee_account, paul.id, asset(initial_balance_core)); + + // 1. Create assets + const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee % + const uint16_t usd_fsf_percent = 3 * GRAPHENE_1_PERCENT; // 3% Force-settlement fee % (BSIP87) + create_smart_asset("USDBIT", assetowner.id, usd_fso_percent, usd_fsf_percent); + + generate_block(); + set_expiration(db, trx); + trx.clear(); + + const auto &bitusd = get_asset("USDBIT"); + const auto &core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // 2. Publish a feed for the smart asset + update_feed_producers(bitusd_id(db), {feedproducer_id}); + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + // Requirement of 20 satoshi bitUSD for 1 satoshi Core -> 0.2 bitUSD for 0.00001 Core = 20000 bitUSD for 1 Core + current_feed.settlement_price = bitusd.amount(20) / core.amount(1); + publish_feed(bitusd, feedproducer, current_feed); + + + /////// + // 3. Michael borrows 0.06 bitUSD + /////// + int64_t michael_initial_usd = 6; // 0.06 USD + int64_t michael_initial_core = 8; + const call_order_object &call_michael = *borrow(michael, bitusd.amount(michael_initial_usd), + core.amount(michael_initial_core)); + call_order_id_type call_michael_id = call_michael.id; + + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd); + BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core - michael_initial_core); + + + /////// + // 4. Paul borrows 1000 bitUSD + /////// + // Paul will borrow bitUSD by providing 2x collateral required: 2 * 1/20 = 1/10 + int64_t paul_initial_usd = 1000 * std::pow(10, bitusd.precision); // 100000 + int64_t paul_initial_core = paul_initial_usd * 2 / 20; // 10000 + const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd), + core.amount(paul_initial_core)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL(get_balance(paul, bitusd), paul_initial_usd); + + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 5. Paul transfers 200 bitUSD to Rachel + /////// + int64_t rachel_initial_usd = 200 * std::pow(10, bitusd.precision); + transfer(paul.id, rachel.id, asset(rachel_initial_usd, bitusd.id)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd); + + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 6. Rachel force settles 20 bitUSD + /////// + const int64_t rachel_settle_amount = 20 * std::pow(10, bitusd.precision); + operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); + + force_settlement_id_type rachel_settle_id = result.get(); + BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount); + + // Check Rachel's balance + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd - rachel_settle_amount); + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + // Check Paul's debt to the blockchain + BOOST_CHECK_EQUAL(paul_initial_usd, call_paul.debt.value); + BOOST_CHECK_EQUAL(paul_initial_core, call_paul.collateral.value); + + // Check Michael's balance + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd); + BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core - michael_initial_core); + + // Check Michael's debt to the blockchain + BOOST_CHECK_EQUAL(michael_initial_usd, call_michael.debt.value); + BOOST_CHECK_EQUAL(michael_initial_core, call_michael.collateral.value); + + + /////// + // Advance time and update the price feed + /////// + generate_blocks(db.head_block_time() + fc::hours(20)); + set_expiration(db, trx); + trx.clear(); + + // The default feed and settlement expires at the same time + // Publish another feed to have a valid price to exit + publish_feed(bitusd_id(db), feedproducer_id(db), current_feed); + + + /////// + // Advance time to trigger the conclusion of the force settlement + /////// + generate_blocks(db.head_block_time() + fc::hours(6)); + set_expiration(db, trx); + trx.clear(); + + + ////// + // Check + ////// + // Rachel's settlement should have completed and should no longer be present + BOOST_CHECK(!db.find(rachel_settle_id)); + + // Check Rachel's balance + // Rachel redeemed some smart asset and should get the equivalent collateral amount (according to the feed price) + // minus the force_settlement_offset_fee - force_settlement_fee + // Rachel redeemed 20 USD (2000 satoshi bitUSD) and should get + // 100 satoshi Core - 5 satoshi Core - 2 satoshi Core; 3% * (100 - 5) = 2.85 trunacted to 2 satoshi Core + uint64_t rachel_settle_core = rachel_settle_amount * 1 / 20; // Settle amount * feed price + uint64_t rachel_fso_fee_core = rachel_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT; // 5 satoshi Core + uint64_t rachel_fso_remainder_core = rachel_settle_core - rachel_fso_fee_core; // 95 satoshi Core + uint64_t rachel_fsf_fee_core = + (rachel_fso_remainder_core) * usd_fsf_percent / GRAPHENE_100_PERCENT; // 2 satoshi Core + uint64_t expected_rachel_core = + rachel_settle_core - rachel_fso_fee_core - rachel_fsf_fee_core; // 93 satoshi Core + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd - rachel_settle_amount); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), expected_rachel_core); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), paul_initial_usd - rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), initial_balance_core - paul_initial_core); + + // Check Paul's debt to the blockchain + // Rachel redeemed 20 usd from the blockchain, and the blockchain closed this amount from Paul's debt to it + BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount, call_paul_id(db).debt.value); + // The call order has the original amount of collateral less what was redeemed by Rachel + BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core, call_paul_id(db).collateral.value); + + // Check Michael's balance + // Rachel's redemption should not have affected Michael's balance + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd); + BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core - michael_initial_core); + + // Check Michael's debt to the blockchain + // Rachel's redemption should not have affected Michael's debt to the blockchain + BOOST_CHECK_EQUAL(michael_initial_usd, call_michael_id(db).debt.value); + BOOST_CHECK_EQUAL(michael_initial_core, call_michael_id(db).collateral.value); + + // The supply of USD equals the amount borrowed/created by Paul and Michael + // minus the amount redeemed/destroyed by Rachel + BOOST_CHECK_EQUAL(bitusd_id(db).dynamic_data(db).current_supply.value, + paul_initial_usd + michael_initial_usd - rachel_settle_amount); + + // Check the asset owner's vesting fees + // The market fee reward should be zero because the market fee reward % is 0 + const auto assetowner_fs_fees_usd = get_market_fee_reward(assetowner, bitusd); + BOOST_CHECK_EQUAL(assetowner_fs_fees_usd, 0); + + // Check the asset owner's accumulated asset fees + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == rachel_fsf_fee_core); + + } + FC_LOG_AND_RETHROW() + } + +BOOST_AUTO_TEST_SUITE_END() From 27cab11859682f8fed50cce7491cc8decbde973e Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Thu, 30 Apr 2020 12:56:42 -0400 Subject: [PATCH 04/19] BSIP87: Test of force settlement fee with 5 actors --- tests/tests/settle_tests2.cpp | 581 +++++++++++++++++++++++++++++++++- 1 file changed, 578 insertions(+), 3 deletions(-) diff --git a/tests/tests/settle_tests2.cpp b/tests/tests/settle_tests2.cpp index acd8bb0ff2..b3e0108eb7 100644 --- a/tests/tests/settle_tests2.cpp +++ b/tests/tests/settle_tests2.cpp @@ -39,6 +39,25 @@ struct force_settle_database_fixture : database_fixture { : database_fixture() { } + /** + * Create a smart asset without a force settlement fee percent + * @param name Asset name + * @param issuer Issuer ID + * @param force_settlement_offset_percent Force-settlement offset percent + * @return Asset object + */ + const asset_object &create_smart_asset( + const string &name, + account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, + uint16_t force_settlement_offset_percent /* = 100 */ /* 1% */ + ) { + try { + optional force_settlement_fee_percent; // Not specified + return create_smart_asset(name, issuer, force_settlement_offset_percent, force_settlement_fee_percent); + } FC_CAPTURE_AND_RETHROW((name)(issuer)(force_settlement_offset_percent)) + } + + /** * Create a smart asset * @param name Asset name @@ -50,8 +69,8 @@ struct force_settle_database_fixture : database_fixture { const asset_object &create_smart_asset( const string &name, account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, - uint16_t force_settlement_offset_percent /* = 100 */ /* 1% */, - uint16_t force_settlement_fee_percent /* = 100 */ /* 1% */ + uint16_t force_settlement_offset_percent /* 100 = 1% */, + optional force_settlement_fee_percent /* 100 = 1% */ ) { try { uint16_t market_fee_percent = 100; /*1%*/ @@ -74,7 +93,9 @@ struct force_settle_database_fixture : database_fixture { creator.common_options.flags = flags & ~global_settle; creator.common_options.core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)); - creator.common_options.extensions.value.force_settle_fee_percent = force_settlement_fee_percent; + if (force_settlement_fee_percent.valid()) { + creator.common_options.extensions.value.force_settle_fee_percent = force_settlement_fee_percent; + } creator.bitasset_opts = bitasset_options(); creator.bitasset_opts->force_settlement_offset_percent = force_settlement_offset_percent; @@ -312,4 +333,558 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) FC_LOG_AND_RETHROW() } + + /** + * This test evaluates: + * + * - collecting collateral-denominated fees before and after BSIP87, + * - applying different force-settlement fee percentages, + * - accumulating fees from multiple force-settlements, + * - changing the backing asset of a smart asset is prohibited when there are unclaimed collateral-denominated fees. + * + * There are five actors: asset owner, paul, rachel, michael, yanna, vikram + * + * Before HARDFORK_CORE_BSIP87_TIME + * + * 1. Asset owner creates the smart coin called bitUSD + * + * NOTE: To avoid rounding issues in the test, 1 satoshi of the smart asset will be worth more than 1 satoshi + * of the backing asset. This allows force settlements of the smart asset to yield more satoshis of the backing asset + * with controllable truncation and rounding that will not affect the tests. + * 2. The feed price is 1 satoshi bitUSD for 20 satoshi Core = 0.01 bitUSD for 0.00020 Core = 50 bitUSD for 1 Core + * + * 3. Paul borrows 100 bitUSD (10000 satoshis of bitUSD) from the blockchain + * 4. Paul gives Rachel 20 bitUSD and retains 80 bitUSD + * 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position + * 6. Asset owner attempts and fails to claim the collateral fees + * + * + * 7. Activate HARDFORK_CORE_BSIP87_TIME + * + * + * After HARDFORK_CORE_BSIP87_TIME + * + * 8. Paul gives Michael 30 bitUSD and retains 50 bitUSD + * 9. Michael force-settles 5 bitUSD which should be collected from Paul's debt position + * + * 10. Asset owner sets the force-fee percentage to 3% + * 11. Paul gives Yanna 40 bitUSD and retains 10 bitUSD + * 12. Yanna force-settles 10 bitUSD which should be collected from Paul's debt position + * + * 13. Asset owner updates the force-settlement fee to 4% + * 14. Paul gives Vikram 10 bitUSD and retains 0 bitUSD + * 15. Vikram force-settles 10 bitUSD which should be collected from Paul's debt position + * + * 16. Asset owner attempts and fails to change the backing of the smart asset because of its outstanding supply + * 17. All current holders of bitUSD close their bitUSD positions + * 18. Asset owner attempts and fails to change the backing of the smart asset because of unclaimed collateral fees + * 19. Asset owner claims all of the unclaimed collateral fees + * 20. Asset owner attempts and succeeds in changing the backing of the smart asset + */ + BOOST_AUTO_TEST_CASE(force_settle_fee_2_test) { + try { + /////// + // Initialize the scenario + /////// + // Get around Graphene issue #615 feed expiration bug + generate_blocks(HARDFORK_615_TIME); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + trx.clear(); + set_expiration(db, trx); + + // Create actors + ACTORS((assetowner)(feedproducer)(paul)(rachel)(michael)(yanna)(vikram)); + + // Fund actors + uint64_t initial_balance_core = 10000000; + transfer(committee_account, assetowner.id, asset(initial_balance_core)); + transfer(committee_account, feedproducer.id, asset(initial_balance_core)); + transfer(committee_account, michael_id, asset(initial_balance_core)); + transfer(committee_account, paul.id, asset(initial_balance_core)); + + /////// + // 1. Create assets + /////// + const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee % + const uint16_t usd_fsf_percent_0 = 0 * GRAPHENE_1_PERCENT; // 0% Force-settlement offset fee % + + // Attempt and fail to create the smart asset with a force-settlement fee % before HARDFORK_CORE_BSIP87_TIME + trx.clear(); + REQUIRE_EXCEPTION_WITH_TEXT(create_smart_asset("USDBIT", assetowner.id, usd_fso_percent, usd_fsf_percent_0), + "cannot be set before Hardfork BSIP87"); + + + // Create the smart asset without a force-settlement fee % + trx.clear(); + create_smart_asset("USDBIT", assetowner.id, usd_fso_percent); + + generate_block(); + set_expiration(db, trx); + trx.clear(); + + const auto &bitusd = get_asset("USDBIT"); + const auto &core = asset_id_type()(db); + + + /////// + // 2. Publish a feed for the smart asset + /////// + update_feed_producers(bitusd.id, {feedproducer_id}); + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + // Requirement of 20x collateral in satoshis: 1 satoshi bitUSD for 20 satoshi Core + // -> 0.01 bitUSD for 0.00020 Core = 100 bitUSD for 2 Core = 50 bitUSD for 1 Core + current_feed.settlement_price = bitusd.amount(1) / core.amount(20); + publish_feed(bitusd, feedproducer, current_feed); + + + /////// + // 3. Paul borrows 100 bitUSD + /////// + // Paul will borrow bitUSD by providing 2x collateral required: 2 * 20 = 40 + int64_t paul_initial_usd = 100 * std::pow(10, bitusd.precision); // 10000 + int64_t paul_initial_core = paul_initial_usd * 2 * 20; // 400000 + const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd), + core.amount(paul_initial_core)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL(get_balance(paul, bitusd), paul_initial_usd); + + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 4. Paul gives Rachel 20 bitUSD and retains 80 bitUSD + /////// + int64_t rachel_initial_usd = 20 * std::pow(10, bitusd.precision); + transfer(paul.id, rachel.id, asset(rachel_initial_usd, bitusd.id)); + + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position + /////// + const int64_t rachel_settle_amount = 2 * std::pow(10, bitusd.precision); + operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); + + force_settlement_id_type rachel_settle_id = result.get(); + 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 + generate_blocks(db.head_block_time() + fc::hours(26)); + set_expiration(db, trx); + trx.clear(); + publish_feed(bitusd, feedproducer_id(db), current_feed); + trx.clear(); + + // Rachel's settlement should have completed and should no longer be present + BOOST_CHECK(!db.find(rachel_settle_id)); + + // Check Rachel's balance + // Rachel redeemed some smart asset and should get the equivalent collateral amount (according to the feed price) + // minus the force_settlement_offset_fee - force_settlement_fee + // Rachel redeemed 2 bitUSD and should get 4000 satoshi Core - 200 satoshi Core - 0 satoshi Core + uint64_t rachel_settle_core = rachel_settle_amount * 20; // Settle amount * feed price + uint64_t rachel_fso_fee_core = rachel_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT; + uint64_t rachel_fso_remainder_core = rachel_settle_core - rachel_fso_fee_core; + uint64_t rachel_fsf_fee_core = (rachel_fso_remainder_core) * 0 / GRAPHENE_100_PERCENT; + uint64_t expected_rachel_core = rachel_settle_core - rachel_fso_fee_core - rachel_fsf_fee_core; + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd - rachel_settle_amount); + BOOST_CHECK_EQUAL(get_balance(rachel, core), expected_rachel_core); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + // Check Paul's debt to the blockchain + // Rachel redeemed 2 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it + BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount, call_paul_id(db).debt.value); + // The call order has the original amount of collateral less what was redeemed by Rachel + BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core, call_paul_id(db).collateral.value); + + + /////// + // 6. Asset owner attempts to claim the collateral fees. + // Although no collateral-denominated fees should be present, the error should indicate the + // that claiming such fees are not yet active. + /////// + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); // There should be no fees + trx.clear(); + asset_claim_fees_operation claim_op; + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = core.amount(5 * std::pow(10, core.precision)); + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); + + + /////// + // 7. Activate HARDFORK_CORE_BSIP87_TIME + /////// + generate_blocks(HARDFORK_CORE_BSIP87_TIME); + generate_block(); + set_expiration(db, trx); + trx.clear(); + + // Update the price feed + publish_feed(bitusd, feedproducer_id(db), current_feed); + trx.clear(); + + + /////// + // 8. Paul gives Michael 30 bitUSD and retains 50 bitUSD + /////// + int64_t michael_initial_usd = 30 * std::pow(10, bitusd.precision); + transfer(paul.id, michael.id, asset(michael_initial_usd, bitusd.id)); + + // Check Michael's balance + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd); + BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd - michael_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 9. Michael force-settles 5 bitUSD which should be collected from Paul's debt position + /////// + const int64_t michael_settle_amount = 5 * std::pow(10, bitusd.precision); + result = force_settle(michael, bitusd.amount(michael_settle_amount)); + + force_settlement_id_type michael_settle_id = result.get(); + 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 + generate_blocks(db.head_block_time() + fc::hours(26)); + set_expiration(db, trx); + trx.clear(); + publish_feed(bitusd, feedproducer_id(db), current_feed); + + // Michael's settlement should have completed and should no longer be present + BOOST_CHECK(!db.find(michael_settle_id)); + + // Check Michael's balance + // Michael redeemed some smart asset and should get the equivalent collateral amount (according to the feed price) + // minus the force_settlement_offset_fee - force_settlement_fee + // Michael redeemed 5 bitUSD and should get 10000 satoshi Core - 500 satoshi Core - 0 satoshi Core + uint64_t michael_settle_core = michael_settle_amount * 20; // Settle amount * feed price + uint64_t michael_fso_fee_core = michael_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT; + uint64_t michael_fso_remainder_core = michael_settle_core - michael_fso_fee_core; + uint64_t michael_fsf_fee_core = (michael_fso_remainder_core) * usd_fsf_percent_0 / GRAPHENE_100_PERCENT; + uint64_t expected_michael_core = michael_settle_core - michael_fso_fee_core - michael_fsf_fee_core; + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), michael_initial_usd - michael_settle_amount); + BOOST_CHECK_EQUAL(get_balance(michael, core), initial_balance_core + expected_michael_core); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd - michael_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + // Check Paul's debt to the blockchain + // Michael redeemed 5 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it + BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount - michael_settle_amount, + call_paul_id(db).debt.value); + // The call order has the original amount of collateral less what was redeemed by Rachel, Michael + BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core - michael_fso_remainder_core, + call_paul_id(db).collateral.value); + + // The asset's force settlement fee % should still not be set + BOOST_CHECK(!bitusd.options.extensions.value.force_settle_fee_percent.valid()); + // There should be no accumulated asset fees + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); + + + /////// + // 10. Asset owner sets the force-fee percentage to 3% + /////// + const uint16_t usd_fsf_percent_3 = 3 * GRAPHENE_1_PERCENT; // 3% Force-settlement fee % (BSIP87) + asset_update_operation uop; + uop.issuer = assetowner.id; + uop.asset_to_update = bitusd.get_id(); + uop.new_options = bitusd.options; + uop.new_options.extensions.value.force_settle_fee_percent = usd_fsf_percent_3; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, assetowner_private_key); + PUSH_TX(db, trx); + + // The force settlement fee % should be set + BOOST_CHECK(bitusd.options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK_EQUAL(usd_fsf_percent_3, *bitusd.options.extensions.value.force_settle_fee_percent); + + + /////// + // 11. Paul gives Yanna 40 bitUSD and retains 10 bitUSD + /////// + int64_t yanna_initial_usd = 40 * std::pow(10, bitusd.precision); + transfer(paul.id, yanna.id, asset(yanna_initial_usd, bitusd.id)); + + // Check Yanna's balance + BOOST_CHECK_EQUAL(get_balance(yanna, bitusd), yanna_initial_usd); + BOOST_CHECK_EQUAL(get_balance(yanna, core), 0); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), + paul_initial_usd - rachel_initial_usd - michael_initial_usd - yanna_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 12. Yanna force-settles 10 bitUSD which should be collected from Paul's debt position + /////// + const int64_t yanna_settle_amount = 10 * std::pow(10, bitusd.precision); + result = force_settle(yanna, bitusd.amount(yanna_settle_amount)); + + force_settlement_id_type yanna_settle_id = result.get(); + 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 + generate_blocks(db.head_block_time() + fc::hours(26)); + set_expiration(db, trx); + trx.clear(); + publish_feed(bitusd, feedproducer_id(db), current_feed); + + // Yanna's settlement should have completed and should no longer be present + BOOST_CHECK(!db.find(yanna_settle_id)); + + // Check Yanna's balance + // Yanna redeemed some smart asset and should get the equivalent collateral amount (according to the feed price) + // minus the force_settlement_offset_fee - force_settlement_fee + // Yanna redeemed 10 bitUSD and should get 20000 satoshi Core - 1000 satoshi Core - 570 satoshi Core; (20000 - 1000) * 3% = 570 + uint64_t yanna_settle_core = yanna_settle_amount * 20; // Settle amount * feed price + uint64_t yanna_fso_fee_core = yanna_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT; + uint64_t yanna_fso_remainder_core = yanna_settle_core - yanna_fso_fee_core; + uint64_t yanna_fsf_fee_core = (yanna_fso_remainder_core) * usd_fsf_percent_3 / GRAPHENE_100_PERCENT; + uint64_t expected_yanna_core = yanna_settle_core - yanna_fso_fee_core - yanna_fsf_fee_core; + BOOST_CHECK_EQUAL(get_balance(yanna, bitusd), yanna_initial_usd - yanna_settle_amount); + BOOST_CHECK_EQUAL(get_balance(yanna, core), 0 + expected_yanna_core); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), + paul_initial_usd - rachel_initial_usd - michael_initial_usd - yanna_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + // Check Paul's debt to the blockchain + // Yanna redeemed 10 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it + BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount - michael_settle_amount - yanna_settle_amount, + call_paul_id(db).debt.value); + // The call order has the original amount of collateral less what was redeemed by Rachel, Michael, Yanna + BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core - michael_fso_remainder_core - yanna_fso_remainder_core, + call_paul_id(db).collateral.value); + + // The asset's force settlement fee % should still not be set + BOOST_CHECK(bitusd.options.extensions.value.force_settle_fee_percent.valid()); + // There should be some accumulated collateral-deonominated fees + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == yanna_fsf_fee_core); + + + /////// + // 13. Asset owner updates the force-settlement fee to 4% + /////// + const uint16_t usd_fsf_percent_4 = 4 * GRAPHENE_1_PERCENT; // 4% Force-settlement fee % (BSIP87) + uop = asset_update_operation(); + uop.issuer = assetowner.id; + uop.asset_to_update = bitusd.get_id(); + uop.new_options = bitusd.options; + uop.new_options.extensions.value.force_settle_fee_percent = usd_fsf_percent_4; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, assetowner_private_key); + PUSH_TX(db, trx); + + // The force settlement fee % should be set + BOOST_CHECK(bitusd.options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK_EQUAL(usd_fsf_percent_4, *bitusd.options.extensions.value.force_settle_fee_percent); + + + /////// + // 14. Paul gives Vikram 10 bitUSD and retains 0 bitUSD + /////// + int64_t vikram_initial_usd = 10 * std::pow(10, bitusd.precision); + transfer(paul.id, vikram.id, asset(vikram_initial_usd, bitusd.id)); + + // Check Yanna's balance + BOOST_CHECK_EQUAL(get_balance(vikram, bitusd), vikram_initial_usd); + BOOST_CHECK_EQUAL(get_balance(vikram, core), 0); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), + paul_initial_usd - rachel_initial_usd - michael_initial_usd - yanna_initial_usd - + vikram_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 15. Vikram force-settles 10 bitUSD which should be collected from Paul's debt position + /////// + const int64_t vikram_settle_amount = 10 * std::pow(10, bitusd.precision); + result = force_settle(vikram, bitusd.amount(vikram_settle_amount)); + + force_settlement_id_type vikram_settle_id = result.get(); + 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 + generate_blocks(db.head_block_time() + fc::hours(26)); + set_expiration(db, trx); + trx.clear(); + publish_feed(bitusd, feedproducer_id(db), current_feed); + + // Vikrams's settlement should have completed and should no longer be present + BOOST_CHECK(!db.find(vikram_settle_id)); + + // Check Vikrams's balance + // Vikram redeemed some smart asset and should get the equivalent collateral amount (according to the feed price) + // minus the force_settlement_offset_fee - force_settlement_fee + // Vikram redeemed 10 bitUSD and should get 20000 satoshi Core - 1000 satoshi Core - 760 satoshi Core; (20000 - 1000) * 4% = 760 + uint64_t vikram_settle_core = vikram_settle_amount * 20; // Settle amount * feed price + uint64_t vikram_fso_fee_core = vikram_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT; + uint64_t vikram_fso_remainder_core = vikram_settle_core - vikram_fso_fee_core; + uint64_t vikram_fsf_fee_core = (vikram_fso_remainder_core) * usd_fsf_percent_4 / GRAPHENE_100_PERCENT; + uint64_t expected_vikram_core = vikram_settle_core - vikram_fso_fee_core - vikram_fsf_fee_core; + BOOST_CHECK_EQUAL(get_balance(vikram, bitusd), vikram_initial_usd - vikram_settle_amount); + BOOST_CHECK_EQUAL(get_balance(vikram, core), 0 + expected_vikram_core); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), + paul_initial_usd - rachel_initial_usd - michael_initial_usd - yanna_initial_usd - + vikram_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + // Check Paul's debt to the blockchain + // Vikram redeemed 10 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it + BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount - michael_settle_amount - yanna_settle_amount - + vikram_settle_amount, + call_paul_id(db).debt.value); + // The call order has the original amount of collateral less what was redeemed by Rachel, Michael, Yanna, Vikram + BOOST_CHECK_EQUAL( + paul_initial_core - rachel_fso_remainder_core - michael_fso_remainder_core - yanna_fso_remainder_core - + vikram_fso_remainder_core, + call_paul_id(db).collateral.value); + + // The asset's force settlement fee % should still not be set + BOOST_CHECK(bitusd.options.extensions.value.force_settle_fee_percent.valid()); + // There should be some accumulated collateral-deonominated fees + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); + const uint64_t expected_accumulation_fsf_core_amount = yanna_fsf_fee_core + vikram_fsf_fee_core; + BOOST_CHECK( + bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == expected_accumulation_fsf_core_amount); + + + /////// + // 16. Asset owner attempts and fails to change the backing of the smart asset + // because of its outstanding supply + /////// + // Create a new user-issued asset + trx.clear(); + ACTOR(jill); + trx.clear(); + price core_exchange_rate(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + create_user_issued_asset("JCOIN", jill, charge_market_fee, core_exchange_rate, 2, market_fee_percent); + generate_block(); + trx.clear(); + set_expiration(db, trx); + const asset_object& jillcoin = get_asset("JCOIN"); + + + // Attempt to change the backing of the smart asset to the new user-issued asset + trx.clear(); + asset_update_bitasset_operation change_backing_asset_op; + change_backing_asset_op.asset_to_update = bitusd.id; + change_backing_asset_op.issuer = assetowner.id; + change_backing_asset_op.new_options.short_backing_asset = jillcoin.id; + trx.operations.push_back(change_backing_asset_op); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "there is already a current supply"); + + + /////// + // 17. All current holdings of bitUSD are removed + /////// + // Rachel, Michael, and Yanna return their remaining bitUSD to Paul + trx.clear(); + transfer(rachel.id, paul.id, bitusd.amount(get_balance(rachel, bitusd))); + transfer(michael.id, paul.id, bitusd.amount(get_balance(michael, bitusd))); + transfer(yanna.id, paul.id, bitusd.amount(get_balance(yanna, bitusd))); + + // Vikram has no bitUSD to transfer + BOOST_CHECK_EQUAL(get_balance(vikram, bitusd), 0); + + // Paul closes his debt to the blockchain + cover(paul, bitusd.amount(call_paul_id(db).debt), core.amount(call_paul_id(db).collateral.value)); + + // Check the bitUSD holdings of the actors + BOOST_CHECK_EQUAL(get_balance(assetowner, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(yanna, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(vikram, bitusd), 0); + + + /////// + // 18. Asset owner attempts and fails to change the backing of the smart asset + // because of unclaimed collateral fees + /////// + // Repeat check of the accumulated fees + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); + BOOST_CHECK( + bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == expected_accumulation_fsf_core_amount); + + trx.clear(); + trx.operations.push_back(change_backing_asset_op); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Must claim collateral-denominated fees"); + + + /////// + // 19. Asset owner claims all of the unclaimed collateral fees + /////// + trx.clear(); + claim_op = asset_claim_fees_operation(); + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = core.amount(expected_accumulation_fsf_core_amount); + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + PUSH_TX(db, trx); + + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); + + + /////// + // 20. Asset owner attempts and succeeds in changing the backing of the smart asset + /////// + // Confirm that the asset is backed by CORE + const asset_bitasset_data_object& bitusd_bitasset_data = (*bitusd.bitasset_data_id)(db); + BOOST_CHECK(bitusd_bitasset_data.options.short_backing_asset == core.id); + + trx.clear(); + trx.operations.push_back(change_backing_asset_op); + sign(trx, assetowner_private_key); + PUSH_TX(db, trx); + + // Confirm the change to the backing asset + BOOST_CHECK(bitusd_bitasset_data.options.short_backing_asset == jillcoin.id); + + } + FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() From 9376ce0bd167a56d59f0066034ba6a71665e966d Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Tue, 5 May 2020 15:32:45 -0400 Subject: [PATCH 05/19] BSIP87: Test of invalid claims of force settlement fees --- tests/tests/settle_tests2.cpp | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/tests/settle_tests2.cpp b/tests/tests/settle_tests2.cpp index b3e0108eb7..387238ac4e 100644 --- a/tests/tests/settle_tests2.cpp +++ b/tests/tests/settle_tests2.cpp @@ -887,4 +887,79 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) FC_LOG_AND_RETHROW() } + + /** + * Attempt to claim invalid fees + */ + BOOST_AUTO_TEST_CASE(force_settle_fee_invalid_claims_test) { + try { + INVOKE(force_settle_fee_1_test); + + GET_ACTOR(assetowner); + + // Check the asset owner's accumulated asset fees + const auto &core = asset_id_type()(db); + const asset_object& bitusd = get_asset("USDBIT"); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees > 0); + share_type rachel_fsf_fee_core = bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees; + + // Attempt to claim negative fees + trx.clear(); + asset_claim_fees_operation claim_op; + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = core.amount(-5 * std::pow(10, core.precision)); + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "amount_to_claim.amount > 0"); + + // Attempt to claim excessive claim fee + trx.clear(); + claim_op = asset_claim_fees_operation(); + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = rachel_fsf_fee_core + 1; + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Attempt to claim more collateral fees"); + + // Attempt to claim with an invalid asset asset type + trx.clear(); + ACTOR(jill); + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, market_fee_percent); + generate_block(); trx.clear(); set_expiration(db, trx); + const asset_object& jillcoin = get_asset("JCOIN"); + + trx.clear(); + claim_op = asset_claim_fees_operation(); + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = jillcoin.amount(rachel_fsf_fee_core.value); + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "is not backed by asset"); + + // Attempt to claim all that can be claimed + trx.clear(); + claim_op = asset_claim_fees_operation(); + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = rachel_fsf_fee_core; + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + PUSH_TX(db, trx); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); + + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_SUITE_END() From 487e86ab8535b5468aa4976915e06bead87f4fd7 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Tue, 5 May 2020 23:55:59 -0400 Subject: [PATCH 06/19] BSIP87: Test of 100% force-settlement fee --- tests/tests/settle_tests2.cpp | 139 ++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/tests/tests/settle_tests2.cpp b/tests/tests/settle_tests2.cpp index 387238ac4e..0be98507ad 100644 --- a/tests/tests/settle_tests2.cpp +++ b/tests/tests/settle_tests2.cpp @@ -962,4 +962,143 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) } + /** + * Test 100% force settlement fee. + * + * There are two primary actors: paul, rachel + * + * 1. Asset owner creates the smart coin called bitUSD + * 2. The feed price is 1 satoshi bitUSD for 20 satoshi Core = 0.01 bitUSD for 0.00020 Core = 50 bitUSD for 1 Core + * 3. Paul borrows 100 bitUSD (10000 satoshis of bitUSD) from the blockchain with a low amount of collateral + * 4. Paul gives Rachel 20 bitUSD + * 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position + * because of its relatively lower collateral ratio + * + * The force-settlement by Rachel should account for both the force-settlement offset fee, + * and the new force settlement fee from BSIP87. + */ + BOOST_AUTO_TEST_CASE(force_settle_fee_extreme_1_test) { + try { + /////// + // Initialize the scenario + /////// + // Advance to the when the force-settlement fee activates + generate_blocks(HARDFORK_CORE_BSIP87_TIME); + generate_block(); + set_expiration(db, trx); + trx.clear(); + + // Create actors + ACTORS((assetowner)(feedproducer)(paul)(rachel)); + + // Fund actors + uint64_t initial_balance_core = 10000000; + transfer(committee_account, assetowner.id, asset(initial_balance_core)); + transfer(committee_account, feedproducer.id, asset(initial_balance_core)); + transfer(committee_account, paul.id, asset(initial_balance_core)); + + // 1. Create assets + const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee % + const uint16_t usd_fsf_percent = 100 * GRAPHENE_1_PERCENT; // 100% Force-settlement fee % (BSIP87) + create_smart_asset("USDBIT", assetowner.id, usd_fso_percent, usd_fsf_percent); + + generate_block(); + set_expiration(db, trx); + trx.clear(); + + const auto &bitusd = get_asset("USDBIT"); + const auto &core = asset_id_type()(db); + + + /////// + // 2. Publish a feed for the smart asset + /////// + update_feed_producers(bitusd.id, {feedproducer_id}); + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + // Requirement of 20x collateral in satoshis: 1 satoshi bitUSD for 20 satoshi Core + // -> 0.01 bitUSD for 0.00020 Core = 100 bitUSD for 2 Core = 50 bitUSD for 1 Core + current_feed.settlement_price = bitusd.amount(1) / core.amount(20); + publish_feed(bitusd, feedproducer, current_feed); + + + /////// + // 3. Paul borrows 100 bitUSD + /////// + // Paul will borrow bitUSD by providing 2x collateral required: 2 * 20 = 40 + int64_t paul_initial_usd = 100 * std::pow(10, bitusd.precision); // 10000 + int64_t paul_initial_core = paul_initial_usd * 2 * 20; // 400000 + const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd), + core.amount(paul_initial_core)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL(get_balance(paul, bitusd), paul_initial_usd); + + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 4. Paul gives Rachel 20 bitUSD and retains 80 bitUSD + /////// + int64_t rachel_initial_usd = 20 * std::pow(10, bitusd.precision); + transfer(paul.id, rachel.id, asset(rachel_initial_usd, bitusd.id)); + + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + + /////// + // 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position + /////// + const int64_t rachel_settle_amount = 2 * std::pow(10, bitusd.precision); // 200 satoshi bitusd + operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); + + force_settlement_id_type rachel_settle_id = result.get(); + 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 + generate_blocks(db.head_block_time() + fc::hours(26)); + set_expiration(db, trx); + trx.clear(); + publish_feed(bitusd, feedproducer_id(db), current_feed); + trx.clear(); + + // Rachel's settlement should have completed and should no longer be present + BOOST_CHECK(!db.find(rachel_settle_id)); + + // Check Rachel's balance + // Rachel redeemed some smart asset and should get the equivalent collateral amount (according to the feed price) + // minus the force_settlement_offset_fee - force_settlement_fee + // uint64_t rachel_settle_core = 4000; // rachel_settle_amount * 20 + // uint64_t rachel_fso_fee_core = 200; // rachel_settle_core * usd_fso_percent / GRAPHENE_100_PERCENT + uint64_t rachel_fso_remainder_core = 3800; // rachel_settle_core - rachel_fso_fee_core + uint64_t rachel_fsf_fee_core = 3800; // (rachel_fso_remainder_core) * usd_fsf_percent / GRAPHENE_100_PERCENT + // Rachel redeemed 2 bitUSD and should get 4000 satoshi Core - 200 satoshi Core - 3800 satoshi Core + uint64_t expected_rachel_core = 0; // rachel_settle_core - rachel_fso_fee_core - rachel_fsf_fee_core + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd - rachel_settle_amount); + BOOST_CHECK_EQUAL(get_balance(rachel, core), expected_rachel_core); + + // Check Paul's balance + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), paul_initial_usd - rachel_initial_usd); + BOOST_CHECK_EQUAL(get_balance(paul, core), initial_balance_core - paul_initial_core); + + // Check Paul's debt to the blockchain + // Rachel redeemed 2 bitUSD from the blockchain, and the blockchain closed this amount from Paul's debt to it + BOOST_CHECK_EQUAL(paul_initial_usd - rachel_settle_amount, call_paul_id(db).debt.value); + // The call order has the original amount of collateral less what was redeemed by Rachel + BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core, call_paul_id(db).collateral.value); + + // Check the asset owner's accumulated asset fees + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == rachel_fsf_fee_core); + + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_SUITE_END() From 45bf97846e59af69a46aecfd04237e037e96bfe7 Mon Sep 17 00:00:00 2001 From: christophersanborn <23085117+christophersanborn@users.noreply.github.com> Date: Fri, 8 May 2020 01:11:43 -0400 Subject: [PATCH 07/19] Revert "Improve error message in asset_claim_fees_evaluator" This reverts commit 0a789cf3f5915280d379415110a7aaf5a84dd3d5. --- libraries/chain/asset_evaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 1b37738abf..0781321eac 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -998,7 +998,7 @@ void_result asset_claim_fees_evaluator::do_evaluate( const asset_claim_fees_oper FC_ASSERT( o.amount_to_claim.amount <= ((container_asset->get_id() == o.amount_to_claim.asset_id) ? container_ddo->accumulated_fees : container_ddo->accumulated_collateral_fees), - "Attempt to claim more collateral fees than have accumulated within asset ${a} (${id})", + "Attempt to claim more fees than have accumulated within asset ${a} (${id})", ("a",container_asset->symbol)("id",container_asset->id)("ddo",*container_ddo) ); return void_result(); From 89d97c1be26192756ce208d58f0871700dbbc08d Mon Sep 17 00:00:00 2001 From: christophersanborn <23085117+christophersanborn@users.noreply.github.com> Date: Fri, 8 May 2020 12:40:10 -0400 Subject: [PATCH 08/19] Adapt tests to relocated force_settle_fee_percent. One test still fails due to phrasing of insufficent accumulated fees exception. --- tests/tests/settle_tests2.cpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/tests/tests/settle_tests2.cpp b/tests/tests/settle_tests2.cpp index 0be98507ad..c769422997 100644 --- a/tests/tests/settle_tests2.cpp +++ b/tests/tests/settle_tests2.cpp @@ -93,13 +93,12 @@ struct force_settle_database_fixture : database_fixture { creator.common_options.flags = flags & ~global_settle; creator.common_options.core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)); - if (force_settlement_fee_percent.valid()) { - creator.common_options.extensions.value.force_settle_fee_percent = force_settlement_fee_percent; - } - creator.bitasset_opts = bitasset_options(); creator.bitasset_opts->force_settlement_offset_percent = force_settlement_offset_percent; creator.bitasset_opts->short_backing_asset = backing_asset; + if (force_settlement_fee_percent.valid()) { + creator.bitasset_opts->extensions.value.force_settle_fee_percent = force_settlement_fee_percent; + } trx.operations.push_back(std::move(creator)); trx.validate(); @@ -597,7 +596,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) call_paul_id(db).collateral.value); // The asset's force settlement fee % should still not be set - BOOST_CHECK(!bitusd.options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); // There should be no accumulated asset fees BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); @@ -607,10 +606,10 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) // 10. Asset owner sets the force-fee percentage to 3% /////// const uint16_t usd_fsf_percent_3 = 3 * GRAPHENE_1_PERCENT; // 3% Force-settlement fee % (BSIP87) - asset_update_operation uop; + asset_update_bitasset_operation uop; uop.issuer = assetowner.id; uop.asset_to_update = bitusd.get_id(); - uop.new_options = bitusd.options; + uop.new_options = bitusd.bitasset_data(db).options; uop.new_options.extensions.value.force_settle_fee_percent = usd_fsf_percent_3; trx.clear(); @@ -620,9 +619,10 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) PUSH_TX(db, trx); // The force settlement fee % should be set - BOOST_CHECK(bitusd.options.extensions.value.force_settle_fee_percent.valid()); - BOOST_CHECK_EQUAL(usd_fsf_percent_3, *bitusd.options.extensions.value.force_settle_fee_percent); - + BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK_EQUAL( usd_fsf_percent_3, + *bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent + ); /////// // 11. Paul gives Yanna 40 bitUSD and retains 10 bitUSD @@ -684,7 +684,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) call_paul_id(db).collateral.value); // The asset's force settlement fee % should still not be set - BOOST_CHECK(bitusd.options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); // There should be some accumulated collateral-deonominated fees BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == yanna_fsf_fee_core); @@ -694,10 +694,10 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) // 13. Asset owner updates the force-settlement fee to 4% /////// const uint16_t usd_fsf_percent_4 = 4 * GRAPHENE_1_PERCENT; // 4% Force-settlement fee % (BSIP87) - uop = asset_update_operation(); + uop = asset_update_bitasset_operation(); uop.issuer = assetowner.id; uop.asset_to_update = bitusd.get_id(); - uop.new_options = bitusd.options; + uop.new_options = bitusd.bitasset_data(db).options; uop.new_options.extensions.value.force_settle_fee_percent = usd_fsf_percent_4; trx.clear(); @@ -707,9 +707,10 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) PUSH_TX(db, trx); // The force settlement fee % should be set - BOOST_CHECK(bitusd.options.extensions.value.force_settle_fee_percent.valid()); - BOOST_CHECK_EQUAL(usd_fsf_percent_4, *bitusd.options.extensions.value.force_settle_fee_percent); - + BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK_EQUAL( usd_fsf_percent_4, + *bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent + ); /////// // 14. Paul gives Vikram 10 bitUSD and retains 0 bitUSD @@ -776,7 +777,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) call_paul_id(db).collateral.value); // The asset's force settlement fee % should still not be set - BOOST_CHECK(bitusd.options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); // There should be some accumulated collateral-deonominated fees BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); const uint64_t expected_accumulation_fsf_core_amount = yanna_fsf_fee_core + vikram_fsf_fee_core; From ce1b13e9aaaf48920f7259d50c7efa9e3ec17c40 Mon Sep 17 00:00:00 2001 From: christophersanborn <23085117+christophersanborn@users.noreply.github.com> Date: Fri, 8 May 2020 13:55:55 -0400 Subject: [PATCH 09/19] Clarify exception text for insufficient accumulated fees in fee claim. --- libraries/chain/asset_evaluator.cpp | 17 ++++++++++++----- tests/tests/settle_tests2.cpp | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 0781321eac..b350a6dcb4 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -995,11 +995,18 @@ void_result asset_claim_fees_evaluator::do_evaluate( const asset_claim_fees_oper container_ddo = &container_asset->dynamic_asset_data_id(d); - FC_ASSERT( o.amount_to_claim.amount <= ((container_asset->get_id() == o.amount_to_claim.asset_id) ? - container_ddo->accumulated_fees : - container_ddo->accumulated_collateral_fees), - "Attempt to claim more fees than have accumulated within asset ${a} (${id})", - ("a",container_asset->symbol)("id",container_asset->id)("ddo",*container_ddo) ); + if (container_asset->get_id() == o.amount_to_claim.asset_id) { + FC_ASSERT( o.amount_to_claim.amount <= container_ddo->accumulated_fees, + "Attempt to claim more fees than have accumulated within asset ${a} (${id}). " + "Asset DDO: ${ddo}. Fee claim: ${claim}.", ("a",container_asset->symbol) + ("id",container_asset->id)("ddo",*container_ddo)("claim",o.amount_to_claim) ); + } else { + FC_ASSERT( o.amount_to_claim.amount <= container_ddo->accumulated_collateral_fees, + "Attempt to claim more backing-asset fees than have accumulated within asset ${a} (${id}) " + "backed by (${fid}). Asset DDO: ${ddo}. Fee claim: ${claim}.", ("a",container_asset->symbol) + ("id",container_asset->id)("fid",o.amount_to_claim.asset_id)("ddo",*container_ddo) + ("claim",o.amount_to_claim) ); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } diff --git a/tests/tests/settle_tests2.cpp b/tests/tests/settle_tests2.cpp index c769422997..4987a69688 100644 --- a/tests/tests/settle_tests2.cpp +++ b/tests/tests/settle_tests2.cpp @@ -925,7 +925,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) trx.operations.push_back(claim_op); set_expiration(db, trx); sign(trx, assetowner_private_key); - REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Attempt to claim more collateral fees"); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Attempt to claim more backing-asset fees"); // Attempt to claim with an invalid asset asset type trx.clear(); From e9cb1f27958f9273c5e4bb2bab0e076a564ef4f9 Mon Sep 17 00:00:00 2001 From: christophersanborn <23085117+christophersanborn@users.noreply.github.com> Date: Fri, 8 May 2020 14:04:04 -0400 Subject: [PATCH 10/19] Rename settle_tests2.cpp -> force_settle_fee_tests.cpp --- tests/tests/{settle_tests2.cpp => force_settle_fee_tests.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/tests/{settle_tests2.cpp => force_settle_fee_tests.cpp} (100%) diff --git a/tests/tests/settle_tests2.cpp b/tests/tests/force_settle_fee_tests.cpp similarity index 100% rename from tests/tests/settle_tests2.cpp rename to tests/tests/force_settle_fee_tests.cpp From 5cd4fc1dee73d28ac2ffa41688bfde69943f9d36 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 11 May 2020 11:18:52 -0400 Subject: [PATCH 11/19] BSIP74 & BSIP87: Avoid dereferencing objects after block generation --- tests/tests/bitasset_tests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index de0a03876c..d22882246e 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -1434,13 +1434,13 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) generate_block(); trx.clear(); set_expiration(db, trx); const asset_object jillcoin = get_asset("JCOIN"); - create_user_issued_asset("ICOIN", izzy, charge_market_fee, price, 2, market_fee_percent); + create_user_issued_asset("ICOIN", izzy_id(db), charge_market_fee, price, 2, market_fee_percent); generate_block(); const asset_object izzycoin = get_asset("ICOIN"); // Create the smart asset backed by JCOIN const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT; - create_bitasset("SMARTBIT", smartissuer.id, smartbit_market_fee_percent, + create_bitasset("SMARTBIT", smartissuer_id, smartbit_market_fee_percent, charge_market_fee, 2, jillcoin.id); // Obtain asset object after a block is generated to obtain the final object that is commited to the database @@ -1462,7 +1462,7 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) */ trx.clear(); asset_claim_fees_operation claim_op; - claim_op.issuer = smartissuer.id; + claim_op.issuer = smartissuer_id; claim_op.extensions.value.claim_from_asset_id = smartbit.id; claim_op.amount_to_claim = jillcoin.amount(5 * std::pow(10, jillcoin.precision)); trx.operations.push_back(claim_op); @@ -1486,7 +1486,7 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) // More formal tests will be provided with the PR for either BSIP74 or BSIP87. // IMPORTANT: The use of this hack requires that no additional blocks are subsequently generated! asset accumulation_amount = jillcoin.amount(40 * std::pow(10, jillcoin.precision)); // JCOIN - db.adjust_balance(alice.id, -accumulation_amount); // Deduct 40 JCOIN from alice as a "collateral fee" + db.adjust_balance(alice_id, -accumulation_amount); // Deduct 40 JCOIN from alice as a "collateral fee" smartbit.accumulate_fee(db, accumulation_amount); // Add 40 JCOIN from alice as a "collateral fee" @@ -1496,7 +1496,7 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) trx.clear(); asset_update_bitasset_operation change_backing_asset_op; change_backing_asset_op.asset_to_update = smartbit.id; - change_backing_asset_op.issuer = smartissuer.id; + change_backing_asset_op.issuer = smartissuer_id; change_backing_asset_op.new_options.short_backing_asset = izzycoin.id; trx.operations.push_back(change_backing_asset_op); sign(trx, smartissuer_private_key); From 5363c3340ab57665a6c4af0a5e0730b804979f3e Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 11 May 2020 11:52:22 -0400 Subject: [PATCH 12/19] BSIP74 & BSIP87: Avoid non-deterministic floating point functions --- tests/tests/bitasset_tests.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index d22882246e..c9626fce4c 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -1433,6 +1433,7 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, market_fee_percent); generate_block(); trx.clear(); set_expiration(db, trx); const asset_object jillcoin = get_asset("JCOIN"); + const int64_t jillcoin_unit = 100; // 100 satoshi JILLCOIN in 1 JILLCOIN create_user_issued_asset("ICOIN", izzy_id(db), charge_market_fee, price, 2, market_fee_percent); generate_block(); @@ -1451,8 +1452,8 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) BOOST_CHECK(smartbit_bitasset_data.options.short_backing_asset == jillcoin.id); // Fund balances of the actors - issue_uia(alice, jillcoin.amount(5000 * std::pow(10, jillcoin.precision))); - BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 5000 * std::pow(10, jillcoin.precision)); + issue_uia(alice, jillcoin.amount(5000 * jillcoin_unit)); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 5000 * jillcoin_unit); BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit), 0); @@ -1464,7 +1465,7 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) asset_claim_fees_operation claim_op; claim_op.issuer = smartissuer_id; claim_op.extensions.value.claim_from_asset_id = smartbit.id; - claim_op.amount_to_claim = jillcoin.amount(5 * std::pow(10, jillcoin.precision)); + claim_op.amount_to_claim = jillcoin.amount(5 * jillcoin_unit); trx.operations.push_back(claim_op); set_expiration(db, trx); sign(trx, smartissuer_private_key); @@ -1485,7 +1486,7 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) // Therefore, the accumulation for this test will be informally induced by direct manipulation of the database. // More formal tests will be provided with the PR for either BSIP74 or BSIP87. // IMPORTANT: The use of this hack requires that no additional blocks are subsequently generated! - asset accumulation_amount = jillcoin.amount(40 * std::pow(10, jillcoin.precision)); // JCOIN + asset accumulation_amount = jillcoin.amount(40 * jillcoin_unit); // JCOIN db.adjust_balance(alice_id, -accumulation_amount); // Deduct 40 JCOIN from alice as a "collateral fee" smartbit.accumulate_fee(db, accumulation_amount); // Add 40 JCOIN from alice as a "collateral fee" @@ -1508,7 +1509,7 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) * This should fail because positive amounts are required. */ trx.clear(); - claim_op.amount_to_claim = jillcoin.amount(-9 * std::pow(10, jillcoin.precision)); + claim_op.amount_to_claim = jillcoin.amount(-9 * jillcoin_unit); trx.operations.push_back(claim_op); set_expiration(db, trx); sign(trx, smartissuer_private_key); From f9e5c75d37f6d2fd7c573a3c76b197cc83702fa9 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 11 May 2020 11:57:19 -0400 Subject: [PATCH 13/19] BSIP74 & BSIP87: Test of invalid claims --- tests/tests/bitasset_tests.cpp | 66 ++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index c9626fce4c..8174628e31 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -1467,9 +1467,25 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) claim_op.extensions.value.claim_from_asset_id = smartbit.id; claim_op.amount_to_claim = jillcoin.amount(5 * jillcoin_unit); trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, smartissuer_private_key); - GRAPHENE_REQUIRE_THROW(PUSH_TX(db, trx), fc::exception); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); + + + /** + * Propose to claim any amount of collateral asset fees. + * This should fail because claiming such fees are prohibited before HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME. + */ + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(claim_op); + + trx.clear(); + trx.operations.push_back(cop); + // sign(trx, smartissuer_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); /** @@ -1489,6 +1505,8 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) asset accumulation_amount = jillcoin.amount(40 * jillcoin_unit); // JCOIN db.adjust_balance(alice_id, -accumulation_amount); // Deduct 40 JCOIN from alice as a "collateral fee" smartbit.accumulate_fee(db, accumulation_amount); // Add 40 JCOIN from alice as a "collateral fee" + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), (5000 * jillcoin_unit) - (40 * jillcoin_unit)); + BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees == accumulation_amount.amount); /** @@ -1511,20 +1529,56 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) trx.clear(); claim_op.amount_to_claim = jillcoin.amount(-9 * jillcoin_unit); trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, smartissuer_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "amount_to_claim.amount > 0"); /** - * Claim all of the available collateral asset fees + * Attempt to claim 0 amount of the collateral asset fees. + * This should fail because positive amounts are required. */ trx.clear(); - claim_op.amount_to_claim = accumulation_amount; + claim_op.amount_to_claim = jillcoin.amount(0 * jillcoin_unit); + trx.operations.push_back(claim_op); + sign(trx, smartissuer_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "amount_to_claim.amount > 0"); + + + /** + * Attempt to claim excessive amount of collateral asset fees. + * This should fail because there are insufficient collateral fees. + */ + trx.clear(); + claim_op.amount_to_claim = accumulation_amount + jillcoin.amount(1); + trx.operations.push_back(claim_op); + sign(trx, smartissuer_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Attempt to claim more backing-asset fees"); + + + /** + * Claim some of the collateral asset fees + */ + share_type part_of_accumulated_fees = accumulation_amount.amount / 4; + FC_ASSERT(part_of_accumulated_fees.value > 0); // Partial claim should be positive + share_type remainder_accumulated_fees = accumulation_amount.amount - part_of_accumulated_fees; + FC_ASSERT(remainder_accumulated_fees.value > 0); // Planned remainder should be positive + trx.clear(); + claim_op.amount_to_claim = jillcoin.amount(part_of_accumulated_fees); + trx.operations.push_back(claim_op); + sign(trx, smartissuer_private_key); + PUSH_TX(db, trx); + BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees == remainder_accumulated_fees); + + + /** + * Claim all the remaining collateral asset fees + */ + trx.clear(); + claim_op.amount_to_claim = jillcoin.amount(remainder_accumulated_fees); trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, smartissuer_private_key); PUSH_TX(db, trx); + BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); // 0 remainder /** From 666282ac2a2dbfaa29868acf86925ca7301f0126 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 11 May 2020 11:16:14 -0400 Subject: [PATCH 14/19] BSIP87: Comments --- tests/tests/force_settle_fee_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests/force_settle_fee_tests.cpp b/tests/tests/force_settle_fee_tests.cpp index 4987a69688..94fd4f4f62 100644 --- a/tests/tests/force_settle_fee_tests.cpp +++ b/tests/tests/force_settle_fee_tests.cpp @@ -405,7 +405,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) // 1. Create assets /////// const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee % - const uint16_t usd_fsf_percent_0 = 0 * GRAPHENE_1_PERCENT; // 0% Force-settlement offset fee % + const uint16_t usd_fsf_percent_0 = 0 * GRAPHENE_1_PERCENT; // 0% Force-settlement fee % // Attempt and fail to create the smart asset with a force-settlement fee % before HARDFORK_CORE_BSIP87_TIME trx.clear(); @@ -683,7 +683,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) BOOST_CHECK_EQUAL(paul_initial_core - rachel_fso_remainder_core - michael_fso_remainder_core - yanna_fso_remainder_core, call_paul_id(db).collateral.value); - // The asset's force settlement fee % should still not be set + // The asset's force settlement fee % should be valid BOOST_CHECK(bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); // There should be some accumulated collateral-deonominated fees BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); From 1175ec090d746d67298b1e953b806ea2a31fa80a Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 11 May 2020 11:20:02 -0400 Subject: [PATCH 15/19] BSIP87: Remove unnecessary code --- tests/tests/force_settle_fee_tests.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/tests/force_settle_fee_tests.cpp b/tests/tests/force_settle_fee_tests.cpp index 94fd4f4f62..50f6d547fa 100644 --- a/tests/tests/force_settle_fee_tests.cpp +++ b/tests/tests/force_settle_fee_tests.cpp @@ -96,9 +96,7 @@ struct force_settle_database_fixture : database_fixture { creator.bitasset_opts = bitasset_options(); creator.bitasset_opts->force_settlement_offset_percent = force_settlement_offset_percent; creator.bitasset_opts->short_backing_asset = backing_asset; - if (force_settlement_fee_percent.valid()) { - creator.bitasset_opts->extensions.value.force_settle_fee_percent = force_settlement_fee_percent; - } + creator.bitasset_opts->extensions.value.force_settle_fee_percent = force_settlement_fee_percent; trx.operations.push_back(std::move(creator)); trx.validate(); @@ -520,7 +518,6 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) claim_op.extensions.value.claim_from_asset_id = bitusd.id; claim_op.amount_to_claim = core.amount(5 * std::pow(10, core.precision)); trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); @@ -861,7 +858,6 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) claim_op.extensions.value.claim_from_asset_id = bitusd.id; claim_op.amount_to_claim = core.amount(expected_accumulation_fsf_core_amount); trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, assetowner_private_key); PUSH_TX(db, trx); @@ -912,7 +908,6 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) claim_op.extensions.value.claim_from_asset_id = bitusd.id; claim_op.amount_to_claim = core.amount(-5 * std::pow(10, core.precision)); trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "amount_to_claim.amount > 0"); @@ -923,7 +918,6 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) claim_op.extensions.value.claim_from_asset_id = bitusd.id; claim_op.amount_to_claim = rachel_fsf_fee_core + 1; trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Attempt to claim more backing-asset fees"); @@ -942,7 +936,6 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) claim_op.extensions.value.claim_from_asset_id = bitusd.id; claim_op.amount_to_claim = jillcoin.amount(rachel_fsf_fee_core.value); trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "is not backed by asset"); @@ -953,7 +946,6 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) claim_op.extensions.value.claim_from_asset_id = bitusd.id; claim_op.amount_to_claim = rachel_fsf_fee_core; trx.operations.push_back(claim_op); - set_expiration(db, trx); sign(trx, assetowner_private_key); PUSH_TX(db, trx); BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); From 6b64eaae351e0d4c22aeaf5161d1847407d7aacb Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 11 May 2020 11:51:35 -0400 Subject: [PATCH 16/19] BSIP87: Avoid non-deterministic floating point functions --- tests/tests/force_settle_fee_tests.cpp | 39 +++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/tests/tests/force_settle_fee_tests.cpp b/tests/tests/force_settle_fee_tests.cpp index 50f6d547fa..c75715dd6b 100644 --- a/tests/tests/force_settle_fee_tests.cpp +++ b/tests/tests/force_settle_fee_tests.cpp @@ -169,6 +169,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const auto &core = asset_id_type()(db); asset_id_type bitusd_id = bitusd.id; asset_id_type core_id = core.id; + const int64_t bitusd_unit = asset::scaled_precision(bitusd.precision).value; // 100 satoshi USDBIT in 1 USDBIT // 2. Publish a feed for the smart asset update_feed_producers(bitusd_id(db), {feedproducer_id}); @@ -197,7 +198,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) // 4. Paul borrows 1000 bitUSD /////// // Paul will borrow bitUSD by providing 2x collateral required: 2 * 1/20 = 1/10 - int64_t paul_initial_usd = 1000 * std::pow(10, bitusd.precision); // 100000 + int64_t paul_initial_usd = 1000 * bitusd_unit; // 100000 int64_t paul_initial_core = paul_initial_usd * 2 / 20; // 10000 const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd), core.amount(paul_initial_core)); @@ -211,7 +212,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 5. Paul transfers 200 bitUSD to Rachel /////// - int64_t rachel_initial_usd = 200 * std::pow(10, bitusd.precision); + int64_t rachel_initial_usd = 200 * bitusd_unit; transfer(paul.id, rachel.id, asset(rachel_initial_usd, bitusd.id)); BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); @@ -224,7 +225,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 6. Rachel force settles 20 bitUSD /////// - const int64_t rachel_settle_amount = 20 * std::pow(10, bitusd.precision); + 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(); @@ -421,6 +422,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const auto &bitusd = get_asset("USDBIT"); const auto &core = asset_id_type()(db); + const int64_t core_unit = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE + const int64_t bitusd_unit = asset::scaled_precision(bitusd.precision).value; // 100 satoshi USDBIT in 1 USDBIT /////// @@ -440,7 +443,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) // 3. Paul borrows 100 bitUSD /////// // Paul will borrow bitUSD by providing 2x collateral required: 2 * 20 = 40 - int64_t paul_initial_usd = 100 * std::pow(10, bitusd.precision); // 10000 + int64_t paul_initial_usd = 100 * bitusd_unit; // 10000 int64_t paul_initial_core = paul_initial_usd * 2 * 20; // 400000 const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd), core.amount(paul_initial_core)); @@ -454,7 +457,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 4. Paul gives Rachel 20 bitUSD and retains 80 bitUSD /////// - int64_t rachel_initial_usd = 20 * std::pow(10, bitusd.precision); + int64_t rachel_initial_usd = 20 * bitusd_unit; transfer(paul.id, rachel.id, asset(rachel_initial_usd, bitusd.id)); BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd); @@ -467,7 +470,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position /////// - const int64_t rachel_settle_amount = 2 * std::pow(10, bitusd.precision); + 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(); @@ -516,7 +519,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) asset_claim_fees_operation claim_op; claim_op.issuer = assetowner.id; claim_op.extensions.value.claim_from_asset_id = bitusd.id; - claim_op.amount_to_claim = core.amount(5 * std::pow(10, core.precision)); + claim_op.amount_to_claim = core.amount(5 * core_unit); trx.operations.push_back(claim_op); sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); @@ -538,7 +541,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 8. Paul gives Michael 30 bitUSD and retains 50 bitUSD /////// - int64_t michael_initial_usd = 30 * std::pow(10, bitusd.precision); + int64_t michael_initial_usd = 30 * bitusd_unit; transfer(paul.id, michael.id, asset(michael_initial_usd, bitusd.id)); // Check Michael's balance @@ -553,7 +556,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 9. Michael force-settles 5 bitUSD which should be collected from Paul's debt position /////// - const int64_t michael_settle_amount = 5 * std::pow(10, bitusd.precision); + 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(); @@ -624,7 +627,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 11. Paul gives Yanna 40 bitUSD and retains 10 bitUSD /////// - int64_t yanna_initial_usd = 40 * std::pow(10, bitusd.precision); + int64_t yanna_initial_usd = 40 * bitusd_unit; transfer(paul.id, yanna.id, asset(yanna_initial_usd, bitusd.id)); // Check Yanna's balance @@ -640,7 +643,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 12. Yanna force-settles 10 bitUSD which should be collected from Paul's debt position /////// - const int64_t yanna_settle_amount = 10 * std::pow(10, bitusd.precision); + 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(); @@ -712,7 +715,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 14. Paul gives Vikram 10 bitUSD and retains 0 bitUSD /////// - int64_t vikram_initial_usd = 10 * std::pow(10, bitusd.precision); + int64_t vikram_initial_usd = 10 * bitusd_unit; transfer(paul.id, vikram.id, asset(vikram_initial_usd, bitusd.id)); // Check Yanna's balance @@ -729,7 +732,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 15. Vikram force-settles 10 bitUSD which should be collected from Paul's debt position /////// - const int64_t vikram_settle_amount = 10 * std::pow(10, bitusd.precision); + 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(); @@ -896,6 +899,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) // Check the asset owner's accumulated asset fees const auto &core = asset_id_type()(db); + const int64_t core_unit = asset::scaled_precision(core.precision).value; // 100000 satoshi CORE in 1 CORE const asset_object& bitusd = get_asset("USDBIT"); BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_fees == 0); BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees > 0); @@ -906,7 +910,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) asset_claim_fees_operation claim_op; claim_op.issuer = assetowner.id; claim_op.extensions.value.claim_from_asset_id = bitusd.id; - claim_op.amount_to_claim = core.amount(-5 * std::pow(10, core.precision)); + claim_op.amount_to_claim = core.amount(-5 * core_unit); trx.operations.push_back(claim_op); sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "amount_to_claim.amount > 0"); @@ -1000,6 +1004,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) trx.clear(); const auto &bitusd = get_asset("USDBIT"); + const int64_t bitusd_unit = asset::scaled_precision(bitusd.precision).value; // 100 satoshi USDBIT in 1 USDBIT const auto &core = asset_id_type()(db); @@ -1020,7 +1025,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) // 3. Paul borrows 100 bitUSD /////// // Paul will borrow bitUSD by providing 2x collateral required: 2 * 20 = 40 - int64_t paul_initial_usd = 100 * std::pow(10, bitusd.precision); // 10000 + int64_t paul_initial_usd = 100 * bitusd_unit; // 10000 int64_t paul_initial_core = paul_initial_usd * 2 * 20; // 400000 const call_order_object &call_paul = *borrow(paul, bitusd.amount(paul_initial_usd), core.amount(paul_initial_core)); @@ -1034,7 +1039,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 4. Paul gives Rachel 20 bitUSD and retains 80 bitUSD /////// - int64_t rachel_initial_usd = 20 * std::pow(10, bitusd.precision); + int64_t rachel_initial_usd = 20 * bitusd_unit; transfer(paul.id, rachel.id, asset(rachel_initial_usd, bitusd.id)); BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), rachel_initial_usd); @@ -1047,7 +1052,7 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) /////// // 5. Rachel force-settles 2 bitUSD which should be collected from Paul's debt position /////// - const int64_t rachel_settle_amount = 2 * std::pow(10, bitusd.precision); // 200 satoshi bitusd + 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(); From c0a4896c63fab676d324178dc8828ee7b214769c Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 11 May 2020 17:20:10 -0400 Subject: [PATCH 17/19] BSIP87: Test of invalid claims --- tests/tests/force_settle_fee_tests.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/tests/force_settle_fee_tests.cpp b/tests/tests/force_settle_fee_tests.cpp index c75715dd6b..7755bd3ab2 100644 --- a/tests/tests/force_settle_fee_tests.cpp +++ b/tests/tests/force_settle_fee_tests.cpp @@ -524,6 +524,19 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); + // Early proposals to claim should also fail + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(claim_op); + + trx.clear(); + trx.operations.push_back(cop); + // sign(trx, smartissuer_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); + /////// // 7. Activate HARDFORK_CORE_BSIP87_TIME @@ -915,6 +928,17 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "amount_to_claim.amount > 0"); + // Attempt to claim 0 fees + trx.clear(); + claim_op = asset_claim_fees_operation(); + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = core.amount(0 * core_unit); + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "amount_to_claim.amount > 0"); + // Attempt to claim excessive claim fee trx.clear(); claim_op = asset_claim_fees_operation(); From e323178a61c0260bb8cf300f5b3a577cd23f263f Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 11 May 2020 17:23:57 -0400 Subject: [PATCH 18/19] BSIP87: Test of partial claims --- tests/tests/force_settle_fee_tests.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/tests/force_settle_fee_tests.cpp b/tests/tests/force_settle_fee_tests.cpp index 7755bd3ab2..0e4f4c4e97 100644 --- a/tests/tests/force_settle_fee_tests.cpp +++ b/tests/tests/force_settle_fee_tests.cpp @@ -967,12 +967,28 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) sign(trx, assetowner_private_key); REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "is not backed by asset"); + // Attempt to claim part of all that can be claimed + share_type partial_claim_core = 1; // 1 satoshi + share_type expected_remainder_core = rachel_fsf_fee_core - partial_claim_core; + FC_ASSERT(expected_remainder_core.value > 0); // Remainder should be positive + trx.clear(); + claim_op = asset_claim_fees_operation(); + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = partial_claim_core; + trx.operations.push_back(claim_op); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + PUSH_TX(db, trx); + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == expected_remainder_core); + // Attempt to claim all that can be claimed + generate_block(); trx.clear(); claim_op = asset_claim_fees_operation(); claim_op.issuer = assetowner.id; claim_op.extensions.value.claim_from_asset_id = bitusd.id; - claim_op.amount_to_claim = rachel_fsf_fee_core; + claim_op.amount_to_claim = expected_remainder_core; trx.operations.push_back(claim_op); sign(trx, assetowner_private_key); PUSH_TX(db, trx); From da81dede991eaa91e4e1ee73a9d9f18e8a829a26 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Tue, 12 May 2020 12:52:28 -0400 Subject: [PATCH 19/19] BSIP87: Test prevention of early usage of force-settlement fee and claiming collateral-denominated fees --- tests/tests/force_settle_fee_tests.cpp | 385 +++++++++++++++++++++++-- 1 file changed, 363 insertions(+), 22 deletions(-) diff --git a/tests/tests/force_settle_fee_tests.cpp b/tests/tests/force_settle_fee_tests.cpp index 0e4f4c4e97..d03fd8595c 100644 --- a/tests/tests/force_settle_fee_tests.cpp +++ b/tests/tests/force_settle_fee_tests.cpp @@ -40,33 +40,14 @@ struct force_settle_database_fixture : database_fixture { } /** - * Create a smart asset without a force settlement fee percent - * @param name Asset name - * @param issuer Issuer ID - * @param force_settlement_offset_percent Force-settlement offset percent - * @return Asset object - */ - const asset_object &create_smart_asset( - const string &name, - account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, - uint16_t force_settlement_offset_percent /* = 100 */ /* 1% */ - ) { - try { - optional force_settlement_fee_percent; // Not specified - return create_smart_asset(name, issuer, force_settlement_offset_percent, force_settlement_fee_percent); - } FC_CAPTURE_AND_RETHROW((name)(issuer)(force_settlement_offset_percent)) - } - - - /** - * Create a smart asset + * Create an operation to create a smart asset * @param name Asset name * @param issuer Issuer ID * @param force_settlement_offset_percent Force-settlement offset percent * @param force_settlement_fee_percent Force-settlement fee percent (BSIP87) - * @return Asset object + * @return An asset_create_operation */ - const asset_object &create_smart_asset( + const asset_create_operation create_smart_asset_op( const string &name, account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, uint16_t force_settlement_offset_percent /* 100 = 1% */, @@ -98,6 +79,49 @@ struct force_settle_database_fixture : database_fixture { creator.bitasset_opts->short_backing_asset = backing_asset; creator.bitasset_opts->extensions.value.force_settle_fee_percent = force_settlement_fee_percent; + return creator; + + } FC_CAPTURE_AND_RETHROW((name)(issuer)) + } + + + /** + * Create a smart asset without a force settlement fee percent + * @param name Asset name + * @param issuer Issuer ID + * @param force_settlement_offset_percent Force-settlement offset percent + * @return Asset object + */ + const asset_object &create_smart_asset( + const string &name, + account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, + uint16_t force_settlement_offset_percent /* = 100 */ /* 1% */ + ) { + try { + optional force_settlement_fee_percent; // Not specified + return create_smart_asset(name, issuer, force_settlement_offset_percent, force_settlement_fee_percent); + } FC_CAPTURE_AND_RETHROW((name)(issuer)(force_settlement_offset_percent)) + } + + + /** + * Create a smart asset + * @param name Asset name + * @param issuer Issuer ID + * @param force_settlement_offset_percent Force-settlement offset percent + * @param force_settlement_fee_percent Force-settlement fee percent (BSIP87) + * @return Asset object + */ + const asset_object &create_smart_asset( + const string &name, + account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, + uint16_t force_settlement_offset_percent /* 100 = 1% */, + optional force_settlement_fee_percent /* 100 = 1% */ + ) { + try { + asset_create_operation creator = create_smart_asset_op(name, issuer, force_settlement_offset_percent, + force_settlement_fee_percent); + trx.operations.push_back(std::move(creator)); trx.validate(); processed_transaction ptx = PUSH_TX(db, trx, ~0); @@ -1139,4 +1163,321 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) } + /** + * Test the ability to create and update assets with force-settlement fee % before HARDFORK_CORE_BSIP87_TIME + * + * + * Before HARDFORK_CORE_BSIP87_TIME + * + * 1. Asset owner fails to create the smart coin called USDBIT with a force-settlement fee % + * 2. Asset owner fails to create the smart coin called USDBIT with a force-settlement fee % in a proposal + * 3. Asset owner succeeds to create the smart coin called USDBIT without a force-settlement fee % + * + * 4. Asset owner fails to update the smart coin with a force-settlement fee % + * 5. Asset owner fails to update the smart coin with a force-settlement fee % in a proposal + * + * 6. Asset owner fails to claim collateral-denominated fees + * 7. Asset owner fails to claim collateral-denominated fees in a proposal + * + * + * 8. Activate HARDFORK_CORE_BSIP87_TIME + * + * + * After HARDFORK_CORE_BSIP87_TIME + * + * 9. Asset owner succeeds to create the smart coin called CNYBIT with a force-settlement fee % + * 10. Asset owner succeeds to create the smart coin called RUBBIT with a force-settlement fee % in a proposal + * + * 11. Asset owner succeeds to update the smart coin called CNYBIT with a force-settlement fee % + * 12. Asset owner succeeds to update the smart coin called RUBBIT with a force-settlement fee % in a proposal + */ + BOOST_AUTO_TEST_CASE(prevention_before_hardfork_test) { + try { + /////// + // Initialize the scenario + /////// + // Get around Graphene issue #615 feed expiration bug + generate_blocks(HARDFORK_615_TIME); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + trx.clear(); + set_expiration(db, trx); + + // Create actors + ACTORS((assetowner)); + + // Fund actors + uint64_t initial_balance_core = 10000000; + transfer(committee_account, assetowner.id, asset(initial_balance_core)); + + // Confirm before hardfork activation + BOOST_CHECK(db.head_block_time() < HARDFORK_CORE_BSIP87_TIME); + + + /////// + // 1. Asset owner fails to create the smart coin called bitUSD with a force-settlement fee % + /////// + const uint16_t usd_fso_percent = 5 * GRAPHENE_1_PERCENT; // 5% Force-settlement offset fee % + const uint16_t usd_fsf_percent_0 = 0 * GRAPHENE_1_PERCENT; // 0% Force-settlement fee % + + // Attempt to create the smart asset with a force-settlement fee % + // The attempt should fail because it is before HARDFORK_CORE_BSIP87_TIME + trx.clear(); + REQUIRE_EXCEPTION_WITH_TEXT(create_smart_asset("USDBIT", assetowner.id, usd_fso_percent, usd_fsf_percent_0), + "cannot be set before Hardfork BSIP87"); + + + /////// + // 2. Asset owner fails to create the smart coin called bitUSD with a force-settlement fee % in a proposal + /////// + { + asset_create_operation create_op = create_smart_asset_op("USDBIT", assetowner.id, usd_fso_percent, + usd_fsf_percent_0); + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(create_op); + + trx.clear(); + trx.operations.push_back(cop); + // sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "cannot be set before Hardfork BSIP87"); + } + + + /////// + // 3. Asset owner succeeds to create the smart coin called bitUSD without a force-settlement fee % + /////// + trx.clear(); + create_smart_asset("USDBIT", assetowner.id, usd_fso_percent); + + generate_block(); + set_expiration(db, trx); + trx.clear(); + + const auto &bitusd = get_asset("USDBIT"); + const auto &core = asset_id_type()(db); + + + /////// + // 4. Asset owner fails to update the smart coin with a force-settlement fee % + /////// + const uint16_t usd_fsf_percent_3 = 3 * GRAPHENE_1_PERCENT; // 3% Force-settlement fee % (BSIP87) + asset_update_bitasset_operation uop; + uop.issuer = assetowner.id; + uop.asset_to_update = bitusd.get_id(); + uop.new_options = bitusd.bitasset_data(db).options; + uop.new_options.extensions.value.force_settle_fee_percent = usd_fsf_percent_3; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "cannot be set before Hardfork BSIP87"); + + // The force settlement fee % should not be set + BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); + + + /////// + // 5. Asset owner fails to update the smart coin with a force-settlement fee % in a proposal + /////// + { + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(uop); + + trx.clear(); + trx.operations.push_back(cop); + // sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "cannot be set before Hardfork BSIP87"); + + // The force settlement fee % should not be set + BOOST_CHECK(!bitusd.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); + } + + + /////// + // 6. Asset owner fails to claim collateral-denominated fees + /////// + // Although no collateral-denominated fees should be present, the error should indicate the + // that claiming such fees are not yet active. + BOOST_CHECK(bitusd.dynamic_asset_data_id(db).accumulated_collateral_fees == 0); // There should be no fees + trx.clear(); + asset_claim_fees_operation claim_op; + claim_op.issuer = assetowner.id; + claim_op.extensions.value.claim_from_asset_id = bitusd.id; + claim_op.amount_to_claim = core.amount(5); + trx.operations.push_back(claim_op); + sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); + + + /////// + // 7. Asset owner fails to claim collateral-denominated fees in a proposal + /////// + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(claim_op); + + trx.clear(); + trx.operations.push_back(cop); + // sign(trx, assetowner_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx), "Collateral-denominated fees are not yet active"); + + + + /////// + // 8. Activate HARDFORK_CORE_BSIP87_TIME + /////// + BOOST_CHECK(db.head_block_time() < HARDFORK_CORE_BSIP87_TIME); // Confirm still before hardfork activation + generate_blocks(HARDFORK_CORE_BSIP87_TIME); + generate_block(); + set_expiration(db, trx); + trx.clear(); + + + /////// + // 9. Asset owner succeeds to create the smart coin called CNYBIT with a force-settlement fee % + /////// + const uint16_t fsf_percent_1 = 1 * GRAPHENE_1_PERCENT; // 1% Force-settlement fee % (BSIP87) + const uint16_t fsf_percent_5 = 1 * GRAPHENE_1_PERCENT; // 5% Force-settlement fee % (BSIP87) + trx.clear(); + create_smart_asset("CNYBIT", assetowner.id, usd_fso_percent, fsf_percent_1); + + generate_block(); + set_expiration(db, trx); + trx.clear(); + + const auto &bitcny = get_asset("CNYBIT"); + + // The force settlement fee % should be set + BOOST_CHECK(bitcny.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK_EQUAL(fsf_percent_1, *bitcny.bitasset_data(db).options.extensions.value.force_settle_fee_percent); + + + /////// + // 10. Asset owner succeeds to create the smart coin called RUBBIT with a force-settlement fee % in a proposal + /////// + { + // Create the proposal + asset_create_operation create_op = create_smart_asset_op("RUBBIT", assetowner.id, usd_fso_percent, + fsf_percent_1); + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(create_op); + + trx.clear(); + trx.operations.push_back(cop); + // sign(trx, assetowner_private_key); + processed_transaction processed = PUSH_TX(db, trx); + + + // Approve the proposal + proposal_id_type pid = processed.operation_results[0].get(); + + proposal_update_operation pup; + pup.fee_paying_account = assetowner_id; + pup.proposal = pid; + pup.active_approvals_to_add.insert(assetowner_id); + trx.clear(); + trx.operations.push_back(pup); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + + PUSH_TX(db, trx); // No exception should be thrown + + + // Advance to the activation of the proposal + generate_blocks(cop.expiration_time); + set_expiration(db, trx); + } + const auto &bitrub = get_asset("RUBBIT"); + + // The force settlement fee % should be set + BOOST_CHECK(bitrub.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK_EQUAL(fsf_percent_1, *bitrub.bitasset_data(db).options.extensions.value.force_settle_fee_percent); + + + /////// + // 11. Asset owner succeeds to update the smart coin called CNYBIT with a force-settlement fee % + /////// + uop = asset_update_bitasset_operation(); + uop.issuer = assetowner.id; + uop.asset_to_update = bitcny.get_id(); + uop.new_options = bitcny.bitasset_data(db).options; + uop.new_options.extensions.value.force_settle_fee_percent = fsf_percent_5; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, assetowner_private_key); + PUSH_TX(db, trx); + + // The force settlement fee % should be set + BOOST_CHECK(bitcny.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK_EQUAL(fsf_percent_5, *bitcny.bitasset_data(db).options.extensions.value.force_settle_fee_percent); + + + /////// + // 12. Asset owner succeeds to update the smart coin called RUBBIT with a force-settlement fee % in a proposal + /////// + { + // Create the proposal + uop = asset_update_bitasset_operation(); + uop.issuer = assetowner.id; + uop.asset_to_update = bitrub.get_id(); + uop.new_options = bitrub.bitasset_data(db).options; + uop.new_options.extensions.value.force_settle_fee_percent = fsf_percent_5; + + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(uop); + + trx.clear(); + trx.operations.push_back(cop); + // sign(trx, assetowner_private_key); + processed_transaction processed = PUSH_TX(db, trx); + + + // Approve the proposal + proposal_id_type pid = processed.operation_results[0].get(); + + proposal_update_operation pup; + pup.fee_paying_account = assetowner_id; + pup.proposal = pid; + pup.active_approvals_to_add.insert(assetowner_id); + trx.clear(); + trx.operations.push_back(pup); + set_expiration(db, trx); + sign(trx, assetowner_private_key); + + PUSH_TX(db, trx); // No exception should be thrown + + // Advance to the activation of the proposal + generate_blocks(cop.expiration_time); + set_expiration(db, trx); + } + + // The force settlement fee % should be set + BOOST_CHECK(bitrub.bitasset_data(db).options.extensions.value.force_settle_fee_percent.valid()); + BOOST_CHECK_EQUAL(fsf_percent_5, *bitrub.bitasset_data(db).options.extensions.value.force_settle_fee_percent); + + } + FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END()