From d7d6d590bc6cbd169a3157ad78e858b4856933a0 Mon Sep 17 00:00:00 2001 From: Abit Date: Fri, 30 Apr 2021 23:19:25 +0200 Subject: [PATCH 001/258] Update version for Sonar Scanner --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index a3054446ca..563ba59acb 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.organization=bitshares-on-github sonar.projectKey=bitshares_bitshares-core sonar.projectName=BitShares-Core sonar.projectDescription=BitShares Blockchain implementation and command-line interface -sonar.projectVersion=5.2.0 +sonar.projectVersion=5.3 sonar.host.url=https://sonarcloud.io From 449a4f29c1f3133b9e0b11ebdece91aa1f34af0a Mon Sep 17 00:00:00 2001 From: Abit Date: Wed, 5 May 2021 21:56:31 +0200 Subject: [PATCH 002/258] Update sonar settings for hardfork branch --- sonar-project.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index ba2b54542d..aed59c14cb 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.organization=bitshares-on-github sonar.projectKey=bitshares_bitshares-core sonar.projectName=BitShares-Core sonar.projectDescription=BitShares Blockchain implementation and command-line interface -sonar.projectVersion=5.3.x +sonar.projectVersion=6.0.x sonar.host.url=https://sonarcloud.io @@ -26,4 +26,4 @@ sonar.cfamily.cache.path=sonar_cache # Decide which tree the current build belongs to in SonarCloud. # Managed by the `set_sonar_branch*` script(s) when building with CI. -sonar.branch.target=develop +sonar.branch.target=hardfork From 30cf13e79d758171582c83d4f4529317f7d13e4c Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 16 May 2021 14:51:20 +0000 Subject: [PATCH 003/258] Check white/blacklisted markets for LP exchange op --- libraries/chain/hardfork.d/CORE_2350.hf | 6 ++++ libraries/chain/liquidity_pool_evaluator.cpp | 33 ++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 libraries/chain/hardfork.d/CORE_2350.hf diff --git a/libraries/chain/hardfork.d/CORE_2350.hf b/libraries/chain/hardfork.d/CORE_2350.hf new file mode 100644 index 0000000000..6d9fd2d01a --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2350.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2350 Check whitelisted and blacklisted markets for liquidity pools +#ifndef HARDFORK_CORE_2350_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2350_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2350_PASSED(now) (now >= HARDFORK_CORE_2350_TIME) +#endif diff --git a/libraries/chain/liquidity_pool_evaluator.cpp b/libraries/chain/liquidity_pool_evaluator.cpp index 24619e725d..edc8979d47 100644 --- a/libraries/chain/liquidity_pool_evaluator.cpp +++ b/libraries/chain/liquidity_pool_evaluator.cpp @@ -314,6 +314,7 @@ void_result liquidity_pool_exchange_evaluator::do_evaluate(const liquidity_pool_ || ( op.amount_to_sell.asset_id == _pool->asset_b && op.min_to_receive.asset_id == _pool->asset_a ), "Asset type mismatch" ); + const asset_object& asset_obj_a = _pool->asset_a(d); FC_ASSERT( is_authorized_asset( d, *fee_paying_account, asset_obj_a ), "The account is unauthorized by asset A" ); @@ -322,6 +323,38 @@ void_result liquidity_pool_exchange_evaluator::do_evaluate(const liquidity_pool_ FC_ASSERT( is_authorized_asset( d, *fee_paying_account, asset_obj_b ), "The account is unauthorized by asset B" ); + if( HARDFORK_CORE_2350_PASSED( d.head_block_time() ) ) + { + if( !asset_obj_a.options.whitelist_markets.empty() ) + { + FC_ASSERT( asset_obj_a.options.whitelist_markets.find(_pool->asset_b) + != asset_obj_a.options.whitelist_markets.end(), + "The ${a}:${b} market has not been whitelisted by asset ${a}", + ("a", asset_obj_a.symbol) ("b", asset_obj_b.symbol) ); + } + if( !asset_obj_a.options.blacklist_markets.empty() ) + { + FC_ASSERT( asset_obj_a.options.blacklist_markets.find(_pool->asset_b) + == asset_obj_a.options.blacklist_markets.end(), + "The ${a}:${b} market has been blacklisted by asset ${a}", + ("a", asset_obj_a.symbol) ("b", asset_obj_b.symbol) ); + } + if( !asset_obj_b.options.whitelist_markets.empty() ) + { + FC_ASSERT( asset_obj_b.options.whitelist_markets.find(_pool->asset_a) + != asset_obj_b.options.whitelist_markets.end(), + "The ${a}:${b} market has not been whitelisted by asset ${b}", + ("a", asset_obj_a.symbol) ("b", asset_obj_b.symbol) ); + } + if( !asset_obj_b.options.blacklist_markets.empty() ) + { + FC_ASSERT( asset_obj_b.options.blacklist_markets.find(_pool->asset_a) + == asset_obj_b.options.blacklist_markets.end(), + "The ${a}:${b} market has been blacklisted by asset ${b}", + ("a", asset_obj_a.symbol) ("b", asset_obj_b.symbol) ); + } + } + _pool_receives_asset = ( op.amount_to_sell.asset_id == _pool->asset_a ? &asset_obj_a : &asset_obj_b ); _maker_market_fee = d.calculate_market_fee( *_pool_receives_asset, op.amount_to_sell, true ); From 9e236342012ee9f68eaa33f4d396ad6ddc0b4993 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 16 May 2021 16:10:38 +0000 Subject: [PATCH 004/258] Add tests for white/blacklisted markets about LP --- tests/tests/liquidity_pool_tests.cpp | 234 ++++++++++++++++++++++++++- 1 file changed, 229 insertions(+), 5 deletions(-) diff --git a/tests/tests/liquidity_pool_tests.cpp b/tests/tests/liquidity_pool_tests.cpp index 04fe5abf7e..758a8826d0 100644 --- a/tests/tests/liquidity_pool_tests.cpp +++ b/tests/tests/liquidity_pool_tests.cpp @@ -844,7 +844,31 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 585, usd_id ) ), fc::exception ); + // Setup market blacklists and whitelists + { + asset_update_operation auop; + + auop.issuer = usd_id(db).issuer; + auop.asset_to_update = usd_id; + auop.new_options = usd_id(db).options; + auop.new_options.whitelist_markets.insert( core_id ); + auop.new_options.blacklist_markets.insert( eur_id ); + auop.new_options.blacklist_markets.insert( usd_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + + auop.issuer = eur_id(db).issuer; + auop.asset_to_update = eur_id; + auop.new_options = eur_id(db).options; + auop.new_options.whitelist_markets.insert( core_id ); + auop.new_options.blacklist_markets.insert( eur_id ); + auop.new_options.blacklist_markets.insert( usd_id ); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Ted exchanges with the pool + // BTW reproduces bitshares-core issue #2350: white/blacklists not in effect result = exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 584, usd_id ) ); BOOST_REQUIRE_EQUAL( result.paid.size(), 1u ); @@ -886,6 +910,7 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) fc::exception ); // Ted exchanges with the pool + // BTW reproduces bitshares-core issue #2350: white/blacklists not in effect result = exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 600, eur_id ) ); BOOST_REQUIRE_EQUAL( result.paid.size(), 1u ); @@ -991,11 +1016,210 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 3, head_time - fc::days(3), {}, 63 ); BOOST_CHECK_EQUAL( histories.size(), 1u ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + // Proceeds to the hard fork time that added white/blacklist checks for bitshares-core issue #2350 + generate_blocks( HARDFORK_CORE_2350_TIME ); + + // Ted now fails to exchange due to the white/blacklists + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ), + fc::exception ); + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ), + fc::exception ); + + // Remove market blacklists and whitelists + { + asset_update_operation auop; + + auop.issuer = usd_id(db).issuer; + auop.asset_to_update = usd_id; + auop.new_options = usd_id(db).options; + auop.new_options.whitelist_markets.clear(); + auop.new_options.blacklist_markets.clear(); + trx.operations.clear(); + trx.operations.push_back( auop ); + + auop.issuer = eur_id(db).issuer; + auop.asset_to_update = eur_id; + auop.new_options = eur_id(db).options; + auop.new_options.whitelist_markets.clear(); + auop.new_options.blacklist_markets.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Able to exchange + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ); + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ); + + // Setup a whitelist without EUR for USD + { + asset_update_operation auop; + + auop.issuer = usd_id(db).issuer; + auop.asset_to_update = usd_id; + auop.new_options = usd_id(db).options; + auop.new_options.whitelist_markets.insert( core_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Now unable to exchange + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ), + fc::exception ); + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ), + fc::exception ); + + // Add the USD:EUR market to the whitelist of USD + { + asset_update_operation auop; + + auop.issuer = usd_id(db).issuer; + auop.asset_to_update = usd_id; + auop.new_options = usd_id(db).options; + auop.new_options.whitelist_markets.insert( eur_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Able to exchange + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ); + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ); + + // Setup a blacklist without EUR for USD + { + asset_update_operation auop; + + auop.issuer = usd_id(db).issuer; + auop.asset_to_update = usd_id; + auop.new_options = usd_id(db).options; + auop.new_options.blacklist_markets.insert( usd_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Able to exchange + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ); + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ); + + // Add EUR to blacklist of USD + { + asset_update_operation auop; + + auop.issuer = usd_id(db).issuer; + auop.asset_to_update = usd_id; + auop.new_options = usd_id(db).options; + auop.new_options.whitelist_markets.clear(); + auop.new_options.blacklist_markets.insert( eur_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Now unable to exchange + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ), + fc::exception ); + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ), + fc::exception ); + + // Remove the USD:EUR market from the blacklist of USD + { + asset_update_operation auop; + + auop.issuer = usd_id(db).issuer; + auop.asset_to_update = usd_id; + auop.new_options = usd_id(db).options; + auop.new_options.blacklist_markets.erase( eur_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Able to exchange + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ); + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ); + + // Setup a whitelist without USD for EUR + { + asset_update_operation auop; + + auop.issuer = eur_id(db).issuer; + auop.asset_to_update = eur_id; + auop.new_options = eur_id(db).options; + auop.new_options.whitelist_markets.insert( core_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Now unable to exchange + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ), + fc::exception ); + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ), + fc::exception ); + + // Add the USD:EUR market to the whitelist of EUR + { + asset_update_operation auop; + + auop.issuer = eur_id(db).issuer; + auop.asset_to_update = eur_id; + auop.new_options = eur_id(db).options; + auop.new_options.whitelist_markets.insert( usd_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Able to exchange + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ); + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ); + + // Setup a blacklist without USD for EUR + { + asset_update_operation auop; + + auop.issuer = eur_id(db).issuer; + auop.asset_to_update = eur_id; + auop.new_options = eur_id(db).options; + auop.new_options.blacklist_markets.insert( eur_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Able to exchange + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ); + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ); + + // Add EUR:USD to the blacklist of EUR + { + asset_update_operation auop; + + auop.issuer = eur_id(db).issuer; + auop.asset_to_update = eur_id; + auop.new_options = eur_id(db).options; + auop.new_options.whitelist_markets.clear(); + auop.new_options.blacklist_markets.insert( usd_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Now unable to exchange + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ), + fc::exception ); + BOOST_CHECK_THROW( exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ), + fc::exception ); + + // Remove the USD:EUR market from the blacklist of EUR + { + asset_update_operation auop; + + auop.issuer = eur_id(db).issuer; + auop.asset_to_update = eur_id; + auop.new_options = eur_id(db).options; + auop.new_options.blacklist_markets.erase( usd_id ); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + } + // Able to exchange + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, eur_id ), asset( 1, usd_id ) ); + exchange_with_liquidity_pool( ted_id, lp_id, asset( 1000, usd_id ), asset( 1, eur_id ) ); + +} FC_CAPTURE_LOG_AND_RETHROW( (0) ) } BOOST_AUTO_TEST_CASE( liquidity_pool_apis_test ) { try { From b33b44ed7f0c6d515b1183820b37407c8b3f7f68 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 21 May 2021 07:28:57 +0000 Subject: [PATCH 005/258] Skip override_transfer from_account check after hf --- libraries/chain/hardfork.d/CORE_2295.hf | 6 ++++++ libraries/chain/transfer_evaluator.cpp | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 libraries/chain/hardfork.d/CORE_2295.hf diff --git a/libraries/chain/hardfork.d/CORE_2295.hf b/libraries/chain/hardfork.d/CORE_2295.hf new file mode 100644 index 0000000000..52faf36bd1 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2295.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2295 Skip asset authorization check on from_account for override_transfer +#ifndef HARDFORK_CORE_2295_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2295_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2295_PASSED(now) (now >= HARDFORK_CORE_2295_TIME) +#endif diff --git a/libraries/chain/transfer_evaluator.cpp b/libraries/chain/transfer_evaluator.cpp index f681a35741..9da0aff75e 100644 --- a/libraries/chain/transfer_evaluator.cpp +++ b/libraries/chain/transfer_evaluator.cpp @@ -100,7 +100,10 @@ void_result override_transfer_evaluator::do_evaluate( const override_transfer_op const account_object& to_account = op.to(d); FC_ASSERT( is_authorized_asset( d, to_account, asset_type ) ); - FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); + // Since hard fork core-2295, do not check asset authorization limitations on from_account for override_transfer + // TODO code cleanup: remove the check and the assertion below after the hard fork time, keep the comment above + if( !HARDFORK_CORE_2295_PASSED(d.head_block_time()) ) + FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); FC_ASSERT( d.get_balance( from_account, asset_type ).amount >= op.amount.amount, "", ("total_transfer",op.amount)("balance",d.get_balance(from_account, asset_type).amount) ); From e8ffb325bb0f13bafed42456a0be94314c5246d5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 21 May 2021 08:05:34 +0000 Subject: [PATCH 006/258] Add tests for override_transfer whitelist check --- tests/tests/uia_tests.cpp | 82 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index adb7b541b1..fff0947c09 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -93,6 +93,7 @@ BOOST_AUTO_TEST_CASE( override_transfer_test ) otrans.from = dan.id; otrans.to = eric.id; otrans.amount = advanced.amount(100); + trx.operations.clear(); trx.operations.push_back(otrans); BOOST_TEST_MESSAGE( "Require throwing without signature" ); @@ -138,6 +139,87 @@ BOOST_AUTO_TEST_CASE( override_transfer_test2 ) BOOST_REQUIRE_EQUAL( get_balance( eric, advanced ), 0 ); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( override_transfer_whitelist_test ) +{ try { + ACTORS( (dan)(eric)(sam) ); + const asset_object& advanced = create_user_issued_asset( "ADVANCED", sam, white_list | override_authority ); + asset_id_type advanced_id = advanced.id; + BOOST_TEST_MESSAGE( "Issuing 1000 ADVANCED to dan" ); + issue_uia( dan, advanced.amount( 1000 ) ); + BOOST_TEST_MESSAGE( "Checking dan's balance" ); + BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 1000 ); + + override_transfer_operation otrans; + otrans.issuer = advanced.issuer; + otrans.from = dan.id; + otrans.to = eric.id; + otrans.amount = advanced.amount(100); + trx.operations.clear(); + trx.operations.push_back(otrans); + + PUSH_TX( db, trx, ~0 ); + + BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 900 ); + BOOST_REQUIRE_EQUAL( get_balance( eric, advanced ), 100 ); + + // Make a whitelist, now it should fail + { + BOOST_TEST_MESSAGE( "Changing the whitelist authority" ); + asset_update_operation uop; + uop.issuer = advanced_id(db).issuer; + uop.asset_to_update = advanced_id; + uop.new_options = advanced_id(db).options; + // The whitelist is managed by dan + uop.new_options.whitelist_authorities.insert(dan_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + auto whitelist_auths = advanced_id(db).options.whitelist_authorities; + BOOST_CHECK( whitelist_auths.find(dan_id) != whitelist_auths.end() ); + + // Upgrade dan so that he can manage the whitelist + upgrade_to_lifetime_member( dan_id ); + + // Add eric to the whitelist, but do not add dan + account_whitelist_operation wop; + wop.authorizing_account = dan_id; + wop.account_to_list = eric_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Fail because there is a whitelist authority and dan is not whitelisted + trx.operations.clear(); + trx.operations.push_back(otrans); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + + // Balances did not change + BOOST_REQUIRE_EQUAL( get_balance( dan, advanced ), 900 ); + BOOST_REQUIRE_EQUAL( get_balance( eric, advanced ), 100 ); + + // Apply core-2295 hardfork + generate_blocks( HARDFORK_CORE_2295_TIME ); + set_expiration( db, trx ); + + // Now it's able to override-transfer from dan to eric + PUSH_TX( db, trx, ~0 ); + + // Check new balances + BOOST_REQUIRE_EQUAL( get_balance( dan_id, advanced_id ), 800 ); + BOOST_REQUIRE_EQUAL( get_balance( eric_id, advanced_id ), 200 ); + + // Still can not override-transfer to sam because he is not whitelisted + otrans.to = sam_id; + trx.operations.clear(); + trx.operations.push_back(otrans); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE( issue_whitelist_uia ) { try { From 21899bd15fba4dfdede30054ff898526a1a73556 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 21 May 2021 08:32:48 +0000 Subject: [PATCH 007/258] Update comment about override_transfer --- libraries/chain/transfer_evaluator.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/chain/transfer_evaluator.cpp b/libraries/chain/transfer_evaluator.cpp index 9da0aff75e..10f7891ac3 100644 --- a/libraries/chain/transfer_evaluator.cpp +++ b/libraries/chain/transfer_evaluator.cpp @@ -101,7 +101,8 @@ void_result override_transfer_evaluator::do_evaluate( const override_transfer_op FC_ASSERT( is_authorized_asset( d, to_account, asset_type ) ); // Since hard fork core-2295, do not check asset authorization limitations on from_account for override_transfer - // TODO code cleanup: remove the check and the assertion below after the hard fork time, keep the comment above + // TODO code cleanup: if applicable (could be false due to proposals), + // remove the check and the assertion below after the hard fork time, keep the comment about reasoning above if( !HARDFORK_CORE_2295_PASSED(d.head_block_time()) ) FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); From a5fd69b9f6c000234b223f08b4570b56a42a430f Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 23 May 2021 21:48:08 +0000 Subject: [PATCH 008/258] Add asset authorization checks for some operations Affected operations: - asset_settle_operation - bid_collateral_operation - call_order_update_operation - vesting_balance_create_operation And slightly refactor nearby code for better performance --- libraries/chain/asset_evaluator.cpp | 11 ++++++ libraries/chain/hardfork.d/CORE_973.hf | 6 ++++ .../graphene/chain/market_evaluator.hpp | 2 -- libraries/chain/market_evaluator.cpp | 34 ++++++++++++++----- libraries/chain/transfer_evaluator.cpp | 8 +++-- libraries/chain/vesting_balance_evaluator.cpp | 26 ++++++++------ 6 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_973.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index b48af41d1b..bf26a7865d 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1010,6 +1010,15 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op FC_THROW_EXCEPTION(insufficient_feeds, "Cannot force settle with no price feed."); FC_ASSERT( d.get_balance( op.account, op.amount.asset_id ) >= op.amount, "Insufficient balance" ); + // Since hard fork core-973, check asset authorization limitations + if( HARDFORK_CORE_973_PASSED(d.head_block_time()) ) + { + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *asset_to_settle ), + "The account is not allowed to settle the asset" ); + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, bitasset.options.short_backing_asset(d) ), + "The account is not allowed to receive the backing asset" ); + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -1240,6 +1249,8 @@ void_result asset_claim_fees_evaluator::do_evaluate( const asset_claim_fees_oper "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) ); + // Note: asset authorization check on (account, collateral asset) is skipped here, + // because it is fine to allow the funds to be moved to account balance } return void_result(); diff --git a/libraries/chain/hardfork.d/CORE_973.hf b/libraries/chain/hardfork.d/CORE_973.hf new file mode 100644 index 0000000000..548cf2bba4 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_973.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #973 check asset authorizations for operations +#ifndef HARDFORK_CORE_973_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_973_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_973_PASSED(now) (now >= HARDFORK_CORE_973_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index ca27ff5f9b..80a0c25eab 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -80,7 +80,6 @@ namespace graphene { namespace chain { bool _closing_order = false; const asset_object* _debt_asset = nullptr; - const account_object* _paying_account = nullptr; const call_order_object* _order = nullptr; const asset_bitasset_data_object* _bitasset_data = nullptr; const asset_dynamic_data_object* _dynamic_data_obj = nullptr; @@ -96,7 +95,6 @@ namespace graphene { namespace chain { const asset_object* _debt_asset = nullptr; const asset_bitasset_data_object* _bitasset_data = nullptr; - const account_object* _paying_account = nullptr; const collateral_bid_object* _bid = nullptr; }; diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 2e8923aacd..089d281118 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -184,7 +184,7 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope auto next_maintenance_time = d.get_dynamic_global_properties().next_maintenance_time; - _paying_account = &o.funding_account(d); + // Note: funding_account is the fee payer thus exists in the database _debt_asset = &o.delta_debt.asset_id(d); FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.", ("sym", _debt_asset->symbol) ); @@ -220,6 +220,15 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope else if( _bitasset_data->current_feed.settlement_price.is_null() ) FC_THROW_EXCEPTION(insufficient_feeds, "Cannot borrow asset with no price feed."); + // Since hard fork core-973, check asset authorization limitations + if( HARDFORK_CORE_973_PASSED(d.head_block_time()) ) + { + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_debt_asset ), + "The account is not allowed to transact the debt asset" ); + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _bitasset_data->options.short_backing_asset(d) ), + "The account is not allowed to transact the collateral asset" ); + } + // Note: there was code here checking whether the account has enough balance to increase delta collateral, // which is now removed since the check is implicitly done later by `adjust_balance()` in `do_apply()`. @@ -248,7 +257,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // Adjust the total core in orders accodingly if( o.delta_collateral.asset_id == asset_id_type() ) { - d.modify(_paying_account->statistics(d), [&](account_statistics_object& stats) { + d.modify( d.get_account_stats_by_owner( o.funding_account ), [&](account_statistics_object& stats) { stats.total_core_in_orders += o.delta_collateral.amount; }); } @@ -398,7 +407,7 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation FC_ASSERT( d.head_block_time() > HARDFORK_CORE_216_TIME, "Not yet!" ); - _paying_account = &o.bidder(d); + // Note: bidder is the fee payer thus exists in the database _debt_asset = &o.debt_covered.asset_id(d); FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.", ("sym", _debt_asset->symbol) ); @@ -421,18 +430,27 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation if( o.additional_collateral.amount > 0 ) { + auto collateral_balance = d.get_balance( o.bidder, _bitasset_data->options.short_backing_asset ); if( _bid && d.head_block_time() >= HARDFORK_CORE_1692_TIME ) // TODO: see if HF check can be removed after HF { asset delta = o.additional_collateral - _bid->get_additional_collateral(); - FC_ASSERT( d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)) >= delta, + FC_ASSERT( collateral_balance >= delta, "Cannot increase bid from ${oc} to ${nc} collateral when payer only has ${b}", ("oc", _bid->get_additional_collateral().amount)("nc", o.additional_collateral.amount) - ("b", d.get_balance(*_paying_account, o.additional_collateral.asset_id(d)).amount) ); + ("b", collateral_balance.amount) ); } else - FC_ASSERT( d.get_balance( *_paying_account, - _bitasset_data->options.short_backing_asset(d) ) >= o.additional_collateral, + FC_ASSERT( collateral_balance >= o.additional_collateral, "Cannot bid ${c} collateral when payer only has ${b}", ("c", o.additional_collateral.amount) - ("b", d.get_balance(*_paying_account, o.additional_collateral.asset_id(d)).amount) ); + ("b", collateral_balance.amount) ); + } + + // Since hard fork core-973, check asset authorization limitations + if( HARDFORK_CORE_973_PASSED(d.head_block_time()) ) + { + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_debt_asset ), + "The account is not allowed to transact the debt asset" ); + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _bitasset_data->options.short_backing_asset(d) ), + "The account is not allowed to transact the collateral asset" ); } return void_result(); diff --git a/libraries/chain/transfer_evaluator.cpp b/libraries/chain/transfer_evaluator.cpp index 10f7891ac3..48031957c2 100644 --- a/libraries/chain/transfer_evaluator.cpp +++ b/libraries/chain/transfer_evaluator.cpp @@ -99,12 +99,16 @@ void_result override_transfer_evaluator::do_evaluate( const override_transfer_op const account_object& from_account = op.from(d); const account_object& to_account = op.to(d); - FC_ASSERT( is_authorized_asset( d, to_account, asset_type ) ); + FC_ASSERT( is_authorized_asset( d, to_account, asset_type ), + "The to_account is not allowed to transact the asset" ); // Since hard fork core-2295, do not check asset authorization limitations on from_account for override_transfer // TODO code cleanup: if applicable (could be false due to proposals), // remove the check and the assertion below after the hard fork time, keep the comment about reasoning above if( !HARDFORK_CORE_2295_PASSED(d.head_block_time()) ) - FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); + { + FC_ASSERT( is_authorized_asset( d, from_account, asset_type ), + "The from_account is not allowed to transact the asset" ); + } FC_ASSERT( d.get_balance( from_account, asset_type ).amount >= op.amount.amount, "", ("total_transfer",op.amount)("balance",d.get_balance(from_account, asset_type).amount) ); diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index 6d3891758c..4882229d27 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -34,14 +35,21 @@ void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance { try { const database& d = db(); - const account_object& creator_account = op.creator( d ); - /* const account_object& owner_account = */ op.owner( d ); + const account_object& creator_account = *fee_paying_account; + const account_object& owner_account = op.owner( d ); - // TODO: Check asset authorizations and withdrawals + const asset_object& asset_obj = op.amount.asset_id( d ); - FC_ASSERT( op.amount.amount > 0 ); - FC_ASSERT( d.get_balance( creator_account.id, op.amount.asset_id ) >= op.amount ); - FC_ASSERT( !op.amount.asset_id(d).is_transfer_restricted() ); + FC_ASSERT( !asset_obj.is_transfer_restricted(), "Asset has transfer_restricted flag enabled" ); + + // Since hard fork core-973, check asset authorization limitations + if( HARDFORK_CORE_973_PASSED(d.head_block_time()) ) + { + FC_ASSERT( is_authorized_asset( d, creator_account, asset_obj ), + "The creator account is not allowed to transact the asset" ); + FC_ASSERT( is_authorized_asset( d, owner_account, asset_obj ), + "The owner account is not allowed to transact the asset" ); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -90,7 +98,6 @@ object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance database& d = db(); const time_point_sec now = d.head_block_time(); - FC_ASSERT( d.get_balance( op.creator, op.amount.asset_id ) >= op.amount ); d.adjust_balance( op.creator, -op.amount ); const vesting_balance_object& vbo = d.create< vesting_balance_object >( [&]( vesting_balance_object& obj ) @@ -116,8 +123,8 @@ void_result vesting_balance_withdraw_evaluator::do_evaluate( const vesting_balan FC_ASSERT( vbo.is_withdraw_allowed( now, op.amount ), "", ("now", now)("op", op)("vbo", vbo) ); assert( op.amount <= vbo.balance ); // is_withdraw_allowed should fail before this check is reached - /* const account_object& owner_account = */ op.owner( d ); - // TODO: Check asset authorizations and withdrawals + // Note: asset authorization check is skipped here, + // because it is fine to allow the funds to be moved to account balance return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -139,7 +146,6 @@ void_result vesting_balance_withdraw_evaluator::do_apply( const vesting_balance_ d.adjust_balance( op.owner, op.amount ); - // TODO: Check asset authorizations and withdrawals return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } From 4f1e419b17347417db5f9a52489b50ff6f78cb39 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 24 May 2021 15:22:33 +0000 Subject: [PATCH 009/258] Add tests for asset auth on vesting balance create --- tests/tests/operation_tests.cpp | 142 ++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index cd317e93e9..0a43646512 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2808,6 +2808,148 @@ BOOST_AUTO_TEST_CASE( vesting_balance_create_test ) REQUIRE_OP_EVALUATION_SUCCESS( op, owner, bob_account.get_id() ); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( vesting_balance_create_asset_auth_test ) +{ try { + INVOKE( create_uia ); + + generate_block(); + + ACTORS( (alice)(bob)(cindy) ); + + const asset_object& test_asset = get_asset(UIA_TEST_SYMBOL); + + issue_uia( alice, test_asset.amount( 10000 ) ); + issue_uia( bob, test_asset.amount( 10000 ) ); + + // Success when no whitelist configured + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = test_asset.amount( 100 ); + op.policy = cdd_vesting_policy_initializer{ 60*60*24 }; + + trx.operations.clear(); + trx.operations.push_back(op); + PUSH_TX( db, trx, ~0 ); + + vesting_balance_create_operation op2 = op; + op2.owner = bob_id; + trx.operations.clear(); + trx.operations.push_back(op2); + PUSH_TX( db, trx, ~0 ); + + vesting_balance_create_operation op3 = op; + op3.creator = bob_id; + trx.operations.clear(); + trx.operations.push_back(op3); + PUSH_TX( db, trx, ~0 ); + + vesting_balance_create_operation op4 = op; + op4.creator = bob_id; + op4.owner = bob_id; + trx.operations.clear(); + trx.operations.push_back(op4); + PUSH_TX( db, trx, ~0 ); + + generate_block(); + + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.issuer = test_asset.issuer; + uop.asset_to_update = test_asset.id; + uop.new_options = test_asset.options; + + // Enable whitelisting + uop.new_options.flags = white_list | charge_market_fee; + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // The whitelist is managed by bob + uop.new_options.whitelist_authorities.insert(bob_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade bob so that he can manage the whitelist + upgrade_to_lifetime_member( bob_id ); + + // Add bob to the whitelist, but do not add alice + account_whitelist_operation wop; + wop.authorizing_account = bob_id; + wop.account_to_list = bob_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + generate_block(); + + // Reproduces bitshares-core issue #972: the whitelist is ignored + trx.operations.clear(); + trx.operations.push_back(op); + trx.operations.push_back(op2); + trx.operations.push_back(op3); + trx.operations.push_back(op4); + PUSH_TX( db, trx, ~0 ); + + // Apply core-973 hardfork + generate_blocks( HARDFORK_CORE_973_TIME ); + set_expiration( db, trx ); + + // Now asset authorization is in effect, Alice is unable to create vesting balances for herself + trx.operations.clear(); + trx.operations.push_back(op); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + + // Alice can not create vesting balances for Bob + trx.operations.clear(); + trx.operations.push_back(op2); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + + // Bob can not create vesting balances for Alice + trx.operations.clear(); + trx.operations.push_back(op3); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + + // Bob can still create vesting balances for himself + trx.operations.clear(); + trx.operations.push_back(op4); + PUSH_TX( db, trx, ~0 ); + + { + // Add Alice to the whitelist + account_whitelist_operation wop; + wop.authorizing_account = bob_id; + wop.account_to_list = alice_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Success again + trx.operations.clear(); + trx.operations.push_back(op); + trx.operations.push_back(op2); + trx.operations.push_back(op3); + trx.operations.push_back(op4); + PUSH_TX( db, trx, ~0 ); + + // And Alice still can not create vesting balances for Cindy + vesting_balance_create_operation op5 = op; + op5.owner = cindy_id; + trx.operations.clear(); + trx.operations.push_back(op5); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test ) { try { INVOKE( create_uia ); From 90973cbaecefbd8f7163350414a7a776da125cc0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 28 May 2021 22:35:46 +0000 Subject: [PATCH 010/258] Add tests for asset auth on call order update op --- tests/tests/operation_tests.cpp | 155 ++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 0a43646512..1cc8228ca8 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -287,6 +287,161 @@ BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_583 ) } } +BOOST_AUTO_TEST_CASE( call_order_update_asset_auth_test ) +{ + try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); + set_expiration( db, trx ); + + ACTORS((dan)(sam)); + + const auto& backasset = create_user_issued_asset("BACK", sam, white_list | charge_market_fee); + asset_id_type back_id = backasset.id; + + const auto& bitusd = create_bitasset("USDBIT", sam.id, 10, white_list | charge_market_fee, 3, back_id); + asset_id_type usd_id = bitusd.id; + + issue_uia( dan_id, backasset.amount(10000000) ); + issue_uia( sam_id, backasset.amount(10000000) ); + + update_feed_producers( bitusd, {sam.id} ); + + price_feed current_feed; + current_feed.core_exchange_rate = bitusd.amount( 100 ) / asset( 100 ); + current_feed.settlement_price = bitusd.amount( 100 ) / backasset.amount( 100 ); + current_feed.maintenance_collateral_ratio = 1750; // need to set explicitly, testnet has a different default + publish_feed( bitusd, sam, current_feed ); + + FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); + + BOOST_TEST_MESSAGE( "attempting to borrow using 2x collateral at 1:1 price now that there is a valid order" ); + borrow( dan, bitusd.amount(5000), backasset.amount(10000) ); + BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 ); + BOOST_REQUIRE_EQUAL( get_balance( dan, backasset ), 10000000 - 10000 ); + + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // For BACK + uop.asset_to_update = back_id; + uop.new_options = back_id(db).options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Sam so that he can manage the whitelist + upgrade_to_lifetime_member( sam_id ); + + // Add Sam to the whitelist, but do not add Dan + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Reproduces bitshares-core issue #973: no asset authorization check thus Dan is able to borrow + BOOST_TEST_MESSAGE( "Dan attempting to borrow using 2x collateral at 1:1 price again" ); + borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ); + BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 5000 + 5000); + BOOST_REQUIRE_EQUAL( get_balance( dan_id, back_id ), 10000000 - 10000 - 10000 ); + + // Apply core-973 hardfork + generate_blocks( HARDFORK_CORE_973_TIME ); + set_expiration( db, trx ); + + // Update price feed + publish_feed( usd_id(db), sam_id(db), current_feed ); + + // Sam should be able to borrow, but Dan should be unable to borrow + borrow( sam_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ); + BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 5000 ); + BOOST_REQUIRE_EQUAL( get_balance( sam_id, back_id ), 10000000 - 10000 ); + + GRAPHENE_REQUIRE_THROW( borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ), + fc::exception ); + + // Update USDBIT, disable remove whitelisting + { + BOOST_TEST_MESSAGE( "Disable USDBIT whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + uop.new_options.whitelist_authorities.clear(); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Sam should be able to borrow, but Dan should be unable to borrow + borrow( sam_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ); + GRAPHENE_REQUIRE_THROW( borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ), + fc::exception ); + + // Update BACK, disable whitelisting + { + BOOST_TEST_MESSAGE( "Disable BACK whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = back_id; + uop.new_options = back_id(db).options; + uop.new_options.whitelist_authorities.clear(); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Both Sam and Dan should be able to borrow + borrow( sam_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ); + borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ); + + // Update USDBIT, enable whitelisting + { + BOOST_TEST_MESSAGE( "Enable USDBIT whitelisting again" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + uop.new_options.whitelist_authorities.insert( sam_id ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Sam should be able to borrow, but Dan should be unable to borrow + borrow( sam_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ); + GRAPHENE_REQUIRE_THROW( borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ), + fc::exception ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) { set_expiration( db, trx ); From 18427eed50d5b64556978de414bb44134da9fabe Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 28 May 2021 22:49:16 +0000 Subject: [PATCH 011/258] Add tests for asset auth on asset settle operation --- tests/tests/operation_tests.cpp | 164 ++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 1cc8228ca8..0dfbd3e15d 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -442,6 +442,170 @@ BOOST_AUTO_TEST_CASE( call_order_update_asset_auth_test ) } } +BOOST_AUTO_TEST_CASE( asset_settle_operation_asset_auth_test ) +{ + try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); + set_expiration( db, trx ); + + ACTORS((dan)(sam)); + + const auto& backasset = create_user_issued_asset("BACK", sam, white_list | charge_market_fee); + asset_id_type back_id = backasset.id; + + const auto& bitusd = create_bitasset("USDBIT", sam.id, 10, white_list | charge_market_fee, 3, back_id); + asset_id_type usd_id = bitusd.id; + + issue_uia( dan_id, backasset.amount(10000000) ); + issue_uia( sam_id, backasset.amount(10000000) ); + + update_feed_producers( bitusd, {sam.id} ); + + price_feed current_feed; + current_feed.core_exchange_rate = bitusd.amount( 100 ) / asset( 100 ); + current_feed.settlement_price = bitusd.amount( 100 ) / backasset.amount( 100 ); + current_feed.maintenance_collateral_ratio = 1750; // need to set explicitly, testnet has a different default + publish_feed( bitusd, sam, current_feed ); + + FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); + + BOOST_TEST_MESSAGE( "attempting to borrow using 2x collateral at 1:1 price now that there is a valid order" ); + borrow( dan, bitusd.amount(5000), backasset.amount(10000) ); + BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 ); + BOOST_REQUIRE_EQUAL( get_balance( dan, backasset ), 10000000 - 10000 ); + + transfer( dan, sam, bitusd.amount(2000) ); + BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 3000 ); + BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 2000 ); + + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // For BACK + uop.asset_to_update = back_id; + uop.new_options = back_id(db).options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Sam so that he can manage the whitelist + upgrade_to_lifetime_member( sam_id ); + + // Add Sam to the whitelist, but do not add Dan + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Reproduces bitshares-core issue #973: no asset authorization check thus Dan is able to force-settle + BOOST_TEST_MESSAGE( "Dan and Sam attempting to force-settle" ); + force_settle( dan_id(db), usd_id(db).amount(100) ); + force_settle( sam_id(db), usd_id(db).amount(100) ); + BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2900 ); + BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1900 ); + + // Apply core-973 hardfork + BOOST_TEST_MESSAGE( "Apply core-973 hardfork" ); + generate_blocks( HARDFORK_CORE_973_TIME ); + set_expiration( db, trx ); + + // Update price feed + publish_feed( usd_id(db), sam_id(db), current_feed ); + + // Sam should be able to force-settle, but Dan should be unable to force-settle + BOOST_TEST_MESSAGE( "Dan and Sam attempting to force-settle again" ); + GRAPHENE_REQUIRE_THROW( force_settle( dan_id(db), usd_id(db).amount(100) ), fc::exception ); + force_settle( sam_id(db), usd_id(db).amount(100) ); + BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2900 ); + BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1800 ); + + // Update USDBIT, disable remove whitelisting + { + BOOST_TEST_MESSAGE( "Disable USDBIT whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + uop.new_options.whitelist_authorities.clear(); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Sam should be able to force-settle, but Dan should be unable to force-settle + GRAPHENE_REQUIRE_THROW( force_settle( dan_id(db), usd_id(db).amount(100) ), fc::exception ); + force_settle( sam_id(db), usd_id(db).amount(100) ); + BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2900 ); + BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1700 ); + + // Update BACK, disable whitelisting + { + BOOST_TEST_MESSAGE( "Disable BACK whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = back_id; + uop.new_options = back_id(db).options; + uop.new_options.whitelist_authorities.clear(); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Both Sam and Dan should be able to force-settle + force_settle( dan_id(db), usd_id(db).amount(100) ); + force_settle( sam_id(db), usd_id(db).amount(100) ); + BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2800 ); + BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1600 ); + + // Update USDBIT, enable whitelisting + { + BOOST_TEST_MESSAGE( "Enable USDBIT whitelisting again" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + uop.new_options.whitelist_authorities.insert( sam_id ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Sam should be able to force-settle, but Dan should be unable to force-settle + GRAPHENE_REQUIRE_THROW( force_settle( dan_id(db), usd_id(db).amount(100) ), fc::exception ); + force_settle( sam_id(db), usd_id(db).amount(100) ); + BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2800 ); + BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1500 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) { set_expiration( db, trx ); From 12615d1a01d13eb18b1f453a7065e2bafec8be6a Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 29 May 2021 14:30:39 +0000 Subject: [PATCH 012/258] Add tests for asset auth on collateral bid op --- tests/tests/operation_tests.cpp | 165 ++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 0dfbd3e15d..399da7b374 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -436,6 +436,8 @@ BOOST_AUTO_TEST_CASE( call_order_update_asset_auth_test ) GRAPHENE_REQUIRE_THROW( borrow( dan_id(db), usd_id(db).amount(5000), back_id(db).amount(10000) ), fc::exception ); + generate_block(); + } catch (fc::exception& e) { edump((e.to_detail_string())); throw; @@ -600,6 +602,169 @@ BOOST_AUTO_TEST_CASE( asset_settle_operation_asset_auth_test ) BOOST_REQUIRE_EQUAL( get_balance( dan_id, usd_id ), 2800 ); BOOST_REQUIRE_EQUAL( get_balance( sam_id, usd_id ), 1500 ); + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( bid_collateral_operation_asset_auth_test ) +{ + try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); + set_expiration( db, trx ); + + ACTORS((dan)(sam)); + + const auto& backasset = create_user_issued_asset("BACK", sam, white_list | charge_market_fee); + asset_id_type back_id = backasset.id; + + const auto& bitusd = create_bitasset("USDBIT", sam.id, 10, white_list | charge_market_fee, 3, back_id); + asset_id_type usd_id = bitusd.id; + + issue_uia( dan_id, backasset.amount(10000000) ); + issue_uia( sam_id, backasset.amount(10000000) ); + + update_feed_producers( bitusd, {sam.id} ); + + price_feed current_feed; + current_feed.core_exchange_rate = bitusd.amount( 100 ) / asset( 100 ); + current_feed.settlement_price = bitusd.amount( 100 ) / backasset.amount( 100 ); + current_feed.maintenance_collateral_ratio = 1750; // need to set explicitly, testnet has a different default + publish_feed( bitusd, sam, current_feed ); + + FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); + + BOOST_TEST_MESSAGE( "attempting to borrow using 2x collateral at 1:1 price now that there is a valid order" ); + borrow( dan, bitusd.amount(5000), backasset.amount(10000) ); + BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 ); + BOOST_REQUIRE_EQUAL( get_balance( dan, backasset ), 10000000 - 10000 ); + + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // For BACK + uop.asset_to_update = back_id; + uop.new_options = back_id(db).options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Sam so that he can manage the whitelist + upgrade_to_lifetime_member( sam_id ); + + // Add Sam to the whitelist, but do not add Dan + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Trigger a black swan event, globally settle USDBIT + BOOST_TEST_MESSAGE( "Trigger a black swan event" ); + current_feed.settlement_price = bitusd.amount( 10 ) / backasset.amount( 100 ); + publish_feed( bitusd, sam, current_feed ); + BOOST_REQUIRE( bitusd.bitasset_data(db).has_settlement() ); + + // Reproduces bitshares-core issue #973: no asset authorization check thus Dan is able to bid collateral + BOOST_TEST_MESSAGE( "Dan and Sam attempting to bid collateral" ); + bid_collateral( dan_id(db), back_id(db).amount(1), usd_id(db).amount(100) ); + bid_collateral( sam_id(db), back_id(db).amount(1), usd_id(db).amount(100) ); + + // Apply core-973 hardfork + BOOST_TEST_MESSAGE( "Apply core-973 hardfork" ); + generate_blocks( HARDFORK_CORE_973_TIME ); + set_expiration( db, trx ); + + // Update price feed + publish_feed( usd_id(db), sam_id(db), current_feed ); + + // Sam should be able to bid collateral, but Dan should be unable to bid + BOOST_TEST_MESSAGE( "Dan and Sam attempting to bid collateral again" ); + GRAPHENE_REQUIRE_THROW( bid_collateral( dan_id(db), back_id(db).amount(2), usd_id(db).amount(200) ), + fc::exception ); + bid_collateral( sam_id(db), back_id(db).amount(2), usd_id(db).amount(200) ); + + // Update USDBIT, disable remove whitelisting + { + BOOST_TEST_MESSAGE( "Disable USDBIT whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + uop.new_options.whitelist_authorities.clear(); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Sam should be able to bid collateral, but Dan should be unable to bid + GRAPHENE_REQUIRE_THROW( bid_collateral( dan_id(db), back_id(db).amount(3), usd_id(db).amount(300) ), + fc::exception ); + bid_collateral( sam_id(db), back_id(db).amount(3), usd_id(db).amount(300) ); + + // Update BACK, disable whitelisting + { + BOOST_TEST_MESSAGE( "Disable BACK whitelisting" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = back_id; + uop.new_options = back_id(db).options; + uop.new_options.whitelist_authorities.clear(); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Both Sam and Dan should be able to bid collateral + bid_collateral( dan_id(db), back_id(db).amount(4), usd_id(db).amount(400) ); + bid_collateral( sam_id(db), back_id(db).amount(4), usd_id(db).amount(400) ); + + // Update USDBIT, enable whitelisting + { + BOOST_TEST_MESSAGE( "Enable USDBIT whitelisting again" ); + asset_update_operation uop; + uop.issuer = sam_id; + + // For USDBIT + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).options; + uop.new_options.whitelist_authorities.insert( sam_id ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + } + + // Sam should be able to bid collateral, but Dan should be unable to bid + GRAPHENE_REQUIRE_THROW( bid_collateral( dan_id(db), back_id(db).amount(5), usd_id(db).amount(500) ), + fc::exception ); + bid_collateral( sam_id(db), back_id(db).amount(5), usd_id(db).amount(500) ); + + generate_block(); + } catch (fc::exception& e) { edump((e.to_detail_string())); throw; From 22635210be2dbece797f9a1a3b653516be659966 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 29 May 2021 18:04:54 +0000 Subject: [PATCH 013/258] Explicitly capture variable, fix code smell --- libraries/chain/market_evaluator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 089d281118..007a411739 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -257,7 +257,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // Adjust the total core in orders accodingly if( o.delta_collateral.asset_id == asset_id_type() ) { - d.modify( d.get_account_stats_by_owner( o.funding_account ), [&](account_statistics_object& stats) { + d.modify( d.get_account_stats_by_owner( o.funding_account ), [&o](account_statistics_object& stats) { stats.total_core_in_orders += o.delta_collateral.amount; }); } From be6fbfface4221db66d859b5279af6b9a127de8b Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 01:31:52 +0000 Subject: [PATCH 014/258] Implement no-collateral funding --- libraries/chain/CMakeLists.txt | 1 + libraries/chain/db_block.cpp | 10 + libraries/chain/db_init.cpp | 8 + libraries/chain/db_notify.cpp | 26 +++ libraries/chain/hardfork.d/CORE_2351.hf | 6 + .../graphene/chain/hardfork_visitor.hpp | 8 + .../graphene/chain/samet_fund_evaluator.hpp | 88 +++++++ .../graphene/chain/samet_fund_object.hpp | 104 +++++++++ libraries/chain/proposal_evaluator.cpp | 27 +++ libraries/chain/samet_fund_evaluator.cpp | 219 ++++++++++++++++++ libraries/chain/small_objects.cpp | 2 + libraries/protocol/CMakeLists.txt | 1 + .../include/graphene/protocol/config.hpp | 3 + .../include/graphene/protocol/operations.hpp | 8 +- .../include/graphene/protocol/samet_fund.hpp | 156 +++++++++++++ .../include/graphene/protocol/types.hpp | 1 + libraries/protocol/samet_fund.cpp | 76 ++++++ 17 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 libraries/chain/hardfork.d/CORE_2351.hf create mode 100644 libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp create mode 100644 libraries/chain/include/graphene/chain/samet_fund_object.hpp create mode 100644 libraries/chain/samet_fund_evaluator.cpp create mode 100644 libraries/protocol/include/graphene/protocol/samet_fund.hpp create mode 100644 libraries/protocol/samet_fund.cpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 6843a76029..485ae1f6cc 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -37,6 +37,7 @@ add_library( graphene_chain evaluator.cpp liquidity_pool_evaluator.cpp + samet_fund_evaluator.cpp balance_evaluator.cpp account_evaluator.cpp assert_evaluator.cpp diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index c122542e8a..e72ad808d5 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -342,6 +343,10 @@ processed_transaction database::push_proposal(const proposal_object& proposal) auto session = _undo_db.start_undo_session(true); for( auto& op : proposal.proposed_transaction.operations ) eval_state.operation_results.emplace_back(apply_operation(eval_state, op)); + // Make sure there is no unpaid samet fund debt + const auto& samet_fund_idx = get_index_type().indices().get(); + FC_ASSERT( samet_fund_idx.empty() || samet_fund_idx.begin()->unpaid_amount == 0, + "Unpaid SameT Fund debt detected" ); remove(proposal); session.merge(); } catch ( const fc::exception& e ) { @@ -748,6 +753,11 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx } ptrx.operation_results = std::move(eval_state.operation_results); + // Make sure there is no unpaid samet fund debt + const auto& samet_fund_idx = get_index_type().indices().get(); + FC_ASSERT( samet_fund_idx.empty() || samet_fund_idx.begin()->unpaid_amount == 0, + "Unpaid SameT Fund debt detected" ); + return ptrx; } FC_CAPTURE_AND_RETHROW( (trx) ) } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index d88cb53210..9114657041 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -136,6 +138,11 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -162,6 +169,7 @@ void database::initialize_indexes() add_index< primary_index< custom_authority_index> >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 648ee569a9..6c58a6a927 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -335,6 +336,26 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // account } + void operator()( const samet_fund_create_operation& op ) + { + _impacted.insert( op.fee_payer() ); // owner_account + } + void operator()( const samet_fund_delete_operation& op ) + { + _impacted.insert( op.fee_payer() ); // owner_account + } + void operator()( const samet_fund_update_operation& op ) + { + _impacted.insert( op.fee_payer() ); // owner_account + } + void operator()( const samet_fund_borrow_operation& op ) + { + _impacted.insert( op.fee_payer() ); // borrower + } + void operator()( const samet_fund_repay_operation& op ) + { + _impacted.insert( op.fee_payer() ); // account + } }; } // namespace detail @@ -448,6 +469,11 @@ void get_relevant_accounts( const object* obj, flat_set& accoun } case liquidity_pool_object_type:{ // no account info in the object although it does have an owner break; + } case samet_fund_object_type:{ + const auto* aobj = dynamic_cast( obj ); + FC_ASSERT( aobj != nullptr ); + accounts.insert( aobj->owner_account ); + break; } } } diff --git a/libraries/chain/hardfork.d/CORE_2351.hf b/libraries/chain/hardfork.d/CORE_2351.hf new file mode 100644 index 0000000000..4c0c59e9a5 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2351.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2351 No-collateral Funding +#ifndef HARDFORK_CORE_2351_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2351_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2351_PASSED(now) (now >= HARDFORK_CORE_2351_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp index c2f3c97545..6b1c2fb00b 100644 --- a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp +++ b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp @@ -54,6 +54,11 @@ struct hardfork_visitor { liquidity_pool_deposit_operation, liquidity_pool_withdraw_operation, liquidity_pool_exchange_operation >; + using samet_fund_ops = TL::list< samet_fund_create_operation, + samet_fund_delete_operation, + samet_fund_update_operation, + samet_fund_borrow_operation, + samet_fund_repay_operation >; fc::time_point_sec now; hardfork_visitor(fc::time_point_sec now) : now(now) {} @@ -72,6 +77,9 @@ struct hardfork_visitor { template std::enable_if_t(), bool> visit() { return HARDFORK_LIQUIDITY_POOL_PASSED(now); } + template + std::enable_if_t(), bool> + visit() { return HARDFORK_CORE_2351_PASSED(now); } /// @} /// typelist::runtime::dispatch adaptor diff --git a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp new file mode 100644 index 0000000000..f2ece58763 --- /dev/null +++ b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include + +#include + +namespace graphene { namespace chain { + + class asset_object; + class asset_dynamic_data_object; + class samet_fund_object; + + class samet_fund_create_evaluator : public evaluator + { + public: + using operation_type = samet_fund_create_operation; + + void_result do_evaluate( const samet_fund_create_operation& op ); + object_id_type do_apply( const samet_fund_create_operation& op ); + }; + + class samet_fund_delete_evaluator : public evaluator + { + public: + using operation_type = samet_fund_delete_operation; + + void_result do_evaluate( const samet_fund_delete_operation& op ); + void_result do_apply( const samet_fund_delete_operation& op ); + + const samet_fund_object* _fund = nullptr; + }; + + class samet_fund_update_evaluator : public evaluator + { + public: + using operation_type = samet_fund_update_operation; + + void_result do_evaluate( const samet_fund_update_operation& op ); + void_result do_apply( const samet_fund_update_operation& op ); + + const samet_fund_object* _fund = nullptr; + }; + + class samet_fund_borrow_evaluator : public evaluator + { + public: + using operation_type = samet_fund_borrow_operation; + + void_result do_evaluate( const samet_fund_borrow_operation& op ); + void_result do_apply( const samet_fund_borrow_operation& op ); + + const samet_fund_object* _fund = nullptr; + }; + + class samet_fund_repay_evaluator : public evaluator + { + public: + using operation_type = samet_fund_repay_operation; + + void_result do_evaluate( const samet_fund_repay_operation& op ); + void_result do_apply( const samet_fund_repay_operation& op ); + + const samet_fund_object* _fund = nullptr; + }; + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/samet_fund_object.hpp b/libraries/chain/include/graphene/chain/samet_fund_object.hpp new file mode 100644 index 0000000000..e67a5c1323 --- /dev/null +++ b/libraries/chain/include/graphene/chain/samet_fund_object.hpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include + +#include + +namespace graphene { namespace chain { + +using namespace graphene::db; + +/** + * @brief A SameT Fund is a fund which can be used by a borrower and have to be repaid in the same transaction + * @ingroup object + * @ingroup protocol + * + */ +class samet_fund_object : public abstract_object +{ + public: + static constexpr uint8_t space_id = protocol_ids; + static constexpr uint8_t type_id = samet_fund_object_type; + + account_id_type owner_account; ///< Owner of the fund + asset_id_type asset_type; ///< Asset type in the fund + share_type balance; ///< Usable amount in the fund + uint32_t fee_rate = 0; ///< Fee rate + share_type unpaid_amount; ///< Unpaid amount +}; + +struct by_unpaid; // for protocol +struct by_owner; // for API +struct by_asset_type; // for API + +/** +* @ingroup object_index +*/ +using samet_fund_multi_index_type = multi_index_container< + samet_fund_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< samet_fund_object, + member< samet_fund_object, share_type, &samet_fund_object::unpaid_amount >, + member< object, object_id_type, &object::id> + >, + composite_key_compare< std::greater, std::less > + >, + ordered_unique< tag, + composite_key< samet_fund_object, + member< samet_fund_object, account_id_type, &samet_fund_object::owner_account >, + member< object, object_id_type, &object::id> + > + >, + ordered_unique< tag, + composite_key< samet_fund_object, + member< samet_fund_object, asset_id_type, &samet_fund_object::asset_type >, + member< object, object_id_type, &object::id> + > + > + > +>; + +/** +* @ingroup object_index +*/ +using samet_fund_index = generic_index; + +} } // graphene::chain + +MAP_OBJECT_ID_TO_TYPE( graphene::chain::samet_fund_object ) + +FC_REFLECT_DERIVED( graphene::chain::samet_fund_object, (graphene::db::object), + (owner_account) + (asset_type) + (balance) + (fee_rate) + (unpaid_amount) + ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::samet_fund_object ) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index fa4576a68a..95570a1ea7 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -159,6 +159,18 @@ struct proposal_operation_hardfork_visitor FC_ASSERT(!op.new_parameters.current_fees->exists(), "Unable to define fees for liquidity pool operations prior to the LP hardfork"); } + if (!HARDFORK_CORE_2351_PASSED(block_time)) { + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for samet fund operations prior to the core-2351 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for samet fund operations prior to the core-2351 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for samet fund operations prior to the core-2351 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for samet fund operations prior to the core-2351 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for samet fund operations prior to the core-2351 hardfork"); + } } void operator()(const graphene::chain::htlc_create_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); @@ -209,6 +221,21 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::liquidity_pool_exchange_operation &op) const { FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" ); } + void operator()(const graphene::chain::samet_fund_create_operation &op) const { + FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); + } + void operator()(const graphene::chain::samet_fund_delete_operation &op) const { + FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); + } + void operator()(const graphene::chain::samet_fund_update_operation &op) const { + FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); + } + void operator()(const graphene::chain::samet_fund_borrow_operation &op) const { + FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); + } + void operator()(const graphene::chain::samet_fund_repay_operation &op) const { + FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); + } // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp new file mode 100644 index 0000000000..ff432a06f8 --- /dev/null +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace graphene { namespace chain { + +void_result samet_fund_create_evaluator::do_evaluate(const samet_fund_create_operation& op) +{ try { + const database& d = db(); + const auto block_time = d.head_block_time(); + + FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); + + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, op.asset_type(d) ), + "The account is unauthorized by the asset" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type samet_fund_create_evaluator::do_apply(const samet_fund_create_operation& op) +{ try { + database& d = db(); + + d.adjust_balance( op.owner_account, -asset( op.balance, op.asset_type ) ); + + const auto& new_samet_fund_object = d.create([&op](samet_fund_object& obj){ + obj.owner_account = op.owner_account; + obj.asset_type = op.asset_type; + obj.balance = op.balance; + obj.fee_rate = op.fee_rate; + // unpaid amount is 0 by default + }); + return new_samet_fund_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result samet_fund_delete_evaluator::do_evaluate(const samet_fund_delete_operation& op) +{ try { + const database& d = db(); + + _fund = &op.fund_id(d); + + FC_ASSERT( _fund->owner_account == op.owner_account, "The account is not the owner of the fund" ); + + FC_ASSERT( _fund->unpaid_amount == 0, "Can only delete a fund when the unpaid amount is zero" ); + + // Note: no asset authorization check here, allow funds to be moved to account balance + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result samet_fund_delete_evaluator::do_apply(const samet_fund_delete_operation& op) +{ try { + database& d = db(); + + d.adjust_balance( op.owner_account, asset( _fund->balance, _fund->asset_type ) ); + + d.remove( *_fund ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result samet_fund_update_evaluator::do_evaluate(const samet_fund_update_operation& op) +{ try { + const database& d = db(); + + _fund = &op.fund_id(d); + + FC_ASSERT( _fund->owner_account == op.owner_account, "The account is not the owner of the fund" ); + + if( op.delta_amount.valid() ) + { + FC_ASSERT( _fund->asset_type == op.delta_amount->asset_id, "Asset type mismatch" ); + FC_ASSERT( _fund->unpaid_amount == 0, + "Can only update the balance of a fund when the unpaid amount is zero" ); + + if( op.delta_amount->amount > 0 ) + { + // Check asset authorization only when moving funds from account balance to somewhere else + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _fund->asset_type(d) ), + "The account is unauthorized by the asset" ); + } + else + { + FC_ASSERT( _fund->balance > -op.delta_amount->amount, "Insufficient balance in the fund" ); + } + } + + if( op.new_fee_rate.valid() ) + { + FC_ASSERT( _fund->fee_rate != *op.new_fee_rate, + "New fee rate should not be the same as the original fee rate" ); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result samet_fund_update_evaluator::do_apply( const samet_fund_update_operation& op) +{ try { + database& d = db(); + + if( op.delta_amount.valid() ) + d.adjust_balance( op.owner_account, -(*op.delta_amount) ); + + d.modify( *_fund, [&op]( samet_fund_object& sfo ){ + if( op.delta_amount.valid() ) + sfo.balance += op.delta_amount->amount; + if( op.new_fee_rate.valid() ) + sfo.fee_rate = *op.new_fee_rate; + }); + + // Defensive check + FC_ASSERT( _fund->balance > 0, "Balance in the fund should be positive" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result samet_fund_borrow_evaluator::do_evaluate(const samet_fund_borrow_operation& op) +{ try { + const database& d = db(); + + _fund = &op.fund_id(d); + + FC_ASSERT( _fund->asset_type == op.borrow_amount.asset_id, "Asset type mismatch" ); + + FC_ASSERT( _fund->balance >= _fund->unpaid_amount + op.borrow_amount.amount, + "Insufficient balance in the fund thus unable to borrow" ); + + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _fund->asset_type(d) ), + "The account is unauthorized by the asset" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_operation& op) +{ try { + database& d = db(); + + d.modify( *_fund, [&op]( samet_fund_object& sfo ){ + sfo.unpaid_amount += op.borrow_amount.amount; + }); + + d.adjust_balance( op.borrower, op.borrow_amount ); + + // Defensive check + FC_ASSERT( _fund->balance >= _fund->unpaid_amount, "Should not borrow more than available" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_operation& op) +{ try { + const database& d = db(); + + _fund = &op.fund_id(d); + + FC_ASSERT( _fund->asset_type == op.repay_amount.asset_id, "Asset type mismatch" ); + + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _fund->asset_type(d) ), + "The account is unauthorized by the asset" ); + + FC_ASSERT( op.repay_amount.amount <= _fund->unpaid_amount, + "Repay amount should not be greater than unpaid amount" ); + + auto required_fee = ( fc::uint128_t( op.repay_amount.amount.value ) + GRAPHENE_SAMET_FUND_FEE_DENOM - 1 ) + * _fund->fee_rate / GRAPHENE_SAMET_FUND_FEE_DENOM; // Round up + + FC_ASSERT( fc::uint128_t(op.fund_fee.amount.value) >= required_fee, + "Insuffient fund fee, requires ${r}, offered ${p}", + ("r", required_fee) ("p", op.fund_fee.amount) ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operation& op) +{ try { + database& d = db(); + + d.adjust_balance( op.account, -( op.repay_amount + op.fund_fee ) ); + + d.modify( *_fund, [op]( samet_fund_object& sfo ){ + sfo.balance += op.fund_fee.amount; + sfo.unpaid_amount -= op.repay_amount.amount; + }); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // graphene::chain diff --git a/libraries/chain/small_objects.cpp b/libraries/chain/small_objects.cpp index 238fc8e84e..cea50d708b 100644 --- a/libraries/chain/small_objects.cpp +++ b/libraries/chain/small_objects.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -224,3 +225,4 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::witness_schedule_obj GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::worker_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::custom_authority_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::liquidity_pool_object ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::samet_fund_object ) diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 74721affd8..aaf9c512fd 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -23,6 +23,7 @@ list(APPEND SOURCES account.cpp custom.cpp market.cpp liquidity_pool.cpp + samet_fund.cpp ticket.cpp operations.cpp pts_address.cpp diff --git a/libraries/protocol/include/graphene/protocol/config.hpp b/libraries/protocol/include/graphene/protocol/config.hpp index b535e67158..d97aa5a9a3 100644 --- a/libraries/protocol/include/graphene/protocol/config.hpp +++ b/libraries/protocol/include/graphene/protocol/config.hpp @@ -117,6 +117,9 @@ #define GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO 1500 ///< Stop calling when collateral only pays off 150% of the debt ///@} +/// Denominator for SameT Fund fee calculation +constexpr uint32_t GRAPHENE_SAMET_FUND_FEE_DENOM = 1000000; + /** * Reserved Account IDs with special meaning */ diff --git a/libraries/protocol/include/graphene/protocol/operations.hpp b/libraries/protocol/include/graphene/protocol/operations.hpp index c14f8f3fd5..febc34d59f 100644 --- a/libraries/protocol/include/graphene/protocol/operations.hpp +++ b/libraries/protocol/include/graphene/protocol/operations.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -114,7 +115,12 @@ namespace graphene { namespace protocol { /* 60 */ liquidity_pool_delete_operation, /* 61 */ liquidity_pool_deposit_operation, /* 62 */ liquidity_pool_withdraw_operation, - /* 63 */ liquidity_pool_exchange_operation + /* 63 */ liquidity_pool_exchange_operation, + /* 64 */ samet_fund_create_operation, + /* 65 */ samet_fund_delete_operation, + /* 66 */ samet_fund_update_operation, + /* 67 */ samet_fund_borrow_operation, + /* 68 */ samet_fund_repay_operation > operation; /// @} // operations group diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp new file mode 100644 index 0000000000..f9934d8eaf --- /dev/null +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include + +namespace graphene { namespace protocol { + + /** + * @brief Create a new SameT Fund object + * @ingroup operations + */ + struct samet_fund_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< Operation fee + account_id_type owner_account; ///< Owner of the fund + asset_id_type asset_type; ///< Asset type in the fund + share_type balance; ///< Usable amount in the fund + uint32_t fee_rate = 0; ///< Fee rate + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return owner_account; } + void validate()const; + }; + + /** + * @brief Delete a SameT Fund object + * @ingroup operations + */ + struct samet_fund_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; ///< Operation fee + account_id_type owner_account; ///< The account who owns the SameT Fund object + samet_fund_id_type fund_id; ///< ID of the SameT Fund object + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return owner_account; } + void validate()const; + }; + + /** + * @brief Update a SameT Fund object + * @ingroup operations + */ + struct samet_fund_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< Operation fee + account_id_type owner_account; ///< Owner of the fund + samet_fund_id_type fund_id; ///< ID of the SameT Fund object + optional delta_amount; ///< Delta amount, optional + optional new_fee_rate = 0; ///< New fee rate, optional + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return owner_account; } + void validate()const; + }; + + /** + * @brief Borrow from a SameT Fund + * @ingroup operations + */ + struct samet_fund_borrow_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< Operation fee + account_id_type borrower; ///< The account who borrows from the fund + samet_fund_id_type fund_id; ///< ID of the SameT Fund + asset borrow_amount; ///< The amount to borrow + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return borrower; } + void validate()const; + }; + + /** + * @brief Repay to a SameT Fund + * @ingroup operations + */ + struct samet_fund_repay_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< Operation fee + account_id_type account; ///< The account who repays to the SameT Fund + samet_fund_id_type fund_id; ///< ID of the SameT Fund + asset repay_amount; ///< The amount to repay + asset fund_fee; ///< Fee for using the fund + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return account; } + void validate()const; + }; + +} } // graphene::protocol + +FC_REFLECT( graphene::protocol::samet_fund_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_borrow_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::samet_fund_repay_operation::fee_parameters_type, (fee) ) + +FC_REFLECT( graphene::protocol::samet_fund_create_operation, + (fee)(owner_account)(asset_type)(balance)(fee_rate)(extensions) ) +FC_REFLECT( graphene::protocol::samet_fund_delete_operation, + (fee)(owner_account)(fund_id)(extensions) ) +FC_REFLECT( graphene::protocol::samet_fund_update_operation, + (fee)(owner_account)(fund_id)(delta_amount)(new_fee_rate)(extensions) ) +FC_REFLECT( graphene::protocol::samet_fund_borrow_operation, + (fee)(borrower)(fund_id)(borrow_amount)(extensions) ) +FC_REFLECT( graphene::protocol::samet_fund_repay_operation, + (fee)(account)(fund_id)(repay_amount)(fund_fee)(extensions) ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation::fee_parameters_type ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation ) diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 1afc0a8a99..6ac0f407a8 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -317,6 +317,7 @@ GRAPHENE_DEFINE_IDS(protocol, protocol_ids, /*protocol objects are not prefixed* /* 1.17.x */ (custom_authority) /* 1.18.x */ (ticket) /* 1.19.x */ (liquidity_pool) + /* 1.20.x */ (samet_fund) ) FC_REFLECT(graphene::protocol::public_key_type, (key_data)) diff --git a/libraries/protocol/samet_fund.cpp b/libraries/protocol/samet_fund.cpp new file mode 100644 index 0000000000..b9ef251887 --- /dev/null +++ b/libraries/protocol/samet_fund.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +#include + +namespace graphene { namespace protocol { + +void samet_fund_create_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + FC_ASSERT( balance > 0, "Balance should be positive" ); +} + +void samet_fund_delete_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); +} + +void samet_fund_update_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + FC_ASSERT( delta_amount.valid() || new_fee_rate.valid(), "Should change something" ); + if( delta_amount.valid() ) + FC_ASSERT( delta_amount->amount != 0, "Delta amount should not be zero" ); +} + +void samet_fund_borrow_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + FC_ASSERT( borrow_amount.amount > 0, "Amount to borrow should be positive" ); +} + +void samet_fund_repay_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + FC_ASSERT( repay_amount.amount > 0, "Amount to repay should be positive" ); + FC_ASSERT( fund_fee.amount >= 0, "Fund fee should not be negative" ); + FC_ASSERT( repay_amount.asset_id == fund_fee.asset_id, + "Asset type of repay amount and fund fee should be the same" ); +} + +} } // graphene::protocol + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation::fee_parameters_type ) + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_create_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_delete_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_update_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_borrow_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::samet_fund_repay_operation ) From 7992291d4aff0d5b4518b8c06912c77502db0dc2 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 16:49:41 +0000 Subject: [PATCH 015/258] Update comments --- libraries/chain/include/graphene/chain/samet_fund_object.hpp | 2 +- libraries/protocol/include/graphene/protocol/samet_fund.hpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/chain/include/graphene/chain/samet_fund_object.hpp b/libraries/chain/include/graphene/chain/samet_fund_object.hpp index e67a5c1323..124155bddb 100644 --- a/libraries/chain/include/graphene/chain/samet_fund_object.hpp +++ b/libraries/chain/include/graphene/chain/samet_fund_object.hpp @@ -47,7 +47,7 @@ class samet_fund_object : public abstract_object account_id_type owner_account; ///< Owner of the fund asset_id_type asset_type; ///< Asset type in the fund share_type balance; ///< Usable amount in the fund - uint32_t fee_rate = 0; ///< Fee rate + uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_SAMET_FUND_FEE_DENOM share_type unpaid_amount; ///< Unpaid amount }; diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp index f9934d8eaf..625834bb01 100644 --- a/libraries/protocol/include/graphene/protocol/samet_fund.hpp +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -30,6 +30,8 @@ namespace graphene { namespace protocol { /** * @brief Create a new SameT Fund object * @ingroup operations + * + * A SameT Fund is a fund which can be used by a borrower and have to be repaid in the same transaction. */ struct samet_fund_create_operation : public base_operation { @@ -39,7 +41,7 @@ namespace graphene { namespace protocol { account_id_type owner_account; ///< Owner of the fund asset_id_type asset_type; ///< Asset type in the fund share_type balance; ///< Usable amount in the fund - uint32_t fee_rate = 0; ///< Fee rate + uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_SAMET_FUND_FEE_DENOM extensions_type extensions; ///< Unused. Reserved for future use. From f41db5cbbe814e15233e6f3d6813a4903b185d51 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 17:00:01 +0000 Subject: [PATCH 016/258] Update operation list slicing, fix MinGW build --- libraries/protocol/CMakeLists.txt | 4 +- .../protocol/custom_authorities/list_13.cpp | 41 +++++++++++++++++++ .../protocol/custom_authorities/list_14.cpp | 41 +++++++++++++++++++ .../restriction_predicate.cpp | 5 +++ .../custom_authorities/sliced_lists.hxx | 6 ++- 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 libraries/protocol/custom_authorities/list_13.cpp create mode 100644 libraries/protocol/custom_authorities/list_14.cpp diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index aaf9c512fd..2291a4560b 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -51,7 +51,9 @@ list(APPEND CUSTOM_AUTHS_FILES custom_authorities/list_9.cpp custom_authorities/list_10.cpp custom_authorities/list_11.cpp - custom_authorities/list_12.cpp) + custom_authorities/list_12.cpp + custom_authorities/list_13.cpp + custom_authorities/list_14.cpp) file(GLOB CUSTOM_AUTHS_HEADERS "custom_authorities/*.hxx") diff --git a/libraries/protocol/custom_authorities/list_13.cpp b/libraries/protocol/custom_authorities/list_13.cpp new file mode 100644 index 0000000000..8f77ec6ccd --- /dev/null +++ b/libraries/protocol/custom_authorities/list_13.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" +#include "sliced_lists.hxx" + +namespace graphene { namespace protocol { +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_13(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_13::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/list_14.cpp b/libraries/protocol/custom_authorities/list_14.cpp new file mode 100644 index 0000000000..b863cf8ed8 --- /dev/null +++ b/libraries/protocol/custom_authorities/list_14.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" +#include "sliced_lists.hxx" + +namespace graphene { namespace protocol { +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_14(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_14::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/restriction_predicate.cpp b/libraries/protocol/custom_authorities/restriction_predicate.cpp index 10cc57709a..c0e177fd1a 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.cpp +++ b/libraries/protocol/custom_authorities/restriction_predicate.cpp @@ -56,6 +56,10 @@ restriction_predicate_function get_restriction_predicate(vector rs, return get_restriction_predicate_list_11(typelist::index_of(), std::move(rs)); if (typelist::contains()) return get_restriction_predicate_list_12(typelist::index_of(), std::move(rs)); + if (typelist::contains()) + return get_restriction_predicate_list_13(typelist::index_of(), std::move(rs)); + if (typelist::contains()) + return get_restriction_predicate_list_14(typelist::index_of(), std::move(rs)); if (typelist::contains()) FC_THROW_EXCEPTION( fc::assert_exception, "Virtual operations not allowed!" ); @@ -66,6 +70,7 @@ restriction_predicate_function get_restriction_predicate(vector rs, operation_list_7::list, operation_list_8::list, operation_list_9::list, operation_list_10::list, operation_list_11::list, operation_list_12::list, + operation_list_13::list, operation_list_14::list, virtual_operations_list::list>, Op>(), ""); FC_THROW_EXCEPTION(fc::assert_exception, diff --git a/libraries/protocol/custom_authorities/sliced_lists.hxx b/libraries/protocol/custom_authorities/sliced_lists.hxx index f26b3b77ef..9fbbc5afdc 100644 --- a/libraries/protocol/custom_authorities/sliced_lists.hxx +++ b/libraries/protocol/custom_authorities/sliced_lists.hxx @@ -46,7 +46,9 @@ using operation_list_11 = static_variant ::add_list> ::add // 52 ::finalize>; -using operation_list_12 = static_variant>; +using operation_list_12 = static_variant>; +using operation_list_13 = static_variant>; +using operation_list_14 = static_variant>; using virtual_operations_list = static_variant get_restriction_predicate_list_9(size_t object_restriction_predicate get_restriction_predicate_list_10(size_t idx, vector rs); object_restriction_predicate get_restriction_predicate_list_11(size_t idx, vector rs); object_restriction_predicate get_restriction_predicate_list_12(size_t idx, vector rs); +object_restriction_predicate get_restriction_predicate_list_13(size_t idx, vector rs); +object_restriction_predicate get_restriction_predicate_list_14(size_t idx, vector rs); } } // namespace graphene::protocol From de8242f1bba6f3513eb9ee16a3ed3c4109c5fb32 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 18:33:59 +0000 Subject: [PATCH 017/258] Move fee schedule reflection into a new file --- libraries/protocol/CMakeLists.txt | 1 + libraries/protocol/fee_schedule_calc.cpp | 5 ---- libraries/protocol/fee_schedule_reflect.cpp | 29 +++++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 libraries/protocol/fee_schedule_reflect.cpp diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 2291a4560b..15c9dc1ee4 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -8,6 +8,7 @@ list(APPEND SOURCES account.cpp chain_parameters.cpp fee_schedule.cpp fee_schedule_calc.cpp + fee_schedule_reflect.cpp memo.cpp proposal.cpp transfer.cpp diff --git a/libraries/protocol/fee_schedule_calc.cpp b/libraries/protocol/fee_schedule_calc.cpp index bac9eb424d..bde8d07775 100644 --- a/libraries/protocol/fee_schedule_calc.cpp +++ b/libraries/protocol/fee_schedule_calc.cpp @@ -24,11 +24,8 @@ #include -#include #include -#define MAX_FEE_STABILIZATION_ITERATION 4 - namespace graphene { namespace protocol { struct calc_fee_visitor @@ -96,5 +93,3 @@ namespace graphene { namespace protocol { } } } // graphene::protocol - -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::fee_schedule ) diff --git a/libraries/protocol/fee_schedule_reflect.cpp b/libraries/protocol/fee_schedule_reflect.cpp new file mode 100644 index 0000000000..14dd0f0191 --- /dev/null +++ b/libraries/protocol/fee_schedule_reflect.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., 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 + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::fee_schedule ) From f7e56be44208f242989d7b61eebd72a28ea0fe05 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 20:56:24 +0000 Subject: [PATCH 018/258] Split db_init.cpp into two files --- libraries/chain/CMakeLists.txt | 4 +- libraries/chain/database.cpp | 3 +- libraries/chain/db_genesis.cpp | 553 +++++++++++++++++++++++++++++++++ libraries/chain/db_init.cpp | 510 ------------------------------ 4 files changed, 558 insertions(+), 512 deletions(-) create mode 100644 libraries/chain/db_genesis.cpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 485ae1f6cc..7c4d91e6d6 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -8,14 +8,15 @@ if( GRAPHENE_DISABLE_UNITY_BUILD ) db_balance.cpp db_block.cpp db_debug.cpp + db_genesis.cpp db_getter.cpp db_init.cpp db_maint.cpp db_management.cpp db_market.cpp + db_notify.cpp db_update.cpp db_witness_schedule.cpp - db_notify.cpp ) message( STATUS "Graphene database unity build disabled" ) else( GRAPHENE_DISABLE_UNITY_BUILD ) @@ -81,6 +82,7 @@ target_include_directories( graphene_chain set( GRAPHENE_CHAIN_BIG_FILES db_init.cpp + db_genesis.cpp db_block.cpp db_maint.cpp db_market.cpp diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index 788a29f008..7e599a16ce 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -24,11 +24,12 @@ #include "db_balance.cpp" #include "db_block.cpp" #include "db_debug.cpp" +#include "db_genesis.cpp" #include "db_getter.cpp" #include "db_init.cpp" #include "db_maint.cpp" #include "db_management.cpp" #include "db_market.cpp" +#include "db_notify.cpp" #include "db_update.cpp" #include "db_witness_schedule.cpp" -#include "db_notify.cpp" \ No newline at end of file diff --git a/libraries/chain/db_genesis.cpp b/libraries/chain/db_genesis.cpp new file mode 100644 index 0000000000..9893b7178a --- /dev/null +++ b/libraries/chain/db_genesis.cpp @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2017 Cryptonomex, Inc., 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace graphene { namespace chain { + +void database::init_genesis(const genesis_state_type& genesis_state) +{ try { + FC_ASSERT( genesis_state.initial_timestamp != time_point_sec(), "Must initialize genesis timestamp." ); + FC_ASSERT( genesis_state.initial_timestamp.sec_since_epoch() % GRAPHENE_DEFAULT_BLOCK_INTERVAL == 0, + "Genesis timestamp must be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL." ); + FC_ASSERT(genesis_state.initial_witness_candidates.size() > 0, + "Cannot start a chain with zero witnesses."); + FC_ASSERT(genesis_state.initial_active_witnesses <= genesis_state.initial_witness_candidates.size(), + "initial_active_witnesses is larger than the number of candidate witnesses."); + + _undo_db.disable(); + struct auth_inhibitor { + auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags) + { db.node_properties().skip_flags |= skip_transaction_signatures; } + ~auth_inhibitor() + { db.node_properties().skip_flags = old_flags; } + private: + database& db; + uint32_t old_flags; + } inhibitor(*this); + + transaction_evaluation_state genesis_eval_state(this); + + // Create blockchain accounts + fc::ecc::private_key null_private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key"))); + create([](account_balance_object& b) { + b.balance = GRAPHENE_MAX_SHARE_SUPPLY; + }); + const account_object& committee_account = + create( [&](account_object& n) { + n.membership_expiration_date = time_point_sec::maximum(); + n.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + n.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + n.owner.weight_threshold = 1; + n.active.weight_threshold = 1; + n.name = "committee-account"; + n.statistics = create( [&n](account_statistics_object& s){ + s.owner = n.id; + s.name = n.name; + s.core_in_balance = GRAPHENE_MAX_SHARE_SUPPLY; + }).id; + }); + FC_ASSERT(committee_account.get_id() == GRAPHENE_COMMITTEE_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "witness-account"; + a.statistics = create([&a](account_statistics_object& s){ + s.owner = a.id; + s.name = a.name; + }).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 1; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_WITNESS_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }).get_id() == GRAPHENE_WITNESS_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "relaxed-committee-account"; + a.statistics = create([&a](account_statistics_object& s){ + s.owner = a.id; + s.name = a.name; + }).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 1; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }).get_id() == GRAPHENE_RELAXED_COMMITTEE_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "null-account"; + a.statistics = create([&a](account_statistics_object& s){ + s.owner = a.id; + s.name = a.name; + }).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 1; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_NULL_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = 0; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; + }).get_id() == GRAPHENE_NULL_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "temp-account"; + a.statistics = create([&a](account_statistics_object& s){ + s.owner = a.id; + s.name = a.name; + }).id; + a.owner.weight_threshold = 0; + a.active.weight_threshold = 0; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_TEMP_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }).get_id() == GRAPHENE_TEMP_ACCOUNT); + FC_ASSERT(create([this](account_object& a) { + a.name = "proxy-to-self"; + a.statistics = create([&a](account_statistics_object& s){ + s.owner = a.id; + s.name = a.name; + }).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 1; + a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_NULL_ACCOUNT; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = 0; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; + }).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT); + + // Create more special accounts + while( true ) + { + uint64_t id = get_index().get_next_id().instance(); + if( id >= genesis_state.immutable_parameters.num_special_accounts ) + break; + const account_object& acct = create([this,id](account_object& a) { + a.name = "special-account-" + std::to_string(id); + a.statistics = create([&a](account_statistics_object& s){ + s.owner = a.id; + s.name = a.name; + }).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 1; + a.registrar = a.lifetime_referrer = a.referrer = account_id_type(id); + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }); + FC_ASSERT( acct.get_id() == account_id_type(id) ); + remove( acct ); + } + + // Create core asset + const asset_dynamic_data_object& dyn_asset = + create([](asset_dynamic_data_object& a) { + a.current_supply = GRAPHENE_MAX_SHARE_SUPPLY; + }); + const asset_object& core_asset = + create( [&genesis_state,&dyn_asset]( asset_object& a ) { + a.symbol = GRAPHENE_SYMBOL; + a.options.max_supply = genesis_state.max_core_supply; + a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS; + a.options.flags = 0; + a.options.issuer_permissions = 0; + a.issuer = GRAPHENE_NULL_ACCOUNT; + a.options.core_exchange_rate.base.amount = 1; + a.options.core_exchange_rate.base.asset_id = asset_id_type(0); + a.options.core_exchange_rate.quote.amount = 1; + a.options.core_exchange_rate.quote.asset_id = asset_id_type(0); + a.dynamic_asset_data_id = dyn_asset.id; + }); + FC_ASSERT( dyn_asset.id == asset_dynamic_data_id_type() ); + FC_ASSERT( asset_id_type(core_asset.id) == asset().asset_id ); + FC_ASSERT( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) ); + _p_core_asset_obj = &core_asset; + _p_core_dynamic_data_obj = &dyn_asset; + // Create more special assets + while( true ) + { + uint64_t id = get_index().get_next_id().instance(); + if( id >= genesis_state.immutable_parameters.num_special_assets ) + break; + const asset_dynamic_data_object& dyn_asset = + create([](asset_dynamic_data_object& a) { + a.current_supply = 0; + }); + const asset_object& asset_obj = create( [id,&dyn_asset]( asset_object& a ) { + a.symbol = "SPECIAL" + std::to_string( id ); + a.options.max_supply = 0; + a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS; + a.options.flags = 0; + a.options.issuer_permissions = 0; + a.issuer = GRAPHENE_NULL_ACCOUNT; + a.options.core_exchange_rate.base.amount = 1; + a.options.core_exchange_rate.base.asset_id = asset_id_type(0); + a.options.core_exchange_rate.quote.amount = 1; + a.options.core_exchange_rate.quote.asset_id = asset_id_type(0); + a.dynamic_asset_data_id = dyn_asset.id; + }); + FC_ASSERT( asset_obj.get_id() == asset_id_type(id) ); + remove( asset_obj ); + } + + chain_id_type chain_id = genesis_state.compute_chain_id(); + + // Create global properties + _p_global_prop_obj = & create([&genesis_state](global_property_object& p) { + p.parameters = genesis_state.initial_parameters; + // Set fees to zero initially, so that genesis initialization needs not pay them + // We'll fix it at the end of the function + p.parameters.get_mutable_fees().zero_all_fees(); + + }); + _p_dyn_global_prop_obj = & create([&genesis_state](dynamic_global_property_object& p) { + p.time = genesis_state.initial_timestamp; + p.dynamic_flags = 0; + p.witness_budget = 0; + p.recent_slots_filled = std::numeric_limits::max(); + }); + + FC_ASSERT( (genesis_state.immutable_parameters.min_witness_count & 1) == 1, "min_witness_count must be odd" ); + FC_ASSERT( (genesis_state.immutable_parameters.min_committee_member_count & 1) == 1, "min_committee_member_count must be odd" ); + + _p_chain_property_obj = & create([chain_id,&genesis_state](chain_property_object& p) + { + p.chain_id = chain_id; + p.immutable_parameters = genesis_state.immutable_parameters; + } ); + for (uint32_t i = 0; i <= 0x10000; i++) + create( [&]( block_summary_object&) {}); + + // Create initial accounts + for( const auto& account : genesis_state.initial_accounts ) + { + account_create_operation cop; + cop.name = account.name; + cop.registrar = GRAPHENE_TEMP_ACCOUNT; + cop.owner = authority(1, account.owner_key, 1); + if( account.active_key == public_key_type() ) + { + cop.active = cop.owner; + cop.options.memo_key = account.owner_key; + } + else + { + cop.active = authority(1, account.active_key, 1); + cop.options.memo_key = account.active_key; + } + account_id_type account_id(apply_operation(genesis_eval_state, cop).get()); + + if( account.is_lifetime_member ) + { + account_upgrade_operation op; + op.account_to_upgrade = account_id; + op.upgrade_to_lifetime_member = true; + apply_operation(genesis_eval_state, op); + } + } + + // Helper function to get account ID by name + const auto& accounts_by_name = get_index_type().indices().get(); + auto get_account_id = [&accounts_by_name](const string& name) { + auto itr = accounts_by_name.find(name); + FC_ASSERT(itr != accounts_by_name.end(), + "Unable to find account '${acct}'. Did you forget to add a record for it to initial_accounts?", + ("acct", name)); + return itr->get_id(); + }; + + // Helper function to get asset ID by symbol + const auto& assets_by_symbol = get_index_type().indices().get(); + const auto get_asset_id = [&assets_by_symbol](const string& symbol) { + auto itr = assets_by_symbol.find(symbol); + FC_ASSERT(itr != assets_by_symbol.end(), + "Unable to find asset '${sym}'. Did you forget to add a record for it to initial_assets?", + ("sym", symbol)); + return itr->get_id(); + }; + + map total_supplies; + map total_debts; + + // Create initial assets + for( const genesis_state_type::initial_asset_type& asset : genesis_state.initial_assets ) + { + asset_id_type new_asset_id = get_index_type().get_next_id(); + total_supplies[ new_asset_id ] = 0; + + asset_dynamic_data_id_type dynamic_data_id; + optional bitasset_data_id; + if( asset.is_bitasset ) + { + int collateral_holder_number = 0; + total_debts[ new_asset_id ] = 0; + for( const auto& collateral_rec : asset.collateral_records ) + { + account_create_operation cop; + cop.name = asset.symbol + "-collateral-holder-" + std::to_string(collateral_holder_number); + boost::algorithm::to_lower(cop.name); + cop.registrar = GRAPHENE_TEMP_ACCOUNT; + cop.owner = authority(1, collateral_rec.owner, 1); + cop.active = cop.owner; + account_id_type owner_account_id = apply_operation(genesis_eval_state, cop).get(); + + modify( owner_account_id(*this).statistics(*this), [&collateral_rec]( account_statistics_object& o ) { + o.total_core_in_orders = collateral_rec.collateral; + }); + + create([&](call_order_object& c) { + c.borrower = owner_account_id; + c.collateral = collateral_rec.collateral; + c.debt = collateral_rec.debt; + c.call_price = price::call_price(chain::asset(c.debt, new_asset_id), + chain::asset(c.collateral, core_asset.id), + GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO); + }); + + total_supplies[ asset_id_type(0) ] += collateral_rec.collateral; + total_debts[ new_asset_id ] += collateral_rec.debt; + ++collateral_holder_number; + } + + bitasset_data_id = create([&core_asset,new_asset_id](asset_bitasset_data_object& b) { + b.options.short_backing_asset = core_asset.id; + b.options.minimum_feeds = GRAPHENE_DEFAULT_MINIMUM_FEEDS; + b.asset_id = new_asset_id; + }).id; + } + + dynamic_data_id = create([&asset](asset_dynamic_data_object& d) { + d.accumulated_fees = asset.accumulated_fees; + }).id; + + total_supplies[ new_asset_id ] += asset.accumulated_fees; + + create([&](asset_object& a) { + a.symbol = asset.symbol; + a.options.description = asset.description; + a.precision = asset.precision; + string issuer_name = asset.issuer_name; + a.issuer = get_account_id(issuer_name); + a.options.max_supply = asset.max_supply; + a.options.flags = witness_fed_asset; + a.options.issuer_permissions = charge_market_fee | override_authority | white_list | transfer_restricted | disable_confidential | + ( asset.is_bitasset ? disable_force_settle | global_settle | witness_fed_asset | committee_fed_asset : 0 ); + a.dynamic_asset_data_id = dynamic_data_id; + a.bitasset_data_id = bitasset_data_id; + }); + } + + // Create initial balances + share_type total_allocation; + for( const auto& handout : genesis_state.initial_balances ) + { + const auto asset_id = get_asset_id(handout.asset_symbol); + create([&handout,total_allocation,asset_id](balance_object& b) { + b.balance = asset(handout.amount, asset_id); + b.owner = handout.owner; + }); + + total_supplies[ asset_id ] += handout.amount; + } + + // Create initial vesting balances + for( const genesis_state_type::initial_vesting_balance_type& vest : genesis_state.initial_vesting_balances ) + { + const auto asset_id = get_asset_id(vest.asset_symbol); + create([&](balance_object& b) { + b.owner = vest.owner; + b.balance = asset(vest.amount, asset_id); + + linear_vesting_policy policy; + policy.begin_timestamp = vest.begin_timestamp; + policy.vesting_cliff_seconds = 0; + policy.vesting_duration_seconds = vest.vesting_duration_seconds; + policy.begin_balance = vest.begin_balance; + + b.vesting_policy = std::move(policy); + }); + + total_supplies[ asset_id ] += vest.amount; + } + + if( total_supplies[ asset_id_type(0) ] > 0 ) + { + adjust_balance(GRAPHENE_COMMITTEE_ACCOUNT, -get_balance(GRAPHENE_COMMITTEE_ACCOUNT,{})); + } + else + { + total_supplies[ asset_id_type(0) ] = GRAPHENE_MAX_SHARE_SUPPLY; + } + + const auto& idx = get_index_type().indices().get(); + auto it = idx.begin(); + bool has_imbalanced_assets = false; + + while( it != idx.end() ) + { + if( it->bitasset_data_id.valid() ) + { + auto supply_itr = total_supplies.find( it->id ); + auto debt_itr = total_debts.find( it->id ); + FC_ASSERT( supply_itr != total_supplies.end() ); + FC_ASSERT( debt_itr != total_debts.end() ); + if( supply_itr->second != debt_itr->second ) + { + has_imbalanced_assets = true; + elog( "Genesis for asset ${aname} is not balanced\n" + " Debt is ${debt}\n" + " Supply is ${supply}\n", + ("aname", it->symbol) + ("debt", debt_itr->second) + ("supply", supply_itr->second) + ); + } + } + ++it; + } + FC_ASSERT( !has_imbalanced_assets ); + + // Save tallied supplies + for( const auto& item : total_supplies ) + { + const auto asset_id = item.first; + const auto total_supply = item.second; + + modify( get( asset_id ), [ & ]( asset_object& asset ) { + modify( get( asset.dynamic_asset_data_id ), [ & ]( asset_dynamic_data_object& asset_data ) { + asset_data.current_supply = total_supply; + } ); + } ); + } + + // Create special witness account + const witness_object& wit = create([&](witness_object& w) {}); + FC_ASSERT( wit.id == GRAPHENE_NULL_WITNESS ); + remove(wit); + + // Create initial witnesses + std::for_each(genesis_state.initial_witness_candidates.begin(), genesis_state.initial_witness_candidates.end(), + [&](const genesis_state_type::initial_witness_type& witness) { + witness_create_operation op; + op.witness_account = get_account_id(witness.owner_name); + op.block_signing_key = witness.block_signing_key; + apply_operation(genesis_eval_state, op); + }); + + // Create initial committee members + std::for_each(genesis_state.initial_committee_candidates.begin(), genesis_state.initial_committee_candidates.end(), + [&](const genesis_state_type::initial_committee_member_type& member) { + committee_member_create_operation op; + op.committee_member_account = get_account_id(member.owner_name); + apply_operation(genesis_eval_state, op); + }); + + // Create initial workers + std::for_each(genesis_state.initial_worker_candidates.begin(), genesis_state.initial_worker_candidates.end(), + [&](const genesis_state_type::initial_worker_type& worker) + { + worker_create_operation op; + op.owner = get_account_id(worker.owner_name); + op.work_begin_date = genesis_state.initial_timestamp; + op.work_end_date = time_point_sec::maximum(); + op.daily_pay = worker.daily_pay; + op.name = "Genesis-Worker-" + worker.owner_name; + op.initializer = vesting_balance_worker_initializer{uint16_t(0)}; + + apply_operation(genesis_eval_state, std::move(op)); + }); + + // Set active witnesses + modify(get_global_properties(), [&genesis_state](global_property_object& p) { + for( uint32_t i = 1; i <= genesis_state.initial_active_witnesses; ++i ) + { + p.active_witnesses.insert(witness_id_type(i)); + } + }); + + // Enable fees + modify(get_global_properties(), [&genesis_state](global_property_object& p) { + p.parameters.get_mutable_fees() = genesis_state.initial_parameters.get_current_fees(); + }); + + // Create witness scheduler + _p_witness_schedule_obj = & create([this]( witness_schedule_object& wso ) + { + for( const witness_id_type& wid : get_global_properties().active_witnesses ) + wso.current_shuffled_witnesses.push_back( wid ); + }); + + // Create FBA counters + create([&]( fba_accumulator_object& acc ) + { + FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_to_blind ) ); + acc.accumulated_fba_fees = 0; +#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET + acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; +#endif + }); + + create([&]( fba_accumulator_object& acc ) + { + FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_blind_transfer ) ); + acc.accumulated_fba_fees = 0; +#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET + acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; +#endif + }); + + create([&]( fba_accumulator_object& acc ) + { + FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_from_blind ) ); + acc.accumulated_fba_fees = 0; +#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET + acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; +#endif + }); + + FC_ASSERT( get_index().get_next_id() == fba_accumulator_id_type( fba_accumulator_id_count ) ); + + //debug_dump(); + + _undo_db.enable(); +} FC_CAPTURE_AND_RETHROW() } + +} } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 9114657041..d90baf4f67 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -23,7 +23,6 @@ */ #include -#include #include #include @@ -72,10 +71,6 @@ #include #include -#include - -#include - namespace graphene { namespace chain { void database::initialize_evaluators() @@ -192,509 +187,4 @@ void database::initialize_indexes() add_index< primary_index< simple_index< fba_accumulator_object > > >(); } -void database::init_genesis(const genesis_state_type& genesis_state) -{ try { - FC_ASSERT( genesis_state.initial_timestamp != time_point_sec(), "Must initialize genesis timestamp." ); - FC_ASSERT( genesis_state.initial_timestamp.sec_since_epoch() % GRAPHENE_DEFAULT_BLOCK_INTERVAL == 0, - "Genesis timestamp must be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL." ); - FC_ASSERT(genesis_state.initial_witness_candidates.size() > 0, - "Cannot start a chain with zero witnesses."); - FC_ASSERT(genesis_state.initial_active_witnesses <= genesis_state.initial_witness_candidates.size(), - "initial_active_witnesses is larger than the number of candidate witnesses."); - - _undo_db.disable(); - struct auth_inhibitor { - auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags) - { db.node_properties().skip_flags |= skip_transaction_signatures; } - ~auth_inhibitor() - { db.node_properties().skip_flags = old_flags; } - private: - database& db; - uint32_t old_flags; - } inhibitor(*this); - - transaction_evaluation_state genesis_eval_state(this); - - // Create blockchain accounts - fc::ecc::private_key null_private_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key"))); - create([](account_balance_object& b) { - b.balance = GRAPHENE_MAX_SHARE_SUPPLY; - }); - const account_object& committee_account = - create( [&](account_object& n) { - n.membership_expiration_date = time_point_sec::maximum(); - n.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - n.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - n.owner.weight_threshold = 1; - n.active.weight_threshold = 1; - n.name = "committee-account"; - n.statistics = create( [&n](account_statistics_object& s){ - s.owner = n.id; - s.name = n.name; - s.core_in_balance = GRAPHENE_MAX_SHARE_SUPPLY; - }).id; - }); - FC_ASSERT(committee_account.get_id() == GRAPHENE_COMMITTEE_ACCOUNT); - FC_ASSERT(create([this](account_object& a) { - a.name = "witness-account"; - a.statistics = create([&a](account_statistics_object& s){ - s.owner = a.id; - s.name = a.name; - }).id; - a.owner.weight_threshold = 1; - a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_WITNESS_ACCOUNT; - a.membership_expiration_date = time_point_sec::maximum(); - a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - }).get_id() == GRAPHENE_WITNESS_ACCOUNT); - FC_ASSERT(create([this](account_object& a) { - a.name = "relaxed-committee-account"; - a.statistics = create([&a](account_statistics_object& s){ - s.owner = a.id; - s.name = a.name; - }).id; - a.owner.weight_threshold = 1; - a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT; - a.membership_expiration_date = time_point_sec::maximum(); - a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - }).get_id() == GRAPHENE_RELAXED_COMMITTEE_ACCOUNT); - FC_ASSERT(create([this](account_object& a) { - a.name = "null-account"; - a.statistics = create([&a](account_statistics_object& s){ - s.owner = a.id; - s.name = a.name; - }).id; - a.owner.weight_threshold = 1; - a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_NULL_ACCOUNT; - a.membership_expiration_date = time_point_sec::maximum(); - a.network_fee_percentage = 0; - a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; - }).get_id() == GRAPHENE_NULL_ACCOUNT); - FC_ASSERT(create([this](account_object& a) { - a.name = "temp-account"; - a.statistics = create([&a](account_statistics_object& s){ - s.owner = a.id; - s.name = a.name; - }).id; - a.owner.weight_threshold = 0; - a.active.weight_threshold = 0; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_TEMP_ACCOUNT; - a.membership_expiration_date = time_point_sec::maximum(); - a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - }).get_id() == GRAPHENE_TEMP_ACCOUNT); - FC_ASSERT(create([this](account_object& a) { - a.name = "proxy-to-self"; - a.statistics = create([&a](account_statistics_object& s){ - s.owner = a.id; - s.name = a.name; - }).id; - a.owner.weight_threshold = 1; - a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_NULL_ACCOUNT; - a.membership_expiration_date = time_point_sec::maximum(); - a.network_fee_percentage = 0; - a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; - }).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT); - - // Create more special accounts - while( true ) - { - uint64_t id = get_index().get_next_id().instance(); - if( id >= genesis_state.immutable_parameters.num_special_accounts ) - break; - const account_object& acct = create([this,id](account_object& a) { - a.name = "special-account-" + std::to_string(id); - a.statistics = create([&a](account_statistics_object& s){ - s.owner = a.id; - s.name = a.name; - }).id; - a.owner.weight_threshold = 1; - a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = account_id_type(id); - a.membership_expiration_date = time_point_sec::maximum(); - a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; - }); - FC_ASSERT( acct.get_id() == account_id_type(id) ); - remove( acct ); - } - - // Create core asset - const asset_dynamic_data_object& dyn_asset = - create([](asset_dynamic_data_object& a) { - a.current_supply = GRAPHENE_MAX_SHARE_SUPPLY; - }); - const asset_object& core_asset = - create( [&genesis_state,&dyn_asset]( asset_object& a ) { - a.symbol = GRAPHENE_SYMBOL; - a.options.max_supply = genesis_state.max_core_supply; - a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS; - a.options.flags = 0; - a.options.issuer_permissions = 0; - a.issuer = GRAPHENE_NULL_ACCOUNT; - a.options.core_exchange_rate.base.amount = 1; - a.options.core_exchange_rate.base.asset_id = asset_id_type(0); - a.options.core_exchange_rate.quote.amount = 1; - a.options.core_exchange_rate.quote.asset_id = asset_id_type(0); - a.dynamic_asset_data_id = dyn_asset.id; - }); - FC_ASSERT( dyn_asset.id == asset_dynamic_data_id_type() ); - FC_ASSERT( asset_id_type(core_asset.id) == asset().asset_id ); - FC_ASSERT( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) ); - _p_core_asset_obj = &core_asset; - _p_core_dynamic_data_obj = &dyn_asset; - // Create more special assets - while( true ) - { - uint64_t id = get_index().get_next_id().instance(); - if( id >= genesis_state.immutable_parameters.num_special_assets ) - break; - const asset_dynamic_data_object& dyn_asset = - create([](asset_dynamic_data_object& a) { - a.current_supply = 0; - }); - const asset_object& asset_obj = create( [id,&dyn_asset]( asset_object& a ) { - a.symbol = "SPECIAL" + std::to_string( id ); - a.options.max_supply = 0; - a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS; - a.options.flags = 0; - a.options.issuer_permissions = 0; - a.issuer = GRAPHENE_NULL_ACCOUNT; - a.options.core_exchange_rate.base.amount = 1; - a.options.core_exchange_rate.base.asset_id = asset_id_type(0); - a.options.core_exchange_rate.quote.amount = 1; - a.options.core_exchange_rate.quote.asset_id = asset_id_type(0); - a.dynamic_asset_data_id = dyn_asset.id; - }); - FC_ASSERT( asset_obj.get_id() == asset_id_type(id) ); - remove( asset_obj ); - } - - chain_id_type chain_id = genesis_state.compute_chain_id(); - - // Create global properties - _p_global_prop_obj = & create([&genesis_state](global_property_object& p) { - p.parameters = genesis_state.initial_parameters; - // Set fees to zero initially, so that genesis initialization needs not pay them - // We'll fix it at the end of the function - p.parameters.get_mutable_fees().zero_all_fees(); - - }); - _p_dyn_global_prop_obj = & create([&genesis_state](dynamic_global_property_object& p) { - p.time = genesis_state.initial_timestamp; - p.dynamic_flags = 0; - p.witness_budget = 0; - p.recent_slots_filled = std::numeric_limits::max(); - }); - - FC_ASSERT( (genesis_state.immutable_parameters.min_witness_count & 1) == 1, "min_witness_count must be odd" ); - FC_ASSERT( (genesis_state.immutable_parameters.min_committee_member_count & 1) == 1, "min_committee_member_count must be odd" ); - - _p_chain_property_obj = & create([chain_id,&genesis_state](chain_property_object& p) - { - p.chain_id = chain_id; - p.immutable_parameters = genesis_state.immutable_parameters; - } ); - for (uint32_t i = 0; i <= 0x10000; i++) - create( [&]( block_summary_object&) {}); - - // Create initial accounts - for( const auto& account : genesis_state.initial_accounts ) - { - account_create_operation cop; - cop.name = account.name; - cop.registrar = GRAPHENE_TEMP_ACCOUNT; - cop.owner = authority(1, account.owner_key, 1); - if( account.active_key == public_key_type() ) - { - cop.active = cop.owner; - cop.options.memo_key = account.owner_key; - } - else - { - cop.active = authority(1, account.active_key, 1); - cop.options.memo_key = account.active_key; - } - account_id_type account_id(apply_operation(genesis_eval_state, cop).get()); - - if( account.is_lifetime_member ) - { - account_upgrade_operation op; - op.account_to_upgrade = account_id; - op.upgrade_to_lifetime_member = true; - apply_operation(genesis_eval_state, op); - } - } - - // Helper function to get account ID by name - const auto& accounts_by_name = get_index_type().indices().get(); - auto get_account_id = [&accounts_by_name](const string& name) { - auto itr = accounts_by_name.find(name); - FC_ASSERT(itr != accounts_by_name.end(), - "Unable to find account '${acct}'. Did you forget to add a record for it to initial_accounts?", - ("acct", name)); - return itr->get_id(); - }; - - // Helper function to get asset ID by symbol - const auto& assets_by_symbol = get_index_type().indices().get(); - const auto get_asset_id = [&assets_by_symbol](const string& symbol) { - auto itr = assets_by_symbol.find(symbol); - FC_ASSERT(itr != assets_by_symbol.end(), - "Unable to find asset '${sym}'. Did you forget to add a record for it to initial_assets?", - ("sym", symbol)); - return itr->get_id(); - }; - - map total_supplies; - map total_debts; - - // Create initial assets - for( const genesis_state_type::initial_asset_type& asset : genesis_state.initial_assets ) - { - asset_id_type new_asset_id = get_index_type().get_next_id(); - total_supplies[ new_asset_id ] = 0; - - asset_dynamic_data_id_type dynamic_data_id; - optional bitasset_data_id; - if( asset.is_bitasset ) - { - int collateral_holder_number = 0; - total_debts[ new_asset_id ] = 0; - for( const auto& collateral_rec : asset.collateral_records ) - { - account_create_operation cop; - cop.name = asset.symbol + "-collateral-holder-" + std::to_string(collateral_holder_number); - boost::algorithm::to_lower(cop.name); - cop.registrar = GRAPHENE_TEMP_ACCOUNT; - cop.owner = authority(1, collateral_rec.owner, 1); - cop.active = cop.owner; - account_id_type owner_account_id = apply_operation(genesis_eval_state, cop).get(); - - modify( owner_account_id(*this).statistics(*this), [&collateral_rec]( account_statistics_object& o ) { - o.total_core_in_orders = collateral_rec.collateral; - }); - - create([&](call_order_object& c) { - c.borrower = owner_account_id; - c.collateral = collateral_rec.collateral; - c.debt = collateral_rec.debt; - c.call_price = price::call_price(chain::asset(c.debt, new_asset_id), - chain::asset(c.collateral, core_asset.id), - GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO); - }); - - total_supplies[ asset_id_type(0) ] += collateral_rec.collateral; - total_debts[ new_asset_id ] += collateral_rec.debt; - ++collateral_holder_number; - } - - bitasset_data_id = create([&core_asset,new_asset_id](asset_bitasset_data_object& b) { - b.options.short_backing_asset = core_asset.id; - b.options.minimum_feeds = GRAPHENE_DEFAULT_MINIMUM_FEEDS; - b.asset_id = new_asset_id; - }).id; - } - - dynamic_data_id = create([&asset](asset_dynamic_data_object& d) { - d.accumulated_fees = asset.accumulated_fees; - }).id; - - total_supplies[ new_asset_id ] += asset.accumulated_fees; - - create([&](asset_object& a) { - a.symbol = asset.symbol; - a.options.description = asset.description; - a.precision = asset.precision; - string issuer_name = asset.issuer_name; - a.issuer = get_account_id(issuer_name); - a.options.max_supply = asset.max_supply; - a.options.flags = witness_fed_asset; - a.options.issuer_permissions = charge_market_fee | override_authority | white_list | transfer_restricted | disable_confidential | - ( asset.is_bitasset ? disable_force_settle | global_settle | witness_fed_asset | committee_fed_asset : 0 ); - a.dynamic_asset_data_id = dynamic_data_id; - a.bitasset_data_id = bitasset_data_id; - }); - } - - // Create initial balances - share_type total_allocation; - for( const auto& handout : genesis_state.initial_balances ) - { - const auto asset_id = get_asset_id(handout.asset_symbol); - create([&handout,total_allocation,asset_id](balance_object& b) { - b.balance = asset(handout.amount, asset_id); - b.owner = handout.owner; - }); - - total_supplies[ asset_id ] += handout.amount; - } - - // Create initial vesting balances - for( const genesis_state_type::initial_vesting_balance_type& vest : genesis_state.initial_vesting_balances ) - { - const auto asset_id = get_asset_id(vest.asset_symbol); - create([&](balance_object& b) { - b.owner = vest.owner; - b.balance = asset(vest.amount, asset_id); - - linear_vesting_policy policy; - policy.begin_timestamp = vest.begin_timestamp; - policy.vesting_cliff_seconds = 0; - policy.vesting_duration_seconds = vest.vesting_duration_seconds; - policy.begin_balance = vest.begin_balance; - - b.vesting_policy = std::move(policy); - }); - - total_supplies[ asset_id ] += vest.amount; - } - - if( total_supplies[ asset_id_type(0) ] > 0 ) - { - adjust_balance(GRAPHENE_COMMITTEE_ACCOUNT, -get_balance(GRAPHENE_COMMITTEE_ACCOUNT,{})); - } - else - { - total_supplies[ asset_id_type(0) ] = GRAPHENE_MAX_SHARE_SUPPLY; - } - - const auto& idx = get_index_type().indices().get(); - auto it = idx.begin(); - bool has_imbalanced_assets = false; - - while( it != idx.end() ) - { - if( it->bitasset_data_id.valid() ) - { - auto supply_itr = total_supplies.find( it->id ); - auto debt_itr = total_debts.find( it->id ); - FC_ASSERT( supply_itr != total_supplies.end() ); - FC_ASSERT( debt_itr != total_debts.end() ); - if( supply_itr->second != debt_itr->second ) - { - has_imbalanced_assets = true; - elog( "Genesis for asset ${aname} is not balanced\n" - " Debt is ${debt}\n" - " Supply is ${supply}\n", - ("aname", it->symbol) - ("debt", debt_itr->second) - ("supply", supply_itr->second) - ); - } - } - ++it; - } - FC_ASSERT( !has_imbalanced_assets ); - - // Save tallied supplies - for( const auto& item : total_supplies ) - { - const auto asset_id = item.first; - const auto total_supply = item.second; - - modify( get( asset_id ), [ & ]( asset_object& asset ) { - modify( get( asset.dynamic_asset_data_id ), [ & ]( asset_dynamic_data_object& asset_data ) { - asset_data.current_supply = total_supply; - } ); - } ); - } - - // Create special witness account - const witness_object& wit = create([&](witness_object& w) {}); - FC_ASSERT( wit.id == GRAPHENE_NULL_WITNESS ); - remove(wit); - - // Create initial witnesses - std::for_each(genesis_state.initial_witness_candidates.begin(), genesis_state.initial_witness_candidates.end(), - [&](const genesis_state_type::initial_witness_type& witness) { - witness_create_operation op; - op.witness_account = get_account_id(witness.owner_name); - op.block_signing_key = witness.block_signing_key; - apply_operation(genesis_eval_state, op); - }); - - // Create initial committee members - std::for_each(genesis_state.initial_committee_candidates.begin(), genesis_state.initial_committee_candidates.end(), - [&](const genesis_state_type::initial_committee_member_type& member) { - committee_member_create_operation op; - op.committee_member_account = get_account_id(member.owner_name); - apply_operation(genesis_eval_state, op); - }); - - // Create initial workers - std::for_each(genesis_state.initial_worker_candidates.begin(), genesis_state.initial_worker_candidates.end(), - [&](const genesis_state_type::initial_worker_type& worker) - { - worker_create_operation op; - op.owner = get_account_id(worker.owner_name); - op.work_begin_date = genesis_state.initial_timestamp; - op.work_end_date = time_point_sec::maximum(); - op.daily_pay = worker.daily_pay; - op.name = "Genesis-Worker-" + worker.owner_name; - op.initializer = vesting_balance_worker_initializer{uint16_t(0)}; - - apply_operation(genesis_eval_state, std::move(op)); - }); - - // Set active witnesses - modify(get_global_properties(), [&genesis_state](global_property_object& p) { - for( uint32_t i = 1; i <= genesis_state.initial_active_witnesses; ++i ) - { - p.active_witnesses.insert(witness_id_type(i)); - } - }); - - // Enable fees - modify(get_global_properties(), [&genesis_state](global_property_object& p) { - p.parameters.get_mutable_fees() = genesis_state.initial_parameters.get_current_fees(); - }); - - // Create witness scheduler - _p_witness_schedule_obj = & create([this]( witness_schedule_object& wso ) - { - for( const witness_id_type& wid : get_global_properties().active_witnesses ) - wso.current_shuffled_witnesses.push_back( wid ); - }); - - // Create FBA counters - create([&]( fba_accumulator_object& acc ) - { - FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_to_blind ) ); - acc.accumulated_fba_fees = 0; -#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET - acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; -#endif - }); - - create([&]( fba_accumulator_object& acc ) - { - FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_blind_transfer ) ); - acc.accumulated_fba_fees = 0; -#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET - acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; -#endif - }); - - create([&]( fba_accumulator_object& acc ) - { - FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_from_blind ) ); - acc.accumulated_fba_fees = 0; -#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET - acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; -#endif - }); - - FC_ASSERT( get_index().get_next_id() == fba_accumulator_id_type( fba_accumulator_id_count ) ); - - //debug_dump(); - - _undo_db.enable(); -} FC_CAPTURE_AND_RETHROW() } - } } From 16c0ef0532577fb586b5f560ea210ff8b424dfb2 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 21:33:23 +0000 Subject: [PATCH 019/258] Remove unnecessary using-directive --- libraries/chain/include/graphene/chain/samet_fund_object.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/chain/include/graphene/chain/samet_fund_object.hpp b/libraries/chain/include/graphene/chain/samet_fund_object.hpp index 124155bddb..d26d648065 100644 --- a/libraries/chain/include/graphene/chain/samet_fund_object.hpp +++ b/libraries/chain/include/graphene/chain/samet_fund_object.hpp @@ -30,8 +30,6 @@ namespace graphene { namespace chain { -using namespace graphene::db; - /** * @brief A SameT Fund is a fund which can be used by a borrower and have to be repaid in the same transaction * @ingroup object From 24ee4ccf61c2865b988b6a4e90047f8ec50a8c13 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 21:59:03 +0000 Subject: [PATCH 020/258] Fix code smells --- libraries/chain/proposal_evaluator.cpp | 28 +++++++++---------- libraries/chain/samet_fund_evaluator.cpp | 4 +-- .../include/graphene/protocol/base.hpp | 8 +++--- .../include/graphene/protocol/samet_fund.hpp | 10 +++---- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 95570a1ea7..d8f158d107 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -185,10 +185,10 @@ struct proposal_operation_hardfork_visitor "HASH160 unavailable until after HARDFORK BSIP64" ); } } - void operator()(const graphene::chain::htlc_redeem_operation &op) const { + void operator()(const graphene::chain::htlc_redeem_operation&) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); } - void operator()(const graphene::chain::htlc_extend_operation &op) const { + void operator()(const graphene::chain::htlc_extend_operation&) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); } void operator()(const graphene::chain::custom_authority_create_operation&) const { @@ -200,40 +200,40 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::custom_authority_delete_operation&) const { FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" ); } - void operator()(const graphene::chain::ticket_create_operation &op) const { + void operator()(const graphene::chain::ticket_create_operation&) const { FC_ASSERT( HARDFORK_CORE_2103_PASSED(block_time), "Not allowed until hardfork 2103" ); } - void operator()(const graphene::chain::ticket_update_operation &op) const { + void operator()(const graphene::chain::ticket_update_operation&) const { FC_ASSERT( HARDFORK_CORE_2103_PASSED(block_time), "Not allowed until hardfork 2103" ); } - void operator()(const graphene::chain::liquidity_pool_create_operation &op) const { + void operator()(const graphene::chain::liquidity_pool_create_operation&) const { FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" ); } - void operator()(const graphene::chain::liquidity_pool_delete_operation &op) const { + void operator()(const graphene::chain::liquidity_pool_delete_operation&) const { FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" ); } - void operator()(const graphene::chain::liquidity_pool_deposit_operation &op) const { + void operator()(const graphene::chain::liquidity_pool_deposit_operation&) const { FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" ); } - void operator()(const graphene::chain::liquidity_pool_withdraw_operation &op) const { + void operator()(const graphene::chain::liquidity_pool_withdraw_operation&) const { FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" ); } - void operator()(const graphene::chain::liquidity_pool_exchange_operation &op) const { + void operator()(const graphene::chain::liquidity_pool_exchange_operation&) const { FC_ASSERT( HARDFORK_LIQUIDITY_POOL_PASSED(block_time), "Not allowed until the LP hardfork" ); } - void operator()(const graphene::chain::samet_fund_create_operation &op) const { + void operator()(const graphene::chain::samet_fund_create_operation&) const { FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); } - void operator()(const graphene::chain::samet_fund_delete_operation &op) const { + void operator()(const graphene::chain::samet_fund_delete_operation&) const { FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); } - void operator()(const graphene::chain::samet_fund_update_operation &op) const { + void operator()(const graphene::chain::samet_fund_update_operation&) const { FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); } - void operator()(const graphene::chain::samet_fund_borrow_operation &op) const { + void operator()(const graphene::chain::samet_fund_borrow_operation&) const { FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); } - void operator()(const graphene::chain::samet_fund_repay_operation &op) const { + void operator()(const graphene::chain::samet_fund_repay_operation&) const { FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); } diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp index ff432a06f8..2c9ea8c61f 100644 --- a/libraries/chain/samet_fund_evaluator.cpp +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -192,8 +192,8 @@ void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_opera FC_ASSERT( op.repay_amount.amount <= _fund->unpaid_amount, "Repay amount should not be greater than unpaid amount" ); - auto required_fee = ( fc::uint128_t( op.repay_amount.amount.value ) + GRAPHENE_SAMET_FUND_FEE_DENOM - 1 ) - * _fund->fee_rate / GRAPHENE_SAMET_FUND_FEE_DENOM; // Round up + auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) + GRAPHENE_SAMET_FUND_FEE_DENOM ) - 1 ) + * _fund->fee_rate ) / GRAPHENE_SAMET_FUND_FEE_DENOM; // Round up FC_ASSERT( fc::uint128_t(op.fund_fee.amount.value) >= required_fee, "Insuffient fund fee, requires ${r}, offered ${p}", diff --git a/libraries/protocol/include/graphene/protocol/base.hpp b/libraries/protocol/include/graphene/protocol/base.hpp index 9297c3e878..53f956d950 100644 --- a/libraries/protocol/include/graphene/protocol/base.hpp +++ b/libraries/protocol/include/graphene/protocol/base.hpp @@ -114,10 +114,10 @@ namespace graphene { namespace protocol { { return params.fee; } - void get_required_authorities( vector& )const{} - void get_required_active_authorities( flat_set& )const{} - void get_required_owner_authorities( flat_set& )const{} - void validate()const{} + virtual void get_required_authorities( vector& )const{} + virtual void get_required_active_authorities( flat_set& )const{} + virtual void get_required_owner_authorities( flat_set& )const{} + virtual void validate()const{} fc::optional< fc::future > validate_parallel( uint32_t skip )const; static uint64_t calculate_data_fee( uint64_t bytes, uint64_t price_per_kbyte ); diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp index 625834bb01..d384800565 100644 --- a/libraries/protocol/include/graphene/protocol/samet_fund.hpp +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -46,7 +46,7 @@ namespace graphene { namespace protocol { extensions_type extensions; ///< Unused. Reserved for future use. account_id_type fee_payer()const { return owner_account; } - void validate()const; + void validate()const override; }; /** @@ -64,7 +64,7 @@ namespace graphene { namespace protocol { extensions_type extensions; ///< Unused. Reserved for future use. account_id_type fee_payer()const { return owner_account; } - void validate()const; + void validate()const override; }; /** @@ -84,7 +84,7 @@ namespace graphene { namespace protocol { extensions_type extensions; ///< Unused. Reserved for future use. account_id_type fee_payer()const { return owner_account; } - void validate()const; + void validate()const override; }; /** @@ -103,7 +103,7 @@ namespace graphene { namespace protocol { extensions_type extensions; ///< Unused. Reserved for future use. account_id_type fee_payer()const { return borrower; } - void validate()const; + void validate()const override; }; /** @@ -123,7 +123,7 @@ namespace graphene { namespace protocol { extensions_type extensions; ///< Unused. Reserved for future use. account_id_type fee_payer()const { return account; } - void validate()const; + void validate()const override; }; } } // graphene::protocol From d92b1ef28998637b89a01fe2b6774972eb94caa7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 22:20:58 +0000 Subject: [PATCH 021/258] Rename long function names --- .../protocol/custom_authorities/list_1.cpp | 2 +- .../protocol/custom_authorities/list_10.cpp | 2 +- .../protocol/custom_authorities/list_11.cpp | 2 +- .../protocol/custom_authorities/list_12.cpp | 2 +- .../protocol/custom_authorities/list_13.cpp | 2 +- .../protocol/custom_authorities/list_14.cpp | 2 +- .../protocol/custom_authorities/list_2.cpp | 2 +- .../protocol/custom_authorities/list_3.cpp | 2 +- .../protocol/custom_authorities/list_4.cpp | 2 +- .../protocol/custom_authorities/list_5.cpp | 2 +- .../protocol/custom_authorities/list_6.cpp | 2 +- .../protocol/custom_authorities/list_7.cpp | 2 +- .../protocol/custom_authorities/list_8.cpp | 2 +- .../protocol/custom_authorities/list_9.cpp | 2 +- .../restriction_predicate.cpp | 28 +++++++++---------- .../custom_authorities/sliced_lists.hxx | 28 +++++++++---------- 16 files changed, 42 insertions(+), 42 deletions(-) diff --git a/libraries/protocol/custom_authorities/list_1.cpp b/libraries/protocol/custom_authorities/list_1.cpp index 7102fe2ecc..4c185e192d 100644 --- a/libraries/protocol/custom_authorities/list_1.cpp +++ b/libraries/protocol/custom_authorities/list_1.cpp @@ -28,7 +28,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_1(size_t idx, vector rs) { +result_type get_restriction_pred_list_1(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_1::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_10.cpp b/libraries/protocol/custom_authorities/list_10.cpp index 6bcd0a9a10..cfeaba7a73 100644 --- a/libraries/protocol/custom_authorities/list_10.cpp +++ b/libraries/protocol/custom_authorities/list_10.cpp @@ -28,7 +28,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_10(size_t idx, vector rs) { +result_type get_restriction_pred_list_10(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_10::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_11.cpp b/libraries/protocol/custom_authorities/list_11.cpp index d626102113..3b51666b5d 100644 --- a/libraries/protocol/custom_authorities/list_11.cpp +++ b/libraries/protocol/custom_authorities/list_11.cpp @@ -28,7 +28,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_11(size_t idx, vector rs) { +result_type get_restriction_pred_list_11(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_11::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_12.cpp b/libraries/protocol/custom_authorities/list_12.cpp index 9a4cee9c2f..267d6fc6da 100644 --- a/libraries/protocol/custom_authorities/list_12.cpp +++ b/libraries/protocol/custom_authorities/list_12.cpp @@ -28,7 +28,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_12(size_t idx, vector rs) { +result_type get_restriction_pred_list_12(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_12::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_13.cpp b/libraries/protocol/custom_authorities/list_13.cpp index 8f77ec6ccd..7e2afd1ff6 100644 --- a/libraries/protocol/custom_authorities/list_13.cpp +++ b/libraries/protocol/custom_authorities/list_13.cpp @@ -28,7 +28,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_13(size_t idx, vector rs) { +result_type get_restriction_pred_list_13(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_13::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_14.cpp b/libraries/protocol/custom_authorities/list_14.cpp index b863cf8ed8..d1e1d67535 100644 --- a/libraries/protocol/custom_authorities/list_14.cpp +++ b/libraries/protocol/custom_authorities/list_14.cpp @@ -28,7 +28,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_14(size_t idx, vector rs) { +result_type get_restriction_pred_list_14(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_14::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_2.cpp b/libraries/protocol/custom_authorities/list_2.cpp index c42458dcf1..62bc7abfc3 100644 --- a/libraries/protocol/custom_authorities/list_2.cpp +++ b/libraries/protocol/custom_authorities/list_2.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_2(size_t idx, vector rs) { +result_type get_restriction_pred_list_2(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_2::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_3.cpp b/libraries/protocol/custom_authorities/list_3.cpp index 9158d6e003..e23db34f42 100644 --- a/libraries/protocol/custom_authorities/list_3.cpp +++ b/libraries/protocol/custom_authorities/list_3.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_3(size_t idx, vector rs) { +result_type get_restriction_pred_list_3(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_3::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_4.cpp b/libraries/protocol/custom_authorities/list_4.cpp index 2ff2fefb64..32f094585b 100644 --- a/libraries/protocol/custom_authorities/list_4.cpp +++ b/libraries/protocol/custom_authorities/list_4.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_4(size_t idx, vector rs) { +result_type get_restriction_pred_list_4(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_4::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_5.cpp b/libraries/protocol/custom_authorities/list_5.cpp index a1b93b9a44..d67b9997ad 100644 --- a/libraries/protocol/custom_authorities/list_5.cpp +++ b/libraries/protocol/custom_authorities/list_5.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_5(size_t idx, vector rs) { +result_type get_restriction_pred_list_5(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_5::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_6.cpp b/libraries/protocol/custom_authorities/list_6.cpp index e5d5962c0e..872c490732 100644 --- a/libraries/protocol/custom_authorities/list_6.cpp +++ b/libraries/protocol/custom_authorities/list_6.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_6(size_t idx, vector rs) { +result_type get_restriction_pred_list_6(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_6::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_7.cpp b/libraries/protocol/custom_authorities/list_7.cpp index 9ac5c1bfcc..2cd386d5cc 100644 --- a/libraries/protocol/custom_authorities/list_7.cpp +++ b/libraries/protocol/custom_authorities/list_7.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_7(size_t idx, vector rs) { +result_type get_restriction_pred_list_7(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_7::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_8.cpp b/libraries/protocol/custom_authorities/list_8.cpp index 32774e285b..a0b3b34289 100644 --- a/libraries/protocol/custom_authorities/list_8.cpp +++ b/libraries/protocol/custom_authorities/list_8.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_8(size_t idx, vector rs) { +result_type get_restriction_pred_list_8(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_8::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/list_9.cpp b/libraries/protocol/custom_authorities/list_9.cpp index 27b2e069dd..4f1e2a7524 100644 --- a/libraries/protocol/custom_authorities/list_9.cpp +++ b/libraries/protocol/custom_authorities/list_9.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; -result_type get_restriction_predicate_list_9(size_t idx, vector rs) { +result_type get_restriction_pred_list_9(size_t idx, vector rs) { return typelist::runtime::dispatch(operation_list_9::list(), idx, [&rs] (auto t) -> result_type { using Op = typename decltype(t)::type; return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { diff --git a/libraries/protocol/custom_authorities/restriction_predicate.cpp b/libraries/protocol/custom_authorities/restriction_predicate.cpp index c0e177fd1a..f42ad6a016 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.cpp +++ b/libraries/protocol/custom_authorities/restriction_predicate.cpp @@ -33,33 +33,33 @@ restriction_predicate_function get_restriction_predicate(vector rs, auto f = typelist::runtime::dispatch(operation::list(), op_type, [&rs](auto t) -> restriction_predicate_function { using Op = typename decltype(t)::type; if (typelist::contains()) - return get_restriction_predicate_list_1(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_1(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_2(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_2(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_3(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_3(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_4(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_4(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_5(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_5(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_6(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_6(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_7(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_7(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_8(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_8(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_9(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_9(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_10(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_10(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_11(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_11(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_12(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_12(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_13(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_13(typelist::index_of(), std::move(rs)); if (typelist::contains()) - return get_restriction_predicate_list_14(typelist::index_of(), std::move(rs)); + return get_restriction_pred_list_14(typelist::index_of(), std::move(rs)); if (typelist::contains()) FC_THROW_EXCEPTION( fc::assert_exception, "Virtual operations not allowed!" ); diff --git a/libraries/protocol/custom_authorities/sliced_lists.hxx b/libraries/protocol/custom_authorities/sliced_lists.hxx index 9fbbc5afdc..ed0b42eed5 100644 --- a/libraries/protocol/custom_authorities/sliced_lists.hxx +++ b/libraries/protocol/custom_authorities/sliced_lists.hxx @@ -57,19 +57,19 @@ using virtual_operations_list = static_variant; -object_restriction_predicate get_restriction_predicate_list_1(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_2(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_3(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_4(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_5(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_6(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_7(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_8(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_9(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_10(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_11(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_12(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_13(size_t idx, vector rs); -object_restriction_predicate get_restriction_predicate_list_14(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_1(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_2(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_3(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_4(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_5(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_6(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_7(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_8(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_9(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_10(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_11(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_12(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_13(size_t idx, vector rs); +object_restriction_predicate get_restriction_pred_list_14(size_t idx, vector rs); } } // namespace graphene::protocol From 2b96d68cf97e2f540fe9a523d667406d8ad6a6ee Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 22:24:00 +0000 Subject: [PATCH 022/258] Add const qualifier to some member functions --- .../include/graphene/chain/samet_fund_evaluator.hpp | 12 ++++++------ libraries/chain/samet_fund_evaluator.cpp | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp index f2ece58763..39b75ef172 100644 --- a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp @@ -37,8 +37,8 @@ namespace graphene { namespace chain { public: using operation_type = samet_fund_create_operation; - void_result do_evaluate( const samet_fund_create_operation& op ); - object_id_type do_apply( const samet_fund_create_operation& op ); + void_result do_evaluate( const samet_fund_create_operation& op ) const; + object_id_type do_apply( const samet_fund_create_operation& op ) const; }; class samet_fund_delete_evaluator : public evaluator @@ -47,7 +47,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_delete_operation; void_result do_evaluate( const samet_fund_delete_operation& op ); - void_result do_apply( const samet_fund_delete_operation& op ); + void_result do_apply( const samet_fund_delete_operation& op ) const; const samet_fund_object* _fund = nullptr; }; @@ -58,7 +58,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_update_operation; void_result do_evaluate( const samet_fund_update_operation& op ); - void_result do_apply( const samet_fund_update_operation& op ); + void_result do_apply( const samet_fund_update_operation& op ) const; const samet_fund_object* _fund = nullptr; }; @@ -69,7 +69,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_borrow_operation; void_result do_evaluate( const samet_fund_borrow_operation& op ); - void_result do_apply( const samet_fund_borrow_operation& op ); + void_result do_apply( const samet_fund_borrow_operation& op ) const; const samet_fund_object* _fund = nullptr; }; @@ -80,7 +80,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_repay_operation; void_result do_evaluate( const samet_fund_repay_operation& op ); - void_result do_apply( const samet_fund_repay_operation& op ); + void_result do_apply( const samet_fund_repay_operation& op ) const; const samet_fund_object* _fund = nullptr; }; diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp index 2c9ea8c61f..2f59c0978e 100644 --- a/libraries/chain/samet_fund_evaluator.cpp +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -35,7 +35,7 @@ namespace graphene { namespace chain { -void_result samet_fund_create_evaluator::do_evaluate(const samet_fund_create_operation& op) +void_result samet_fund_create_evaluator::do_evaluate(const samet_fund_create_operation& op) const { try { const database& d = db(); const auto block_time = d.head_block_time(); @@ -48,7 +48,7 @@ void_result samet_fund_create_evaluator::do_evaluate(const samet_fund_create_ope return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -object_id_type samet_fund_create_evaluator::do_apply(const samet_fund_create_operation& op) +object_id_type samet_fund_create_evaluator::do_apply(const samet_fund_create_operation& op) const { try { database& d = db(); @@ -79,7 +79,7 @@ void_result samet_fund_delete_evaluator::do_evaluate(const samet_fund_delete_ope return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result samet_fund_delete_evaluator::do_apply(const samet_fund_delete_operation& op) +void_result samet_fund_delete_evaluator::do_apply(const samet_fund_delete_operation& op) const { try { database& d = db(); @@ -125,7 +125,7 @@ void_result samet_fund_update_evaluator::do_evaluate(const samet_fund_update_ope return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result samet_fund_update_evaluator::do_apply( const samet_fund_update_operation& op) +void_result samet_fund_update_evaluator::do_apply( const samet_fund_update_operation& op) const { try { database& d = db(); @@ -162,7 +162,7 @@ void_result samet_fund_borrow_evaluator::do_evaluate(const samet_fund_borrow_ope return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_operation& op) +void_result samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_operation& op) const { try { database& d = db(); @@ -202,7 +202,7 @@ void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_opera return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operation& op) +void_result samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operation& op) const { try { database& d = db(); From a68796b645bad7ebe4c4865d8d798016da09da42 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 5 Jun 2021 22:29:08 +0000 Subject: [PATCH 023/258] Make the return types of lambda functions implicit --- libraries/protocol/custom_authorities/list_1.cpp | 5 +++-- libraries/protocol/custom_authorities/list_10.cpp | 5 +++-- libraries/protocol/custom_authorities/list_11.cpp | 5 +++-- libraries/protocol/custom_authorities/list_12.cpp | 5 +++-- libraries/protocol/custom_authorities/list_13.cpp | 5 +++-- libraries/protocol/custom_authorities/list_14.cpp | 5 +++-- libraries/protocol/custom_authorities/list_2.cpp | 5 +++-- libraries/protocol/custom_authorities/list_3.cpp | 5 +++-- libraries/protocol/custom_authorities/list_4.cpp | 5 +++-- libraries/protocol/custom_authorities/list_5.cpp | 5 +++-- libraries/protocol/custom_authorities/list_6.cpp | 5 +++-- libraries/protocol/custom_authorities/list_7.cpp | 5 +++-- libraries/protocol/custom_authorities/list_8.cpp | 5 +++-- libraries/protocol/custom_authorities/list_9.cpp | 5 +++-- 14 files changed, 42 insertions(+), 28 deletions(-) diff --git a/libraries/protocol/custom_authorities/list_1.cpp b/libraries/protocol/custom_authorities/list_1.cpp index 4c185e192d..99893b808d 100644 --- a/libraries/protocol/custom_authorities/list_1.cpp +++ b/libraries/protocol/custom_authorities/list_1.cpp @@ -29,13 +29,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_1(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_1::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_1::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_10.cpp b/libraries/protocol/custom_authorities/list_10.cpp index cfeaba7a73..3e768c8e12 100644 --- a/libraries/protocol/custom_authorities/list_10.cpp +++ b/libraries/protocol/custom_authorities/list_10.cpp @@ -29,13 +29,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_10(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_10::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_10::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_11.cpp b/libraries/protocol/custom_authorities/list_11.cpp index 3b51666b5d..3ec1e93252 100644 --- a/libraries/protocol/custom_authorities/list_11.cpp +++ b/libraries/protocol/custom_authorities/list_11.cpp @@ -29,13 +29,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_11(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_11::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_11::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_12.cpp b/libraries/protocol/custom_authorities/list_12.cpp index 267d6fc6da..8311b66c0d 100644 --- a/libraries/protocol/custom_authorities/list_12.cpp +++ b/libraries/protocol/custom_authorities/list_12.cpp @@ -29,13 +29,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_12(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_12::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_12::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_13.cpp b/libraries/protocol/custom_authorities/list_13.cpp index 7e2afd1ff6..8c4f74c508 100644 --- a/libraries/protocol/custom_authorities/list_13.cpp +++ b/libraries/protocol/custom_authorities/list_13.cpp @@ -29,13 +29,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_13(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_13::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_13::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_14.cpp b/libraries/protocol/custom_authorities/list_14.cpp index d1e1d67535..2560321b49 100644 --- a/libraries/protocol/custom_authorities/list_14.cpp +++ b/libraries/protocol/custom_authorities/list_14.cpp @@ -29,13 +29,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_14(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_14::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_14::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_2.cpp b/libraries/protocol/custom_authorities/list_2.cpp index 62bc7abfc3..6cf5b982c5 100644 --- a/libraries/protocol/custom_authorities/list_2.cpp +++ b/libraries/protocol/custom_authorities/list_2.cpp @@ -30,13 +30,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_2(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_2::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_2::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_3.cpp b/libraries/protocol/custom_authorities/list_3.cpp index e23db34f42..9ba40efce3 100644 --- a/libraries/protocol/custom_authorities/list_3.cpp +++ b/libraries/protocol/custom_authorities/list_3.cpp @@ -30,13 +30,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_3(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_3::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_3::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_4.cpp b/libraries/protocol/custom_authorities/list_4.cpp index 32f094585b..2453728c83 100644 --- a/libraries/protocol/custom_authorities/list_4.cpp +++ b/libraries/protocol/custom_authorities/list_4.cpp @@ -30,13 +30,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_4(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_4::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_4::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_5.cpp b/libraries/protocol/custom_authorities/list_5.cpp index d67b9997ad..9997c20b86 100644 --- a/libraries/protocol/custom_authorities/list_5.cpp +++ b/libraries/protocol/custom_authorities/list_5.cpp @@ -30,13 +30,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_5(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_5::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_5::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_6.cpp b/libraries/protocol/custom_authorities/list_6.cpp index 872c490732..60933d5142 100644 --- a/libraries/protocol/custom_authorities/list_6.cpp +++ b/libraries/protocol/custom_authorities/list_6.cpp @@ -30,13 +30,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_6(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_6::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_6::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_7.cpp b/libraries/protocol/custom_authorities/list_7.cpp index 2cd386d5cc..4bb31c9315 100644 --- a/libraries/protocol/custom_authorities/list_7.cpp +++ b/libraries/protocol/custom_authorities/list_7.cpp @@ -30,13 +30,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_7(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_7::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_7::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_8.cpp b/libraries/protocol/custom_authorities/list_8.cpp index a0b3b34289..9d576305bd 100644 --- a/libraries/protocol/custom_authorities/list_8.cpp +++ b/libraries/protocol/custom_authorities/list_8.cpp @@ -30,13 +30,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_8(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_8::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_8::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } diff --git a/libraries/protocol/custom_authorities/list_9.cpp b/libraries/protocol/custom_authorities/list_9.cpp index 4f1e2a7524..f709960a57 100644 --- a/libraries/protocol/custom_authorities/list_9.cpp +++ b/libraries/protocol/custom_authorities/list_9.cpp @@ -30,13 +30,14 @@ namespace graphene { namespace protocol { using result_type = object_restriction_predicate; result_type get_restriction_pred_list_9(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_9::list(), idx, [&rs] (auto t) -> result_type { + return typelist::runtime::dispatch(operation_list_9::list(), idx, [&rs] (auto t) { using Op = typename decltype(t)::type; - return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); return p(op.get()); }; + return to_return; }); } } } From 009bec5c44eb47bf8aaa5fd1a4224a0a67c1790b Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 6 Jun 2021 20:58:41 +0000 Subject: [PATCH 024/258] Add a cleanup step in sonar-scan workflow --- .github/workflows/sonar-scan.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index cda85540b1..409bf92ab1 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -183,3 +183,17 @@ jobs: run: | sonar-scanner \ -Dsonar.login=${{ secrets.SONAR_TOKEN }} + - name: Cleanup + run: | + df -h + echo "Final cleanup" + rm -rf _build/programs/witness_node/witness_node + rm -rf _build/programs/cli_wallet/cli_wallet + rm -rf _build/programs/network_mapper/network_mapper + rm -rf _build/programs/js_operation_serializer/js_operation_serializer + rm -rf _build/programs/genesis_util/get_dev_key + rm -rf _build/tests/app_test + rm -rf _build/tests/chain_test + rm -rf _build/tests/cli_test + rm -rf _build/tests/es_test + df -h From 45ba6115e8a25bf9180f7b35711842dfa751894e Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 6 Jun 2021 22:17:37 +0000 Subject: [PATCH 025/258] fix code smells in db_genesis.cpp --- libraries/chain/db_genesis.cpp | 126 +++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 55 deletions(-) diff --git a/libraries/chain/db_genesis.cpp b/libraries/chain/db_genesis.cpp index 9893b7178a..a37bcbad46 100644 --- a/libraries/chain/db_genesis.cpp +++ b/libraries/chain/db_genesis.cpp @@ -57,14 +57,16 @@ void database::init_genesis(const genesis_state_type& genesis_state) _undo_db.disable(); struct auth_inhibitor { - auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags) + explicit auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags) { db.node_properties().skip_flags |= skip_transaction_signatures; } ~auth_inhibitor() { db.node_properties().skip_flags = old_flags; } + auth_inhibitor(const auth_inhibitor&) = delete; private: database& db; uint32_t old_flags; - } inhibitor(*this); + }; + auth_inhibitor inhibitor(*this); transaction_evaluation_state genesis_eval_state(this); @@ -74,7 +76,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) b.balance = GRAPHENE_MAX_SHARE_SUPPLY; }); const account_object& committee_account = - create( [&](account_object& n) { + create( [this](account_object& n) { n.membership_expiration_date = time_point_sec::maximum(); n.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; n.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; @@ -168,7 +170,9 @@ void database::init_genesis(const genesis_state_type& genesis_state) }).id; a.owner.weight_threshold = 1; a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = account_id_type(id); + a.registrar = account_id_type(id); + a.referrer = a.registrar; + a.lifetime_referrer = a.registrar; a.membership_expiration_date = time_point_sec::maximum(); a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; @@ -178,12 +182,12 @@ void database::init_genesis(const genesis_state_type& genesis_state) } // Create core asset - const asset_dynamic_data_object& dyn_asset = + const asset_dynamic_data_object& core_dyn_asset = create([](asset_dynamic_data_object& a) { a.current_supply = GRAPHENE_MAX_SHARE_SUPPLY; }); const asset_object& core_asset = - create( [&genesis_state,&dyn_asset]( asset_object& a ) { + create( [&genesis_state,&core_dyn_asset]( asset_object& a ) { a.symbol = GRAPHENE_SYMBOL; a.options.max_supply = genesis_state.max_core_supply; a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS; @@ -194,13 +198,13 @@ void database::init_genesis(const genesis_state_type& genesis_state) a.options.core_exchange_rate.base.asset_id = asset_id_type(0); a.options.core_exchange_rate.quote.amount = 1; a.options.core_exchange_rate.quote.asset_id = asset_id_type(0); - a.dynamic_asset_data_id = dyn_asset.id; + a.dynamic_asset_data_id = core_dyn_asset.id; }); - FC_ASSERT( dyn_asset.id == asset_dynamic_data_id_type() ); + FC_ASSERT( core_dyn_asset.id == asset_dynamic_data_id_type() ); FC_ASSERT( asset_id_type(core_asset.id) == asset().asset_id ); - FC_ASSERT( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) ); + FC_ASSERT( get_balance(account_id_type(), asset_id_type()) == asset(core_dyn_asset.current_supply) ); _p_core_asset_obj = &core_asset; - _p_core_dynamic_data_obj = &dyn_asset; + _p_core_dynamic_data_obj = &core_dyn_asset; // Create more special assets while( true ) { @@ -238,7 +242,8 @@ void database::init_genesis(const genesis_state_type& genesis_state) p.parameters.get_mutable_fees().zero_all_fees(); }); - _p_dyn_global_prop_obj = & create([&genesis_state](dynamic_global_property_object& p) { + _p_dyn_global_prop_obj = & create( + [&genesis_state](dynamic_global_property_object& p) { p.time = genesis_state.initial_timestamp; p.dynamic_flags = 0; p.witness_budget = 0; @@ -246,15 +251,22 @@ void database::init_genesis(const genesis_state_type& genesis_state) }); FC_ASSERT( (genesis_state.immutable_parameters.min_witness_count & 1) == 1, "min_witness_count must be odd" ); - FC_ASSERT( (genesis_state.immutable_parameters.min_committee_member_count & 1) == 1, "min_committee_member_count must be odd" ); + FC_ASSERT( (genesis_state.immutable_parameters.min_committee_member_count & 1) == 1, + "min_committee_member_count must be odd" ); _p_chain_property_obj = & create([chain_id,&genesis_state](chain_property_object& p) { p.chain_id = chain_id; p.immutable_parameters = genesis_state.immutable_parameters; } ); - for (uint32_t i = 0; i <= 0x10000; i++) - create( [&]( block_summary_object&) {}); + + constexpr uint32_t block_summary_object_count = 0x10000; + for (uint32_t i = 0; i <= block_summary_object_count; ++i) + { + create( [](const block_summary_object&) { + // Nothing to do + } ); + } // Create initial accounts for( const auto& account : genesis_state.initial_accounts ) @@ -308,21 +320,21 @@ void database::init_genesis(const genesis_state_type& genesis_state) map total_debts; // Create initial assets - for( const genesis_state_type::initial_asset_type& asset : genesis_state.initial_assets ) + for( const genesis_state_type::initial_asset_type& asst : genesis_state.initial_assets ) { asset_id_type new_asset_id = get_index_type().get_next_id(); total_supplies[ new_asset_id ] = 0; asset_dynamic_data_id_type dynamic_data_id; optional bitasset_data_id; - if( asset.is_bitasset ) + if( asst.is_bitasset ) { - int collateral_holder_number = 0; + size_t collateral_holder_number = 0; total_debts[ new_asset_id ] = 0; - for( const auto& collateral_rec : asset.collateral_records ) + for( const auto& collateral_rec : asst.collateral_records ) { account_create_operation cop; - cop.name = asset.symbol + "-collateral-holder-" + std::to_string(collateral_holder_number); + cop.name = asst.symbol + "-collateral-holder-" + std::to_string(collateral_holder_number); boost::algorithm::to_lower(cop.name); cop.registrar = GRAPHENE_TEMP_ACCOUNT; cop.owner = authority(1, collateral_rec.owner, 1); @@ -333,7 +345,8 @@ void database::init_genesis(const genesis_state_type& genesis_state) o.total_core_in_orders = collateral_rec.collateral; }); - create([&](call_order_object& c) { + create( + [&owner_account_id,&collateral_rec,&new_asset_id,&core_asset](call_order_object& c) { c.borrower = owner_account_id; c.collateral = collateral_rec.collateral; c.debt = collateral_rec.debt; @@ -354,22 +367,22 @@ void database::init_genesis(const genesis_state_type& genesis_state) }).id; } - dynamic_data_id = create([&asset](asset_dynamic_data_object& d) { - d.accumulated_fees = asset.accumulated_fees; + dynamic_data_id = create([&asst](asset_dynamic_data_object& d) { + d.accumulated_fees = asst.accumulated_fees; }).id; - total_supplies[ new_asset_id ] += asset.accumulated_fees; + total_supplies[ new_asset_id ] += asst.accumulated_fees; - create([&](asset_object& a) { - a.symbol = asset.symbol; - a.options.description = asset.description; - a.precision = asset.precision; - string issuer_name = asset.issuer_name; + create([&asst,&get_account_id,&dynamic_data_id,&bitasset_data_id](asset_object& a) { + a.symbol = asst.symbol; + a.options.description = asst.description; + a.precision = asst.precision; + string issuer_name = asst.issuer_name; a.issuer = get_account_id(issuer_name); - a.options.max_supply = asset.max_supply; + a.options.max_supply = asst.max_supply; a.options.flags = witness_fed_asset; - a.options.issuer_permissions = charge_market_fee | override_authority | white_list | transfer_restricted | disable_confidential | - ( asset.is_bitasset ? disable_force_settle | global_settle | witness_fed_asset | committee_fed_asset : 0 ); + a.options.issuer_permissions = ( asst.is_bitasset ? ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK + : DEFAULT_UIA_ASSET_ISSUER_PERMISSION ); a.dynamic_asset_data_id = dynamic_data_id; a.bitasset_data_id = bitasset_data_id; }); @@ -392,7 +405,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) for( const genesis_state_type::initial_vesting_balance_type& vest : genesis_state.initial_vesting_balances ) { const auto asset_id = get_asset_id(vest.asset_symbol); - create([&](balance_object& b) { + create([&vest,&asset_id](balance_object& b) { b.owner = vest.owner; b.balance = asset(vest.amount, asset_id); @@ -448,41 +461,44 @@ void database::init_genesis(const genesis_state_type& genesis_state) // Save tallied supplies for( const auto& item : total_supplies ) { - const auto asset_id = item.first; - const auto total_supply = item.second; - - modify( get( asset_id ), [ & ]( asset_object& asset ) { - modify( get( asset.dynamic_asset_data_id ), [ & ]( asset_dynamic_data_object& asset_data ) { - asset_data.current_supply = total_supply; - } ); - } ); + const auto& asset_id = item.first; + const auto& total_supply = item.second; + + modify( get(get(asset_id).dynamic_asset_data_id), [&total_supply]( asset_dynamic_data_object& asset_data ) { + asset_data.current_supply = total_supply; + } ); } - // Create special witness account - const witness_object& wit = create([&](witness_object& w) {}); + // Create special witness account and remove it, reserve the id + const witness_object& wit = create([](const witness_object&) { + // Nothing to do + }); FC_ASSERT( wit.id == GRAPHENE_NULL_WITNESS ); remove(wit); // Create initial witnesses - std::for_each(genesis_state.initial_witness_candidates.begin(), genesis_state.initial_witness_candidates.end(), - [&](const genesis_state_type::initial_witness_type& witness) { + std::for_each( genesis_state.initial_witness_candidates.begin(), + genesis_state.initial_witness_candidates.end(), + [this,&get_account_id,&genesis_eval_state](const auto& witness) { witness_create_operation op; op.witness_account = get_account_id(witness.owner_name); op.block_signing_key = witness.block_signing_key; - apply_operation(genesis_eval_state, op); + this->apply_operation(genesis_eval_state, op); }); // Create initial committee members - std::for_each(genesis_state.initial_committee_candidates.begin(), genesis_state.initial_committee_candidates.end(), - [&](const genesis_state_type::initial_committee_member_type& member) { + std::for_each( genesis_state.initial_committee_candidates.begin(), + genesis_state.initial_committee_candidates.end(), + [this,&get_account_id,&genesis_eval_state](const auto& member) { committee_member_create_operation op; op.committee_member_account = get_account_id(member.owner_name); - apply_operation(genesis_eval_state, op); + this->apply_operation(genesis_eval_state, op); }); // Create initial workers - std::for_each(genesis_state.initial_worker_candidates.begin(), genesis_state.initial_worker_candidates.end(), - [&](const genesis_state_type::initial_worker_type& worker) + std::for_each( genesis_state.initial_worker_candidates.begin(), + genesis_state.initial_worker_candidates.end(), + [this,&get_account_id,&genesis_state,&genesis_eval_state](const auto& worker) { worker_create_operation op; op.owner = get_account_id(worker.owner_name); @@ -492,7 +508,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) op.name = "Genesis-Worker-" + worker.owner_name; op.initializer = vesting_balance_worker_initializer{uint16_t(0)}; - apply_operation(genesis_eval_state, std::move(op)); + this->apply_operation(genesis_eval_state, std::move(op)); }); // Set active witnesses @@ -516,7 +532,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) }); // Create FBA counters - create([&]( fba_accumulator_object& acc ) + create([]( fba_accumulator_object& acc ) { FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_to_blind ) ); acc.accumulated_fba_fees = 0; @@ -525,7 +541,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) #endif }); - create([&]( fba_accumulator_object& acc ) + create([]( fba_accumulator_object& acc ) { FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_blind_transfer ) ); acc.accumulated_fba_fees = 0; @@ -534,7 +550,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) #endif }); - create([&]( fba_accumulator_object& acc ) + create([]( fba_accumulator_object& acc ) { FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_from_blind ) ); acc.accumulated_fba_fees = 0; @@ -545,7 +561,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) FC_ASSERT( get_index().get_next_id() == fba_accumulator_id_type( fba_accumulator_id_count ) ); - //debug_dump(); + //debug_dump(); // for debug _undo_db.enable(); } FC_CAPTURE_AND_RETHROW() } From 9d78a652809f75e25d3604cb23f0f4cfa591713d Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 6 Jun 2021 22:21:13 +0000 Subject: [PATCH 026/258] Remove temp files in ubuntu-debug workflow --- .github/workflows/build-and-test.ubuntu-debug.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build-and-test.ubuntu-debug.yml b/.github/workflows/build-and-test.ubuntu-debug.yml index 3a2de40eaa..90927ab047 100644 --- a/.github/workflows/build-and-test.ubuntu-debug.yml +++ b/.github/workflows/build-and-test.ubuntu-debug.yml @@ -96,14 +96,19 @@ jobs: run: | _build/tests/app_test -l test_suite df -h + rm -rf /tmp/graphene* curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_cluster/settings \ -d '{ "transient": { "cluster.routing.allocation.disk.threshold_enabled": false } }' echo _build/tests/es_test -l test_suite df -h + rm -rf /tmp/graphene* libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite + df -h + rm -rf /tmp/graphene* _build/tests/cli_test -l test_suite df -h + rm -rf /tmp/graphene* - name: Quick test for program arguments run: | _build/programs/witness_node/witness_node --version From d05f130b1aa207c6f286968ef71a55230485a46b Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 7 Jun 2021 08:04:50 +0000 Subject: [PATCH 027/258] Fix code smells, update comments --- libraries/chain/db_genesis.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/libraries/chain/db_genesis.cpp b/libraries/chain/db_genesis.cpp index a37bcbad46..311d15f41d 100644 --- a/libraries/chain/db_genesis.cpp +++ b/libraries/chain/db_genesis.cpp @@ -98,7 +98,9 @@ void database::init_genesis(const genesis_state_type& genesis_state) }).id; a.owner.weight_threshold = 1; a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_WITNESS_ACCOUNT; + a.registrar = GRAPHENE_WITNESS_ACCOUNT; + a.referrer = a.registrar; + a.lifetime_referrer = a.registrar; a.membership_expiration_date = time_point_sec::maximum(); a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; @@ -111,7 +113,9 @@ void database::init_genesis(const genesis_state_type& genesis_state) }).id; a.owner.weight_threshold = 1; a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT; + a.registrar = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT; + a.referrer = a.registrar; + a.lifetime_referrer = a.registrar; a.membership_expiration_date = time_point_sec::maximum(); a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; @@ -124,7 +128,9 @@ void database::init_genesis(const genesis_state_type& genesis_state) }).id; a.owner.weight_threshold = 1; a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_NULL_ACCOUNT; + a.registrar = GRAPHENE_NULL_ACCOUNT; + a.referrer = a.registrar; + a.lifetime_referrer = a.registrar; a.membership_expiration_date = time_point_sec::maximum(); a.network_fee_percentage = 0; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; @@ -137,7 +143,9 @@ void database::init_genesis(const genesis_state_type& genesis_state) }).id; a.owner.weight_threshold = 0; a.active.weight_threshold = 0; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_TEMP_ACCOUNT; + a.registrar = GRAPHENE_TEMP_ACCOUNT; + a.referrer = a.registrar; + a.lifetime_referrer = a.registrar; a.membership_expiration_date = time_point_sec::maximum(); a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; @@ -150,13 +158,15 @@ void database::init_genesis(const genesis_state_type& genesis_state) }).id; a.owner.weight_threshold = 1; a.active.weight_threshold = 1; - a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_NULL_ACCOUNT; + a.registrar = GRAPHENE_NULL_ACCOUNT; + a.referrer = a.registrar; + a.lifetime_referrer = a.registrar; a.membership_expiration_date = time_point_sec::maximum(); a.network_fee_percentage = 0; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; }).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT); - // Create more special accounts + // Create more special accounts and remove them, reserve the IDs while( true ) { uint64_t id = get_index().get_next_id().instance(); @@ -179,6 +189,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) }); FC_ASSERT( acct.get_id() == account_id_type(id) ); remove( acct ); + // Note: the account statistics objects are not removed } // Create core asset @@ -205,7 +216,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) FC_ASSERT( get_balance(account_id_type(), asset_id_type()) == asset(core_dyn_asset.current_supply) ); _p_core_asset_obj = &core_asset; _p_core_dynamic_data_obj = &core_dyn_asset; - // Create more special assets + // Create more special assets and remove them, reserve the IDs while( true ) { uint64_t id = get_index().get_next_id().instance(); @@ -230,6 +241,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) }); FC_ASSERT( asset_obj.get_id() == asset_id_type(id) ); remove( asset_obj ); + // Note: the asset dynamic data objects are not removed } chain_id_type chain_id = genesis_state.compute_chain_id(); From 96cc676832a037afba1a417f355209c1fd537b5d Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 7 Jun 2021 08:07:52 +0000 Subject: [PATCH 028/258] Remove temp objects --- libraries/chain/db_genesis.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/db_genesis.cpp b/libraries/chain/db_genesis.cpp index 311d15f41d..a7702db407 100644 --- a/libraries/chain/db_genesis.cpp +++ b/libraries/chain/db_genesis.cpp @@ -188,8 +188,8 @@ void database::init_genesis(const genesis_state_type& genesis_state) a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; }); FC_ASSERT( acct.get_id() == account_id_type(id) ); + remove( acct.statistics(*this) ); remove( acct ); - // Note: the account statistics objects are not removed } // Create core asset @@ -240,8 +240,8 @@ void database::init_genesis(const genesis_state_type& genesis_state) a.dynamic_asset_data_id = dyn_asset.id; }); FC_ASSERT( asset_obj.get_id() == asset_id_type(id) ); + remove( dyn_asset ); remove( asset_obj ); - // Note: the asset dynamic data objects are not removed } chain_id_type chain_id = genesis_state.compute_chain_id(); From 1d2578601ef88640b707c6b458773346211b6065 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 7 Jun 2021 10:55:38 +0000 Subject: [PATCH 029/258] Refactor to resolve duplicate code issue --- libraries/chain/db_genesis.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/libraries/chain/db_genesis.cpp b/libraries/chain/db_genesis.cpp index a7702db407..b3267fbffd 100644 --- a/libraries/chain/db_genesis.cpp +++ b/libraries/chain/db_genesis.cpp @@ -120,8 +120,8 @@ void database::init_genesis(const genesis_state_type& genesis_state) a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; }).get_id() == GRAPHENE_RELAXED_COMMITTEE_ACCOUNT); - FC_ASSERT(create([this](account_object& a) { - a.name = "null-account"; + // The same data set is assigned to more than one account + auto init_account_data_as_null = [this](account_object& a) { a.statistics = create([&a](account_statistics_object& s){ s.owner = a.id; s.name = a.name; @@ -134,6 +134,10 @@ void database::init_genesis(const genesis_state_type& genesis_state) a.membership_expiration_date = time_point_sec::maximum(); a.network_fee_percentage = 0; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; + }; + FC_ASSERT(create([&init_account_data_as_null](account_object& a) { + a.name = "null-account"; + init_account_data_as_null(a); }).get_id() == GRAPHENE_NULL_ACCOUNT); FC_ASSERT(create([this](account_object& a) { a.name = "temp-account"; @@ -150,20 +154,9 @@ void database::init_genesis(const genesis_state_type& genesis_state) a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; }).get_id() == GRAPHENE_TEMP_ACCOUNT); - FC_ASSERT(create([this](account_object& a) { + FC_ASSERT(create([&init_account_data_as_null](account_object& a) { a.name = "proxy-to-self"; - a.statistics = create([&a](account_statistics_object& s){ - s.owner = a.id; - s.name = a.name; - }).id; - a.owner.weight_threshold = 1; - a.active.weight_threshold = 1; - a.registrar = GRAPHENE_NULL_ACCOUNT; - a.referrer = a.registrar; - a.lifetime_referrer = a.registrar; - a.membership_expiration_date = time_point_sec::maximum(); - a.network_fee_percentage = 0; - a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT; + init_account_data_as_null(a); }).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT); // Create more special accounts and remove them, reserve the IDs From eb11adc78bde1d185f2f884f846433539a7042ad Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 7 Jun 2021 21:53:32 +0000 Subject: [PATCH 030/258] Return amount released when deleting a SameT Fund --- .../chain/include/graphene/chain/samet_fund_evaluator.hpp | 2 +- libraries/chain/samet_fund_evaluator.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp index 39b75ef172..75f1bd5098 100644 --- a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp @@ -47,7 +47,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_delete_operation; void_result do_evaluate( const samet_fund_delete_operation& op ); - void_result do_apply( const samet_fund_delete_operation& op ) const; + asset do_apply( const samet_fund_delete_operation& op ) const; const samet_fund_object* _fund = nullptr; }; diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp index 2f59c0978e..fcd2a4e4d0 100644 --- a/libraries/chain/samet_fund_evaluator.cpp +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -79,15 +79,17 @@ void_result samet_fund_delete_evaluator::do_evaluate(const samet_fund_delete_ope return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result samet_fund_delete_evaluator::do_apply(const samet_fund_delete_operation& op) const +asset samet_fund_delete_evaluator::do_apply(const samet_fund_delete_operation& op) const { try { database& d = db(); - d.adjust_balance( op.owner_account, asset( _fund->balance, _fund->asset_type ) ); + asset released( _fund->balance, _fund->asset_type ); + + d.adjust_balance( op.owner_account, released ); d.remove( *_fund ); - return void_result(); + return released; } FC_CAPTURE_AND_RETHROW( (op) ) } void_result samet_fund_update_evaluator::do_evaluate(const samet_fund_update_operation& op) From 75efd7d6ab16703693d9dbe7b653d5058f4e60c1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 7 Jun 2021 21:54:49 +0000 Subject: [PATCH 031/258] Add some tests for SameT Funds --- tests/common/database_fixture.cpp | 140 +++++++++++++++ tests/common/database_fixture.hpp | 26 +++ tests/tests/samet_fund_tests.cpp | 282 ++++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 tests/tests/samet_fund_tests.cpp diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 1ad83d031d..5ccc695742 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -548,6 +548,10 @@ void database_fixture_base::verify_asset_supplies( const database& db ) total_balances[o.asset_a] += o.balance_a; total_balances[o.asset_b] += o.balance_b; } + for( const samet_fund_object& o : db.get_index_type().indices() ) + { + total_balances[o.asset_type] += (o.balance - o.unpaid_amount); + } total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget; @@ -1500,6 +1504,142 @@ generic_exchange_operation_result database_fixture_base::exchange_with_liquidity return op_result.get(); } +samet_fund_create_operation database_fixture_base::make_samet_fund_create_op( + account_id_type account, asset_id_type asset_type, + share_type balance, uint32_t fee_rate )const +{ + samet_fund_create_operation op; + op.owner_account = account; + op.asset_type = asset_type; + op.balance = balance, + op.fee_rate = fee_rate; + return op; +} + +const samet_fund_object& database_fixture_base::create_samet_fund( + account_id_type account, asset_id_type asset_type, + share_type balance, uint32_t fee_rate ) +{ + samet_fund_create_operation op = make_samet_fund_create_op( account, asset_type, balance, fee_rate ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + trx.operations.clear(); + verify_asset_supplies(db); + return db.get( op_result.get() ); +} + +samet_fund_delete_operation database_fixture_base::make_samet_fund_delete_op( + account_id_type account, samet_fund_id_type fund_id )const +{ + samet_fund_delete_operation op; + op.owner_account = account; + op.fund_id = fund_id; + return op; +} + +asset database_fixture_base::delete_samet_fund( account_id_type account, samet_fund_id_type fund_id ) +{ + samet_fund_delete_operation op = make_samet_fund_delete_op( account, fund_id ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + trx.operations.clear(); + verify_asset_supplies(db); + return op_result.get(); +} + +samet_fund_update_operation database_fixture_base::make_samet_fund_update_op( + account_id_type account, samet_fund_id_type fund_id, + const optional& delta_amount, + const optional& new_fee_rate )const +{ + samet_fund_update_operation op; + op.owner_account = account; + op.fund_id = fund_id; + op.delta_amount = delta_amount; + op.new_fee_rate = new_fee_rate; + return op; +} + +void database_fixture_base::update_samet_fund( account_id_type account, samet_fund_id_type fund_id, + const optional& delta_amount, + const optional& new_fee_rate ) +{ + samet_fund_update_operation op = make_samet_fund_update_op( account, fund_id, delta_amount, new_fee_rate ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + verify_asset_supplies(db); +} + +samet_fund_borrow_operation database_fixture_base::make_samet_fund_borrow_op( + account_id_type account, samet_fund_id_type fund_id, + const asset& borrow_amount )const +{ + samet_fund_borrow_operation op; + op.borrower = account; + op.fund_id = fund_id; + op.borrow_amount = borrow_amount; + return op; +} + +void database_fixture_base::borrow_from_samet_fund( account_id_type account, samet_fund_id_type fund_id, + const asset& borrow_amount ) +{ + samet_fund_borrow_operation op = make_samet_fund_borrow_op( account, fund_id, borrow_amount ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + verify_asset_supplies(db); +} + +samet_fund_repay_operation database_fixture_base::make_samet_fund_repay_op( + account_id_type account, samet_fund_id_type fund_id, + const asset& repay_amount, const asset& fund_fee )const +{ + samet_fund_repay_operation op; + op.account = account; + op.fund_id = fund_id; + op.repay_amount = repay_amount; + op.fund_fee = fund_fee; + return op; +} + +void database_fixture_base::repay_to_samet_fund( account_id_type account, samet_fund_id_type fund_id, + const asset& repay_amount, const asset& fund_fee ) +{ + samet_fund_repay_operation op = make_samet_fund_repay_op( account, fund_id, repay_amount, fund_fee ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + verify_asset_supplies(db); +} void database_fixture_base::enable_fees() { diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 9658fb4019..b73761f474 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -416,6 +417,8 @@ struct database_fixture_base { void transfer( account_id_type from, account_id_type to, const asset& amount, const asset& fee = asset() ); void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() ); void fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount ); + + // Tickets ticket_create_operation make_ticket_create_op( account_id_type account, ticket_type type, const asset& amount )const; const ticket_object& create_ticket( account_id_type account, ticket_type type, const asset& amount ); @@ -424,6 +427,7 @@ struct database_fixture_base { generic_operation_result update_ticket( const ticket_object& ticket, ticket_type type, const optional& amount ); + // Liquidity Pools liquidity_pool_create_operation make_liquidity_pool_create_op( account_id_type account, asset_id_type asset_a, asset_id_type asset_b, asset_id_type share_asset, uint16_t taker_fee_percent, uint16_t withdrawal_fee_percent )const; @@ -450,6 +454,28 @@ struct database_fixture_base { liquidity_pool_id_type pool, const asset& amount_to_sell, const asset& min_to_receive ); + // SameT Funds + samet_fund_create_operation make_samet_fund_create_op( account_id_type account, asset_id_type asset_type, + share_type balance, uint32_t fee_rate )const; + const samet_fund_object& create_samet_fund( account_id_type account, asset_id_type asset_type, + share_type balance, uint32_t fee_rate ); + samet_fund_delete_operation make_samet_fund_delete_op( account_id_type account, samet_fund_id_type fund_id )const; + asset delete_samet_fund( account_id_type account, samet_fund_id_type fund_id ); + samet_fund_update_operation make_samet_fund_update_op( account_id_type account, samet_fund_id_type fund_id, + const optional& delta_amount, + const optional& new_fee_rate )const; + void update_samet_fund( account_id_type account, samet_fund_id_type fund_id, + const optional& delta_amount, + const optional& new_fee_rate ); + samet_fund_borrow_operation make_samet_fund_borrow_op( account_id_type account, samet_fund_id_type fund_id, + const asset& borrow_amount )const; + void borrow_from_samet_fund( account_id_type account, samet_fund_id_type fund_id, + const asset& borrow_amount ); + samet_fund_repay_operation make_samet_fund_repay_op( account_id_type account, samet_fund_id_type fund_id, + const asset& repay_amount, const asset& fund_fee )const; + void repay_to_samet_fund( account_id_type account, samet_fund_id_type fund_id, + const asset& repay_amount, const asset& fund_fee ); + /** * NOTE: This modifies the database directly. You will probably have to call this each time you * finish creating a block diff --git a/tests/tests/samet_fund_tests.cpp b/tests/tests/samet_fund_tests.cpp new file mode 100644 index 0000000000..ab7a775184 --- /dev/null +++ b/tests/tests/samet_fund_tests.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( samet_fund_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( samet_fund_hardfork_time_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_2262_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + + // Before the hard fork, unable to create a samet fund or transact against a samet fund, + // or do any of them with proposals + BOOST_CHECK_THROW( create_samet_fund( sam_id, core.id, 10000, 100 ), fc::exception ); + + samet_fund_id_type tmp_sf_id; + BOOST_CHECK_THROW( delete_samet_fund( sam_id, tmp_sf_id ), fc::exception ); + BOOST_CHECK_THROW( update_samet_fund( sam_id, tmp_sf_id, core.amount(100), 200 ), fc::exception ); + BOOST_CHECK_THROW( borrow_from_samet_fund( sam_id, tmp_sf_id, core.amount(100) ), fc::exception ); + BOOST_CHECK_THROW( repay_to_samet_fund( sam_id, tmp_sf_id, core.amount(100), core.amount(100) ), + fc::exception ); + + samet_fund_create_operation cop = make_samet_fund_create_op( sam_id, core.id, 10000, 100 ); + BOOST_CHECK_THROW( propose( cop ), fc::exception ); + + samet_fund_delete_operation dop = make_samet_fund_delete_op( sam_id, tmp_sf_id ); + BOOST_CHECK_THROW( propose( dop ), fc::exception ); + + samet_fund_update_operation uop = make_samet_fund_update_op( sam_id, tmp_sf_id, core.amount(100), 200 ); + BOOST_CHECK_THROW( propose( uop ), fc::exception ); + + samet_fund_borrow_operation bop = make_samet_fund_borrow_op( sam_id, tmp_sf_id, core.amount(100) ); + BOOST_CHECK_THROW( propose( bop ), fc::exception ); + + samet_fund_repay_operation rop = + make_samet_fund_repay_op( sam_id, tmp_sf_id, core.amount(100), core.amount(100) ); + BOOST_CHECK_THROW( propose( rop ), fc::exception ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( samet_fund_create_delete_proposal_test ) +{ try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2351_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(ted)(por)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + asset_id_type core_id; + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.id; + issue_uia( sam, usd.amount(init_amount) ); + issue_uia( ted, usd.amount(init_amount) ); + + const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); + asset_id_type eur_id = eur.id; + issue_uia( sam, eur.amount(init_amount) ); + issue_uia( ted, eur.amount(init_amount) ); + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = eur.id; + uop.issuer = sam_id; + uop.new_options = eur.options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Sam so that he can manage the whitelist + upgrade_to_lifetime_member( sam_id ); + + // Add Sam to the whitelist, but do not add others + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + asset_id_type no_asset_id( core.id + 100 ); + BOOST_REQUIRE( !db.find( no_asset_id ) ); + + // Able to propose + { + samet_fund_create_operation cop = make_samet_fund_create_op( sam_id, core.id, 10000, 100 ); + propose( cop ); + + samet_fund_id_type tmp_sf_id; + + samet_fund_delete_operation dop = make_samet_fund_delete_op( sam_id, tmp_sf_id ); + propose( dop ); + + samet_fund_update_operation uop = make_samet_fund_update_op( sam_id, tmp_sf_id, core.amount(100), 200 ); + propose( uop ); + + samet_fund_borrow_operation bop = make_samet_fund_borrow_op( sam_id, tmp_sf_id, core.amount(100) ); + propose( bop ); + + samet_fund_repay_operation rop = + make_samet_fund_repay_op( sam_id, tmp_sf_id, core.amount(100), core.amount(100) ); + propose( rop ); + } + + int64_t expected_balance_sam_core = init_amount; + int64_t expected_balance_sam_usd = init_amount; + int64_t expected_balance_sam_eur = init_amount; + int64_t expected_balance_ted_core = init_amount; + int64_t expected_balance_ted_usd = init_amount; + int64_t expected_balance_ted_eur = init_amount; + + const auto& check_balances = [&]() { + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur ); + }; + + check_balances(); + + // Able to create samet funds with valid data + const samet_fund_object& sfo1 = create_samet_fund( sam_id, core.id, 10000, 100u ); + samet_fund_id_type sf1_id = sfo1.id; + BOOST_CHECK( sfo1.owner_account == sam_id ); + BOOST_CHECK( sfo1.asset_type == core.id ); + BOOST_CHECK( sfo1.balance == 10000 ); + BOOST_CHECK( sfo1.fee_rate == 100u ); + BOOST_CHECK( sfo1.unpaid_amount == 0 ); + + expected_balance_sam_core -= 10000; + check_balances(); + + const samet_fund_object& sfo2 = create_samet_fund( ted_id, usd.id, 1, 10000000u ); + samet_fund_id_type sf2_id = sfo2.id; + BOOST_CHECK( sfo2.owner_account == ted_id ); + BOOST_CHECK( sfo2.asset_type == usd.id ); + BOOST_CHECK( sfo2.balance == 1 ); + BOOST_CHECK( sfo2.fee_rate == 10000000u ); + BOOST_CHECK( sfo2.unpaid_amount == 0 ); + + expected_balance_ted_usd -= 1; + check_balances(); + + const samet_fund_object& sfo3 = create_samet_fund( sam_id, eur.id, 10, 1u ); // Account is whitelisted + samet_fund_id_type sf3_id = sfo3.id; + + expected_balance_sam_eur -= 10; + check_balances(); + + // Unable to create a samet fund with invalid data + // Non-positive balance + BOOST_CHECK_THROW( create_samet_fund( sam_id, core.id, -1, 100u ), fc::exception ); + BOOST_CHECK_THROW( create_samet_fund( ted_id, usd.id, 0, 10000000u ), fc::exception ); + // Insufficient account balance + BOOST_CHECK_THROW( create_samet_fund( por_id, usd.id, 1, 100u ), fc::exception ); + // Nonexistent asset type + BOOST_CHECK_THROW( create_samet_fund( sam_id, no_asset_id, 1, 100u ), fc::exception ); + // Account is not whitelisted + BOOST_CHECK_THROW( create_samet_fund( ted_id, eur.id, 10, 1u ), fc::exception ); + + check_balances(); + + // Sam is able to delete his own fund + asset released = delete_samet_fund( sam_id, sf1_id ); + + BOOST_REQUIRE( !db.find( sf1_id ) ); + BOOST_REQUIRE( db.find( sf2_id ) ); + BOOST_REQUIRE( db.find( sf3_id ) ); + + BOOST_CHECK( released == asset( 10000, core_id ) ); + + expected_balance_sam_core += 10000; + check_balances(); + + // Unable to delete a fund that does not exist + BOOST_CHECK_THROW( delete_samet_fund( sam_id, sf1_id ), fc::exception ); + // Unable to delete a fund that is not owned by him + BOOST_CHECK_THROW( delete_samet_fund( sam_id, sfo2.id ), fc::exception ); + + BOOST_REQUIRE( !db.find( sf1_id ) ); + BOOST_REQUIRE( db.find( sf2_id ) ); + BOOST_REQUIRE( db.find( sf3_id ) ); + + check_balances(); + + { + // Add Ted to the whitelist and remove Sam + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = ted_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::no_listing; + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Sam is still able to delete the fund + released = delete_samet_fund( sam_id, sf3_id ); + BOOST_REQUIRE( !db.find( sf3_id ) ); + + BOOST_CHECK( released == asset( 10, eur_id ) ); + + expected_balance_sam_eur += 10; + check_balances(); + + // Same is unable to recreate the fund + BOOST_CHECK_THROW( create_samet_fund( sam_id, eur.id, 10, 1u ), fc::exception ); + check_balances(); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() From 67f2f67a25177eec809096e7065c6af1cd61ac98 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 9 Jun 2021 17:38:36 -0400 Subject: [PATCH 032/258] Add tests to reproduce LP deposit stats issue --- tests/tests/liquidity_pool_tests.cpp | 92 +++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 17 deletions(-) diff --git a/tests/tests/liquidity_pool_tests.cpp b/tests/tests/liquidity_pool_tests.cpp index 04fe5abf7e..aeb35fc288 100644 --- a/tests/tests/liquidity_pool_tests.cpp +++ b/tests/tests/liquidity_pool_tests.cpp @@ -776,28 +776,52 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) // The owner do the initial deposit generic_exchange_operation_result result; - result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 1000, eur_id ), asset( 1200, usd_id ) ); + result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 100, eur_id ), asset( 120, usd_id ) ); BOOST_REQUIRE_EQUAL( result.paid.size(), 2u ); - BOOST_CHECK( result.paid.front() == asset( 1000, eur_id ) ); - BOOST_CHECK( result.paid.back() == asset( 1200, usd_id ) ); + BOOST_CHECK( result.paid.front() == asset( 100, eur_id ) ); + BOOST_CHECK( result.paid.back() == asset( 120, usd_id ) ); BOOST_REQUIRE_EQUAL( result.received.size(), 1u ); - BOOST_CHECK( result.received.front() == asset( 1200, lpa_id ) ); + BOOST_CHECK( result.received.front() == asset( 120, lpa_id ) ); BOOST_REQUIRE_EQUAL( result.fees.size(), 0u ); - expected_pool_balance_a = 1000; - expected_pool_balance_b = 1200; - expected_lp_supply = 1200; + expected_pool_balance_a = 100; + expected_pool_balance_b = 120; + expected_lp_supply = 120; BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a); BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b); BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply ); - expected_balance_sam_eur -= 1000; - expected_balance_sam_usd -= 1200; - expected_balance_sam_lpa += 1200; + expected_balance_sam_eur -= 100; + expected_balance_sam_usd -= 120; + expected_balance_sam_lpa += 120; + check_balances(); + + // Deposit again with 900 EUR and 3000 USD, the pool only takes 900 EUR and 1080 USD + result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 900, eur_id ), asset( 3000, usd_id ) ); + + BOOST_REQUIRE_EQUAL( result.paid.size(), 2u ); + BOOST_CHECK( result.paid.front() == asset( 900, eur_id ) ); + BOOST_CHECK( result.paid.back() == asset( 1080, usd_id ) ); + BOOST_REQUIRE_EQUAL( result.received.size(), 1u ); + BOOST_CHECK( result.received.front() == asset( 1080, lpa_id ) ); + BOOST_REQUIRE_EQUAL( result.fees.size(), 0u ); + + expected_pool_balance_a += 900; + expected_pool_balance_b += 1080; + expected_lp_supply += 1080; + BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a); + BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b); + BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); + BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply ); + + expected_balance_sam_eur -= 900; + expected_balance_sam_usd -= 1080; + expected_balance_sam_lpa += 1080; check_balances(); + // Unable to exchange if data is invalid // non-positive amounts for( int64_t i = -1; i <= 1; ++i ) @@ -913,6 +937,9 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) expected_balance_ted_usd -= 1000; check_balances(); + // Withdraw + result = withdraw_from_liquidity_pool( sam_id, lp_id, asset( 1000, lpa_id) ); + // Generates a block generate_block(); BOOST_CHECK_EQUAL( eur_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_eur ); @@ -923,6 +950,16 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) BOOST_CHECK_EQUAL( ticker.total_exchange_a2b_count, 1u ); BOOST_CHECK_EQUAL( ticker._24h_exchange_b2a_count, 1u ); BOOST_CHECK_EQUAL( ticker.total_exchange_b2a_count, 1u ); + BOOST_CHECK_EQUAL( ticker._24h_deposit_count, 2u ); + BOOST_CHECK( ticker._24h_deposit_amount_a == 1000u ); + BOOST_CHECK( ticker._24h_deposit_amount_b == 1200u ); + BOOST_CHECK( ticker._24h_deposit_share_amount == 1200u ); + BOOST_CHECK_EQUAL( ticker.total_deposit_count, 2u ); + BOOST_CHECK( ticker.total_deposit_amount_a == 1000u ); + BOOST_CHECK( ticker.total_deposit_amount_b == 1200u ); + BOOST_CHECK( ticker.total_deposit_share_amount == 1200u ); + BOOST_CHECK_EQUAL( ticker._24h_withdrawal_count, 1u ); + BOOST_CHECK_EQUAL( ticker.total_withdrawal_count, 1u ); // Check database API graphene::app::database_api db_api( db, &( app.get_options() ) ); @@ -943,6 +980,16 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) BOOST_CHECK_EQUAL( pools.front()->statistics->total_exchange_a2b_count, 1u ); BOOST_CHECK_EQUAL( pools.front()->statistics->_24h_exchange_b2a_count, 1u ); BOOST_CHECK_EQUAL( pools.front()->statistics->total_exchange_b2a_count, 1u ); + BOOST_CHECK_EQUAL( pools.front()->statistics->_24h_deposit_count, 2u ); + BOOST_CHECK( pools.front()->statistics->_24h_deposit_amount_a == 1000u ); + BOOST_CHECK( pools.front()->statistics->_24h_deposit_amount_b == 1200u ); + BOOST_CHECK( pools.front()->statistics->_24h_deposit_share_amount == 1200u ); + BOOST_CHECK_EQUAL( pools.front()->statistics->total_deposit_count, 2u ); + BOOST_CHECK( pools.front()->statistics->total_deposit_amount_a == 1000u ); + BOOST_CHECK( pools.front()->statistics->total_deposit_amount_b == 1200u ); + BOOST_CHECK( pools.front()->statistics->total_deposit_share_amount == 1200u ); + BOOST_CHECK_EQUAL( pools.front()->statistics->_24h_withdrawal_count, 1u ); + BOOST_CHECK_EQUAL( pools.front()->statistics->total_withdrawal_count, 1u ); generate_blocks( db.head_block_time() + fc::days(2) ); @@ -950,22 +997,32 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) BOOST_CHECK_EQUAL( ticker.total_exchange_a2b_count, 1u ); BOOST_CHECK_EQUAL( ticker._24h_exchange_b2a_count, 0u ); BOOST_CHECK_EQUAL( ticker.total_exchange_b2a_count, 1u ); + BOOST_CHECK_EQUAL( ticker._24h_deposit_count, 0u ); + BOOST_CHECK( ticker._24h_deposit_amount_a == 0u ); + BOOST_CHECK( ticker._24h_deposit_amount_b == 0u ); + BOOST_CHECK( ticker._24h_deposit_share_amount == 0u ); + BOOST_CHECK_EQUAL( ticker.total_deposit_count, 2u ); + BOOST_CHECK( ticker.total_deposit_amount_a == 1000u ); + BOOST_CHECK( ticker.total_deposit_amount_b == 1200u ); + BOOST_CHECK( ticker.total_deposit_share_amount == 1200u ); + BOOST_CHECK_EQUAL( ticker._24h_withdrawal_count, 0u ); + BOOST_CHECK_EQUAL( ticker.total_withdrawal_count, 1u ); // Check history API graphene::app::history_api hist_api(app); auto head_time = db.head_block_time(); - // all histories + // all histories : 1:create, 2:deposit, 3:deposit, 4:exchange, 5:exchange, 6:withdrawal auto histories = hist_api.get_liquidity_pool_history( lp_id ); - BOOST_CHECK_EQUAL( histories.size(), 4u ); + BOOST_CHECK_EQUAL( histories.size(), 6u ); // limit = 3 histories = hist_api.get_liquidity_pool_history( lp_id, {}, {}, 3 ); BOOST_CHECK_EQUAL( histories.size(), 3u ); // only deposits - histories = hist_api.get_liquidity_pool_history( lp_id, {}, {}, {}, 59 ); - BOOST_CHECK_EQUAL( histories.size(), 1u ); + histories = hist_api.get_liquidity_pool_history( lp_id, {}, {}, {}, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 2u ); // time too early histories = hist_api.get_liquidity_pool_history( lp_id, head_time - fc::days(3) ); @@ -979,7 +1036,8 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) histories = hist_api.get_liquidity_pool_history( lp_id, {}, head_time - fc::days(3), {}, 63 ); BOOST_CHECK_EQUAL( histories.size(), 2u ); - // start = 2, limit = 3, so result sequence == {1,2} + // start = 2, limit = 3, so result sequence == {2,1} + // note: range is (stop, start] histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, {}, 3 ); BOOST_CHECK_EQUAL( histories.size(), 2u ); @@ -987,8 +1045,8 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, {}, 1 ); BOOST_CHECK_EQUAL( histories.size(), 1u ); - // start = 3, limit is default, but exchange only, so result sequence == {3} - histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 3, head_time - fc::days(3), {}, 63 ); + // start = 4, limit is default, but exchange only, so result sequence == {4} + histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, head_time - fc::days(3), {}, 63 ); BOOST_CHECK_EQUAL( histories.size(), 1u ); } catch (fc::exception& e) { From 3c5d37da404fa08904a861ff4711ac297fc96fb5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 10 Jun 2021 10:56:38 -0400 Subject: [PATCH 033/258] Fix LP deposit statistics --- .../chain/include/graphene/chain/config.hpp | 2 +- .../market_history/market_history_plugin.cpp | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 7fd7f42cef..372f3a74dc 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -32,7 +32,7 @@ #define GRAPHENE_MAX_NESTED_OBJECTS (200) -const std::string GRAPHENE_CURRENT_DB_VERSION = "20210222"; +const std::string GRAPHENE_CURRENT_DB_VERSION = "20210609"; #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 diff --git a/libraries/plugins/market_history/market_history_plugin.cpp b/libraries/plugins/market_history/market_history_plugin.cpp index adca4f5b77..638b6c29b4 100644 --- a/libraries/plugins/market_history/market_history_plugin.cpp +++ b/libraries/plugins/market_history/market_history_plugin.cpp @@ -417,11 +417,11 @@ void market_history_plugin_impl::update_market_histories( const signed_block& b auto& result = oho.result.get< generic_exchange_operation_result >(); db.modify( *ticker, [&op,&result]( liquidity_pool_ticker_object& t ) { t._24h_deposit_count -= 1; - t._24h_deposit_amount_a -= op.amount_a.amount.value; - t._24h_deposit_amount_b -= op.amount_b.amount.value; + t._24h_deposit_amount_a -= result.paid.front().amount.value; + t._24h_deposit_amount_b -= result.paid.back().amount.value; t._24h_deposit_share_amount -= result.received.front().amount.value; - t._24h_balance_delta_a -= op.amount_a.amount.value; - t._24h_balance_delta_b -= op.amount_b.amount.value; + t._24h_balance_delta_a -= result.paid.front().amount.value; + t._24h_balance_delta_b -= result.paid.back().amount.value; }); } else if( oho.op.is_type< liquidity_pool_withdraw_operation >() ) @@ -643,14 +643,14 @@ void market_history_plugin_impl::update_liquidity_pool_histories( db.modify( *ticker, [&op,&result]( liquidity_pool_ticker_object& t ) { t._24h_deposit_count += 1; - t._24h_deposit_amount_a += op.amount_a.amount.value; - t._24h_deposit_amount_b += op.amount_b.amount.value; + t._24h_deposit_amount_a += result.paid.front().amount.value; + t._24h_deposit_amount_b += result.paid.back().amount.value; t._24h_deposit_share_amount += result.received.front().amount.value; - t._24h_balance_delta_a += op.amount_a.amount.value; - t._24h_balance_delta_b += op.amount_b.amount.value; + t._24h_balance_delta_a += result.paid.front().amount.value; + t._24h_balance_delta_b += result.paid.back().amount.value; t.total_deposit_count += 1; - t.total_deposit_amount_a += op.amount_a.amount.value; - t.total_deposit_amount_b += op.amount_b.amount.value; + t.total_deposit_amount_a += result.paid.front().amount.value; + t.total_deposit_amount_b += result.paid.back().amount.value; t.total_deposit_share_amount += result.received.front().amount.value; }); From 25c5e3654c517e104103f03b6b0cad423690b0d3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 10 Jun 2021 13:37:22 -0400 Subject: [PATCH 034/258] Remove unused lambda capture --- libraries/plugins/market_history/market_history_plugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/plugins/market_history/market_history_plugin.cpp b/libraries/plugins/market_history/market_history_plugin.cpp index 638b6c29b4..943b04a08e 100644 --- a/libraries/plugins/market_history/market_history_plugin.cpp +++ b/libraries/plugins/market_history/market_history_plugin.cpp @@ -415,7 +415,7 @@ void market_history_plugin_impl::update_market_histories( const signed_block& b { auto& op = oho.op.get< liquidity_pool_deposit_operation >(); auto& result = oho.result.get< generic_exchange_operation_result >(); - db.modify( *ticker, [&op,&result]( liquidity_pool_ticker_object& t ) { + db.modify( *ticker, [&result]( liquidity_pool_ticker_object& t ) { t._24h_deposit_count -= 1; t._24h_deposit_amount_a -= result.paid.front().amount.value; t._24h_deposit_amount_b -= result.paid.back().amount.value; @@ -641,7 +641,7 @@ void market_history_plugin_impl::update_liquidity_pool_histories( auto& op = oho.op.get< liquidity_pool_deposit_operation >(); auto& result = oho.result.get< generic_exchange_operation_result >(); - db.modify( *ticker, [&op,&result]( liquidity_pool_ticker_object& t ) { + db.modify( *ticker, [&result]( liquidity_pool_ticker_object& t ) { t._24h_deposit_count += 1; t._24h_deposit_amount_a += result.paid.front().amount.value; t._24h_deposit_amount_b += result.paid.back().amount.value; From f5b0bce269fe3245a209b7e647bd2a58ebfbc6e1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 10 Jun 2021 19:17:42 -0400 Subject: [PATCH 035/258] Remove unused variables --- libraries/plugins/market_history/market_history_plugin.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/plugins/market_history/market_history_plugin.cpp b/libraries/plugins/market_history/market_history_plugin.cpp index 943b04a08e..853f541c9c 100644 --- a/libraries/plugins/market_history/market_history_plugin.cpp +++ b/libraries/plugins/market_history/market_history_plugin.cpp @@ -413,7 +413,6 @@ void market_history_plugin_impl::update_market_histories( const signed_block& b const operation_history_object& oho = history_itr->op; if( oho.op.is_type< liquidity_pool_deposit_operation >() ) { - auto& op = oho.op.get< liquidity_pool_deposit_operation >(); auto& result = oho.result.get< generic_exchange_operation_result >(); db.modify( *ticker, [&result]( liquidity_pool_ticker_object& t ) { t._24h_deposit_count -= 1; @@ -638,7 +637,6 @@ void market_history_plugin_impl::update_liquidity_pool_histories( { if( oho.op.is_type< liquidity_pool_deposit_operation >() ) { - auto& op = oho.op.get< liquidity_pool_deposit_operation >(); auto& result = oho.result.get< generic_exchange_operation_result >(); db.modify( *ticker, [&result]( liquidity_pool_ticker_object& t ) { From fbc6dd4e7c0270b0286389b837e271cdf86d11d6 Mon Sep 17 00:00:00 2001 From: Abit Date: Fri, 11 Jun 2021 18:22:35 +0200 Subject: [PATCH 036/258] Upgrade Docker to Ubuntu Focal Fossa (20.04 LTS) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2b376c3b10..fcce4cbef0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM phusion/baseimage:0.11 +FROM phusion/baseimage:focal-1.0.0 MAINTAINER The bitshares decentralized organisation ENV LANG=en_US.UTF-8 From e0f7317caacc0dbc8afefe63c400840b1efab1bc Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 11 Jun 2021 17:03:55 +0000 Subject: [PATCH 037/258] Add tests for SameT Fund update --- tests/tests/samet_fund_tests.cpp | 103 +++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 5 deletions(-) diff --git a/tests/tests/samet_fund_tests.cpp b/tests/tests/samet_fund_tests.cpp index ab7a775184..9efe7d8a28 100644 --- a/tests/tests/samet_fund_tests.cpp +++ b/tests/tests/samet_fund_tests.cpp @@ -86,7 +86,7 @@ BOOST_AUTO_TEST_CASE( samet_fund_hardfork_time_test ) } } -BOOST_AUTO_TEST_CASE( samet_fund_create_delete_proposal_test ) +BOOST_AUTO_TEST_CASE( samet_fund_crud_and_proposal_test ) { try { // Pass the hard fork time @@ -204,6 +204,11 @@ BOOST_AUTO_TEST_CASE( samet_fund_create_delete_proposal_test ) const samet_fund_object& sfo3 = create_samet_fund( sam_id, eur.id, 10, 1u ); // Account is whitelisted samet_fund_id_type sf3_id = sfo3.id; + BOOST_CHECK( sfo3.owner_account == sam_id ); + BOOST_CHECK( sfo3.asset_type == eur_id ); + BOOST_CHECK( sfo3.balance == 10 ); + BOOST_CHECK( sfo3.fee_rate == 1u ); + BOOST_CHECK( sfo3.unpaid_amount == 0 ); expected_balance_sam_eur -= 10; check_balances(); @@ -221,6 +226,60 @@ BOOST_AUTO_TEST_CASE( samet_fund_create_delete_proposal_test ) check_balances(); + // Uable to update a fund with invalid data + // Changes nothing + BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, {}, {} ), fc::exception ); + // Zero delta + BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(0), 10u ), fc::exception ); + // Specified new fee rate but no change + BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(1), sf1_id(db).fee_rate ), fc::exception ); + // Fund owner mismatch + BOOST_CHECK_THROW( update_samet_fund( ted_id, sf1_id, asset(1), {} ), fc::exception ); + // Asset type mismatch + BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(1, usd_id), {} ), fc::exception ); + // Trying to withdraw too much + BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(-10000), {} ), fc::exception ); + // Insufficient account balance + BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(init_amount), {} ), fc::exception ); + + check_balances(); + + // Able to update a fund with valid data + // Only deposit + update_samet_fund( sam_id, sf1_id, asset(1), {} ); + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 10001 ); + BOOST_CHECK( sf1_id(db).fee_rate == 100u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core -= 1; + check_balances(); + + // Only update fee rate + update_samet_fund( sam_id, sf1_id, {}, 101u ); + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 10001 ); + BOOST_CHECK( sf1_id(db).fee_rate == 101u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + check_balances(); + + // Withdraw and update fee rate + update_samet_fund( sam_id, sf1_id, asset(-9999), 10u ); + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 2 ); + BOOST_CHECK( sf1_id(db).fee_rate == 10u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core += 9999; + check_balances(); + // Sam is able to delete his own fund asset released = delete_samet_fund( sam_id, sf1_id ); @@ -228,11 +287,13 @@ BOOST_AUTO_TEST_CASE( samet_fund_create_delete_proposal_test ) BOOST_REQUIRE( db.find( sf2_id ) ); BOOST_REQUIRE( db.find( sf3_id ) ); - BOOST_CHECK( released == asset( 10000, core_id ) ); + BOOST_CHECK( released == asset( 2, core_id ) ); - expected_balance_sam_core += 10000; + expected_balance_sam_core += 2; check_balances(); + // Unable to update a fund that does not exist + BOOST_CHECK_THROW( update_samet_fund( sam_id, sf1_id, asset(1), {} ), fc::exception ); // Unable to delete a fund that does not exist BOOST_CHECK_THROW( delete_samet_fund( sam_id, sf1_id ), fc::exception ); // Unable to delete a fund that is not owned by him @@ -258,13 +319,45 @@ BOOST_AUTO_TEST_CASE( samet_fund_create_delete_proposal_test ) PUSH_TX( db, trx, ~0 ); } + // Sam is now unable to deposit to the fund + BOOST_CHECK_THROW( update_samet_fund( sam_id, sf3_id, asset(1, eur_id), {} ), fc::exception ); + + BOOST_CHECK( sf3_id(db).owner_account == sam_id ); + BOOST_CHECK( sf3_id(db).asset_type == eur_id ); + BOOST_CHECK( sf3_id(db).balance == 10 ); + BOOST_CHECK( sf3_id(db).fee_rate == 1u ); + BOOST_CHECK( sf3_id(db).unpaid_amount == 0 ); + + check_balances(); + + // Sam is still able to withdraw from the fund + update_samet_fund( sam_id, sf3_id, asset(-1, eur_id), {} ); + BOOST_CHECK( sf3_id(db).owner_account == sam_id ); + BOOST_CHECK( sf3_id(db).asset_type == eur_id ); + BOOST_CHECK( sf3_id(db).balance == 9 ); + BOOST_CHECK( sf3_id(db).fee_rate == 1u ); + BOOST_CHECK( sf3_id(db).unpaid_amount == 0 ); + + expected_balance_sam_eur += 1; + check_balances(); + + // Sam is still able to update fee rate + update_samet_fund( sam_id, sf3_id, {}, 2u ); + BOOST_CHECK( sf3_id(db).owner_account == sam_id ); + BOOST_CHECK( sf3_id(db).asset_type == eur_id ); + BOOST_CHECK( sf3_id(db).balance == 9 ); + BOOST_CHECK( sf3_id(db).fee_rate == 2u ); + BOOST_CHECK( sf3_id(db).unpaid_amount == 0 ); + + check_balances(); + // Sam is still able to delete the fund released = delete_samet_fund( sam_id, sf3_id ); BOOST_REQUIRE( !db.find( sf3_id ) ); - BOOST_CHECK( released == asset( 10, eur_id ) ); + BOOST_CHECK( released == asset( 9, eur_id ) ); - expected_balance_sam_eur += 10; + expected_balance_sam_eur += 9; check_balances(); // Same is unable to recreate the fund From c6b96697833f69b1e3afe4489553482802f3ee15 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 11 Jun 2021 19:02:28 +0000 Subject: [PATCH 038/258] Fix samet fund fee calculation --- libraries/chain/samet_fund_evaluator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp index fcd2a4e4d0..6342440383 100644 --- a/libraries/chain/samet_fund_evaluator.cpp +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -194,8 +194,8 @@ void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_opera FC_ASSERT( op.repay_amount.amount <= _fund->unpaid_amount, "Repay amount should not be greater than unpaid amount" ); - auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) + GRAPHENE_SAMET_FUND_FEE_DENOM ) - 1 ) - * _fund->fee_rate ) / GRAPHENE_SAMET_FUND_FEE_DENOM; // Round up + auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) * _fund->fee_rate ) + + GRAPHENE_SAMET_FUND_FEE_DENOM ) - 1 ) / GRAPHENE_SAMET_FUND_FEE_DENOM; // Round up FC_ASSERT( fc::uint128_t(op.fund_fee.amount.value) >= required_fee, "Insuffient fund fee, requires ${r}, offered ${p}", From c59d10e6e96d10403972d776e0b0fcdf2250b882 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 11 Jun 2021 21:27:34 +0000 Subject: [PATCH 039/258] Update assertion messages --- libraries/chain/samet_fund_evaluator.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp index 6342440383..c768e16506 100644 --- a/libraries/chain/samet_fund_evaluator.cpp +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -70,9 +70,9 @@ void_result samet_fund_delete_evaluator::do_evaluate(const samet_fund_delete_ope _fund = &op.fund_id(d); - FC_ASSERT( _fund->owner_account == op.owner_account, "The account is not the owner of the fund" ); + FC_ASSERT( _fund->owner_account == op.owner_account, "The account is not the owner of the SameT Fund" ); - FC_ASSERT( _fund->unpaid_amount == 0, "Can only delete a fund when the unpaid amount is zero" ); + FC_ASSERT( _fund->unpaid_amount == 0, "Can only delete a SameT Fund when the unpaid amount is zero" ); // Note: no asset authorization check here, allow funds to be moved to account balance @@ -98,13 +98,13 @@ void_result samet_fund_update_evaluator::do_evaluate(const samet_fund_update_ope _fund = &op.fund_id(d); - FC_ASSERT( _fund->owner_account == op.owner_account, "The account is not the owner of the fund" ); + FC_ASSERT( _fund->owner_account == op.owner_account, "The account is not the owner of the SameT Fund" ); if( op.delta_amount.valid() ) { FC_ASSERT( _fund->asset_type == op.delta_amount->asset_id, "Asset type mismatch" ); FC_ASSERT( _fund->unpaid_amount == 0, - "Can only update the balance of a fund when the unpaid amount is zero" ); + "Can only update the balance of a SameT Fund when the unpaid amount is zero" ); if( op.delta_amount->amount > 0 ) { @@ -114,7 +114,7 @@ void_result samet_fund_update_evaluator::do_evaluate(const samet_fund_update_ope } else { - FC_ASSERT( _fund->balance > -op.delta_amount->amount, "Insufficient balance in the fund" ); + FC_ASSERT( _fund->balance > -op.delta_amount->amount, "Insufficient balance in the SameT Fund" ); } } @@ -142,7 +142,7 @@ void_result samet_fund_update_evaluator::do_apply( const samet_fund_update_opera }); // Defensive check - FC_ASSERT( _fund->balance > 0, "Balance in the fund should be positive" ); + FC_ASSERT( _fund->balance > 0, "Balance in the SameT Fund should be positive" ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -156,7 +156,7 @@ void_result samet_fund_borrow_evaluator::do_evaluate(const samet_fund_borrow_ope FC_ASSERT( _fund->asset_type == op.borrow_amount.asset_id, "Asset type mismatch" ); FC_ASSERT( _fund->balance >= _fund->unpaid_amount + op.borrow_amount.amount, - "Insufficient balance in the fund thus unable to borrow" ); + "Insufficient balance in the SameT Fund thus unable to borrow" ); FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _fund->asset_type(d) ), "The account is unauthorized by the asset" ); From 9a264493d1be4a315123d7f778ca203c1632b7ae Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 11 Jun 2021 21:28:10 +0000 Subject: [PATCH 040/258] Add tests for SameT Fund borrow and repay --- tests/common/database_fixture.hpp | 2 +- tests/tests/samet_fund_tests.cpp | 654 ++++++++++++++++++++++++++++++ 2 files changed, 655 insertions(+), 1 deletion(-) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index b73761f474..032d9df789 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -378,7 +378,7 @@ struct database_fixture_base { const worker_object& create_worker(account_id_type owner, const share_type daily_pay = 1000, const fc::microseconds& duration = fc::days(2)); template proposal_create_operation make_proposal_create_op( const T& op, account_id_type proposer = GRAPHENE_TEMP_ACCOUNT, - uint32_t timeout = 300, uint32_t review_period = 0 ) const + uint32_t timeout = 300, optional review_period = {} ) const { proposal_create_operation cop; cop.fee_paying_account = proposer; diff --git a/tests/tests/samet_fund_tests.cpp b/tests/tests/samet_fund_tests.cpp index 9efe7d8a28..9718d2cad0 100644 --- a/tests/tests/samet_fund_tests.cpp +++ b/tests/tests/samet_fund_tests.cpp @@ -372,4 +372,658 @@ BOOST_AUTO_TEST_CASE( samet_fund_crud_and_proposal_test ) } } +BOOST_AUTO_TEST_CASE( samet_fund_borrow_repay_test ) +{ try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2351_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(ted)(por)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + asset_id_type core_id; + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.id; + issue_uia( sam, usd.amount(init_amount) ); + issue_uia( ted, usd.amount(init_amount) ); + + const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); + asset_id_type eur_id = eur.id; + issue_uia( sam, eur.amount(init_amount) ); + issue_uia( ted, eur.amount(init_amount) ); + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = eur.id; + uop.issuer = sam_id; + uop.new_options = eur.options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Sam so that he can manage the whitelist + upgrade_to_lifetime_member( sam_id ); + + // Add Sam to the whitelist, but do not add others + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + asset_id_type no_asset_id( core.id + 100 ); + BOOST_REQUIRE( !db.find( no_asset_id ) ); + + int64_t expected_balance_sam_core = init_amount; + int64_t expected_balance_sam_usd = init_amount; + int64_t expected_balance_sam_eur = init_amount; + int64_t expected_balance_ted_core = init_amount; + int64_t expected_balance_ted_usd = init_amount; + int64_t expected_balance_ted_eur = init_amount; + + const auto& check_balances = [&]() { + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur ); + }; + + check_balances(); + + // create samet funds + const samet_fund_object& sfo1 = create_samet_fund( sam_id, core.id, 10000, 10000u ); // fee rate is 1% + samet_fund_id_type sf1_id = sfo1.id; + + expected_balance_sam_core -= 10000; + check_balances(); + + const samet_fund_object& sfo2 = create_samet_fund( ted_id, usd.id, 1, 10000000u ); // fee rate is 1000% + samet_fund_id_type sf2_id = sfo2.id; + + expected_balance_ted_usd -= 1; + check_balances(); + + const samet_fund_object& sfo3 = create_samet_fund( sam_id, eur.id, 10, 1u ); // Account is whitelisted + samet_fund_id_type sf3_id = sfo3.id; + + expected_balance_sam_eur -= 10; + check_balances(); + + // Unable to borrow without repayment + BOOST_CHECK_THROW( borrow_from_samet_fund( sam_id, sf1_id, asset(1) ), fc::exception ); + // Unable to repay without borrowing + BOOST_CHECK_THROW( repay_to_samet_fund( sam_id, sf1_id, asset(1), asset(100) ), fc::exception ); + + // Valid : borrow and repay + { + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(1) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(1), asset(1) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + PUSH_TX( db, trx, ~0 ); + } + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 10001 ); + BOOST_CHECK( sf1_id(db).fee_rate == 10000u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core -= 1; + check_balances(); + + // Valid : borrow multiple times and repay at once + { + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(1) ); + auto bop2 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(2) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(3), asset(1) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(bop2); + trx.operations.push_back(rop1); + PUSH_TX( db, trx, ~0 ); + } + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 10002 ); + BOOST_CHECK( sf1_id(db).fee_rate == 10000u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core -= 1; + check_balances(); + + // Valid : borrow with one account and repay with another account + { + auto bop1 = make_samet_fund_borrow_op( ted_id, sf1_id, asset(5) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(5), asset(1) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + PUSH_TX( db, trx, ~0 ); + } + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 10003 ); + BOOST_CHECK( sf1_id(db).fee_rate == 10000u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_ted_core += 5; + expected_balance_sam_core -= 6; + check_balances(); + + // Valid : borrow at once, repay via multiple times + { + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(7) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(3), asset(1) ); + auto rop2 = make_samet_fund_repay_op( ted_id, sf1_id, asset(4), asset(1) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + trx.operations.push_back(rop2); + PUSH_TX( db, trx, ~0 ); + } + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 10005 ); + BOOST_CHECK( sf1_id(db).fee_rate == 10000u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core += 3; + expected_balance_ted_core -= 5; + check_balances(); + + // Valid : borrow from multiple funds and repay + { + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(7) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(3), asset(1) ); + auto bop2 = make_samet_fund_borrow_op( ted_id, sf2_id, asset(1, usd_id) ); + auto rop2 = make_samet_fund_repay_op( ted_id, sf1_id, asset(4), asset(1) ); + auto rop3 = make_samet_fund_repay_op( sam_id, sf2_id, asset(1, usd_id), asset(10, usd_id) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + trx.operations.push_back(bop2); + trx.operations.push_back(rop2); + trx.operations.push_back(rop3); + PUSH_TX( db, trx, ~0 ); + } + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 10007 ); + BOOST_CHECK( sf1_id(db).fee_rate == 10000u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + BOOST_CHECK( sf2_id(db).owner_account == ted_id ); + BOOST_CHECK( sf2_id(db).asset_type == usd_id ); + BOOST_CHECK( sf2_id(db).balance == 11 ); + BOOST_CHECK( sf2_id(db).fee_rate == 10000000u ); + BOOST_CHECK( sf2_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core += 3; + expected_balance_ted_core -= 5; + expected_balance_sam_usd -= 11; + expected_balance_ted_usd += 1; + check_balances(); + + // Valid : borrow and repay with more fee than enough + { + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(1) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(1), asset(2) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + PUSH_TX( db, trx, ~0 ); + } + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == 10009 ); + BOOST_CHECK( sf1_id(db).fee_rate == 10000u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core -= 2; + check_balances(); + + // Valid: account whitelisted by asset + { + auto bop1 = make_samet_fund_borrow_op( sam_id, sf3_id, asset(1, eur_id) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf3_id, asset(1, eur_id), asset(1, eur_id) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + PUSH_TX( db, trx, ~0 ); + } + + BOOST_CHECK( sf3_id(db).owner_account == sam_id ); + BOOST_CHECK( sf3_id(db).asset_type == eur_id ); + BOOST_CHECK( sf3_id(db).balance == 11 ); + BOOST_CHECK( sf3_id(db).fee_rate == 1u ); + BOOST_CHECK( sf3_id(db).unpaid_amount == 0 ); + + expected_balance_sam_eur -= 1; + check_balances(); + + // Invalid operations + { + // Borrow 0 + auto bop = make_samet_fund_borrow_op( sam_id, sf1_id, asset(0) ); + BOOST_CHECK_THROW( bop.validate(), fc::exception ); + BOOST_CHECK_THROW( propose( bop ), fc::exception ); + + // Borrow a negative amount + bop = make_samet_fund_borrow_op( sam_id, sf1_id, asset(-1) ); + BOOST_CHECK_THROW( bop.validate(), fc::exception ); + BOOST_CHECK_THROW( propose( bop ), fc::exception ); + + // Repay 0 + auto rop = make_samet_fund_repay_op( sam_id, sf1_id, asset(0), asset(1) ); + BOOST_CHECK_THROW( rop.validate(), fc::exception ); + BOOST_CHECK_THROW( propose( rop ), fc::exception ); + + // Repay a negative amount + rop = make_samet_fund_repay_op( sam_id, sf1_id, asset(-1), asset(1) ); + BOOST_CHECK_THROW( rop.validate(), fc::exception ); + BOOST_CHECK_THROW( propose( rop ), fc::exception ); + + // Repay with a negative fee + rop = make_samet_fund_repay_op( sam_id, sf1_id, asset(1), asset(-1) ); + BOOST_CHECK_THROW( rop.validate(), fc::exception ); + BOOST_CHECK_THROW( propose( rop ), fc::exception ); + + // Repay amount and fee in different assets + rop = make_samet_fund_repay_op( sam_id, sf1_id, asset(1), asset(1, usd_id) ); + BOOST_CHECK_THROW( rop.validate(), fc::exception ); + BOOST_CHECK_THROW( propose( rop ), fc::exception ); + } + + // Valid : borrow all from a fund + auto expected_sf1_balance = sf1_id(db).balance; + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + PUSH_TX( db, trx, ~0 ); + + expected_sf1_balance += fund_fee; + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance ); + BOOST_CHECK( sf1_id(db).fee_rate == 10000u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core -= fund_fee.value; + check_balances(); + } + + // Valid : update fund fee rate after borrowed + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto uop1 = make_samet_fund_update_op( sam_id, sf1_id, {}, 9999u ); // new fee rate is 0.9999% + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(uop1); + trx.operations.push_back(rop1); + PUSH_TX( db, trx, ~0 ); + + expected_sf1_balance += fund_fee; + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance ); + BOOST_CHECK( sf1_id(db).fee_rate == 9999u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core -= fund_fee.value; + check_balances(); + + // Able to do the same via a proposal + auto cop = make_proposal_create_op( bop1, sam_id, 300, {} ); + auto uop2 = make_samet_fund_update_op( sam_id, sf1_id, {}, 9998u ); // new fee rate is 0.9998% + cop.proposed_ops.emplace_back(uop2); + cop.proposed_ops.emplace_back(rop1); + trx.operations.clear(); + trx.operations.push_back( cop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + proposal_id_type pid = op_result.get(); + + proposal_update_operation puo; + puo.proposal = pid; + puo.fee_paying_account = sam_id; + puo.active_approvals_to_add.emplace( sam_id ); + trx.operations.clear(); + trx.operations.push_back(puo); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !db.find(pid) ); + + expected_sf1_balance += fund_fee; + + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance ); + BOOST_CHECK( sf1_id(db).fee_rate == 9998u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + expected_balance_sam_core -= fund_fee.value; + check_balances(); + } + + vector proposals; + auto make_proposal_from_trx = [&]() { + proposal_create_operation cop; + cop.fee_paying_account = sam_id; + cop.expiration_time = db.head_block_time() + 30; + cop.review_period_seconds = {}; + for( auto& op : trx.operations ) + { + cop.proposed_ops.emplace_back( op ); + } + for( auto& o : cop.proposed_ops ) db.current_fee_schedule().set_fee(o.op); + + trx.operations.clear(); + trx.operations.push_back( cop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + proposal_id_type pid = op_result.get(); + proposals.push_back( pid ); + }; + + // Invalid : borrow more amount than available + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance + 1; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : borrow more amount than available + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance + 1; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow - 2) ); + auto bop2 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(2) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(bop2); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : borrow asset type mismatch + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow, usd_id) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + + rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow, usd_id), asset(fund_fee, usd_id) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : repay asset type mismatch + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow, usd_id), asset(fund_fee, usd_id) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : repay less than borrowed + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow - 1), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : repay more than borrowed + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = (to_borrow + 1) / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow + 1), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + + // Invalid too if repaid more and borrow again + auto bop2 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(1) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + trx.operations.push_back(bop2); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : insufficient fund fee paid + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = ( to_borrow - 1 ) / 100; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : insufficient account balance to repay the debt + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( por_id, sf1_id, asset(to_borrow) ); + auto rop1 = make_samet_fund_repay_op( por_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : update fund balance after borrowed + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto uop1 = make_samet_fund_update_op( sam_id, sf1_id, asset(1), {} ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(uop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + + auto uop2 = make_samet_fund_update_op( sam_id, sf1_id, asset(-1), {} ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(uop2); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid : delete fund after borrowed + { + auto balance = sf1_id(db).balance; + auto to_borrow = balance; + auto fund_fee = to_borrow / 100 + 1; + auto bop1 = make_samet_fund_borrow_op( sam_id, sf1_id, asset(to_borrow) ); + auto dop1 = make_samet_fund_delete_op( sam_id, sf1_id ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(dop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + + auto rop1 = make_samet_fund_repay_op( sam_id, sf1_id, asset(to_borrow), asset(fund_fee) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(dop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid: borrow account is not whitelisted by asset + { + auto bop1 = make_samet_fund_borrow_op( ted_id, sf3_id, asset(1, eur_id) ); + auto rop1 = make_samet_fund_repay_op( sam_id, sf3_id, asset(1, eur_id), asset(1, eur_id) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + // Invalid: repay account is not whitelisted by asset + { + auto bop1 = make_samet_fund_borrow_op( sam_id, sf3_id, asset(1, eur_id) ); + auto rop1 = make_samet_fund_repay_op( ted_id, sf3_id, asset(1, eur_id), asset(1, eur_id) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + BOOST_CHECK_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); + make_proposal_from_trx(); + } + + generate_block(); + + // Nothing changed + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance ); + BOOST_CHECK( sf1_id(db).fee_rate == 9998u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + check_balances(); + + // Approve the proposals + for( auto& pid : proposals ) + { + auto& p = pid(db); + proposal_update_operation puo; + puo.proposal = pid; + puo.fee_paying_account = sam_id; + for(auto& req : p.required_active_approvals) + puo.active_approvals_to_add.emplace( req ); + trx.operations.clear(); + trx.operations.push_back(puo); + PUSH_TX(db, trx, ~0); + + // Approved but failed to execute + BOOST_CHECK( pid(db).is_authorized_to_execute(db) ); + BOOST_CHECK( !pid(db).fail_reason.empty() ); + + // Nothing changed + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance ); + BOOST_CHECK( sf1_id(db).fee_rate == 9998u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + check_balances(); + } + + // Nothing changed + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance ); + BOOST_CHECK( sf1_id(db).fee_rate == 9998u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + check_balances(); + + // Time goes by + generate_blocks( db.head_block_time() + fc::seconds(300) ); + + // proposals expired + for( auto& pid : proposals ) + { + BOOST_CHECK( !db.find(pid) ); + } + + // Nothing changed + BOOST_CHECK( sf1_id(db).owner_account == sam_id ); + BOOST_CHECK( sf1_id(db).asset_type == core.id ); + BOOST_CHECK( sf1_id(db).balance == expected_sf1_balance ); + BOOST_CHECK( sf1_id(db).fee_rate == 9998u ); + BOOST_CHECK( sf1_id(db).unpaid_amount == 0 ); + + check_balances(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 54249625250f5708e8634f9b90dd9f3016575ad8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 12 Jun 2021 15:44:26 +0000 Subject: [PATCH 041/258] Add database APIs for SameT Funds - list_samet_funds( limit, start_id ) - get_samet_funds_by_owner( account_name_or_id, limit, start_id ) - get_samet_funds_by_asset( asset_symbol_or_id, limit, start_id ) --- libraries/app/application.cpp | 5 + libraries/app/database_api.cpp | 123 ++++++++++++++++++ libraries/app/database_api_impl.hxx | 13 ++ .../app/include/graphene/app/application.hpp | 1 + .../app/include/graphene/app/database_api.hpp | 65 ++++++++- 5 files changed, 204 insertions(+), 3 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index f8436ec844..b3a26bca83 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -394,6 +394,9 @@ void application_impl::set_api_limit() { _app_options.api_limit_get_liquidity_pool_history = _options->at("api-limit-get-liquidity-pool-history").as(); } + if(_options->count("api-limit-get-samet-funds") > 0) { + _app_options.api_limit_get_samet_funds = _options->at("api-limit-get-samet-funds").as(); + } } graphene::chain::genesis_state_type application_impl::initialize_genesis_state() const @@ -1188,6 +1191,8 @@ void application::set_program_options(boost::program_options::options_descriptio "Set maximum limit value for database APIs which query for liquidity pools") ("api-limit-get-liquidity-pool-history", boost::program_options::value()->default_value(101), "Set maximum limit value for APIs which query for history of liquidity pools") + ("api-limit-get-samet-funds", boost::program_options::value()->default_value(101), + "Set maximum limit value for database APIs which query for SameT Funds") ; command_line_options.add(configuration_file_options); command_line_options.add_options() diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index a86305005e..612af1f5f8 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -2075,6 +2075,129 @@ vector database_api_impl::get_liquidity_pools_by return results; } +////////////////////////////////////////////////////////////////////// +// // +// SameT Funds // +// // +////////////////////////////////////////////////////////////////////// + +vector database_api::list_samet_funds( + const optional& limit, + const optional& start_id )const +{ + return my->list_samet_funds( limit, start_id ); +} + +vector database_api_impl::list_samet_funds( + const optional& olimit, + const optional& ostart_id )const +{ + uint32_t limit = olimit.valid() ? *olimit : 101; + + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_samet_funds; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + + vector results; + + samet_fund_id_type start_id = ostart_id.valid() ? *ostart_id : samet_fund_id_type(); + + const auto& idx = _db.get_index_type().indices().get(); + auto lower_itr = idx.lower_bound( start_id ); + auto upper_itr = idx.end(); + + results.reserve( limit ); + for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) + { + results.emplace_back( *lower_itr ); + } + + return results; +} + +vector database_api::get_samet_funds_by_owner( + const std::string& account_name_or_id, + const optional& limit, + const optional& start_id )const +{ + return my->get_samet_funds_by_owner( account_name_or_id, limit, start_id ); +} + +vector database_api_impl::get_samet_funds_by_owner( + const std::string& account_name_or_id, + const optional& olimit, + const optional& ostart_id )const +{ + uint32_t limit = olimit.valid() ? *olimit : 101; + + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_samet_funds; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + + account_id_type owner = get_account_from_string(account_name_or_id)->id; + + vector results; + + samet_fund_id_type start_id = ostart_id.valid() ? *ostart_id : samet_fund_id_type(); + + const auto& idx = _db.get_index_type().indices().get(); + auto lower_itr = idx.lower_bound( std::make_tuple( owner, start_id ) ); + auto upper_itr = idx.upper_bound( owner ); + + results.reserve( limit ); + for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) + { + results.emplace_back( *lower_itr ); + } + + return results; +} + +vector database_api::get_samet_funds_by_asset( + const std::string& asset_symbol_or_id, + const optional& limit, + const optional& start_id )const +{ + return my->get_samet_funds_by_asset( asset_symbol_or_id, limit, start_id ); +} + +vector database_api_impl::get_samet_funds_by_asset( + const std::string& asset_symbol_or_id, + const optional& olimit, + const optional& ostart_id )const +{ + uint32_t limit = olimit.valid() ? *olimit : 101; + + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_samet_funds; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + + asset_id_type asset_type = get_asset_from_string(asset_symbol_or_id)->id; + + vector results; + + samet_fund_id_type start_id = ostart_id.valid() ? *ostart_id : samet_fund_id_type(); + + const auto& idx = _db.get_index_type().indices().get(); + auto lower_itr = idx.lower_bound( std::make_tuple( asset_type, start_id ) ); + auto upper_itr = idx.upper_bound( asset_type ); + + results.reserve( limit ); + for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) + { + results.emplace_back( *lower_itr ); + } + + return results; +} + + ////////////////////////////////////////////////////////////////////// // // // Witnesses // diff --git a/libraries/app/database_api_impl.hxx b/libraries/app/database_api_impl.hxx index 2088045245..4f77f3852d 100644 --- a/libraries/app/database_api_impl.hxx +++ b/libraries/app/database_api_impl.hxx @@ -178,6 +178,19 @@ class database_api_impl : public std::enable_shared_from_this optional start_id = optional(), optional with_statistics = false )const; + // SameT Funds + vector list_samet_funds( + const optional& limit = 101, + const optional& start_id = optional() )const; + vector get_samet_funds_by_owner( + const std::string& account_name_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + vector get_samet_funds_by_asset( + const std::string& asset_symbol_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + // Witnesses vector> get_witnesses(const vector& witness_ids)const; fc::optional get_witness_by_account(const std::string account_id_or_name)const; diff --git a/libraries/app/include/graphene/app/application.hpp b/libraries/app/include/graphene/app/application.hpp index 3b0cbb2808..bacdf7ff3f 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -75,6 +75,7 @@ namespace graphene { namespace app { uint64_t api_limit_get_tickets = 101; uint64_t api_limit_get_liquidity_pools = 101; uint64_t api_limit_get_liquidity_pool_history = 101; + uint64_t api_limit_get_samet_funds = 101; }; class application diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index fed25b05d5..47d9f2e29d 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -33,8 +33,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -42,8 +42,6 @@ #include #include -#include - #include #include @@ -788,6 +786,62 @@ class database_api optional start_id = optional(), optional with_statistics = false )const; + ///////////////////// + // SameT Funds // + ///////////////////// + + /** + * @brief Get a list of SameT Funds + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start SameT Fund id, fetch items whose IDs are greater than or equal to this ID + * @return The SameT Funds + * + * @note + * 1. @p limit can be omitted or be null, if so the default value 101 will be used + * 2. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 3. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector list_samet_funds( + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of SameT Funds by the name or ID of the owner account + * @param account_name_or_id name or ID of the owner account + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start Samet Fund id, fetch items whose IDs are greater than or equal to this ID + * @return The SameT Funds + * + * @note + * 1. if @p account_name_or_id cannot be tied to an account, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_samet_funds_by_owner( + const std::string& account_name_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of SameT Funds by the symbole or ID of the asset type + * @param asset_symbol_or_id symbol or ID of the asset type + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start Samet Fund id, fetch items whose IDs are greater than or equal to this ID + * @return The SameT Funds + * + * @note + * 1. if @p asset_symbol_or_id cannot be tied to an asset, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_samet_funds_by_asset( + const std::string& asset_symbol_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + + /////////////// // Witnesses // /////////////// @@ -1205,6 +1259,11 @@ FC_API(graphene::app::database_api, (get_liquidity_pools_by_share_asset) (get_liquidity_pools_by_owner) + // SameT Funds + (list_samet_funds) + (get_samet_funds_by_owner) + (get_samet_funds_by_asset) + // Witnesses (get_witnesses) (get_witness_by_account) From a72830cae524a6e7ef0e328e3e2218f00588b04c Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 12 Jun 2021 16:24:31 +0000 Subject: [PATCH 042/258] Add tests for database APIs about SameT Funds --- tests/tests/samet_fund_tests.cpp | 136 +++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tests/tests/samet_fund_tests.cpp b/tests/tests/samet_fund_tests.cpp index 9718d2cad0..bed7a4c125 100644 --- a/tests/tests/samet_fund_tests.cpp +++ b/tests/tests/samet_fund_tests.cpp @@ -1026,4 +1026,140 @@ BOOST_AUTO_TEST_CASE( samet_fund_borrow_repay_test ) } } +BOOST_AUTO_TEST_CASE( samet_fund_apis_test ) +{ try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2351_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(ted)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + asset_id_type core_id; + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.id; + issue_uia( sam, usd.amount(init_amount) ); + issue_uia( ted, usd.amount(init_amount) ); + + const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); + asset_id_type eur_id = eur.id; + issue_uia( sam, eur.amount(init_amount) ); + issue_uia( ted, eur.amount(init_amount) ); + + // create samet funds + const samet_fund_object& sfo1 = create_samet_fund( sam_id, core_id, 10000, 10000u ); // fee rate is 1% + samet_fund_id_type sf1_id = sfo1.id; + + const samet_fund_object& sfo2 = create_samet_fund( ted_id, usd_id, 1, 10000000u ); // fee rate is 1000% + samet_fund_id_type sf2_id = sfo2.id; + + const samet_fund_object& sfo3 = create_samet_fund( sam_id, eur_id, 10, 1u ); + samet_fund_id_type sf3_id = sfo3.id; + + const samet_fund_object& sfo4 = create_samet_fund( sam_id, eur_id, 10, 2u ); + samet_fund_id_type sf4_id = sfo4.id; + + const samet_fund_object& sfo5 = create_samet_fund( sam_id, usd_id, 100, 20u ); + samet_fund_id_type sf5_id = sfo5.id; + + const samet_fund_object& sfo6 = create_samet_fund( ted_id, usd_id, 1000, 200u ); + samet_fund_id_type sf6_id = sfo6.id; + + generate_block(); + + // Check database API + graphene::app::database_api db_api( db, &( app.get_options() ) ); + + // List all SameT Funds + auto funds = db_api.list_samet_funds(); + BOOST_REQUIRE_EQUAL( funds.size(), 6u ); + BOOST_CHECK( funds.front().id == sf1_id ); + BOOST_CHECK( funds.back().id == sf6_id ); + + // Pagination : the first page + funds = db_api.list_samet_funds( 5 ); + BOOST_REQUIRE_EQUAL( funds.size(), 5u ); + BOOST_CHECK( funds.front().id == sf1_id ); + BOOST_CHECK( funds.back().id == sf5_id ); + + // Pagination : the last page + funds = db_api.list_samet_funds( 5, sf3_id ); + BOOST_REQUIRE_EQUAL( funds.size(), 4u ); + BOOST_CHECK( funds.front().id == sf3_id ); + BOOST_CHECK( funds.back().id == sf6_id ); + + // Limit too large + BOOST_CHECK_THROW( db_api.list_samet_funds( 102 ), fc::exception ); + + // Get all SameT Funds owned by Sam + funds = db_api.get_samet_funds_by_owner( "sam" ); + BOOST_REQUIRE_EQUAL( funds.size(), 4u ); + BOOST_CHECK( funds.front().id == sf1_id ); + BOOST_CHECK( funds.back().id == sf5_id ); + + // Pagination : the first page + funds = db_api.get_samet_funds_by_owner( "sam", 3, {} ); + BOOST_REQUIRE_EQUAL( funds.size(), 3u ); + BOOST_CHECK( funds.front().id == sf1_id ); + BOOST_CHECK( funds.back().id == sf4_id ); + + // Pagination : another page + funds = db_api.get_samet_funds_by_owner( "sam", 3, sf2_id ); + BOOST_REQUIRE_EQUAL( funds.size(), 3u ); + BOOST_CHECK( funds.front().id == sf3_id ); + BOOST_CHECK( funds.back().id == sf5_id ); + + // Pagination : the first page of SameT Funds owned by Ted + funds = db_api.get_samet_funds_by_owner( string("1.2.")+fc::to_string(ted_id.instance.value), 3 ); + BOOST_REQUIRE_EQUAL( funds.size(), 2u ); + BOOST_CHECK( funds.front().id == sf2_id ); + BOOST_CHECK( funds.back().id == sf6_id ); + + // Nonexistent account + BOOST_CHECK_THROW( db_api.get_samet_funds_by_owner( "nonexistent-account" ), fc::exception ); + + // Limit too large + BOOST_CHECK_THROW( db_api.get_samet_funds_by_owner( "ted", 102 ), fc::exception ); + + // Get all SameT Funds whose asset type is USD + funds = db_api.get_samet_funds_by_asset( "MYUSD" ); + BOOST_REQUIRE_EQUAL( funds.size(), 3u ); + BOOST_CHECK( funds.front().id == sf2_id ); + BOOST_CHECK( funds.back().id == sf6_id ); + + // Pagination : the first page + funds = db_api.get_samet_funds_by_asset( "MYUSD", 2 ); + BOOST_REQUIRE_EQUAL( funds.size(), 2u ); + BOOST_CHECK( funds.front().id == sf2_id ); + BOOST_CHECK( funds.back().id == sf5_id ); + + // Pagination : another page + funds = db_api.get_samet_funds_by_asset( "MYUSD", 2, sf4_id ); + BOOST_REQUIRE_EQUAL( funds.size(), 2u ); + BOOST_CHECK( funds.front().id == sf5_id ); + BOOST_CHECK( funds.back().id == sf6_id ); + + // Pagination : the first page of SameT Funds whose asset type is CORE + funds = db_api.get_samet_funds_by_asset( "1.3.0", 2, {} ); + BOOST_REQUIRE_EQUAL( funds.size(), 1u ); + BOOST_CHECK( funds.front().id == sf1_id ); + BOOST_CHECK( funds.back().id == sf1_id ); + + // Nonexistent asset + BOOST_CHECK_THROW( db_api.get_samet_funds_by_asset( "NOSUCHASSET" ), fc::exception ); + + // Limit too large + BOOST_CHECK_THROW( db_api.get_samet_funds_by_asset( "MYUSD", 102 ), fc::exception ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 7c77425d4297fbb10a158b23229770dc769479d9 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 12 Jun 2021 17:35:08 +0000 Subject: [PATCH 043/258] Do cleanups more frequently in sonar-scan workflow --- .github/workflows/sonar-scan.yml | 52 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index 409bf92ab1..f554bfad98 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -114,21 +114,6 @@ jobs: du -hs _build/libraries/* _build/programs/* _build/tests/* du -hs _build/* du -hs /_build/* - - name: Unit-Tests - run: | - _build/tests/app_test -l test_suite - df -h - curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_cluster/settings \ - -d '{ "transient": { "cluster.routing.allocation.disk.threshold_enabled": false } }' - echo - _build/tests/es_test -l test_suite - df -h - libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite - _build/tests/cli_test -l test_suite - df -h - echo "Cleanup" - rm -rf /tmp/graphene* - df -h - name: Quick test for program arguments run: | _build/programs/witness_node/witness_node --version @@ -156,6 +141,38 @@ jobs: else \ echo "Pass: got expected error."; \ fi + - name: Remove binaries that we no longer need + run: | + df -h + echo "Cleanup" + rm -rf _build/programs/witness_node/witness_node + rm -rf _build/programs/cli_wallet/cli_wallet + rm -rf _build/programs/network_mapper/network_mapper + rm -rf _build/programs/js_operation_serializer/js_operation_serializer + rm -rf _build/programs/genesis_util/get_dev_key + df -h + - name: Unit-Tests + run: | + _build/tests/app_test -l test_suite + df -h + echo "Cleanup" + rm -rf /tmp/graphene* + curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_cluster/settings \ + -d '{ "transient": { "cluster.routing.allocation.disk.threshold_enabled": false } }' + echo + _build/tests/es_test -l test_suite + df -h + echo "Cleanup" + rm -rf /tmp/graphene* + libraries/fc/tests/run-parallel-tests.sh _build/tests/chain_test -l test_suite + df -h + echo "Cleanup" + rm -rf /tmp/graphene* + _build/tests/cli_test -l test_suite + df -h + echo "Cleanup" + rm -rf /tmp/graphene* + df -h - name: Prepare for scanning with SonarScanner run: | mkdir -p sonar_cache @@ -187,11 +204,6 @@ jobs: run: | df -h echo "Final cleanup" - rm -rf _build/programs/witness_node/witness_node - rm -rf _build/programs/cli_wallet/cli_wallet - rm -rf _build/programs/network_mapper/network_mapper - rm -rf _build/programs/js_operation_serializer/js_operation_serializer - rm -rf _build/programs/genesis_util/get_dev_key rm -rf _build/tests/app_test rm -rf _build/tests/chain_test rm -rf _build/tests/cli_test From 82c1f5dc64b67d51ebf5ac8f996e2cc4bc6ee0fa Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 15 Jun 2021 03:49:37 -0400 Subject: [PATCH 044/258] Move samet_fund_object reflection into cpp file --- libraries/chain/include/graphene/chain/asset_object.hpp | 1 + .../include/graphene/chain/liquidity_pool_object.hpp | 1 + .../chain/include/graphene/chain/samet_fund_object.hpp | 8 +------- libraries/chain/small_objects.cpp | 7 +++++++ 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 2b364521cf..eee7def65f 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -416,6 +416,7 @@ MAP_OBJECT_ID_TO_TYPE(graphene::chain::asset_bitasset_data_object) FC_REFLECT_DERIVED( graphene::chain::price_feed_with_icr, (graphene::protocol::price_feed), (initial_collateral_ratio) ) +// Note: this is left here but not moved to a cpp file due to the extended_asset_object struct in API. FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object), (symbol) (precision) diff --git a/libraries/chain/include/graphene/chain/liquidity_pool_object.hpp b/libraries/chain/include/graphene/chain/liquidity_pool_object.hpp index 90fffeac47..2853e6c087 100644 --- a/libraries/chain/include/graphene/chain/liquidity_pool_object.hpp +++ b/libraries/chain/include/graphene/chain/liquidity_pool_object.hpp @@ -107,6 +107,7 @@ typedef generic_index li MAP_OBJECT_ID_TO_TYPE( graphene::chain::liquidity_pool_object ) +// Note: this is left here but not moved to a cpp file due to the extended_liquidity_pool_object struct in API. FC_REFLECT_DERIVED( graphene::chain::liquidity_pool_object, (graphene::db::object), (asset_a) (asset_b) diff --git a/libraries/chain/include/graphene/chain/samet_fund_object.hpp b/libraries/chain/include/graphene/chain/samet_fund_object.hpp index d26d648065..11ffec307d 100644 --- a/libraries/chain/include/graphene/chain/samet_fund_object.hpp +++ b/libraries/chain/include/graphene/chain/samet_fund_object.hpp @@ -91,12 +91,6 @@ using samet_fund_index = generic_index Date: Thu, 17 Jun 2021 21:51:07 +0200 Subject: [PATCH 045/258] Clear operation_results when generating block To save disk space and network bandwidth. --- libraries/chain/db_block.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index c122542e8a..1953924ab4 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -447,6 +447,9 @@ signed_block database::_generate_block( { auto temp_session = _undo_db.start_undo_session(); processed_transaction ptx = _apply_transaction( tx ); + // Clear results to save disk space and network bandwidth. + // This may break client applications which rely on the results. + ptx.operation_results.clear(); // We have to recompute pack_size(ptx) because it may be different // than pack_size(tx) (i.e. if one or more results increased @@ -658,8 +661,11 @@ void database::_apply_block( const signed_block& next_block ) notify_changed_objects(); } FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) } - - +/** + * @note if a @c processed_transaction is passed in, it is cast into @c signed_transaction here. + * It also means that the @c operation_results field is ignored by consensus, although it + * is a part of block data. + */ processed_transaction database::apply_transaction(const signed_transaction& trx, uint32_t skip) { processed_transaction result; From 052d3e1da413a9e8d731f2fda03481d92ce74f6e Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 17 Jun 2021 16:33:42 -0400 Subject: [PATCH 046/258] Return owner account for SameT Fund borrow, repay so that the fund owner would get notified when a fund got used. --- .../chain/include/graphene/chain/samet_fund_evaluator.hpp | 6 ++---- libraries/chain/samet_fund_evaluator.cpp | 8 ++++---- .../plugins/account_history/account_history_plugin.cpp | 4 ++++ libraries/plugins/elasticsearch/elasticsearch_plugin.cpp | 4 ++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp index 75f1bd5098..931a34b7cf 100644 --- a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp @@ -28,8 +28,6 @@ namespace graphene { namespace chain { - class asset_object; - class asset_dynamic_data_object; class samet_fund_object; class samet_fund_create_evaluator : public evaluator @@ -69,7 +67,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_borrow_operation; void_result do_evaluate( const samet_fund_borrow_operation& op ); - void_result do_apply( const samet_fund_borrow_operation& op ) const; + object_id_type do_apply( const samet_fund_borrow_operation& op ) const; const samet_fund_object* _fund = nullptr; }; @@ -80,7 +78,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_repay_operation; void_result do_evaluate( const samet_fund_repay_operation& op ); - void_result do_apply( const samet_fund_repay_operation& op ) const; + object_id_type do_apply( const samet_fund_repay_operation& op ) const; const samet_fund_object* _fund = nullptr; }; diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp index c768e16506..9dc21fe680 100644 --- a/libraries/chain/samet_fund_evaluator.cpp +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -164,7 +164,7 @@ void_result samet_fund_borrow_evaluator::do_evaluate(const samet_fund_borrow_ope return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_operation& op) const +object_id_type samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_operation& op) const { try { database& d = db(); @@ -177,7 +177,7 @@ void_result samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_opera // Defensive check FC_ASSERT( _fund->balance >= _fund->unpaid_amount, "Should not borrow more than available" ); - return void_result(); + return _fund->owner_account; } FC_CAPTURE_AND_RETHROW( (op) ) } void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_operation& op) @@ -204,7 +204,7 @@ void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_opera return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operation& op) const +object_id_type samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operation& op) const { try { database& d = db(); @@ -215,7 +215,7 @@ void_result samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operati sfo.unpaid_amount -= op.repay_amount.amount; }); - return void_result(); + return _fund->owner_account; } FC_CAPTURE_AND_RETHROW( (op) ) } } } // graphene::chain diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index dd8a5c6215..6b6eadae63 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -135,8 +135,12 @@ void account_history_plugin_impl::update_account_histories( const signed_block& if( op.op.is_type< account_create_operation >() ) impacted.insert( op.result.get() ); else + { operation_get_impacted_accounts( op.op, impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); + if( op.op.is_type< samet_fund_borrow_operation >() || op.op.is_type< samet_fund_repay_operation >() ) + impacted.insert( op.result.get() ); + } for( auto& a : other ) for( auto& item : a.account_auths ) diff --git a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp index d583afccc0..fd51bf9eb8 100644 --- a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp +++ b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp @@ -165,8 +165,12 @@ bool elasticsearch_plugin_impl::update_account_histories( const signed_block& b if( op.op.is_type< account_create_operation >() ) impacted.insert( op.result.get() ); else + { operation_get_impacted_accounts( op.op, impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); + if( op.op.is_type< samet_fund_borrow_operation >() || op.op.is_type< samet_fund_repay_operation >() ) + impacted.insert( op.result.get() ); + } for( auto& a : other ) for( auto& item : a.account_auths ) From 069d87d78d9ea9dca9e8f87d908ce7aa25524e19 Mon Sep 17 00:00:00 2001 From: Abit Date: Thu, 17 Jun 2021 22:52:57 +0200 Subject: [PATCH 047/258] Fix test cases due to operation_results changes --- tests/cli/main.cpp | 14 ++++++++------ tests/tests/htlc_tests.cpp | 30 ++++++++++++------------------ 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index df4347dc9c..a65c6b5073 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -1668,7 +1668,8 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) BOOST_CHECK(generate_block(app1, result_block)); // get the ID: - htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1].operation_results[0].get(); + auto tmp_hist = con.wallet_api_ptr->get_account_history("alice", 1); + htlc_id_type htlc_id = tmp_hist[0].op.result.get(); alice_htlc_id_as_string = (std::string)(object_id_type)htlc_id; BOOST_TEST_MESSAGE("Alice shares the HTLC ID with Bob. The HTLC ID is: " + alice_htlc_id_as_string); } @@ -1690,7 +1691,8 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) BOOST_CHECK(generate_block(app1, result_block)); // get the ID: - htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1].operation_results[0].get(); + auto tmp_hist = con.wallet_api_ptr->get_account_history("bob", 1); + htlc_id_type htlc_id = tmp_hist[0].op.result.get(); bob_htlc_id_as_string = (std::string)(object_id_type)htlc_id; BOOST_TEST_MESSAGE("Bob shares the HTLC ID with Alice. The HTLC ID is: " + bob_htlc_id_as_string); } @@ -2213,8 +2215,8 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc_bsip64 ) BOOST_CHECK(generate_block(app1, result_block)); // get the ID: - htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1] - .operation_results[0].get(); + auto tmp_hist = con.wallet_api_ptr->get_account_history("alice", 1); + htlc_id_type htlc_id = tmp_hist[0].op.result.get(); alice_htlc_id_as_string = (std::string)(object_id_type)htlc_id; BOOST_TEST_MESSAGE("Alice shares the HTLC ID with Bob. The HTLC ID is: " + alice_htlc_id_as_string); } @@ -2237,8 +2239,8 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc_bsip64 ) BOOST_CHECK(generate_block(app1, result_block)); // get the ID: - htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1] - .operation_results[0].get(); + auto tmp_hist = con.wallet_api_ptr->get_account_history("bob", 1); + htlc_id_type htlc_id = tmp_hist[0].op.result.get(); bob_htlc_id_as_string = (std::string)(object_id_type)htlc_id; BOOST_TEST_MESSAGE("Bob shares the HTLC ID with Alice. The HTLC ID is: " + bob_htlc_id_as_string); } diff --git a/tests/tests/htlc_tests.cpp b/tests/tests/htlc_tests.cpp index ce8a1430a8..15ad4caa8c 100644 --- a/tests/tests/htlc_tests.cpp +++ b/tests/tests/htlc_tests.cpp @@ -103,10 +103,9 @@ try { create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation); trx.operations.push_back(create_operation); sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); + processed_transaction alice_trx = PUSH_TX(db, trx, ~0); trx.clear(); - graphene::chain::signed_block blk = generate_block(); - processed_transaction alice_trx = blk.transactions[0]; + generate_block(); alice_htlc_id = alice_trx.operation_results[0].get(); generate_block(); } @@ -553,10 +552,9 @@ try { create_operation.fee = db.current_fee_schedule().calculate_fee( create_operation ); trx.operations.push_back( create_operation ); sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); + processed_transaction alice_trx = PUSH_TX(db, trx, ~0); trx.clear(); - graphene::chain::signed_block blk = generate_block(); - processed_transaction alice_trx = blk.transactions[0]; + generate_block(); alice_htlc_id = alice_trx.operation_results[0].get(); } @@ -1053,10 +1051,9 @@ try { trx.operations.push_back( create_operation ); sign(trx, alice_private_key); // bob can now accept it, so it works - PUSH_TX( db, trx, ~0 ); + processed_transaction alice_trx = PUSH_TX( db, trx, ~0 ); trx.clear(); - graphene::chain::signed_block blk = generate_block(); - processed_transaction alice_trx = blk.transactions[0]; + generate_block(); alice_htlc_id = alice_trx.operation_results[0].get(); } @@ -1129,11 +1126,10 @@ try { create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation); trx.operations.push_back(create_operation); sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); + processed_transaction alice_trx = PUSH_TX(db, trx, ~0); trx.clear(); set_expiration( db, trx ); - graphene::chain::signed_block blk = generate_block(); - processed_transaction alice_trx = blk.transactions[0]; + generate_block(); alice_htlc_id_bob = alice_trx.operation_results[0].get(); generate_block(); set_expiration( db, trx ); @@ -1153,11 +1149,10 @@ try { create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation); trx.operations.push_back(create_operation); sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); + processed_transaction alice_trx = PUSH_TX(db, trx, ~0); trx.clear(); set_expiration( db, trx ); - graphene::chain::signed_block blk = generate_block(); - processed_transaction alice_trx = blk.transactions[0]; + generate_block(); alice_htlc_id_carl = alice_trx.operation_results[0].get(); generate_block(); set_expiration( db, trx ); @@ -1177,11 +1172,10 @@ try { create_operation.fee = db.get_global_properties().parameters.current_fees->calculate_fee(create_operation); trx.operations.push_back(create_operation); sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); + processed_transaction alice_trx = PUSH_TX(db, trx, ~0); trx.clear(); set_expiration( db, trx ); - graphene::chain::signed_block blk = generate_block(); - processed_transaction alice_trx = blk.transactions[0]; + generate_block(); alice_htlc_id_dan = alice_trx.operation_results[0].get(); generate_block(); set_expiration( db, trx ); From 5d15671c35df8bf669744488b912cd38a77e8eb9 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 18 Jun 2021 12:37:11 +0000 Subject: [PATCH 048/258] Add tests for SameT Fund related account history --- tests/tests/samet_fund_tests.cpp | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/tests/samet_fund_tests.cpp b/tests/tests/samet_fund_tests.cpp index bed7a4c125..a0d40a0eba 100644 --- a/tests/tests/samet_fund_tests.cpp +++ b/tests/tests/samet_fund_tests.cpp @@ -1162,4 +1162,68 @@ BOOST_AUTO_TEST_CASE( samet_fund_apis_test ) } } +BOOST_AUTO_TEST_CASE( samet_fund_account_history_test ) +{ try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2351_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(ted)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + asset_id_type core_id; + + // create samet funds + const samet_fund_object& sfo1 = create_samet_fund( sam_id, core_id, 10000, 10000u ); // fee rate is 1% + samet_fund_id_type sf1_id = sfo1.id; + + generate_block(); + + // Check history API + graphene::app::history_api hist_api(app); + + // Sam's last operation is fund creation + auto histories = hist_api.get_relative_account_history( "sam", 0, 1, 0 ); + BOOST_REQUIRE_EQUAL( histories.size(), 1u ); + BOOST_CHECK( histories[0].op.is_type() ); + + // Ted's last operation is transfer + histories = hist_api.get_relative_account_history( "ted", 0, 1, 0 ); + BOOST_REQUIRE_EQUAL( histories.size(), 1u ); + BOOST_CHECK( histories[0].op.is_type() ); + + // Ted borrow and repay + { + auto bop1 = make_samet_fund_borrow_op( ted_id, sf1_id, asset(1) ); + auto rop1 = make_samet_fund_repay_op( ted_id, sf1_id, asset(1), asset(1) ); + trx.operations.clear(); + trx.operations.push_back(bop1); + trx.operations.push_back(rop1); + PUSH_TX( db, trx, ~0 ); + } + + generate_block(); + + // Sam's last 2 operations are Ted's borrowing and repayment + histories = hist_api.get_relative_account_history( "sam", 0, 2, 0 ); + BOOST_REQUIRE_EQUAL( histories.size(), 2u ); + BOOST_CHECK( histories[0].op.is_type() ); + BOOST_CHECK( histories[1].op.is_type() ); + + // Ted's last 2 operations are the same + auto histories_ted = hist_api.get_relative_account_history( "ted", 0, 2, 0 ); + BOOST_REQUIRE_EQUAL( histories_ted.size(), 2u ); + BOOST_CHECK( histories[0].id == histories_ted[0].id ); + BOOST_CHECK( histories[1].id == histories_ted[1].id ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 4118c44f5fd339d00c286e3115d3cd942be7b23d Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 18 Jun 2021 12:41:23 +0000 Subject: [PATCH 049/258] Add comments --- libraries/protocol/include/graphene/protocol/samet_fund.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp index d384800565..c2a2138e85 100644 --- a/libraries/protocol/include/graphene/protocol/samet_fund.hpp +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -90,6 +90,7 @@ namespace graphene { namespace protocol { /** * @brief Borrow from a SameT Fund * @ingroup operations + * @note The result of this operation is the ID of the owner account of the SameT Fund */ struct samet_fund_borrow_operation : public base_operation { @@ -109,6 +110,7 @@ namespace graphene { namespace protocol { /** * @brief Repay to a SameT Fund * @ingroup operations + * @note The result of this operation is the ID of the owner account of the SameT Fund */ struct samet_fund_repay_operation : public base_operation { From 49fafcf49acc46090016e3925c506a6951df02a2 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 22 Jun 2021 04:43:05 +0000 Subject: [PATCH 050/258] Implement simple collateralized P2P funding --- libraries/chain/CMakeLists.txt | 1 + libraries/chain/credit_offer_evaluator.cpp | 454 ++++++++++++++++++ libraries/chain/db_block.cpp | 1 + libraries/chain/db_init.cpp | 10 + libraries/chain/db_notify.cpp | 43 ++ libraries/chain/db_update.cpp | 71 +++ libraries/chain/hardfork.d/CORE_2362.hf | 6 + .../graphene/chain/credit_offer_evaluator.hpp | 87 ++++ .../graphene/chain/credit_offer_object.hpp | 237 +++++++++ .../chain/include/graphene/chain/database.hpp | 1 + .../graphene/chain/hardfork_visitor.hpp | 9 + .../graphene/chain/samet_fund_object.hpp | 2 +- .../chain/include/graphene/chain/types.hpp | 1 + libraries/chain/proposal_evaluator.cpp | 30 ++ libraries/chain/samet_fund_evaluator.cpp | 3 +- libraries/chain/small_objects.cpp | 38 ++ .../account_history_plugin.cpp | 11 + .../elasticsearch/elasticsearch_plugin.cpp | 11 + libraries/protocol/CMakeLists.txt | 4 +- libraries/protocol/asset.cpp | 17 +- libraries/protocol/credit_offer.cpp | 173 +++++++ .../custom_authorities/sliced_lists.hxx | 20 +- ...pp => fee_schedule_serialization_pack.cpp} | 2 +- .../fee_schedule_serialization_variant.cpp | 29 ++ .../include/graphene/protocol/asset.hpp | 4 +- .../include/graphene/protocol/base.hpp | 36 +- .../include/graphene/protocol/config.hpp | 13 +- .../graphene/protocol/credit_offer.hpp | 277 +++++++++++ .../include/graphene/protocol/operations.hpp | 20 +- .../include/graphene/protocol/samet_fund.hpp | 2 +- .../include/graphene/protocol/types.hpp | 33 +- libraries/protocol/operations.cpp | 1 + libraries/wallet/operation_printer.cpp | 6 + libraries/wallet/operation_printer.hpp | 1 + libraries/wallet/wallet_builder.cpp | 27 +- tests/tests/authority_tests.cpp | 4 +- tests/tests/basic_tests.cpp | 27 ++ 37 files changed, 1649 insertions(+), 63 deletions(-) create mode 100644 libraries/chain/credit_offer_evaluator.cpp create mode 100644 libraries/chain/hardfork.d/CORE_2362.hf create mode 100644 libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp create mode 100644 libraries/chain/include/graphene/chain/credit_offer_object.hpp create mode 100644 libraries/protocol/credit_offer.cpp rename libraries/protocol/{fee_schedule_reflect.cpp => fee_schedule_serialization_pack.cpp} (93%) create mode 100644 libraries/protocol/fee_schedule_serialization_variant.cpp create mode 100644 libraries/protocol/include/graphene/protocol/credit_offer.hpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 7c4d91e6d6..c13440f5d1 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -39,6 +39,7 @@ add_library( graphene_chain evaluator.cpp liquidity_pool_evaluator.cpp samet_fund_evaluator.cpp + credit_offer_evaluator.cpp balance_evaluator.cpp account_evaluator.cpp assert_evaluator.cpp diff --git a/libraries/chain/credit_offer_evaluator.cpp b/libraries/chain/credit_offer_evaluator.cpp new file mode 100644 index 0000000000..984c3f1a6b --- /dev/null +++ b/libraries/chain/credit_offer_evaluator.cpp @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace graphene { namespace chain { + +void_result credit_offer_create_evaluator::do_evaluate(const credit_offer_create_operation& op) const +{ try { + const database& d = db(); + const auto block_time = d.head_block_time(); + + FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); + + if( op.enabled ) + { + FC_ASSERT( op.auto_disable_time > block_time, "Auto-disable time should be in the future" ); + FC_ASSERT( op.auto_disable_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS), + "Auto-disable time should not be later than ${d} days in the future", + ("d", GRAPHENE_MAX_CREDIT_OFFER_DAYS) ); + } + + // Make sure all the collateral asset types exist + for( const auto& collateral : op.acceptable_collateral ) + { + collateral.first(d); + } + + // Make sure all the accounts exist + for( const auto& borrower : op.acceptable_borrowers ) + { + borrower.first(d); + } + + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, op.asset_type(d) ), + "The account is unauthorized by the asset" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type credit_offer_create_evaluator::do_apply(const credit_offer_create_operation& op) const +{ try { + database& d = db(); + + d.adjust_balance( op.owner_account, -asset( op.balance, op.asset_type ) ); + + const auto& new_credit_offer_object = d.create([&op](credit_offer_object& obj){ + obj.owner_account = op.owner_account; + obj.asset_type = op.asset_type; + obj.total_balance = op.balance; + obj.current_balance = op.balance; + obj.fee_rate = op.fee_rate; + obj.max_duration_seconds = op.max_duration_seconds; + obj.min_deal_amount = op.min_deal_amount; + obj.enabled = op.enabled; + obj.auto_disable_time = op.auto_disable_time; + obj.acceptable_collateral = op.acceptable_collateral; + obj.acceptable_borrowers = op.acceptable_borrowers; + }); + return new_credit_offer_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result credit_offer_delete_evaluator::do_evaluate(const credit_offer_delete_operation& op) +{ try { + const database& d = db(); + + _offer = &op.offer_id(d); + + FC_ASSERT( _offer->owner_account == op.owner_account, "The account is not the owner of the credit offer" ); + + FC_ASSERT( _offer->total_balance == _offer->current_balance, + "Can only delete a credit offer when the unpaid amount is zero" ); + + // Note: no asset authorization check here, allow funds to be moved to account balance + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +asset credit_offer_delete_evaluator::do_apply(const credit_offer_delete_operation& op) const +{ try { + database& d = db(); + + asset released( _offer->current_balance, _offer->asset_type ); + + if( _offer->current_balance != 0 ) + { + d.adjust_balance( op.owner_account, released ); + } + + d.remove( *_offer ); + + return released; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result credit_offer_update_evaluator::do_evaluate(const credit_offer_update_operation& op) +{ try { + const database& d = db(); + const auto block_time = d.head_block_time(); + + _offer = &op.offer_id(d); + + FC_ASSERT( _offer->owner_account == op.owner_account, "The account is not the owner of the credit offer" ); + + if( op.delta_amount.valid() ) + { + FC_ASSERT( _offer->asset_type == op.delta_amount->asset_id, "Asset type mismatch" ); + + if( op.delta_amount->amount > 0 ) + { + // Check asset authorization only when moving funds from account balance to somewhere else + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, _offer->asset_type(d) ), + "The account is unauthorized by the asset" ); + } + else + { + FC_ASSERT( _offer->total_balance > -op.delta_amount->amount, + "Should leave some funds in the credit offer when updating" ); + FC_ASSERT( _offer->current_balance >= -op.delta_amount->amount, "Insufficient balance in the credit offer" ); + } + } + + bool enabled = ( op.enabled.valid() ? *op.enabled : _offer->enabled ); + if( enabled ) + { + auto auto_disable_time = ( op.auto_disable_time.valid() ? *op.auto_disable_time : _offer->auto_disable_time ); + FC_ASSERT( auto_disable_time > block_time, "Auto-disable time should be in the future" ); + FC_ASSERT( auto_disable_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS), + "Auto-disable time should not be later than ${d} days in the future", + ("d", GRAPHENE_MAX_CREDIT_OFFER_DAYS) ); + } + + // Make sure all the collateral asset types exist + if( op.acceptable_collateral.valid() ) + { + for( const auto& collateral : *op.acceptable_collateral ) + { + collateral.first(d); + FC_ASSERT( _offer->asset_type == collateral.second.base.asset_id, + "Asset type mismatch in a price of acceptable collateral" ); + } + } + + // Make sure all the accounts exist + if( op.acceptable_borrowers.valid() ) + { + for( const auto& borrower : *op.acceptable_borrowers ) + { + borrower.first(d); + } + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result credit_offer_update_evaluator::do_apply( const credit_offer_update_operation& op) const +{ try { + database& d = db(); + + if( op.delta_amount.valid() ) + d.adjust_balance( op.owner_account, -(*op.delta_amount) ); + + d.modify( *_offer, [&op,this]( credit_offer_object& coo ){ + if( op.delta_amount.valid() ) + { + coo.total_balance += op.delta_amount->amount; + coo.current_balance += op.delta_amount->amount; + } + if( op.fee_rate.valid() ) + coo.fee_rate = *op.fee_rate; + if( op.max_duration_seconds.valid() ) + coo.max_duration_seconds = *op.max_duration_seconds; + if( op.min_deal_amount.valid() ) + coo.min_deal_amount = *op.min_deal_amount; + if( op.enabled.valid() ) + coo.enabled = *op.enabled; + if( op.auto_disable_time.valid() ) + coo.auto_disable_time = *op.auto_disable_time; + if( op.acceptable_collateral.valid() ) + coo.acceptable_collateral = *op.acceptable_collateral; + if( op.acceptable_borrowers.valid() ) + coo.acceptable_borrowers = *op.acceptable_borrowers; + }); + + // Defensive checks + FC_ASSERT( _offer->total_balance > 0, "Total balance in the credit offer should be positive" ); + FC_ASSERT( _offer->current_balance >= 0, "Current balance in the credit offer should not be negative" ); + FC_ASSERT( _offer->total_balance >= _offer->current_balance, + "Total balance in the credit offer should not be less than current balance" ); + if( _offer->enabled ) + { + FC_ASSERT( _offer->auto_disable_time > d.head_block_time(), + "Auto-disable time should be in the future if the credit offer is enabled" ); + FC_ASSERT( _offer->auto_disable_time - d.head_block_time() <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS), + "Auto-disable time should not be too late in the future" ); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result credit_offer_accept_evaluator::do_evaluate(const credit_offer_accept_operation& op) +{ try { + const database& d = db(); + + _offer = &op.offer_id(d); + + FC_ASSERT( _offer->enabled, "The credit offer is not enabled" ); + + FC_ASSERT( _offer->asset_type == op.borrow_amount.asset_id, "Asset type mismatch" ); + + FC_ASSERT( _offer->current_balance >= op.borrow_amount.amount, + "Insufficient balance in the credit offer thus unable to borrow" ); + + FC_ASSERT( _offer->min_deal_amount <= op.borrow_amount.amount, + "Borrowing amount should not be less than minimum deal amount" ); + + auto coll_itr = _offer->acceptable_collateral.find( op.collateral.asset_id ); + FC_ASSERT( coll_itr != _offer->acceptable_collateral.end(), + "Collateral asset type is not acceptable by the credit offer" ); + + const asset_object& debt_asset_obj = _offer->asset_type(d); + const asset_object& collateral_asset_obj = op.collateral.asset_id(d); + + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, debt_asset_obj ), + "The borrower is unauthorized by the borrowing asset" ); + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, collateral_asset_obj ), + "The borrower is unauthorized by the collateral asset" ); + + const account_object& offer_owner = _offer->owner_account(d); + + FC_ASSERT( is_authorized_asset( d, offer_owner, debt_asset_obj ), + "The owner of the credit offer is unauthorized by the borrowing asset" ); + FC_ASSERT( is_authorized_asset( d, offer_owner, collateral_asset_obj ), + "The owner of the credit offer is unauthorized by the collateral asset" ); + + auto required_collateral = op.borrow_amount.multiply_and_round_up( coll_itr->second ); + FC_ASSERT( required_collateral.amount <= op.collateral.amount, + "Insufficient collateral provided, requires ${r}, provided ${p}", + ("r", required_collateral.amount) ("p", op.collateral.amount) ); + + if( !_offer->acceptable_borrowers.empty() ) + { + auto itr = _offer->acceptable_borrowers.find( op.borrower ); + FC_ASSERT( itr != _offer->acceptable_borrowers.end(), "Account is not in acceptable borrowers" ); + share_type max_allowed = itr->second; + + share_type already_borrowed = 0; + const auto& deal_summary_idx = d.get_index_type().indices().get(); + auto summ_itr = deal_summary_idx.find( boost::make_tuple( op.offer_id, op.borrower ) ); + if( summ_itr != deal_summary_idx.end() ) + { + _deal_summary = &(*summ_itr); + already_borrowed = _deal_summary->total_debt_amount; + } + FC_ASSERT( already_borrowed + op.borrow_amount.amount <= max_allowed, + "Unable to borrow ${b}, already borrowed ${a}, maximum allowed ${m}", + ("b", op.borrow_amount.amount) ("a", already_borrowed) ("m", max_allowed) ); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +extendable_operation_result credit_offer_accept_evaluator::do_apply( const credit_offer_accept_operation& op) const +{ try { + database& d = db(); + + d.adjust_balance( op.borrower, -op.collateral ); + d.adjust_balance( op.borrower, op.borrow_amount ); + + d.modify( *_offer, [&op]( credit_offer_object& coo ){ + coo.current_balance -= op.borrow_amount.amount; + }); + + const auto block_time = d.head_block_time(); + auto repay_time = ( ( ( fc::time_point_sec::maximum() - block_time ) >= fc::seconds(_offer->max_duration_seconds) ) + ? ( block_time + _offer->max_duration_seconds ) + : fc::time_point_sec::maximum() ); + + const auto& new_deal = d.create([&op,this,&repay_time](credit_deal_object& obj){ + obj.borrower = op.borrower; + obj.offer_id = op.offer_id; + obj.offer_owner = _offer->owner_account; + obj.debt_asset = _offer->asset_type; + obj.debt_amount = op.borrow_amount.amount; + obj.collateral_asset = op.collateral.asset_id; + obj.collateral_amount = op.collateral.amount; + obj.fee_rate = _offer->fee_rate; + obj.latest_repay_time = repay_time; + }); + + if( _deal_summary != nullptr ) + { + d.modify( *_deal_summary, [&op]( credit_deal_summary_object& obj ){ + obj.total_debt_amount += op.borrow_amount.amount; + }); + } + else + { + d.create([&op,this](credit_deal_summary_object& obj){ + obj.borrower = op.borrower; + obj.offer_id = op.offer_id; + obj.offer_owner = _offer->owner_account; + obj.debt_asset = _offer->asset_type; + obj.total_debt_amount = op.borrow_amount.amount; + }); + } + + // Defensive check + FC_ASSERT( _offer->total_balance > 0, "Total balance in the credit offer should be positive" ); + FC_ASSERT( _offer->current_balance >= 0, "Current balance in the credit offer should not be negative" ); + FC_ASSERT( _offer->total_balance >= _offer->current_balance, + "Total balance in the credit offer should not be less than current balance" ); + FC_ASSERT( new_deal.latest_repay_time > block_time, + "Latest repayment time should be in the future" ); + FC_ASSERT( new_deal.latest_repay_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_DEAL_DAYS), + "Latest repayment time should not be too late in the future" ); + + extendable_operation_result result; + // Note: only return the deal here, deal summary is impl so we do not return it + result.value.new_objects = flat_set({ new_deal.id }); + result.value.impacted_accounts = flat_set({ _offer->owner_account }); + + return result; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result credit_deal_repay_evaluator::do_evaluate(const credit_deal_repay_operation& op) +{ try { + const database& d = db(); + + _deal = &op.deal_id(d); + + FC_ASSERT( _deal->borrower == op.account, "A credit deal can only be repaid by the borrower" ); + + FC_ASSERT( _deal->debt_asset == op.repay_amount.asset_id, "Asset type mismatch" ); + + FC_ASSERT( _deal->debt_amount >= op.repay_amount.amount, + "Repay amount should not be greater than unpaid amount" ); + + // Note: the result can be larger than 64 bit, but since we don't store it, it is allowed + auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) * _deal->fee_rate ) + + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up + + FC_ASSERT( fc::uint128_t(op.credit_fee.amount.value) >= required_fee, + "Insuffient credit fee, requires ${r}, offered ${p}", + ("r", required_fee) ("p", op.credit_fee.amount) ); + + const asset_object& debt_asset_obj = _deal->debt_asset(d); + // Note: allow collateral to be moved to account balance regardless of collateral asset authorization + + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, debt_asset_obj ), + "The account is unauthorized by the repaying asset" ); + FC_ASSERT( is_authorized_asset( d, _deal->offer_owner(d), debt_asset_obj ), + "The owner of the credit offer is unauthorized by the repaying asset" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +extendable_operation_result credit_deal_repay_evaluator::do_apply( const credit_deal_repay_operation& op) const +{ try { + database& d = db(); + + share_type total_amount = op.repay_amount.amount + op.credit_fee.amount; + + d.adjust_balance( op.account, asset( -total_amount, op.repay_amount.asset_id ) ); + + // Update offer + const credit_offer_object& offer = _deal->offer_id(d); + d.modify( offer, [&op,&total_amount]( credit_offer_object& obj ){ + obj.total_balance += op.credit_fee.amount; + obj.current_balance += total_amount; + }); + // Defensive check + FC_ASSERT( offer.total_balance >= offer.current_balance, + "Total balance in the credit offer should not be less than current balance" ); + + extendable_operation_result result; + result.value.impacted_accounts = flat_set({ offer.owner_account }); + result.value.updated_objects = flat_set({ offer.id }); + + // Process deal summary + const auto& deal_summary_idx = d.get_index_type().indices().get(); + auto summ_itr = deal_summary_idx.find( boost::make_tuple( _deal->offer_id, op.account ) ); + FC_ASSERT( summ_itr != deal_summary_idx.end(), "Internal error" ); + + const credit_deal_summary_object& summ_obj = *summ_itr; + if( summ_obj.total_debt_amount == op.repay_amount.amount ) + { + d.remove( summ_obj ); + } + else + { + d.modify( summ_obj, [&op]( credit_deal_summary_object& obj ){ + obj.total_debt_amount -= op.repay_amount.amount; + }); + } + + // Process deal + asset collateral_released( _deal->collateral_amount, _deal->collateral_asset ); + if( _deal->debt_amount == op.repay_amount.amount ) // to fully repay + { + result.value.removed_objects = flat_set({ _deal->id }); + d.remove( *_deal ); + } + else // to partially repay + { + auto amount_to_release = ( fc::uint128_t( op.repay_amount.amount.value ) * _deal->collateral_amount.value ) + / _deal->debt_amount.value; // Round down + FC_ASSERT( amount_to_release < fc::uint128_t( _deal->collateral_amount.value ), "Internal error" ); + collateral_released.amount = static_cast( amount_to_release ); + + d.modify( *_deal, [&op,&collateral_released]( credit_deal_object& obj ){ + obj.debt_amount -= op.repay_amount.amount; + obj.collateral_amount -= collateral_released.amount; + }); + + result.value.updated_objects->insert( _deal->id ); + } + + d.adjust_balance( op.account, collateral_released ); + result.value.received = vector({ collateral_released }); + + return result; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // graphene::chain diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index f6b5e62c2b..520f0614a0 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -648,6 +648,7 @@ void database::_apply_block( const signed_block& next_block ) update_expired_feeds(); // this will update expired feeds and some core exchange rates update_core_exchange_rates(); // this will update remaining core exchange rates update_withdraw_permissions(); + update_credit_offers_and_deals(); // n.b., update_maintenance_flag() happens this late // because get_slot_time() / get_slot_at_time() is needed above diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index d90baf4f67..5e2a459589 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -138,6 +140,11 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -165,6 +172,8 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); @@ -185,6 +194,7 @@ void database::initialize_indexes() add_index< primary_index< buyback_index > >(); add_index< primary_index >(); add_index< primary_index< simple_index< fba_accumulator_object > > >(); + add_index< primary_index >(); } } } diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 6c58a6a927..0c5c74a9ed 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -356,6 +357,31 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // account } + void operator()( const credit_offer_create_operation& op ) + { + _impacted.insert( op.fee_payer() ); // owner_account + } + void operator()( const credit_offer_delete_operation& op ) + { + _impacted.insert( op.fee_payer() ); // owner_account + } + void operator()( const credit_offer_update_operation& op ) + { + _impacted.insert( op.fee_payer() ); // owner_account + } + void operator()( const credit_offer_accept_operation& op ) + { + _impacted.insert( op.fee_payer() ); // borrower + } + void operator()( const credit_deal_repay_operation& op ) + { + _impacted.insert( op.fee_payer() ); // account + } + void operator()( const credit_deal_expired_operation& op ) + { + _impacted.insert( op.offer_owner ); + _impacted.insert( op.borrower ); + } }; } // namespace detail @@ -474,6 +500,17 @@ void get_relevant_accounts( const object* obj, flat_set& accoun FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->owner_account ); break; + } case credit_offer_object_type:{ + const auto* aobj = dynamic_cast( obj ); + FC_ASSERT( aobj != nullptr ); + accounts.insert( aobj->owner_account ); + break; + } case credit_deal_object_type:{ + const auto* aobj = dynamic_cast( obj ); + FC_ASSERT( aobj != nullptr ); + accounts.insert( aobj->offer_owner ); + accounts.insert( aobj->borrower ); + break; } } } @@ -537,6 +574,12 @@ void get_relevant_accounts( const object* obj, flat_set& accoun FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->bidder ); break; + } case impl_credit_deal_summary_object_type:{ + const auto& aobj = dynamic_cast(obj); + FC_ASSERT( aobj != nullptr ); + accounts.insert( aobj->offer_owner ); + accounts.insert( aobj->borrower ); + break; } } } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 7c0fec421c..b1b09c6c01 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -697,4 +698,74 @@ generic_operation_result database::process_tickets() return result; } +void database::update_credit_offers_and_deals() +{ + const auto head_time = head_block_time(); + + // Auto-disable offers + const auto& offer_idx = get_index_type().indices().get(); + auto offer_itr = offer_idx.lower_bound( true ); + auto offer_itr_end = offer_idx.upper_bound( boost::make_tuple( true, head_time ) ); + while( offer_itr != offer_itr_end ) + { + const credit_offer_object& offer = *offer_itr; + ++offer_itr; + modify( offer, []( credit_offer_object& obj ) { + obj.enabled = false; + }); + } + + // Auto-process deals + const auto& deal_idx = get_index_type().indices().get(); + const auto& deal_summary_idx = get_index_type().indices().get(); + auto deal_itr_end = deal_idx.upper_bound( head_time ); + for( auto deal_itr = deal_idx.begin(); deal_itr != deal_itr_end; deal_itr = deal_idx.begin() ) + { + const credit_deal_object& deal = *deal_itr; + + // Update offer + // Note: offer balance can be zero after updated. TODO remove zero-balance offers after a period + const credit_offer_object& offer = deal.offer_id(*this); + modify( offer, [&deal]( credit_offer_object& obj ){ + obj.total_balance -= deal.debt_amount; + }); + + // Process deal summary + auto summ_itr = deal_summary_idx.find( boost::make_tuple( deal.offer_id, deal.borrower ) ); + if( summ_itr == deal_summary_idx.end() ) // This should not happen, just be defensive here + { + // We do not do FC_ASSERT or FC_THROW here to avoid halting the chain + elog( "Error: unable to find the credit deal summary object for credit deal ${d}", + ("d", deal) ); + } + else + { + const credit_deal_summary_object& summ_obj = *summ_itr; + if( summ_obj.total_debt_amount == deal.debt_amount ) + { + remove( summ_obj ); + } + else + { + modify( summ_obj, [&deal]( credit_deal_summary_object& obj ){ + obj.total_debt_amount -= deal.debt_amount; + }); + } + } + + // Adjust balance + adjust_balance( deal.offer_owner, asset( deal.collateral_amount, deal.collateral_asset ) ); + + // Notify related parties + push_applied_operation( credit_deal_expired_operation ( + deal.id, deal.offer_id, deal.offer_owner, deal.borrower, + asset( deal.debt_amount, deal.debt_asset ), + asset( deal.collateral_amount, deal.collateral_asset ), + deal.fee_rate ) ); + + // Remove the deal + remove( deal ); + } +} + } } diff --git a/libraries/chain/hardfork.d/CORE_2362.hf b/libraries/chain/hardfork.d/CORE_2362.hf new file mode 100644 index 0000000000..af8402bad4 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2362.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2362 Simple collateralized P2P funding +#ifndef HARDFORK_CORE_2362_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2362_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2362_PASSED(now) (now >= HARDFORK_CORE_2362_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp b/libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp new file mode 100644 index 0000000000..359e3a686d --- /dev/null +++ b/libraries/chain/include/graphene/chain/credit_offer_evaluator.hpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include + +#include + +namespace graphene { namespace chain { + + class credit_offer_object; + + class credit_offer_create_evaluator : public evaluator + { + public: + using operation_type = credit_offer_create_operation; + + void_result do_evaluate( const credit_offer_create_operation& op ) const; + object_id_type do_apply( const credit_offer_create_operation& op ) const; + }; + + class credit_offer_delete_evaluator : public evaluator + { + public: + using operation_type = credit_offer_delete_operation; + + void_result do_evaluate( const credit_offer_delete_operation& op ); + asset do_apply( const credit_offer_delete_operation& op ) const; + + const credit_offer_object* _offer = nullptr; + }; + + class credit_offer_update_evaluator : public evaluator + { + public: + using operation_type = credit_offer_update_operation; + + void_result do_evaluate( const credit_offer_update_operation& op ); + void_result do_apply( const credit_offer_update_operation& op ) const; + + const credit_offer_object* _offer = nullptr; + }; + + class credit_offer_accept_evaluator : public evaluator + { + public: + using operation_type = credit_offer_accept_operation; + + void_result do_evaluate( const credit_offer_accept_operation& op ); + extendable_operation_result do_apply( const credit_offer_accept_operation& op ) const; + + const credit_offer_object* _offer = nullptr; + const credit_deal_summary_object* _deal_summary = nullptr; + }; + + class credit_deal_repay_evaluator : public evaluator + { + public: + using operation_type = credit_deal_repay_operation; + + void_result do_evaluate( const credit_deal_repay_operation& op ); + extendable_operation_result do_apply( const credit_deal_repay_operation& op ) const; + + const credit_deal_object* _deal = nullptr; + }; + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/credit_offer_object.hpp b/libraries/chain/include/graphene/chain/credit_offer_object.hpp new file mode 100644 index 0000000000..ab8ce3be1f --- /dev/null +++ b/libraries/chain/include/graphene/chain/credit_offer_object.hpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include + +#include + +namespace graphene { namespace chain { + +/** + * @brief A credit offer is a fund that can be used by other accounts who provide certain collateral. + * @ingroup object + * @ingroup protocol + * + */ +class credit_offer_object : public abstract_object +{ + public: + static constexpr uint8_t space_id = protocol_ids; + static constexpr uint8_t type_id = credit_offer_object_type; + + account_id_type owner_account; ///< Owner of the fund + asset_id_type asset_type; ///< Asset type in the fund + share_type total_balance; ///< Total size of the fund + share_type current_balance; ///< Usable amount in the fund + uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM + uint32_t max_duration_seconds = 0; ///< The time limit that borrowed funds should be repaid + share_type min_deal_amount; ///< Minimum amount to borrow for each new deal + bool enabled = false; ///< Whether this offer is available + time_point_sec auto_disable_time; ///< The time when this offer will be disabled automatically + + /// Types and rates of acceptable collateral + flat_map acceptable_collateral; + + /// Allowed borrowers and their maximum amounts to borrow. No limitation if empty. + flat_map acceptable_borrowers; +}; + +struct by_auto_disable_time; // for protocol +struct by_owner; // for API +struct by_asset_type; // for API + +/** +* @ingroup object_index +*/ +using credit_offer_multi_index_type = multi_index_container< + credit_offer_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< credit_offer_object, + member< credit_offer_object, bool, &credit_offer_object::enabled >, + member< credit_offer_object, time_point_sec, &credit_offer_object::auto_disable_time >, + member< object, object_id_type, &object::id> + > + >, + ordered_unique< tag, + composite_key< credit_offer_object, + member< credit_offer_object, account_id_type, &credit_offer_object::owner_account >, + member< object, object_id_type, &object::id> + > + >, + ordered_unique< tag, + composite_key< credit_offer_object, + member< credit_offer_object, asset_id_type, &credit_offer_object::asset_type >, + member< object, object_id_type, &object::id> + > + > + > +>; + +/** +* @ingroup object_index +*/ +using credit_offer_index = generic_index; + + +/** + * @brief A credit deal describes the details of a borrower's borrowing of funds from a credit offer. + * @ingroup object + * @ingroup protocol + * + */ +class credit_deal_object : public abstract_object +{ + public: + static constexpr uint8_t space_id = protocol_ids; + static constexpr uint8_t type_id = credit_deal_object_type; + + account_id_type borrower; ///< Borrower + credit_offer_id_type offer_id; ///< ID of the credit offer + account_id_type offer_owner; ///< Owner of the credit offer, redundant info for ease of querying + asset_id_type debt_asset; ///< Asset type of the debt, redundant info for ease of querying + share_type debt_amount; ///< How much funds borrowed + asset_id_type collateral_asset; ///< Asset type of the collateral + share_type collateral_amount; ///< How much funds in collateral + uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM + time_point_sec latest_repay_time; ///< The deadline when the debt should be repaid +}; + +struct by_latest_repay_time; // for protocol +struct by_offer_id; // for API +struct by_offer_owner; // for API +struct by_borrower; // for API +struct by_debt_asset; // for API +struct by_collateral_asset; // for API + +/** +* @ingroup object_index +*/ +using credit_deal_multi_index_type = multi_index_container< + credit_deal_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< credit_deal_object, + member< credit_deal_object, time_point_sec, &credit_deal_object::latest_repay_time >, + member< object, object_id_type, &object::id> + > + >, + ordered_unique< tag, + composite_key< credit_deal_object, + member< credit_deal_object, credit_offer_id_type, &credit_deal_object::offer_id >, + member< object, object_id_type, &object::id> + > + >, + ordered_unique< tag, + composite_key< credit_deal_object, + member< credit_deal_object, account_id_type, &credit_deal_object::offer_owner >, + member< object, object_id_type, &object::id> + > + >, + ordered_unique< tag, + composite_key< credit_deal_object, + member< credit_deal_object, account_id_type, &credit_deal_object::borrower >, + member< object, object_id_type, &object::id> + > + >, + ordered_unique< tag, + composite_key< credit_deal_object, + member< credit_deal_object, asset_id_type, &credit_deal_object::debt_asset >, + member< object, object_id_type, &object::id> + > + >, + ordered_unique< tag, + composite_key< credit_deal_object, + member< credit_deal_object, asset_id_type, &credit_deal_object::collateral_asset >, + member< object, object_id_type, &object::id> + > + > + > +>; + +/** +* @ingroup object_index +*/ +using credit_deal_index = generic_index; + + +/** + * @brief A credit deal summary describes the summary of a borrower's borrowing of funds from a credit offer. + * @ingroup object + * @ingroup implementation + * + */ +class credit_deal_summary_object : public abstract_object +{ + public: + static constexpr uint8_t space_id = implementation_ids; + static constexpr uint8_t type_id = impl_credit_deal_summary_object_type; + + account_id_type borrower; ///< Borrower + credit_offer_id_type offer_id; ///< ID of the credit offer + account_id_type offer_owner; ///< Owner of the credit offer, redundant info for ease of querying + asset_id_type debt_asset; ///< Asset type of the debt, redundant info for ease of querying + share_type total_debt_amount; ///< How much funds borrowed +}; + +struct by_offer_borrower; // for protocol + +/** +* @ingroup object_index +*/ +using credit_deal_summary_multi_index_type = multi_index_container< + credit_deal_summary_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< credit_deal_summary_object, + member< credit_deal_summary_object, credit_offer_id_type, &credit_deal_summary_object::offer_id >, + member< credit_deal_summary_object, account_id_type, &credit_deal_summary_object::borrower > + > + > + > +>; + +/** +* @ingroup object_index +*/ +using credit_deal_summary_index = generic_index; + +} } // graphene::chain + +MAP_OBJECT_ID_TO_TYPE( graphene::chain::credit_offer_object ) +MAP_OBJECT_ID_TO_TYPE( graphene::chain::credit_deal_object ) +MAP_OBJECT_ID_TO_TYPE( graphene::chain::credit_deal_summary_object ) + +FC_REFLECT_TYPENAME( graphene::chain::credit_offer_object ) +FC_REFLECT_TYPENAME( graphene::chain::credit_deal_object ) +FC_REFLECT_TYPENAME( graphene::chain::credit_deal_summary_object ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::credit_offer_object ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::credit_deal_object ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::credit_deal_summary_object ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 3804701be0..300010d127 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -599,6 +599,7 @@ namespace graphene { namespace chain { void update_core_exchange_rates(); void update_maintenance_flag( bool new_maintenance_flag ); void update_withdraw_permissions(); + void update_credit_offers_and_deals(); bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, const asset_bitasset_data_object* bitasset_ptr = nullptr ); void clear_expired_htlcs(); diff --git a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp index 6b1c2fb00b..00cae3d93c 100644 --- a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp +++ b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp @@ -59,6 +59,12 @@ struct hardfork_visitor { samet_fund_update_operation, samet_fund_borrow_operation, samet_fund_repay_operation >; + using credit_offer_ops = TL::list< credit_offer_create_operation, + credit_offer_delete_operation, + credit_offer_update_operation, + credit_offer_accept_operation, + credit_deal_repay_operation, + credit_deal_expired_operation >; fc::time_point_sec now; hardfork_visitor(fc::time_point_sec now) : now(now) {} @@ -80,6 +86,9 @@ struct hardfork_visitor { template std::enable_if_t(), bool> visit() { return HARDFORK_CORE_2351_PASSED(now); } + template + std::enable_if_t(), bool> + visit() { return HARDFORK_CORE_2362_PASSED(now); } /// @} /// typelist::runtime::dispatch adaptor diff --git a/libraries/chain/include/graphene/chain/samet_fund_object.hpp b/libraries/chain/include/graphene/chain/samet_fund_object.hpp index 11ffec307d..f1301d0822 100644 --- a/libraries/chain/include/graphene/chain/samet_fund_object.hpp +++ b/libraries/chain/include/graphene/chain/samet_fund_object.hpp @@ -45,7 +45,7 @@ class samet_fund_object : public abstract_object account_id_type owner_account; ///< Owner of the fund asset_id_type asset_type; ///< Asset type in the fund share_type balance; ///< Usable amount in the fund - uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_SAMET_FUND_FEE_DENOM + uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM share_type unpaid_amount; ///< Unpaid amount }; diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/types.hpp index 1ec4a4a0ce..3c7b46eeb3 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/types.hpp @@ -47,4 +47,5 @@ GRAPHENE_DEFINE_IDS(chain, implementation_ids, impl_, /* 2.15.x */ (buyback) /* 2.16.x */ (fba_accumulator) /* 2.17.x */ (collateral_bid) + /* 2.18.x */ (credit_deal_summary) ) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index d8f158d107..1b23666dfc 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -171,6 +171,20 @@ struct proposal_operation_hardfork_visitor FC_ASSERT(!op.new_parameters.current_fees->exists(), "Unable to define fees for samet fund operations prior to the core-2351 hardfork"); } + if (!HARDFORK_CORE_2362_PASSED(block_time)) { + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for credit offer operations prior to the core-2362 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for credit offer operations prior to the core-2362 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for credit offer operations prior to the core-2362 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for credit offer operations prior to the core-2362 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for credit offer operations prior to the core-2362 hardfork"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for credit offer operations prior to the core-2362 hardfork"); + } } void operator()(const graphene::chain::htlc_create_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); @@ -236,6 +250,22 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::samet_fund_repay_operation&) const { FC_ASSERT( HARDFORK_CORE_2351_PASSED(block_time), "Not allowed until the core-2351 hardfork" ); } + void operator()(const graphene::chain::credit_offer_create_operation&) const { + FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); + } + void operator()(const graphene::chain::credit_offer_delete_operation&) const { + FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); + } + void operator()(const graphene::chain::credit_offer_update_operation&) const { + FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); + } + void operator()(const graphene::chain::credit_offer_accept_operation&) const { + FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); + } + void operator()(const graphene::chain::credit_deal_repay_operation&) const { + FC_ASSERT( HARDFORK_CORE_2362_PASSED(block_time), "Not allowed until the core-2362 hardfork" ); + } + // Note: credit_deal_expired_operation is a virtual operation thus no need to add code here // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp index 9dc21fe680..b244a15908 100644 --- a/libraries/chain/samet_fund_evaluator.cpp +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -194,8 +194,9 @@ void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_opera FC_ASSERT( op.repay_amount.amount <= _fund->unpaid_amount, "Repay amount should not be greater than unpaid amount" ); + // Note: the result can be larger than 64 bit, but since we don't store it, it is allowed auto required_fee = ( ( ( fc::uint128_t( op.repay_amount.amount.value ) * _fund->fee_rate ) - + GRAPHENE_SAMET_FUND_FEE_DENOM ) - 1 ) / GRAPHENE_SAMET_FUND_FEE_DENOM; // Round up + + GRAPHENE_FEE_RATE_DENOM ) - 1 ) / GRAPHENE_FEE_RATE_DENOM; // Round up FC_ASSERT( fc::uint128_t(op.fund_fee.amount.value) >= required_fee, "Insuffient fund fee, requires ${r}, offered ${p}", diff --git a/libraries/chain/small_objects.cpp b/libraries/chain/small_objects.cpp index 9a01e27435..d2aa0664aa 100644 --- a/libraries/chain/small_objects.cpp +++ b/libraries/chain/small_objects.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -209,6 +210,40 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::samet_fund_object, (graphene::d (unpaid_amount) ) +FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::credit_offer_object, (graphene::db::object), + (owner_account) + (asset_type) + (total_balance) + (current_balance) + (fee_rate) + (max_duration_seconds) + (min_deal_amount) + (enabled) + (auto_disable_time) + (acceptable_collateral) + (acceptable_borrowers) + ) + +FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::credit_deal_object, (graphene::db::object), + (borrower) + (offer_id) + (offer_owner) + (debt_asset) + (debt_amount) + (collateral_asset) + (collateral_amount) + (fee_rate) + (latest_repay_time) + ) + +FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::credit_deal_summary_object, (graphene::db::object), + (borrower) + (offer_id) + (offer_owner) + (debt_asset) + (total_debt_amount) + ) + GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::balance_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::block_summary_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::budget_record ) @@ -233,3 +268,6 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::worker_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::custom_authority_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::liquidity_pool_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::samet_fund_object ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::credit_offer_object ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::credit_deal_object ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::credit_deal_summary_object ) diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 6b6eadae63..062e999052 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -138,10 +138,21 @@ void account_history_plugin_impl::update_account_histories( const signed_block& { operation_get_impacted_accounts( op.op, impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); + // TODO change return type if( op.op.is_type< samet_fund_borrow_operation >() || op.op.is_type< samet_fund_repay_operation >() ) impacted.insert( op.result.get() ); } + if( op.result.is_type() ) + { + const auto& op_result = op.result.get(); + if( op_result.value.impacted_accounts.valid() ) + { + for( const auto& a : *op_result.value.impacted_accounts ) + impacted.insert( a ); + } + } + for( auto& a : other ) for( auto& item : a.account_auths ) impacted.insert( item.first ); diff --git a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp index fd51bf9eb8..49fbc7119a 100644 --- a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp +++ b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp @@ -168,10 +168,21 @@ bool elasticsearch_plugin_impl::update_account_histories( const signed_block& b { operation_get_impacted_accounts( op.op, impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); + // TODO change return type if( op.op.is_type< samet_fund_borrow_operation >() || op.op.is_type< samet_fund_repay_operation >() ) impacted.insert( op.result.get() ); } + if( op.result.is_type() ) + { + const auto& op_result = op.result.get(); + if( op_result.value.impacted_accounts.valid() ) + { + for( const auto& a : *op_result.value.impacted_accounts ) + impacted.insert( a ); + } + } + for( auto& a : other ) for( auto& item : a.account_auths ) impacted.insert( item.first ); diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 15c9dc1ee4..da2ffa9adc 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -8,7 +8,8 @@ list(APPEND SOURCES account.cpp chain_parameters.cpp fee_schedule.cpp fee_schedule_calc.cpp - fee_schedule_reflect.cpp + fee_schedule_serialization_pack.cpp + fee_schedule_serialization_variant.cpp memo.cpp proposal.cpp transfer.cpp @@ -23,6 +24,7 @@ list(APPEND SOURCES account.cpp committee_member.cpp custom.cpp market.cpp + credit_offer.cpp liquidity_pool.cpp samet_fund.cpp ticket.cpp diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index c26f75e0a6..0e7418a99b 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -228,11 +228,20 @@ namespace graphene { namespace protocol { return ( base.asset_id == asset_id_type() && quote.asset_id == asset_id_type() ); } - void price::validate() const + void price::validate( bool check_upper_bound /* = false */ )const { try { - FC_ASSERT( base.amount > share_type(0) ); - FC_ASSERT( quote.amount > share_type(0) ); - FC_ASSERT( base.asset_id != quote.asset_id ); + FC_ASSERT( base.amount.value > 0, "Base amount should be positive" ); + FC_ASSERT( quote.amount.value > 0, "Quote amount should be positive" ); + FC_ASSERT( base.asset_id != quote.asset_id, "Base asset ID and quote asset ID should be different" ); + if( check_upper_bound ) + { + FC_ASSERT( base.amount.value <= GRAPHENE_MAX_SHARE_SUPPLY, + "Base amount should not be greater than ${max}", + ("max", GRAPHENE_MAX_SHARE_SUPPLY) ); + FC_ASSERT( quote.amount.value <= GRAPHENE_MAX_SHARE_SUPPLY, + "Quote amount should not be greater than ${max}", + ("max", GRAPHENE_MAX_SHARE_SUPPLY) ); + } } FC_CAPTURE_AND_RETHROW( (base)(quote) ) } void price_feed::validate() const diff --git a/libraries/protocol/credit_offer.cpp b/libraries/protocol/credit_offer.cpp new file mode 100644 index 0000000000..be15d9222f --- /dev/null +++ b/libraries/protocol/credit_offer.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +#include + +namespace graphene { namespace protocol { + +static void validate_acceptable_collateral( const flat_map& acceptable_collateral, + const asset_id_type* p_asset_type = nullptr ) +{ + FC_ASSERT( !acceptable_collateral.empty(), "Acceptable collateral list should not be empty" ); + + asset_id_type asset_type = ( p_asset_type != nullptr ? *p_asset_type + : acceptable_collateral.begin()->second.base.asset_id ); + + for( const auto& collateral : acceptable_collateral ) + { + const auto& collateral_asset_type = collateral.first; + const auto& collateral_price = collateral.second; + FC_ASSERT( collateral_price.base.asset_id == asset_type, + "Base asset ID in price of acceptable collateral should be same as offer asset type" ); + FC_ASSERT( collateral_price.quote.asset_id == collateral_asset_type, + "Quote asset ID in price of acceptable collateral should be same as collateral asset type" ); + collateral_price.validate( true ); + } +} + +static void validate_acceptable_borrowers( const flat_map& acceptable_borrowers ) +{ + for( const auto& borrower : acceptable_borrowers ) + { + const auto& max_borrow_amount = borrower.second.value; + FC_ASSERT( max_borrow_amount >= 0, + "Maximum amount to borrow for acceptable borrowers should not be negative" ); + FC_ASSERT( max_borrow_amount <= GRAPHENE_MAX_SHARE_SUPPLY, + "Maximum amount to borrow for acceptable borrowers should not be greater than ${max}", + ("max", GRAPHENE_MAX_SHARE_SUPPLY) ); + } +} + +void credit_offer_create_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + FC_ASSERT( balance > 0, "Balance should be positive" ); + FC_ASSERT( max_duration_seconds <= GRAPHENE_MAX_CREDIT_DEAL_SECS, + "Maximum duration should not be greater than ${d} days", + ("d", GRAPHENE_MAX_CREDIT_DEAL_DAYS) ); + FC_ASSERT( min_deal_amount >= 0, "Minimum deal amount should not be negative" ); + FC_ASSERT( min_deal_amount <= GRAPHENE_MAX_SHARE_SUPPLY, + "Minimum deal amount should not be greater than ${max}", + ("max", GRAPHENE_MAX_SHARE_SUPPLY) ); + + validate_acceptable_collateral( acceptable_collateral, &asset_type ); + validate_acceptable_borrowers( acceptable_borrowers ); +} + +share_type credit_offer_create_operation::calculate_fee( const fee_parameters_type& schedule )const +{ + share_type core_fee_required = schedule.fee; + core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), schedule.price_per_kbyte ); + return core_fee_required; +} + +void credit_offer_delete_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); +} + +void credit_offer_update_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + bool updating_something = false; + + if( delta_amount.valid() ) + { + updating_something = true; + FC_ASSERT( delta_amount->amount != 0, "Delta amount should not be zero" ); + } + if( fee_rate.valid() ) + updating_something = true; + if( max_duration_seconds.valid() ) + { + updating_something = true; + FC_ASSERT( *max_duration_seconds <= GRAPHENE_MAX_CREDIT_DEAL_SECS, + "Maximum duration should not be greater than ${d} days", + ("d", GRAPHENE_MAX_CREDIT_DEAL_DAYS) ); + } + if( min_deal_amount.valid() ) + { + updating_something = true; + FC_ASSERT( *min_deal_amount >= 0, "Minimum deal amount should not be negative" ); + FC_ASSERT( *min_deal_amount <= GRAPHENE_MAX_SHARE_SUPPLY, + "Minimum deal amount should not be greater than ${max}", + ("max", GRAPHENE_MAX_SHARE_SUPPLY) ); + } + if( enabled.valid() ) + updating_something = true; + if( auto_disable_time.valid() ) + updating_something = true; + if( acceptable_collateral.valid() ) + { + updating_something = true; + validate_acceptable_collateral( *acceptable_collateral ); // Note: check base asset ID in evaluator + } + if( acceptable_borrowers.valid() ) + { + updating_something = true; + validate_acceptable_borrowers( *acceptable_borrowers ); + } + + FC_ASSERT( updating_something, + "Should change something - at least one of the optional data fields should be present" ); +} + +share_type credit_offer_update_operation::calculate_fee( const fee_parameters_type& schedule )const +{ + share_type core_fee_required = schedule.fee; + core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), schedule.price_per_kbyte ); + return core_fee_required; +} + +void credit_offer_accept_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + FC_ASSERT( borrow_amount.amount > 0, "Amount to borrow should be positive" ); + FC_ASSERT( collateral.amount > 0, "Collateral amount should be positive" ); +} + +void credit_deal_repay_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee should not be negative" ); + FC_ASSERT( repay_amount.amount > 0, "Amount to repay should be positive" ); + FC_ASSERT( credit_fee.amount >= 0, "Credit fee should not be negative" ); + FC_ASSERT( repay_amount.asset_id == credit_fee.asset_id, + "Asset type of repay amount and credit fee should be the same" ); +} + +} } // graphene::protocol + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation::fee_parameters_type ) + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_expired_operation ) diff --git a/libraries/protocol/custom_authorities/sliced_lists.hxx b/libraries/protocol/custom_authorities/sliced_lists.hxx index ed0b42eed5..923f489179 100644 --- a/libraries/protocol/custom_authorities/sliced_lists.hxx +++ b/libraries/protocol/custom_authorities/sliced_lists.hxx @@ -48,14 +48,18 @@ using operation_list_11 = static_variant ::finalize>; using operation_list_12 = static_variant>; using operation_list_13 = static_variant>; -using operation_list_14 = static_variant>; -using virtual_operations_list = static_variant; +using operation_list_14 = static_variant>; +// Note: supported list ends at 64 so far, new operations are added to virtual_operations_list +// TODO support new operations +using virtual_operations_list = static_variant + ::add // 4 + ::add // 42 + ::add // 44 + ::add // 46 + ::add // 51 + ::add // 53 + ::add_list> // Unsupported ops + ::finalize>; object_restriction_predicate get_restriction_pred_list_1(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_2(size_t idx, vector rs); diff --git a/libraries/protocol/fee_schedule_reflect.cpp b/libraries/protocol/fee_schedule_serialization_pack.cpp similarity index 93% rename from libraries/protocol/fee_schedule_reflect.cpp rename to libraries/protocol/fee_schedule_serialization_pack.cpp index 14dd0f0191..057932902d 100644 --- a/libraries/protocol/fee_schedule_reflect.cpp +++ b/libraries/protocol/fee_schedule_serialization_pack.cpp @@ -26,4 +26,4 @@ #include -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::fee_schedule ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION_PACK( graphene::protocol::fee_schedule ) diff --git a/libraries/protocol/fee_schedule_serialization_variant.cpp b/libraries/protocol/fee_schedule_serialization_variant.cpp new file mode 100644 index 0000000000..e405c11ddd --- /dev/null +++ b/libraries/protocol/fee_schedule_serialization_variant.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., 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 + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION_VARIANT( graphene::protocol::fee_schedule ) diff --git a/libraries/protocol/include/graphene/protocol/asset.hpp b/libraries/protocol/include/graphene/protocol/asset.hpp index 915887cffa..ffd26a8a1d 100644 --- a/libraries/protocol/include/graphene/protocol/asset.hpp +++ b/libraries/protocol/include/graphene/protocol/asset.hpp @@ -133,7 +133,9 @@ namespace graphene { namespace protocol { double to_real()const { return double(base.amount.value)/double(quote.amount.value); } bool is_null()const; - void validate()const; + /// @brief Check if the object is valid + /// @param check_upper_bound Whether to check if the amounts in the price are too large + void validate( bool check_upper_bound = false )const; }; price operator / ( const asset& base, const asset& quote ); diff --git a/libraries/protocol/include/graphene/protocol/base.hpp b/libraries/protocol/include/graphene/protocol/base.hpp index 53f956d950..edc03ab5f0 100644 --- a/libraries/protocol/include/graphene/protocol/base.hpp +++ b/libraries/protocol/include/graphene/protocol/base.hpp @@ -99,13 +99,27 @@ namespace graphene { namespace protocol { vector fees; }; - typedef fc::static_variant < - void_result, - object_id_type, - asset, - generic_operation_result, - generic_exchange_operation_result - > operation_result; + struct extendable_operation_result_dtl + { + optional> impacted_accounts; + optional> new_objects; + optional> updated_objects; + optional> removed_objects; + optional> paid; + optional> received; + optional> fees; + }; + + using extendable_operation_result = extension; + + using operation_result = fc::static_variant < + /* 0 */ void_result, + /* 1 */ object_id_type, + /* 2 */ asset, + /* 3 */ generic_operation_result, + /* 4 */ generic_exchange_operation_result, + /* 5 */ extendable_operation_result + >; struct base_operation { @@ -129,7 +143,7 @@ namespace graphene { namespace protocol { * always add new types to a static_variant without breaking backward * compatibility. */ - typedef static_variant future_extensions; + using future_extensions = static_variant; /** * A flat_set is used to make sure that only one extension of @@ -144,12 +158,18 @@ namespace graphene { namespace protocol { } } // graphene::protocol +FC_REFLECT_TYPENAME( graphene::protocol::extendable_operation_result ) FC_REFLECT_TYPENAME( graphene::protocol::operation_result ) FC_REFLECT_TYPENAME( graphene::protocol::future_extensions ) +FC_REFLECT_TYPENAME( graphene::protocol::extensions_type ) FC_REFLECT( graphene::protocol::void_result, ) FC_REFLECT( graphene::protocol::generic_operation_result, (new_objects)(updated_objects)(removed_objects) ) FC_REFLECT( graphene::protocol::generic_exchange_operation_result, (paid)(received)(fees) ) +FC_REFLECT( graphene::protocol::extendable_operation_result_dtl, + (impacted_accounts)(new_objects)(updated_objects)(removed_objects)(paid)(received)(fees) ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::generic_operation_result ) // impl in operations.cpp // impl in operations.cpp GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::generic_exchange_operation_result ) +// impl in operations.cpp +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::extendable_operation_result_dtl ) diff --git a/libraries/protocol/include/graphene/protocol/config.hpp b/libraries/protocol/include/graphene/protocol/config.hpp index d97aa5a9a3..64379ff427 100644 --- a/libraries/protocol/include/graphene/protocol/config.hpp +++ b/libraries/protocol/include/graphene/protocol/config.hpp @@ -35,7 +35,7 @@ #define GRAPHENE_MIN_ASSET_SYMBOL_LENGTH 3 #define GRAPHENE_MAX_ASSET_SYMBOL_LENGTH 16 -#define GRAPHENE_MAX_SHARE_SUPPLY int64_t(1000000000000000ll) +constexpr int64_t GRAPHENE_MAX_SHARE_SUPPLY (1000000000000000LL); // 10 ^ 15 #define GRAPHENE_MAX_WORKER_NAME_LENGTH 63 #define GRAPHENE_MAX_URL_LENGTH 127 @@ -118,7 +118,16 @@ ///@} /// Denominator for SameT Fund fee calculation -constexpr uint32_t GRAPHENE_SAMET_FUND_FEE_DENOM = 1000000; +constexpr uint32_t GRAPHENE_FEE_RATE_DENOM = 1000000; + +/// How long a credit offer will be kept active, in days +constexpr int64_t GRAPHENE_MAX_CREDIT_OFFER_DAYS = 380; +/// How long a credit offer will be kept active, in seconds +constexpr int64_t GRAPHENE_MAX_CREDIT_OFFER_SECS = GRAPHENE_MAX_CREDIT_OFFER_DAYS * 86400; +/// How long a credit deal will be kept, in days +constexpr int64_t GRAPHENE_MAX_CREDIT_DEAL_DAYS = 380; +/// How long a credit deal will be kept, in seconds +constexpr int64_t GRAPHENE_MAX_CREDIT_DEAL_SECS = GRAPHENE_MAX_CREDIT_DEAL_DAYS * 86400; /** * Reserved Account IDs with special meaning diff --git a/libraries/protocol/include/graphene/protocol/credit_offer.hpp b/libraries/protocol/include/graphene/protocol/credit_offer.hpp new file mode 100644 index 0000000000..719c897176 --- /dev/null +++ b/libraries/protocol/include/graphene/protocol/credit_offer.hpp @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include + +namespace graphene { namespace protocol { + + /** + * @brief Create a new credit offer + * @ingroup operations + * + * A credit offer is a fund that can be used by other accounts who provide certain collateral. + */ + struct credit_offer_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; ///< Operation fee + account_id_type owner_account; ///< Owner of the credit offer + asset_id_type asset_type; ///< Asset type in the credit offer + share_type balance; ///< Usable amount in the credit offer + uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM + uint32_t max_duration_seconds = 0; ///< The time limit that borrowed funds should be repaid + share_type min_deal_amount; ///< Minimum amount to borrow for each new deal + bool enabled = false; ///< Whether this offer is available + time_point_sec auto_disable_time; ///< The time when this offer will be disabled automatically + + /// Types and rates of acceptable collateral + flat_map acceptable_collateral; + + /// Allowed borrowers and their maximum amounts to borrow. No limitation if empty. + flat_map acceptable_borrowers; + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return owner_account; } + void validate()const override; + share_type calculate_fee(const fee_parameters_type& k)const; + }; + + /** + * @brief Delete a credit offer + * @ingroup operations + */ + struct credit_offer_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; ///< Operation fee + account_id_type owner_account; ///< The account who owns the credit offer + credit_offer_id_type offer_id; ///< ID of the credit offer + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return owner_account; } + void validate()const override; + }; + + /** + * @brief Update a credit offer + * @ingroup operations + */ + struct credit_offer_update_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; ///< Operation fee + account_id_type owner_account; ///< Owner of the credit offer + credit_offer_id_type offer_id; ///< ID of the credit offer + optional delta_amount; ///< Delta amount, optional + optional fee_rate; ///< New fee rate, optional + optional max_duration_seconds; ///< New repayment time limit, optional + optional min_deal_amount; ///< Minimum amount to borrow for each new deal, optional + optional enabled; ///< Whether this offer is available, optional + optional auto_disable_time; ///< New time to disable automatically, optional + + /// New types and rates of acceptable collateral, optional + optional> acceptable_collateral; + + /// New allowed borrowers and their maximum amounts to borrow, optional + optional> acceptable_borrowers; + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return owner_account; } + void validate()const override; + share_type calculate_fee(const fee_parameters_type& k)const; + }; + + /** + * @brief Accept a creadit offer and create a credit deal + * @ingroup operations + */ + struct credit_offer_accept_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< Operation fee + account_id_type borrower; ///< The account who accepts the offer + credit_offer_id_type offer_id; ///< ID of the credit offer + asset borrow_amount; ///< The amount to borrow + asset collateral; ///< The collateral + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return borrower; } + void validate()const override; + }; + + /** + * @brief Repay a credit deal + * @ingroup operations + */ + struct credit_deal_repay_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; ///< Operation fee + account_id_type account; ///< The account who repays to the credit offer + credit_deal_id_type deal_id; ///< ID of the credit deal + asset repay_amount; ///< The amount to repay + asset credit_fee; ///< The credit fee relative to the amount to repay + + extensions_type extensions; ///< Unused. Reserved for future use. + + account_id_type fee_payer()const { return account; } + void validate()const override; + }; + + /** + * @brief A credit deal expired without being fully repaid + * @ingroup operations + * @note This is a virtual operation. + */ + struct credit_deal_expired_operation : public base_operation + { + struct fee_parameters_type {}; + + credit_deal_expired_operation() = default; + + credit_deal_expired_operation( const credit_deal_id_type& did, const credit_offer_id_type& oid, + const account_id_type& o, const account_id_type& b, const asset& u, const asset& c, const uint32_t fr) + : deal_id(did), offer_id(oid), offer_owner(o), borrower(b), unpaid_amount(u), collateral(c), fee_rate(fr) + { /* Nothing to do */ } + + asset fee; ///< Only for compatibility, unused + credit_deal_id_type deal_id; ///< ID of the credit deal + credit_offer_id_type offer_id; ///< ID of the credit offer + account_id_type offer_owner; ///< Owner of the credit offer + account_id_type borrower; ///< The account who repays to the credit offer + asset unpaid_amount; ///< The amount that is unpaid + asset collateral; ///< The collateral liquidated + uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM + + account_id_type fee_payer()const { return borrower; } + void validate()const override { FC_ASSERT( !"virtual operation" ); } + + /// This is a virtual operation; there is no fee + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // graphene::protocol + +FC_REFLECT( graphene::protocol::credit_offer_create_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::credit_offer_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::credit_offer_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::protocol::credit_offer_accept_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::credit_deal_repay_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::credit_deal_expired_operation::fee_parameters_type, ) // VIRTUAL + +FC_REFLECT( graphene::protocol::credit_offer_create_operation, + (fee) + (owner_account) + (asset_type) + (balance) + (fee_rate) + (max_duration_seconds) + (min_deal_amount) + (enabled) + (auto_disable_time) + (acceptable_collateral) + (acceptable_borrowers) + (extensions) + ) + +FC_REFLECT( graphene::protocol::credit_offer_delete_operation, + (fee) + (owner_account) + (offer_id) + (extensions) + ) + +FC_REFLECT( graphene::protocol::credit_offer_update_operation, + (fee) + (owner_account) + (offer_id) + (delta_amount) + (fee_rate) + (max_duration_seconds) + (min_deal_amount) + (enabled) + (auto_disable_time) + (acceptable_collateral) + (acceptable_borrowers) + (extensions) + ) + +FC_REFLECT( graphene::protocol::credit_offer_accept_operation, + (fee) + (borrower) + (offer_id) + (borrow_amount) + (collateral) + (extensions) + ) + +FC_REFLECT( graphene::protocol::credit_deal_repay_operation, + (fee) + (account) + (deal_id) + (repay_amount) + (credit_fee) + (extensions) + ) + +FC_REFLECT( graphene::protocol::credit_deal_expired_operation, + (fee) + (deal_id) + (offer_id) + (offer_owner) + (borrower) + (unpaid_amount) + (collateral) + (fee_rate) + ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation::fee_parameters_type ) +// Note: credit_deal_expired_operation is virtual so no external serialization for its fee_parameters_type + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_create_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_delete_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_update_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_offer_accept_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_repay_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::credit_deal_expired_operation ) diff --git a/libraries/protocol/include/graphene/protocol/operations.hpp b/libraries/protocol/include/graphene/protocol/operations.hpp index febc34d59f..0b32f7e5da 100644 --- a/libraries/protocol/include/graphene/protocol/operations.hpp +++ b/libraries/protocol/include/graphene/protocol/operations.hpp @@ -27,11 +27,13 @@ #include #include #include -#include #include #include +#include +#include #include #include +#include #include #include #include @@ -42,7 +44,6 @@ #include #include #include -#include namespace graphene { namespace protocol { @@ -51,7 +52,7 @@ namespace graphene { namespace protocol { * * Defines the set of valid operations as a discriminated union type. */ - typedef fc::static_variant< + using operation = fc::static_variant< /* 0 */ transfer_operation, /* 1 */ limit_order_create_operation, /* 2 */ limit_order_cancel_operation, @@ -120,8 +121,14 @@ namespace graphene { namespace protocol { /* 65 */ samet_fund_delete_operation, /* 66 */ samet_fund_update_operation, /* 67 */ samet_fund_borrow_operation, - /* 68 */ samet_fund_repay_operation - > operation; + /* 68 */ samet_fund_repay_operation, + /* 69 */ credit_offer_create_operation, + /* 70 */ credit_offer_delete_operation, + /* 71 */ credit_offer_update_operation, + /* 72 */ credit_offer_accept_operation, + /* 73 */ credit_deal_repay_operation, + /* 74 */ credit_deal_expired_operation // VIRTUAL + >; /// @} // operations group @@ -144,8 +151,7 @@ namespace graphene { namespace protocol { */ struct op_wrapper { - public: - op_wrapper(const operation& op = operation()):op(op){} + explicit op_wrapper(const operation& op = operation()):op(op){} operation op; }; diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp index c2a2138e85..098fd6774e 100644 --- a/libraries/protocol/include/graphene/protocol/samet_fund.hpp +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -41,7 +41,7 @@ namespace graphene { namespace protocol { account_id_type owner_account; ///< Owner of the fund asset_id_type asset_type; ///< Asset type in the fund share_type balance; ///< Usable amount in the fund - uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_SAMET_FUND_FEE_DENOM + uint32_t fee_rate = 0; ///< Fee rate, the demominator is GRAPHENE_FEE_RATE_DENOM extensions_type extensions; ///< Unused. Reserved for future use. diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 6ac0f407a8..1cbbd25285 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -60,19 +60,36 @@ #include #include -#define GRAPHENE_EXTERNAL_SERIALIZATION(ext, type) \ +#define GRAPHENE_EXTERNAL_SERIALIZATION_VARIANT(ext, type) \ namespace fc { \ ext template void from_variant( const variant& v, type& vo, uint32_t max_depth ); \ ext template void to_variant( const type& v, variant& vo, uint32_t max_depth ); \ -namespace raw { \ - ext template void pack< datastream, type >( datastream& s, const type& tx, uint32_t _max_depth ); \ - ext template void pack< sha256::encoder, type >( sha256::encoder& s, const type& tx, uint32_t _max_depth ); \ - ext template void pack< datastream, type >( datastream& s, const type& tx, uint32_t _max_depth ); \ - ext template void unpack< datastream, type >( datastream& s, type& tx, uint32_t _max_depth ); \ -} } // fc::raw +} + +#define GRAPHENE_EXTERNAL_SERIALIZATION_PACK(ext, type) \ +namespace fc { namespace raw { \ + ext template void pack< datastream, type >( \ + datastream& s, const type& tx, uint32_t _max_depth ); \ + ext template void pack< sha256::encoder, type >( \ + sha256::encoder& s, const type& tx, uint32_t _max_depth ); \ + ext template void pack< datastream, type >( \ + datastream& s, const type& tx, uint32_t _max_depth ); \ + ext template void unpack< datastream, type >( \ + datastream& s, type& tx, uint32_t _max_depth ); \ +} } + +#define GRAPHENE_EXTERNAL_SERIALIZATION(ext, type) \ + GRAPHENE_EXTERNAL_SERIALIZATION_VARIANT(ext, type) \ + GRAPHENE_EXTERNAL_SERIALIZATION_PACK(ext, type) + #define GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION(type) GRAPHENE_EXTERNAL_SERIALIZATION(extern, type) #define GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION(type) GRAPHENE_EXTERNAL_SERIALIZATION(/*not extern*/, type) +#define GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION_VARIANT(type) \ + GRAPHENE_EXTERNAL_SERIALIZATION_VARIANT(/*not extern*/, type) +#define GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION_PACK(type) \ + GRAPHENE_EXTERNAL_SERIALIZATION_PACK(/*not extern*/, type) + #define GRAPHENE_NAME_TO_OBJECT_TYPE(x, prefix, name) BOOST_PP_CAT(prefix, BOOST_PP_CAT(name, _object_type)) #define GRAPHENE_NAME_TO_ID_TYPE(x, y, name) BOOST_PP_CAT(name, _id_type) #define GRAPHENE_DECLARE_ID(x, space_prefix_seq, name) \ @@ -318,6 +335,8 @@ GRAPHENE_DEFINE_IDS(protocol, protocol_ids, /*protocol objects are not prefixed* /* 1.18.x */ (ticket) /* 1.19.x */ (liquidity_pool) /* 1.20.x */ (samet_fund) + /* 1.21.x */ (credit_offer) + /* 1.22.x */ (credit_deal) ) FC_REFLECT(graphene::protocol::public_key_type, (key_data)) diff --git a/libraries/protocol/operations.cpp b/libraries/protocol/operations.cpp index 12914d2310..c92a7750eb 100644 --- a/libraries/protocol/operations.cpp +++ b/libraries/protocol/operations.cpp @@ -113,5 +113,6 @@ void operation_get_required_authorities( const operation& op, GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::generic_operation_result ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::generic_exchange_operation_result ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::extendable_operation_result_dtl ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::op_wrapper ) diff --git a/libraries/wallet/operation_printer.cpp b/libraries/wallet/operation_printer.cpp index 8d5baace58..8d96bd9527 100644 --- a/libraries/wallet/operation_printer.cpp +++ b/libraries/wallet/operation_printer.cpp @@ -385,4 +385,10 @@ std::string operation_result_printer::operator()(const generic_exchange_operatio return fc::json::to_string(r); } +std::string operation_result_printer::operator()(const extendable_operation_result& r) +{ + // TODO show pretty amounts instead of raw json + return fc::json::to_string(r); +} + }}} // graphene::wallet::detail diff --git a/libraries/wallet/operation_printer.hpp b/libraries/wallet/operation_printer.hpp index a0520dc5ab..a14a29cfd2 100644 --- a/libraries/wallet/operation_printer.hpp +++ b/libraries/wallet/operation_printer.hpp @@ -49,6 +49,7 @@ struct operation_result_printer std::string operator()(const graphene::protocol::asset& a); std::string operator()(const graphene::protocol::generic_operation_result& r); std::string operator()(const graphene::protocol::generic_exchange_operation_result& r); + std::string operator()(const graphene::protocol::extendable_operation_result& r); }; // BLOCK TRX OP VOP diff --git a/libraries/wallet/wallet_builder.cpp b/libraries/wallet/wallet_builder.cpp index 55396e3512..661699e124 100644 --- a/libraries/wallet/wallet_builder.cpp +++ b/libraries/wallet/wallet_builder.cpp @@ -101,33 +101,22 @@ namespace graphene { namespace wallet { namespace detail { signed_transaction wallet_api_impl::propose_builder_transaction( transaction_handle_type handle, time_point_sec expiration, uint32_t review_period_seconds, bool broadcast) { - FC_ASSERT(_builder_transactions.count(handle) > 0); - proposal_create_operation op; - op.expiration_time = expiration; - signed_transaction& trx = _builder_transactions[handle]; - std::transform(trx.operations.begin(), trx.operations.end(), std::back_inserter(op.proposed_ops), - [](const operation& op) -> op_wrapper { return op; }); - if( review_period_seconds ) - op.review_period_seconds = review_period_seconds; - trx.operations = {op}; - _remote_db->get_global_properties().parameters.get_current_fees().set_fee( trx.operations.front() ); - - return trx = sign_transaction(trx, broadcast); + return propose_builder_transaction2( handle, "1.2.0", expiration, review_period_seconds, broadcast ); } signed_transaction wallet_api_impl::propose_builder_transaction2( transaction_handle_type handle, string account_name_or_id, time_point_sec expiration, uint32_t review_period_seconds, bool broadcast ) { FC_ASSERT(_builder_transactions.count(handle) > 0); - proposal_create_operation op; - op.fee_paying_account = get_account(account_name_or_id).get_id(); - op.expiration_time = expiration; + proposal_create_operation pcop; + pcop.fee_paying_account = get_account(account_name_or_id).get_id(); + pcop.expiration_time = expiration; signed_transaction& trx = _builder_transactions[handle]; - std::transform(trx.operations.begin(), trx.operations.end(), std::back_inserter(op.proposed_ops), - [](const operation& op) -> op_wrapper { return op; }); + std::transform(trx.operations.begin(), trx.operations.end(), std::back_inserter(pcop.proposed_ops), + [](const operation& op) -> op_wrapper { return op_wrapper(op); }); if( review_period_seconds ) - op.review_period_seconds = review_period_seconds; - trx.operations = {op}; + pcop.review_period_seconds = review_period_seconds; + trx.operations = {pcop}; _remote_db->get_global_properties().parameters.get_current_fees().set_fee( trx.operations.front() ); return trx = sign_transaction(trx, broadcast); diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index 4595d41c40..f991559e03 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -403,7 +403,7 @@ BOOST_AUTO_TEST_CASE( proposal_failure ) top.from = bob_id; top.amount = asset(2000000); proposal_create_operation pop; - pop.proposed_ops.push_back( { top } ); + pop.proposed_ops.push_back( { op_wrapper(top) } ); pop.expiration_time = db.head_block_time() + fc::days(1); pop.fee_paying_account = bob_id; trx.operations.push_back( pop ); @@ -458,7 +458,7 @@ BOOST_AUTO_TEST_CASE( committee_authority ) auto _sign = [&] { trx.clear_signatures(); sign( trx, nathan_key ); }; proposal_create_operation pop; - pop.proposed_ops.push_back({trx.operations.front()}); + pop.proposed_ops.push_back({op_wrapper(trx.operations.front())}); pop.expiration_time = db.head_block_time() + global_params.committee_proposal_review_period*2; pop.fee_paying_account = nathan.id; trx.operations = {pop}; diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 3139b1928c..1769eb6e39 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -169,6 +169,33 @@ BOOST_AUTO_TEST_CASE( price_test ) BOOST_CHECK(a == c); BOOST_CHECK(!(b == c)); + BOOST_CHECK_THROW( price( asset(-1), asset(1, asset_id_type(1)) ).validate(), fc::exception ); + BOOST_CHECK_THROW( price( asset(0), asset(1, asset_id_type(1)) ).validate(), fc::exception ); + BOOST_CHECK_THROW( price( asset(1), asset(0, asset_id_type(1)) ).validate(), fc::exception ); + BOOST_CHECK_THROW( price( asset(1), asset(-1, asset_id_type(1)) ).validate(), fc::exception ); + BOOST_CHECK_THROW( price( asset(1), asset(1) ).validate(), fc::exception ); + BOOST_CHECK_THROW( price( asset(1, asset_id_type(1)), asset(1, asset_id_type(1)) ).validate(), fc::exception ); + + constexpr int64_t max_amount = GRAPHENE_MAX_SHARE_SUPPLY; + constexpr int64_t too_big_amount = GRAPHENE_MAX_SHARE_SUPPLY + 1; + + price( asset(1), asset(max_amount, asset_id_type(1)) ).validate(); + price( asset(max_amount), asset(1, asset_id_type(1)) ).validate(); + price( asset(max_amount), asset(max_amount, asset_id_type(1)) ).validate(); + price( asset(1), asset(max_amount, asset_id_type(1)) ).validate(true); + price( asset(max_amount), asset(1, asset_id_type(1)) ).validate(true); + price( asset(max_amount), asset(max_amount, asset_id_type(1)) ).validate(true); + + price( asset(1), asset(too_big_amount, asset_id_type(1)) ).validate(); + price( asset(too_big_amount), asset(1, asset_id_type(1)) ).validate(); + price( asset(too_big_amount), asset(too_big_amount, asset_id_type(1)) ).validate(); + BOOST_CHECK_THROW( price( asset(1), asset(too_big_amount, asset_id_type(1)) ).validate(true), + fc::exception ); + BOOST_CHECK_THROW( price( asset(too_big_amount), asset(1, asset_id_type(1)) ).validate(true), + fc::exception ); + BOOST_CHECK_THROW( price( asset(too_big_amount), asset(too_big_amount, asset_id_type(1)) ).validate(true), + fc::exception ); + GRAPHENE_REQUIRE_THROW( price(asset(1), asset(1)) * ratio_type(1,1), fc::exception ); GRAPHENE_REQUIRE_THROW( price(asset(0), asset(1, asset_id_type(1))) * ratio_type(1,1), fc::exception ); GRAPHENE_REQUIRE_THROW( price(asset(-1), asset(1, asset_id_type(1))) * ratio_type(1,1), fc::exception ); From 23263b0e649ed657b79dd7168f046416d07cd9d0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 22 Jun 2021 08:35:37 +0000 Subject: [PATCH 051/258] Fix code smells --- libraries/chain/credit_offer_evaluator.cpp | 5 ++--- libraries/chain/db_notify.cpp | 6 +++--- .../include/graphene/protocol/credit_offer.hpp | 2 +- libraries/wallet/operation_printer.cpp | 10 +++++----- libraries/wallet/operation_printer.hpp | 10 +++++----- libraries/wallet/wallet_builder.cpp | 2 +- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/libraries/chain/credit_offer_evaluator.cpp b/libraries/chain/credit_offer_evaluator.cpp index 984c3f1a6b..1632a311b0 100644 --- a/libraries/chain/credit_offer_evaluator.cpp +++ b/libraries/chain/credit_offer_evaluator.cpp @@ -189,9 +189,8 @@ void_result credit_offer_update_evaluator::do_apply( const credit_offer_update_o if( op.delta_amount.valid() ) d.adjust_balance( op.owner_account, -(*op.delta_amount) ); - d.modify( *_offer, [&op,this]( credit_offer_object& coo ){ - if( op.delta_amount.valid() ) - { + d.modify( *_offer, [&op]( credit_offer_object& coo ){ + if( op.delta_amount.valid() ) { coo.total_balance += op.delta_amount->amount; coo.current_balance += op.delta_amount->amount; } diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 0c5c74a9ed..62a6964411 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -500,12 +500,12 @@ void get_relevant_accounts( const object* obj, flat_set& accoun FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->owner_account ); break; - } case credit_offer_object_type:{ + } case credit_offer_object_type:{ const auto* aobj = dynamic_cast( obj ); FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->owner_account ); break; - } case credit_deal_object_type:{ + } case credit_deal_object_type:{ const auto* aobj = dynamic_cast( obj ); FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->offer_owner ); @@ -575,7 +575,7 @@ void get_relevant_accounts( const object* obj, flat_set& accoun accounts.insert( aobj->bidder ); break; } case impl_credit_deal_summary_object_type:{ - const auto& aobj = dynamic_cast(obj); + const auto* aobj = dynamic_cast(obj); FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->offer_owner ); accounts.insert( aobj->borrower ); diff --git a/libraries/protocol/include/graphene/protocol/credit_offer.hpp b/libraries/protocol/include/graphene/protocol/credit_offer.hpp index 719c897176..4496c2224b 100644 --- a/libraries/protocol/include/graphene/protocol/credit_offer.hpp +++ b/libraries/protocol/include/graphene/protocol/credit_offer.hpp @@ -184,7 +184,7 @@ namespace graphene { namespace protocol { void validate()const override { FC_ASSERT( !"virtual operation" ); } /// This is a virtual operation; there is no fee - share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + share_type calculate_fee(const fee_parameters_type&)const { return 0; } }; } } // graphene::protocol diff --git a/libraries/wallet/operation_printer.cpp b/libraries/wallet/operation_printer.cpp index 8d96bd9527..604b30e26b 100644 --- a/libraries/wallet/operation_printer.cpp +++ b/libraries/wallet/operation_printer.cpp @@ -364,28 +364,28 @@ std::string operation_result_printer::operator()(const void_result& x) const return ""; } -std::string operation_result_printer::operator()(const object_id_type& oid) +std::string operation_result_printer::operator()(const object_id_type& oid) const { return std::string(oid); } -std::string operation_result_printer::operator()(const asset& a) +std::string operation_result_printer::operator()(const asset& a) const { return _wallet.get_asset(a.asset_id).amount_to_pretty_string(a); } -std::string operation_result_printer::operator()(const generic_operation_result& r) +std::string operation_result_printer::operator()(const generic_operation_result& r) const { return fc::json::to_string(r); } -std::string operation_result_printer::operator()(const generic_exchange_operation_result& r) +std::string operation_result_printer::operator()(const generic_exchange_operation_result& r) const { // TODO show pretty amounts instead of raw json return fc::json::to_string(r); } -std::string operation_result_printer::operator()(const extendable_operation_result& r) +std::string operation_result_printer::operator()(const extendable_operation_result& r) const { // TODO show pretty amounts instead of raw json return fc::json::to_string(r); diff --git a/libraries/wallet/operation_printer.hpp b/libraries/wallet/operation_printer.hpp index a14a29cfd2..7fbb9b9b2a 100644 --- a/libraries/wallet/operation_printer.hpp +++ b/libraries/wallet/operation_printer.hpp @@ -45,11 +45,11 @@ struct operation_result_printer typedef std::string result_type; std::string operator()(const graphene::protocol::void_result& x) const; - std::string operator()(const graphene::protocol::object_id_type& oid); - std::string operator()(const graphene::protocol::asset& a); - std::string operator()(const graphene::protocol::generic_operation_result& r); - std::string operator()(const graphene::protocol::generic_exchange_operation_result& r); - std::string operator()(const graphene::protocol::extendable_operation_result& r); + std::string operator()(const graphene::protocol::object_id_type& oid) const; + std::string operator()(const graphene::protocol::asset& a) const; + std::string operator()(const graphene::protocol::generic_operation_result& r) const; + std::string operator()(const graphene::protocol::generic_exchange_operation_result& r) const; + std::string operator()(const graphene::protocol::extendable_operation_result& r) const; }; // BLOCK TRX OP VOP diff --git a/libraries/wallet/wallet_builder.cpp b/libraries/wallet/wallet_builder.cpp index 661699e124..864daf28f8 100644 --- a/libraries/wallet/wallet_builder.cpp +++ b/libraries/wallet/wallet_builder.cpp @@ -113,7 +113,7 @@ namespace graphene { namespace wallet { namespace detail { pcop.expiration_time = expiration; signed_transaction& trx = _builder_transactions[handle]; std::transform(trx.operations.begin(), trx.operations.end(), std::back_inserter(pcop.proposed_ops), - [](const operation& op) -> op_wrapper { return op_wrapper(op); }); + [](const operation& op) { return op_wrapper(op); }); if( review_period_seconds ) pcop.review_period_seconds = review_period_seconds; trx.operations = {pcop}; From 2527a5227456c021f68370838a8eed551392d32e Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 25 Jun 2021 02:06:59 +0000 Subject: [PATCH 052/258] Fix credit offer accept operation When a credit offer's acceptable borrowers map is empty and a borrower already has an active credit deal made from that offer, the borrower was unable to make a new credit deal from that offer. --- libraries/chain/credit_offer_evaluator.cpp | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/libraries/chain/credit_offer_evaluator.cpp b/libraries/chain/credit_offer_evaluator.cpp index 1632a311b0..720450d6df 100644 --- a/libraries/chain/credit_offer_evaluator.cpp +++ b/libraries/chain/credit_offer_evaluator.cpp @@ -266,21 +266,26 @@ void_result credit_offer_accept_evaluator::do_evaluate(const credit_offer_accept "Insufficient collateral provided, requires ${r}, provided ${p}", ("r", required_collateral.amount) ("p", op.collateral.amount) ); + optional max_allowed; if( !_offer->acceptable_borrowers.empty() ) { auto itr = _offer->acceptable_borrowers.find( op.borrower ); FC_ASSERT( itr != _offer->acceptable_borrowers.end(), "Account is not in acceptable borrowers" ); - share_type max_allowed = itr->second; + max_allowed = itr->second; + } - share_type already_borrowed = 0; - const auto& deal_summary_idx = d.get_index_type().indices().get(); - auto summ_itr = deal_summary_idx.find( boost::make_tuple( op.offer_id, op.borrower ) ); - if( summ_itr != deal_summary_idx.end() ) - { - _deal_summary = &(*summ_itr); - already_borrowed = _deal_summary->total_debt_amount; - } - FC_ASSERT( already_borrowed + op.borrow_amount.amount <= max_allowed, + share_type already_borrowed = 0; + const auto& deal_summary_idx = d.get_index_type().indices().get(); + auto summ_itr = deal_summary_idx.find( boost::make_tuple( op.offer_id, op.borrower ) ); + if( summ_itr != deal_summary_idx.end() ) + { + _deal_summary = &(*summ_itr); + already_borrowed = _deal_summary->total_debt_amount; + } + + if( max_allowed.valid() ) + { + FC_ASSERT( already_borrowed + op.borrow_amount.amount <= *max_allowed, "Unable to borrow ${b}, already borrowed ${a}, maximum allowed ${m}", ("b", op.borrow_amount.amount) ("a", already_borrowed) ("m", max_allowed) ); } From a4cc66b5bbde86fefbe9b6729791594f3a99c4dd Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 25 Jun 2021 02:21:23 +0000 Subject: [PATCH 053/258] Add tests for credit offers and deals --- tests/common/database_fixture.cpp | 209 +++- tests/common/database_fixture.hpp | 49 + tests/tests/credit_offer_tests.cpp | 1699 ++++++++++++++++++++++++++++ tests/tests/samet_fund_tests.cpp | 2 +- 4 files changed, 1957 insertions(+), 2 deletions(-) create mode 100644 tests/tests/credit_offer_tests.cpp diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 5ccc695742..94df251f36 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -553,6 +553,33 @@ void database_fixture_base::verify_asset_supplies( const database& db ) total_balances[o.asset_type] += (o.balance - o.unpaid_amount); } + map credit_offer_debts_in_offers; + map credit_offer_debts_in_deals; + map credit_offer_debts_in_summs; + for( const credit_offer_object& o : db.get_index_type().indices() ) + { + total_balances[o.asset_type] += o.current_balance; + if( o.total_balance != o.current_balance) + credit_offer_debts_in_offers[o.asset_type] += (o.total_balance - o.current_balance); + } + for( const credit_deal_object& o : db.get_index_type().indices() ) + { + total_balances[o.collateral_asset] += o.collateral_amount; + credit_offer_debts_in_deals[o.debt_asset] += o.debt_amount; + } + for( const credit_deal_summary_object& o : db.get_index_type().indices() ) + { + credit_offer_debts_in_summs[o.debt_asset] += o.total_debt_amount; + } + + BOOST_CHECK_EQUAL( credit_offer_debts_in_offers.size(), credit_offer_debts_in_deals.size() ); + BOOST_CHECK_EQUAL( credit_offer_debts_in_offers.size(), credit_offer_debts_in_summs.size() ); + for( const auto& item : credit_offer_debts_in_offers ) + { + BOOST_CHECK_EQUAL( item.second.value, credit_offer_debts_in_deals[item.first].value ); + BOOST_CHECK_EQUAL( item.second.value, credit_offer_debts_in_summs[item.first].value ); + } + total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget; for( const auto& item : total_debts ) @@ -1511,7 +1538,7 @@ samet_fund_create_operation database_fixture_base::make_samet_fund_create_op( samet_fund_create_operation op; op.owner_account = account; op.asset_type = asset_type; - op.balance = balance, + op.balance = balance; op.fee_rate = fee_rate; return op; } @@ -1641,6 +1668,186 @@ void database_fixture_base::repay_to_samet_fund( account_id_type account, samet_ verify_asset_supplies(db); } +credit_offer_create_operation database_fixture_base::make_credit_offer_create_op( + account_id_type account, asset_id_type asset_type, + share_type balance, uint32_t fee_rate, uint32_t max_duration, + share_type min_amount, bool enabled, time_point_sec disable_time, + flat_map acceptable_collateral, + flat_map acceptable_borrowers )const +{ + credit_offer_create_operation op; + op.owner_account = account; + op.asset_type = asset_type; + op.balance = balance; + op.fee_rate = fee_rate; + op.max_duration_seconds = max_duration; + op.min_deal_amount = min_amount; + op.enabled = enabled; + op.auto_disable_time = disable_time; + op.acceptable_collateral = acceptable_collateral; + op.acceptable_borrowers = acceptable_borrowers; + return op; +} + +const credit_offer_object& database_fixture_base::create_credit_offer( + account_id_type account, asset_id_type asset_type, + share_type balance, uint32_t fee_rate, uint32_t max_duration, + share_type min_amount, bool enabled, time_point_sec disable_time, + flat_map acceptable_collateral, + flat_map acceptable_borrowers ) +{ + credit_offer_create_operation op = make_credit_offer_create_op( account, asset_type, balance, fee_rate, + max_duration, min_amount, enabled, disable_time, + acceptable_collateral, acceptable_borrowers ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + trx.operations.clear(); + verify_asset_supplies(db); + return db.get( op_result.get() ); +} + +credit_offer_delete_operation database_fixture_base::make_credit_offer_delete_op( account_id_type account, + credit_offer_id_type offer_id )const +{ + credit_offer_delete_operation op; + op.owner_account = account; + op.offer_id = offer_id; + return op; +} + +asset database_fixture_base::delete_credit_offer( account_id_type account, credit_offer_id_type offer_id ) +{ + credit_offer_delete_operation op = make_credit_offer_delete_op( account, offer_id ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + trx.operations.clear(); + verify_asset_supplies(db); + return op_result.get(); +} + +credit_offer_update_operation database_fixture_base::make_credit_offer_update_op( + account_id_type account, credit_offer_id_type offer_id, + const optional& delta_amount, + const optional& new_fee_rate, + const optional& max_duration_seconds, + const optional& min_deal_amount, + const optional& enabled, + const optional& auto_disable_time, + const optional>& acceptable_collateral, + const optional>& acceptable_borrowers + )const +{ + credit_offer_update_operation op; + op.owner_account = account; + op.offer_id = offer_id; + op.delta_amount = delta_amount; + op.fee_rate = new_fee_rate; + op.max_duration_seconds = max_duration_seconds; + op.min_deal_amount = min_deal_amount; + op.enabled = enabled; + op.auto_disable_time = auto_disable_time; + op.acceptable_collateral = acceptable_collateral; + op.acceptable_borrowers = acceptable_borrowers; + return op; +} + +void database_fixture_base::update_credit_offer( account_id_type account, credit_offer_id_type offer_id, + const optional& delta_amount, + const optional& new_fee_rate, + const optional& max_duration_seconds, + const optional& min_deal_amount, + const optional& enabled, + const optional& auto_disable_time, + const optional>& acceptable_collateral, + const optional>& acceptable_borrowers ) +{ + credit_offer_update_operation op = make_credit_offer_update_op( account, offer_id, delta_amount, new_fee_rate, + max_duration_seconds, min_deal_amount, enabled, auto_disable_time, + acceptable_collateral, acceptable_borrowers ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + verify_asset_supplies(db); +} + +credit_offer_accept_operation database_fixture_base::make_credit_offer_accept_op( + account_id_type account, credit_offer_id_type offer_id, + const asset& borrow_amount, const asset& collateral )const +{ + credit_offer_accept_operation op; + op.borrower = account; + op.offer_id = offer_id; + op.borrow_amount = borrow_amount; + op.collateral = collateral; + return op; +} + +const credit_deal_object& database_fixture_base::borrow_from_credit_offer( + account_id_type account, credit_offer_id_type offer_id, + const asset& borrow_amount, const asset& collateral ) +{ + credit_offer_accept_operation op = make_credit_offer_accept_op( account, offer_id, borrow_amount, collateral ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + trx.operations.clear(); + verify_asset_supplies(db); + return db.get( *op_result.get().value.new_objects->begin() ); +} + +credit_deal_repay_operation database_fixture_base::make_credit_deal_repay_op( + account_id_type account, credit_deal_id_type deal_id, + const asset& repay_amount, const asset& credit_fee )const +{ + credit_deal_repay_operation op; + op.account = account; + op.deal_id = deal_id; + op.repay_amount = repay_amount; + op.credit_fee = credit_fee; + return op; +} + +extendable_operation_result_dtl database_fixture_base::repay_credit_deal( + account_id_type account, credit_deal_id_type deal_id, + const asset& repay_amount, const asset& credit_fee ) +{ + credit_deal_repay_operation op = make_credit_deal_repay_op( account, deal_id, repay_amount, credit_fee ); + trx.operations.clear(); + trx.operations.push_back( op ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + set_expiration( db, trx ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + trx.operations.clear(); + verify_asset_supplies(db); + return op_result.get().value; +} + + void database_fixture_base::enable_fees() { db.modify(global_property_id_type()(db), [](global_property_object& gpo) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 032d9df789..e562633e9c 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -476,6 +477,54 @@ struct database_fixture_base { void repay_to_samet_fund( account_id_type account, samet_fund_id_type fund_id, const asset& repay_amount, const asset& fund_fee ); + // credit offer / deal + credit_offer_create_operation make_credit_offer_create_op( + account_id_type account, asset_id_type asset_type, + share_type balance, uint32_t fee_rate, uint32_t max_duration, + share_type min_amount, bool enabled, time_point_sec disable_time, + flat_map acceptable_collateral, + flat_map acceptable_borrowers )const; + const credit_offer_object& create_credit_offer( + account_id_type account, asset_id_type asset_type, + share_type balance, uint32_t fee_rate, uint32_t max_duration, + share_type min_amount, bool enabled, time_point_sec disable_time, + flat_map acceptable_collateral, + flat_map acceptable_borrowers ); + credit_offer_delete_operation make_credit_offer_delete_op( account_id_type account, + credit_offer_id_type offer_id )const; + asset delete_credit_offer( account_id_type account, credit_offer_id_type offer_id ); + credit_offer_update_operation make_credit_offer_update_op( + account_id_type account, credit_offer_id_type offer_id, + const optional& delta_amount, + const optional& new_fee_rate, + const optional& max_duration_seconds, + const optional& min_deal_amount, + const optional& enabled, + const optional& auto_disable_time, + const optional>& acceptable_collateral, + const optional>& acceptable_borrowers + )const; + void update_credit_offer( account_id_type account, credit_offer_id_type offer_id, + const optional& delta_amount, + const optional& new_fee_rate, + const optional& max_duration_seconds, + const optional& min_deal_amount, + const optional& enabled, + const optional& auto_disable_time, + const optional>& acceptable_collateral, + const optional>& acceptable_borrowers ); + credit_offer_accept_operation make_credit_offer_accept_op( + account_id_type account, credit_offer_id_type offer_id, + const asset& borrow_amount, const asset& collateral )const; + const credit_deal_object& borrow_from_credit_offer( + account_id_type account, credit_offer_id_type offer_id, + const asset& borrow_amount, const asset& collateral ); + credit_deal_repay_operation make_credit_deal_repay_op( + account_id_type account, credit_deal_id_type deal_id, + const asset& repay_amount, const asset& credit_fee )const; + extendable_operation_result_dtl repay_credit_deal( account_id_type account, credit_deal_id_type deal_id, + const asset& repay_amount, const asset& credit_fee ); + /** * NOTE: This modifies the database directly. You will probably have to call this each time you * finish creating a block diff --git a/tests/tests/credit_offer_tests.cpp b/tests/tests/credit_offer_tests.cpp new file mode 100644 index 0000000000..b875105232 --- /dev/null +++ b/tests/tests/credit_offer_tests.cpp @@ -0,0 +1,1699 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( credit_offer_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( credit_offer_hardfork_time_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_2262_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.id; + issue_uia( sam, usd.amount(init_amount) ); + + // Before the hard fork, unable to create a credit offer or transact against a credit offer or a credit deal, + // or do any of them with proposals + flat_map collateral_map; + collateral_map[usd_id] = price( asset(1), asset(1, usd_id) ); + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + db.head_block_time() + fc::days(1), collateral_map, {} ), + fc::exception ); + + credit_offer_id_type tmp_co_id; + credit_deal_id_type tmp_cd_id; + BOOST_CHECK_THROW( delete_credit_offer( sam_id, tmp_co_id ), fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, tmp_co_id, core.amount(100), 200, {}, {}, {}, {}, {}, {} ), + fc::exception ); + BOOST_CHECK_THROW( borrow_from_credit_offer( sam_id, tmp_co_id, core.amount(100), usd.amount(1000) ), + fc::exception ); + BOOST_CHECK_THROW( repay_credit_deal( sam_id, tmp_cd_id, core.amount(100), core.amount(100) ), + fc::exception ); + + credit_offer_create_operation cop = make_credit_offer_create_op( sam_id, core.id, 10000, 100, 3600, 0, false, + db.head_block_time() + fc::days(1), collateral_map, {} ); + BOOST_CHECK_THROW( propose( cop ), fc::exception ); + + credit_offer_delete_operation dop = make_credit_offer_delete_op( sam_id, tmp_co_id ); + BOOST_CHECK_THROW( propose( dop ), fc::exception ); + + credit_offer_update_operation uop = make_credit_offer_update_op( sam_id, tmp_co_id, core.amount(100), 200, + {}, {}, {}, {}, {}, {} ); + BOOST_CHECK_THROW( propose( uop ), fc::exception ); + + credit_offer_accept_operation aop = make_credit_offer_accept_op( sam_id, tmp_co_id, core.amount(100), + usd.amount(1000) ); + BOOST_CHECK_THROW( propose( aop ), fc::exception ); + + credit_deal_repay_operation rop = make_credit_deal_repay_op( sam_id, tmp_cd_id, core.amount(100), + core.amount(100) ); + BOOST_CHECK_THROW( propose( rop ), fc::exception ); + + credit_deal_expired_operation eop( tmp_cd_id, tmp_co_id, sam_id, account_id_type(), + core.amount(1), usd.amount(2), 1 ); + BOOST_CHECK_THROW( eop.validate(), fc::exception ); + BOOST_CHECK_THROW( propose( eop ), fc::exception ); + + trx.operations.clear(); + trx.operations.push_back( eop ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + BOOST_CHECK_THROW( trx.validate(), fc::exception ); + set_expiration( db, trx ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( credit_offer_crud_and_proposal_test ) +{ try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2362_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(ted)(por)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + asset_id_type core_id; + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.id; + issue_uia( sam, usd.amount(init_amount) ); + issue_uia( ted, usd.amount(init_amount) ); + + const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); + asset_id_type eur_id = eur.id; + issue_uia( sam, eur.amount(init_amount) ); + issue_uia( ted, eur.amount(init_amount) ); + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = eur.id; + uop.issuer = sam_id; + uop.new_options = eur.options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Sam so that he can manage the whitelist + upgrade_to_lifetime_member( sam_id ); + + // Add Sam to the whitelist, but do not add others + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + asset_id_type no_asset_id( core.id + 100 ); + BOOST_REQUIRE( !db.find( no_asset_id ) ); + + account_id_type no_account_id( sam.id + 1000 ); + BOOST_REQUIRE( !db.find( no_account_id ) ); + + // Able to propose + credit_offer_id_type tmp_co_id; + credit_deal_id_type tmp_cd_id; + { + flat_map collateral_map; + collateral_map[usd_id] = price( asset(1), asset(1, usd_id) ); + + credit_offer_create_operation cop = make_credit_offer_create_op( sam_id, core.id, 10000, 100, 3600, 0, false, + db.head_block_time() + fc::days(1), collateral_map, {} ); + propose( cop ); + + credit_offer_delete_operation dop = make_credit_offer_delete_op( sam_id, tmp_co_id ); + propose( dop ); + + credit_offer_update_operation uop = make_credit_offer_update_op( sam_id, tmp_co_id, core.amount(100), 200, + {}, {}, {}, {}, {}, {} ); + propose( uop ); + + credit_offer_accept_operation aop = make_credit_offer_accept_op( sam_id, tmp_co_id, core.amount(100), + usd.amount(1000) ); + propose( aop ); + + credit_deal_repay_operation rop = make_credit_deal_repay_op( sam_id, tmp_cd_id, core.amount(100), + core.amount(100) ); + propose( rop ); + } + + // Test virtual operation + { + credit_deal_expired_operation eop( tmp_cd_id, tmp_co_id, sam_id, account_id_type(), + core.amount(1), usd.amount(2), 1 ); + BOOST_CHECK_THROW( eop.validate(), fc::exception ); + BOOST_CHECK_THROW( propose( eop ), fc::exception ); + + trx.operations.clear(); + trx.operations.push_back( eop ); + + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + BOOST_CHECK_THROW( trx.validate(), fc::exception ); + set_expiration( db, trx ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + } + + int64_t expected_balance_sam_core = init_amount; + int64_t expected_balance_sam_usd = init_amount; + int64_t expected_balance_sam_eur = init_amount; + int64_t expected_balance_ted_core = init_amount; + int64_t expected_balance_ted_usd = init_amount; + int64_t expected_balance_ted_eur = init_amount; + + const auto& check_balances = [&]() { + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, core_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, usd_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, eur_id ).amount.value, 0 ); + }; + + check_balances(); + + // Able to create credit offers with valid data + // 1. + auto disable_time1 = db.head_block_time() - fc::minutes(1); // a time in the past + + flat_map collateral_map1; + collateral_map1[usd_id] = price( asset(1), asset(2, usd_id) ); + + const credit_offer_object& coo1 = create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, collateral_map1, {} ); + credit_offer_id_type co1_id = coo1.id; + BOOST_CHECK( coo1.owner_account == sam_id ); + BOOST_CHECK( coo1.asset_type == core.id ); + BOOST_CHECK( coo1.total_balance == 10000 ); + BOOST_CHECK( coo1.current_balance == 10000 ); + BOOST_CHECK( coo1.fee_rate == 100u ); + BOOST_CHECK( coo1.max_duration_seconds == 3600u ); + BOOST_CHECK( coo1.min_deal_amount == 0 ); + BOOST_CHECK( coo1.enabled == false ); + BOOST_CHECK( coo1.auto_disable_time == disable_time1 ); + BOOST_CHECK( coo1.acceptable_collateral == collateral_map1 ); + BOOST_CHECK( coo1.acceptable_borrowers.empty() ); + + expected_balance_sam_core -= 10000; + check_balances(); + + // 2. + auto duration2 = GRAPHENE_MAX_CREDIT_DEAL_SECS; + auto disable_time2 = db.head_block_time() + fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS); + + flat_map collateral_map2; + collateral_map2[core_id] = price( asset(2, usd_id), asset(3) ); + collateral_map2[eur_id] = price( asset(3, usd_id), asset(4, eur_id) ); + + flat_map borrower_map2; + borrower_map2[account_id_type()] = 0; + borrower_map2[sam_id] = 1; + borrower_map2[ted_id] = GRAPHENE_MAX_SHARE_SUPPLY; + + const credit_offer_object& coo2 = create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true, + disable_time2, collateral_map2, borrower_map2 ); + credit_offer_id_type co2_id = coo2.id; + BOOST_CHECK( coo2.owner_account == ted_id ); + BOOST_CHECK( coo2.asset_type == usd_id ); + BOOST_CHECK( coo2.total_balance == 1 ); + BOOST_CHECK( coo2.current_balance == 1 ); + BOOST_CHECK( coo2.fee_rate == 10000000u ); + BOOST_CHECK( coo2.max_duration_seconds == duration2 ); + BOOST_CHECK( coo2.min_deal_amount == 10000 ); + BOOST_CHECK( coo2.enabled == true ); + BOOST_CHECK( coo2.auto_disable_time == disable_time2 ); + BOOST_CHECK( coo2.acceptable_collateral == collateral_map2 ); + BOOST_CHECK( coo2.acceptable_borrowers == borrower_map2 ); + + expected_balance_ted_usd -= 1; + check_balances(); + + // 3. + // A time far in the future + auto disable_time3 = db.head_block_time() + fc::seconds(GRAPHENE_MAX_CREDIT_OFFER_SECS + 1); + + flat_map collateral_map3; + collateral_map3[usd_id] = price( asset(1, eur_id), asset(2, usd_id) ); + + const credit_offer_object& coo3 = create_credit_offer( sam_id, eur_id, 10, 1, 30, 1, false, + disable_time3, collateral_map3, {} ); // Account is whitelisted + credit_offer_id_type co3_id = coo3.id; + BOOST_CHECK( coo3.owner_account == sam_id ); + BOOST_CHECK( coo3.asset_type == eur_id ); + BOOST_CHECK( coo3.total_balance == 10 ); + BOOST_CHECK( coo3.current_balance == 10 ); + BOOST_CHECK( coo3.fee_rate == 1u ); + BOOST_CHECK( coo3.max_duration_seconds == 30u ); + BOOST_CHECK( coo3.min_deal_amount == 1 ); + BOOST_CHECK( coo3.enabled == false ); + BOOST_CHECK( coo3.auto_disable_time == disable_time3 ); + BOOST_CHECK( coo3.acceptable_collateral == collateral_map3 ); + BOOST_CHECK( coo3.acceptable_borrowers.empty() ); + + expected_balance_sam_eur -= 10; + check_balances(); + + // Unable to create a credit offer with invalid data + auto too_big_duration = GRAPHENE_MAX_CREDIT_DEAL_SECS + 1; + auto too_late_disable_time = db.head_block_time() + fc::seconds(GRAPHENE_MAX_CREDIT_OFFER_SECS + 1); + + flat_map empty_collateral_map; + + flat_map invalid_collateral_map1_1; + invalid_collateral_map1_1[usd_id] = price( asset(1), asset(0, usd_id) ); // zero amount + + flat_map invalid_collateral_map1_2; + invalid_collateral_map1_2[usd_id] = price( asset(1), asset(2, eur_id) ); // quote asset type mismatch + + flat_map invalid_collateral_map1_3; + invalid_collateral_map1_3[usd_id] = price( asset(1), asset(2, usd_id) ); + invalid_collateral_map1_3[eur_id] = price( asset(1), asset(2, usd_id) ); // quote asset type mismatch + + flat_map invalid_collateral_map1_4; // amount too big + invalid_collateral_map1_4[usd_id] = price( asset(GRAPHENE_MAX_SHARE_SUPPLY + 1), asset(1, usd_id) ); + + flat_map invalid_collateral_map1_5; + invalid_collateral_map1_5[usd_id] = price( asset(2, usd_id), asset(1) ); // base asset type mismatch + + flat_map invalid_collateral_map1_6; + invalid_collateral_map1_6[usd_id] = price( asset(1), asset(2, usd_id) ); + invalid_collateral_map1_6[eur_id] = price( asset(1, usd_id), asset(2, eur_id) ); // base asset type mismatch + + flat_map invalid_collateral_map1_7; + invalid_collateral_map1_7[no_asset_id] = price( asset(1), asset(2, no_asset_id) ); // asset does not exist + + auto invalid_borrower_map2_1 = borrower_map2; + invalid_borrower_map2_1[sam_id] = -1; // negative amount + + auto invalid_borrower_map2_2 = borrower_map2; + invalid_borrower_map2_2[ted_id] = GRAPHENE_MAX_SHARE_SUPPLY + 1; // amount too big + + auto invalid_borrower_map2_3 = borrower_map2; + invalid_borrower_map2_3[no_account_id] = 1; // account does not exist + + // Non-positive balance + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 0, 100, 3600, 0, false, + disable_time1, collateral_map1, {} ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, -1, 10000000u, duration2, 10000, true, + disable_time2, collateral_map2, borrower_map2 ), + fc::exception ); + // Insufficient account balance + BOOST_CHECK_THROW( create_credit_offer( por_id, usd_id, 1, 10000000u, duration2, 10000, true, + disable_time2, collateral_map2, borrower_map2 ), + fc::exception ); + // Nonexistent asset type + BOOST_CHECK_THROW( create_credit_offer( sam_id, no_asset_id, 10000, 100, 3600, 0, false, + disable_time1, collateral_map1, {} ), + fc::exception ); + // Duration too big + BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, too_big_duration, 10000, true, + disable_time2, collateral_map2, borrower_map2 ), + fc::exception ); + // Negative minimum deal amount + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, -1, false, + disable_time1, collateral_map1, {} ), + fc::exception ); + // Too big minimum deal amount + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, GRAPHENE_MAX_SHARE_SUPPLY + 1, false, + disable_time1, collateral_map1, {} ), + fc::exception ); + // Auto-disable time in the past and the offer is enabled + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, true, + disable_time1, collateral_map1, {} ), + fc::exception ); + // Auto-disable time too late + BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true, + too_late_disable_time, collateral_map2, borrower_map2 ), + fc::exception ); + // Empty allowed collateral map + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, empty_collateral_map, {} ), + fc::exception ); + // Invalid allowed collateral map + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, invalid_collateral_map1_1, {} ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, invalid_collateral_map1_2, {} ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, invalid_collateral_map1_3, {} ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, invalid_collateral_map1_4, {} ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, invalid_collateral_map1_5, {} ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, invalid_collateral_map1_6, {} ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( sam_id, core.id, 10000, 100, 3600, 0, false, + disable_time1, invalid_collateral_map1_7, {} ), + fc::exception ); + // Invalid acceptable borrowers map + BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true, + disable_time2, collateral_map2, invalid_borrower_map2_1 ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true, + disable_time2, collateral_map2, invalid_borrower_map2_2 ), + fc::exception ); + BOOST_CHECK_THROW( create_credit_offer( ted_id, usd_id, 1, 10000000u, duration2, 10000, true, + disable_time2, collateral_map2, invalid_borrower_map2_3 ), + fc::exception ); + // Account is not whitelisted + BOOST_CHECK_THROW( create_credit_offer( ted_id, eur_id, 10, 1, 30, 1, false, + disable_time3, collateral_map3, {} ), + fc::exception ); + + check_balances(); + + // Uable to update a credit offer with invalid data + // Changes nothing + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + // Object owner mismatch + BOOST_CHECK_THROW( update_credit_offer( ted_id, co1_id, asset(1), {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + // Zero delta + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(0), {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + // Asset type mismatch + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(1, usd_id), {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + // Trying to withdraw too much + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(-10000), {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + // Insufficient account balance + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(init_amount), {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + // Duration too big + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, too_big_duration, {}, {}, {}, {}, {} ), + fc::exception ); + // Invalid minimum deal amount + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, -1, {}, {}, {}, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, + GRAPHENE_MAX_SHARE_SUPPLY + 1, {}, {}, {}, {} ), + fc::exception ); + // Enabled but auto-disable time in the past + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, true, {}, {}, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( ted_id, co2_id, {}, {}, {}, {}, {}, disable_time1, {}, {} ), + fc::exception ); + // Enabled but auto-disable time too late + BOOST_CHECK_THROW( update_credit_offer( sam_id, co3_id, {}, {}, {}, {}, true, {}, {}, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( ted_id, co2_id, {}, {}, {}, {}, {}, disable_time3, {}, {} ), + fc::exception ); + // Invalid collateral map + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, empty_collateral_map, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_1, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_2, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_3, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_4, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_5, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_6, {} ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, invalid_collateral_map1_7, {} ), + fc::exception ); + // Invalid borrowers map + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, {}, invalid_borrower_map2_1 ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, {}, invalid_borrower_map2_2 ), + fc::exception ); + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, {}, {}, {}, invalid_borrower_map2_3 ), + fc::exception ); + + check_balances(); + + // Able to update a credit offer with valid data + // Only deposit + update_credit_offer( sam_id, co1_id, asset(1), {}, {}, {}, {}, {}, {}, {} ); + + BOOST_CHECK( co1_id(db).owner_account == sam_id ); + BOOST_CHECK( co1_id(db).asset_type == core_id ); + BOOST_CHECK( co1_id(db).total_balance == 10001 ); + BOOST_CHECK( co1_id(db).current_balance == 10001 ); + BOOST_CHECK( co1_id(db).fee_rate == 100u ); + BOOST_CHECK( co1_id(db).max_duration_seconds == 3600u ); + BOOST_CHECK( co1_id(db).min_deal_amount == 0 ); + BOOST_CHECK( co1_id(db).enabled == false ); + BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 ); + BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1 ); + BOOST_CHECK( co1_id(db).acceptable_borrowers.empty() ); + + expected_balance_sam_core -= 1; + check_balances(); + + // Only update fee rate + update_credit_offer( sam_id, co1_id, {}, 101u, {}, {}, {}, {}, {}, {} ); + + BOOST_CHECK( co1_id(db).owner_account == sam_id ); + BOOST_CHECK( co1_id(db).asset_type == core_id ); + BOOST_CHECK( co1_id(db).total_balance == 10001 ); + BOOST_CHECK( co1_id(db).current_balance == 10001 ); + BOOST_CHECK( co1_id(db).fee_rate == 101u ); + BOOST_CHECK( co1_id(db).max_duration_seconds == 3600u ); + BOOST_CHECK( co1_id(db).min_deal_amount == 0 ); + BOOST_CHECK( co1_id(db).enabled == false ); + BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 ); + BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1 ); + BOOST_CHECK( co1_id(db).acceptable_borrowers.empty() ); + + check_balances(); + + // Withdraw, update fee rate and other data + flat_map collateral_map1_1; + collateral_map1_1[usd_id] = price( asset(1), asset(2, usd_id) ); + collateral_map1_1[eur_id] = price( asset(1), asset(3, eur_id) ); + + update_credit_offer( sam_id, co1_id, asset(-9999), 10u, 600u, 100, true, + db.head_block_time() + fc::days(10), collateral_map1_1, borrower_map2 ); + + BOOST_CHECK( co1_id(db).owner_account == sam_id ); + BOOST_CHECK( co1_id(db).asset_type == core_id ); + BOOST_CHECK( co1_id(db).total_balance == 2 ); + BOOST_CHECK( co1_id(db).current_balance == 2 ); + BOOST_CHECK( co1_id(db).fee_rate == 10u ); + BOOST_CHECK( co1_id(db).max_duration_seconds == 600u ); + BOOST_CHECK( co1_id(db).min_deal_amount == 100 ); + BOOST_CHECK( co1_id(db).enabled == true ); + BOOST_CHECK( co1_id(db).auto_disable_time == db.head_block_time() + fc::days(10) ); + BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1_1 ); + BOOST_CHECK( co1_id(db).acceptable_borrowers == borrower_map2 ); + + expected_balance_sam_core += 9999; + check_balances(); + + // Sam is able to delete his own credit offer + asset released = delete_credit_offer( sam_id, co1_id ); + + BOOST_REQUIRE( !db.find( co1_id ) ); + BOOST_REQUIRE( db.find( co2_id ) ); + BOOST_REQUIRE( db.find( co3_id ) ); + + BOOST_CHECK( released == asset( 2, core_id ) ); + + expected_balance_sam_core += 2; + check_balances(); + + // Unable to update a credit offer that does not exist + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(1), {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + // Unable to delete a credit offer that does not exist + BOOST_CHECK_THROW( delete_credit_offer( sam_id, co1_id ), fc::exception ); + // Unable to delete a credit offer that is not owned by him + BOOST_CHECK_THROW( delete_credit_offer( sam_id, co2_id ), fc::exception ); + + BOOST_REQUIRE( !db.find( co1_id ) ); + BOOST_REQUIRE( db.find( co2_id ) ); + BOOST_REQUIRE( db.find( co3_id ) ); + + check_balances(); + + { + // Add Ted to the whitelist and remove Sam + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = ted_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::no_listing; + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Sam is now unable to deposit to the credit offer + BOOST_CHECK_THROW( update_credit_offer( sam_id, co3_id, asset(1, eur_id), {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + + BOOST_CHECK( co3_id(db).owner_account == sam_id ); + BOOST_CHECK( co3_id(db).asset_type == eur_id ); + BOOST_CHECK( co3_id(db).total_balance == 10 ); + BOOST_CHECK( co3_id(db).current_balance == 10 ); + BOOST_CHECK( co3_id(db).fee_rate == 1u ); + BOOST_CHECK( co3_id(db).max_duration_seconds == 30u ); + BOOST_CHECK( co3_id(db).min_deal_amount == 1 ); + BOOST_CHECK( co3_id(db).enabled == false ); + BOOST_CHECK( co3_id(db).auto_disable_time == disable_time3 ); + BOOST_CHECK( co3_id(db).acceptable_collateral == collateral_map3 ); + BOOST_CHECK( co3_id(db).acceptable_borrowers.empty() ); + + check_balances(); + + // Sam is still able to withdraw from the credit offer + update_credit_offer( sam_id, co3_id, asset(-1, eur_id), {}, {}, {}, {}, {}, {}, {} ); + + BOOST_CHECK( co3_id(db).owner_account == sam_id ); + BOOST_CHECK( co3_id(db).asset_type == eur_id ); + BOOST_CHECK( co3_id(db).total_balance == 9 ); + BOOST_CHECK( co3_id(db).current_balance == 9 ); + BOOST_CHECK( co3_id(db).fee_rate == 1u ); + BOOST_CHECK( co3_id(db).max_duration_seconds == 30u ); + BOOST_CHECK( co3_id(db).min_deal_amount == 1 ); + BOOST_CHECK( co3_id(db).enabled == false ); + BOOST_CHECK( co3_id(db).auto_disable_time == disable_time3 ); + BOOST_CHECK( co3_id(db).acceptable_collateral == collateral_map3 ); + BOOST_CHECK( co3_id(db).acceptable_borrowers.empty() ); + + expected_balance_sam_eur += 1; + check_balances(); + + // Sam is still able to update other data + flat_map collateral_map3_1; + collateral_map3_1[core_id] = price( asset(2, eur_id), asset(5, core_id) ); + + update_credit_offer( sam_id, co3_id, {}, 10u, 600u, 100, true, + disable_time2, collateral_map3_1, borrower_map2 ); + + BOOST_CHECK( co3_id(db).owner_account == sam_id ); + BOOST_CHECK( co3_id(db).asset_type == eur_id ); + BOOST_CHECK( co3_id(db).total_balance == 9 ); + BOOST_CHECK( co3_id(db).current_balance == 9 ); + BOOST_CHECK( co3_id(db).fee_rate == 10u ); + BOOST_CHECK( co3_id(db).max_duration_seconds == 600u ); + BOOST_CHECK( co3_id(db).min_deal_amount == 100 ); + BOOST_CHECK( co3_id(db).enabled == true ); + BOOST_CHECK( co3_id(db).auto_disable_time == disable_time2 ); + BOOST_CHECK( co3_id(db).acceptable_collateral == collateral_map3_1 ); + BOOST_CHECK( co3_id(db).acceptable_borrowers == borrower_map2 ); + + check_balances(); + + // Sam is still able to delete the credit offer + released = delete_credit_offer( sam_id, co3_id ); + BOOST_REQUIRE( !db.find( co3_id ) ); + + BOOST_CHECK( released == asset( 9, eur_id ) ); + + expected_balance_sam_eur += 9; + check_balances(); + + // Sam is unable to recreate the credit offer + BOOST_CHECK_THROW( create_credit_offer( sam_id, eur_id, 10, 1, 30, 1, false, + disable_time3, collateral_map3, {} ), + fc::exception ); + check_balances(); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) +{ try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2362_TIME ); + set_expiration( db, trx ); + + ACTORS((ray)(sam)(ted)(por)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( ray, asset(init_amount) ); + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + const asset_object& core = asset_id_type()(db); + asset_id_type core_id; + + const asset_object& usd = create_user_issued_asset( "MYUSD", ted, white_list ); + asset_id_type usd_id = usd.id; + issue_uia( ray, usd.amount(init_amount) ); + issue_uia( sam, usd.amount(init_amount) ); + issue_uia( ted, usd.amount(init_amount) ); + + const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); + asset_id_type eur_id = eur.id; + issue_uia( ray, eur.amount(init_amount) ); + issue_uia( sam, eur.amount(init_amount) ); + issue_uia( ted, eur.amount(init_amount) ); + + const asset_object& cny = create_user_issued_asset( "MYCNY" ); + asset_id_type cny_id = cny.id; + issue_uia( ray, cny.amount(init_amount) ); + issue_uia( sam, cny.amount(init_amount) ); + issue_uia( ted, cny.amount(init_amount) ); + + // Make a whitelist USD managed by Ted + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = usd.id; + uop.issuer = ted_id; + uop.new_options = usd.options; + // The whitelist is managed by Ted + uop.new_options.whitelist_authorities.insert(ted_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Ted so that he can manage the whitelist + upgrade_to_lifetime_member( ted_id ); + + // Add Sam and Ray to the whitelist + account_whitelist_operation wop; + wop.authorizing_account = ted_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + wop.account_to_list = ray_id; + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Make a whitelist : EUR managed by Sam + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = eur.id; + uop.issuer = sam_id; + uop.new_options = eur.options; + // The whitelist is managed by Sam + uop.new_options.whitelist_authorities.insert(sam_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Sam so that he can manage the whitelist + upgrade_to_lifetime_member( sam_id ); + + // Add Ted to the whitelist + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = ted_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + asset_id_type no_asset_id( core.id + 100 ); + BOOST_REQUIRE( !db.find( no_asset_id ) ); + + int64_t expected_balance_ray_core = init_amount; + int64_t expected_balance_ray_usd = init_amount; + int64_t expected_balance_ray_eur = init_amount; + int64_t expected_balance_ray_cny = init_amount; + int64_t expected_balance_sam_core = init_amount; + int64_t expected_balance_sam_usd = init_amount; + int64_t expected_balance_sam_eur = init_amount; + int64_t expected_balance_sam_cny = init_amount; + int64_t expected_balance_ted_core = init_amount; + int64_t expected_balance_ted_usd = init_amount; + int64_t expected_balance_ted_eur = init_amount; + int64_t expected_balance_ted_cny = init_amount; + + const auto& check_balances = [&]() { + BOOST_CHECK_EQUAL( db.get_balance( ray_id, core_id ).amount.value, expected_balance_ray_core ); + BOOST_CHECK_EQUAL( db.get_balance( ray_id, usd_id ).amount.value, expected_balance_ray_usd ); + BOOST_CHECK_EQUAL( db.get_balance( ray_id, eur_id ).amount.value, expected_balance_ray_eur ); + BOOST_CHECK_EQUAL( db.get_balance( ray_id, cny_id ).amount.value, expected_balance_ray_cny ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, core_id ).amount.value, expected_balance_sam_core ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, usd_id ).amount.value, expected_balance_sam_usd ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, eur_id ).amount.value, expected_balance_sam_eur ); + BOOST_CHECK_EQUAL( db.get_balance( sam_id, cny_id ).amount.value, expected_balance_sam_cny ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, core_id ).amount.value, expected_balance_ted_core ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, usd_id ).amount.value, expected_balance_ted_usd ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, eur_id ).amount.value, expected_balance_ted_eur ); + BOOST_CHECK_EQUAL( db.get_balance( ted_id, cny_id ).amount.value, expected_balance_ted_cny ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, core_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, usd_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, eur_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( por_id, cny_id ).amount.value, 0 ); + }; + + check_balances(); + + // Unable to borrow : the credit offer is disabled + credit_offer_id_type tmp_co_id; + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, tmp_co_id, asset(100), asset(200, usd_id) ), + fc::exception ); + + // create a credit offers + auto disable_time1 = db.head_block_time() + fc::minutes(20); // 20 minutes after init + + flat_map collateral_map1; + collateral_map1[usd_id] = price( asset(1), asset(2, usd_id) ); + collateral_map1[eur_id] = price( asset(1), asset(1, eur_id) ); + + const credit_offer_object& coo1 = create_credit_offer( sam_id, core.id, 10000, 30000, 3600, 0, false, + disable_time1, collateral_map1, {} ); + credit_offer_id_type co1_id = coo1.id; + BOOST_CHECK( co1_id(db).owner_account == sam_id ); + BOOST_CHECK( co1_id(db).asset_type == core.id ); + BOOST_CHECK( co1_id(db).total_balance == 10000 ); + BOOST_CHECK( co1_id(db).current_balance == 10000 ); + BOOST_CHECK( co1_id(db).fee_rate == 30000u ); + BOOST_CHECK( co1_id(db).max_duration_seconds == 3600u ); + BOOST_CHECK( co1_id(db).min_deal_amount == 0 ); + BOOST_CHECK( co1_id(db).enabled == false ); + BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 ); + BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1 ); + BOOST_CHECK( co1_id(db).acceptable_borrowers.empty() ); + + expected_balance_sam_core -= 10000; + check_balances(); + + // Unable to borrow : the credit offer is disabled + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id) ), fc::exception ); + + // Enable the offer + update_credit_offer( sam_id, co1_id, {}, {}, {}, {}, true, {}, {}, {} ); + + BOOST_CHECK( co1_id(db).enabled == true ); + + // Now able to borrow + BOOST_TEST_MESSAGE( "Ray borrows" ); + const credit_deal_object& cdo11 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id) ); + credit_deal_id_type cd11_id = cdo11.id; + time_point_sec expected_repay_time11 = db.head_block_time() + fc::seconds(3600); // 60 minutes after init + + BOOST_CHECK( cd11_id(db).borrower == ray_id ); + BOOST_CHECK( cd11_id(db).offer_id == co1_id ); + BOOST_CHECK( cd11_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd11_id(db).debt_asset == core_id ); + BOOST_CHECK( cd11_id(db).debt_amount == 100 ); + BOOST_CHECK( cd11_id(db).collateral_asset == usd_id ); + BOOST_CHECK( cd11_id(db).collateral_amount == 200 ); + BOOST_CHECK( cd11_id(db).fee_rate == 30000u ); + BOOST_CHECK( cd11_id(db).latest_repay_time == expected_repay_time11 ); + + BOOST_CHECK( co1_id(db).total_balance == 10000 ); + BOOST_CHECK( co1_id(db).current_balance == 9900 ); + + expected_balance_ray_core += 100; + expected_balance_ray_usd -= 200; + check_balances(); + + // Unable to delete the credit offer : there exists unpaid debt + BOOST_CHECK_THROW( delete_credit_offer( sam_id, co1_id ), fc::exception ); + // Unable to withdraw more than balance available + BOOST_CHECK_THROW( update_credit_offer( sam_id, co1_id, asset(-9901), {}, {}, {}, {}, {}, {}, {} ), + fc::exception ); + + // Unable to borrow : asset type mismatch + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100, cny_id), asset(200, usd_id) ), + fc::exception ); + // Unable to borrow : zero or negative amount + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(0), asset(200, usd_id) ), fc::exception ); + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(-1), asset(200, usd_id) ), fc::exception ); + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(1), asset(0, usd_id) ), fc::exception ); + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(1), asset(-1, usd_id) ), fc::exception ); + + // Set a minimum deal amount + update_credit_offer( sam_id, co1_id, {}, {}, {}, 100, {}, {}, {}, {} ); + + BOOST_CHECK( co1_id(db).min_deal_amount == 100 ); + + // Unable to borrow : amount too small + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(99), asset(200, usd_id) ), fc::exception ); + // Unable to borrow : collateral amount too small + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(199, usd_id) ), fc::exception ); + // Unable to borrow : collateral not acceptable + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, cny_id) ), fc::exception ); + // Unable to borrow : account not authorized by debt asset + BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(100), asset(200, usd_id) ), fc::exception ); + // Unable to borrow : account not authorized by collateral asset + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, eur_id) ), fc::exception ); + // Unable to borrow : insufficient balance in credit offer + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(9901), asset(20000, usd_id) ), + fc::exception ); + // Unable to borrow : insufficient account balance + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(init_amount, usd_id) ), + fc::exception ); + + // Able to borrow the same amount with the same collateral + BOOST_TEST_MESSAGE( "Ray borrows more" ); + const credit_deal_object& cdo12 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id) ); + credit_deal_id_type cd12_id = cdo12.id; + time_point_sec expected_repay_time12 = db.head_block_time() + fc::seconds(3600); // 60 minutes after init + + BOOST_CHECK( cd12_id(db).borrower == ray_id ); + BOOST_CHECK( cd12_id(db).offer_id == co1_id ); + BOOST_CHECK( cd12_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd12_id(db).debt_asset == core_id ); + BOOST_CHECK( cd12_id(db).debt_amount == 100 ); + BOOST_CHECK( cd12_id(db).collateral_asset == usd_id ); + BOOST_CHECK( cd12_id(db).collateral_amount == 200 ); + BOOST_CHECK( cd12_id(db).fee_rate == 30000u ); + BOOST_CHECK( cd12_id(db).latest_repay_time == expected_repay_time12 ); + + BOOST_CHECK( co1_id(db).total_balance == 10000 ); + BOOST_CHECK( co1_id(db).current_balance == 9800 ); + + expected_balance_ray_core += 100; + expected_balance_ray_usd -= 200; + check_balances(); + + // Time goes by + generate_blocks( db.head_block_time() + fc::minutes(5) ); // now is 5 minutes after init + set_expiration( db, trx ); + + // Able to borrow the same amount with more collateral + BOOST_TEST_MESSAGE( "Ray borrows even more" ); + const credit_deal_object& cdo13 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(499, usd_id) ); + credit_deal_id_type cd13_id = cdo13.id; + time_point_sec expected_repay_time13 = db.head_block_time() + fc::seconds(3600); // 65 minutes after init + + BOOST_CHECK( cd13_id(db).borrower == ray_id ); + BOOST_CHECK( cd13_id(db).offer_id == co1_id ); + BOOST_CHECK( cd13_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd13_id(db).debt_asset == core_id ); + BOOST_CHECK( cd13_id(db).debt_amount == 100 ); + BOOST_CHECK( cd13_id(db).collateral_asset == usd_id ); + BOOST_CHECK( cd13_id(db).collateral_amount == 499 ); + BOOST_CHECK( cd13_id(db).fee_rate == 30000u ); + BOOST_CHECK( cd13_id(db).latest_repay_time == expected_repay_time13 ); + + BOOST_CHECK( co1_id(db).total_balance == 10000 ); + BOOST_CHECK( co1_id(db).current_balance == 9700 ); + + expected_balance_ray_core += 100; + expected_balance_ray_usd -= 499; + check_balances(); + + // The offer changes + auto collateral_map1_new = collateral_map1; + collateral_map1_new[cny_id] = price( asset(1), asset(1, cny_id) ); + BOOST_CHECK( collateral_map1 != collateral_map1_new ); + + flat_map borrower_map1; + borrower_map1[ted_id] = 300; + + update_credit_offer( sam_id, co1_id, {}, 500u, 600u, 0, {}, {}, collateral_map1_new, borrower_map1 ); + + BOOST_CHECK( co1_id(db).owner_account == sam_id ); + BOOST_CHECK( co1_id(db).asset_type == core_id ); + BOOST_CHECK( co1_id(db).total_balance == 10000 ); + BOOST_CHECK( co1_id(db).current_balance == 9700 ); + BOOST_CHECK( co1_id(db).fee_rate == 500u ); + BOOST_CHECK( co1_id(db).max_duration_seconds == 600u ); + BOOST_CHECK( co1_id(db).min_deal_amount == 0 ); + BOOST_CHECK( co1_id(db).enabled == true ); + BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 ); + BOOST_CHECK( co1_id(db).acceptable_collateral == collateral_map1_new ); + BOOST_CHECK( co1_id(db).acceptable_borrowers == borrower_map1 ); + + // Existing credit deals are unchanged + BOOST_CHECK( cd11_id(db).borrower == ray_id ); + BOOST_CHECK( cd11_id(db).offer_id == co1_id ); + BOOST_CHECK( cd11_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd11_id(db).debt_asset == core_id ); + BOOST_CHECK( cd11_id(db).debt_amount == 100 ); + BOOST_CHECK( cd11_id(db).collateral_asset == usd_id ); + BOOST_CHECK( cd11_id(db).collateral_amount == 200 ); + BOOST_CHECK( cd11_id(db).fee_rate == 30000u ); + BOOST_CHECK( cd11_id(db).latest_repay_time == expected_repay_time11 ); + + // Ted is now able to borrow with CNY + const credit_deal_object& cdo14 = borrow_from_credit_offer( ted_id, co1_id, asset(200), asset(200, cny_id) ); + credit_deal_id_type cd14_id = cdo14.id; + time_point_sec expected_repay_time14 = db.head_block_time() + fc::seconds(600); // 15 minutes after init + + BOOST_CHECK( cd14_id(db).borrower == ted_id ); + BOOST_CHECK( cd14_id(db).offer_id == co1_id ); + BOOST_CHECK( cd14_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd14_id(db).debt_asset == core_id ); + BOOST_CHECK( cd14_id(db).debt_amount == 200 ); + BOOST_CHECK( cd14_id(db).collateral_asset == cny_id ); + BOOST_CHECK( cd14_id(db).collateral_amount == 200 ); + BOOST_CHECK( cd14_id(db).fee_rate == 500u ); + BOOST_CHECK( cd14_id(db).latest_repay_time == expected_repay_time14 ); + + BOOST_CHECK( co1_id(db).total_balance == 10000 ); + BOOST_CHECK( co1_id(db).current_balance == 9500 ); + + expected_balance_ted_core += 200; + expected_balance_ted_cny -= 200; + check_balances(); + + // Ray is now unable to borrow + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(200), asset(200, cny_id) ), + fc::exception ); + // Ted is now unable to borrow same amount again because it would exceed the limit + BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(200), asset(200, cny_id) ), + fc::exception ); + + // Ted is able to borrow less with CNY + const credit_deal_object& cdo15 = borrow_from_credit_offer( ted_id, co1_id, asset(50), asset(100, cny_id) ); + credit_deal_id_type cd15_id = cdo15.id; + time_point_sec expected_repay_time15 = db.head_block_time() + fc::seconds(600); // 15 minutes after init + + BOOST_CHECK( cd15_id(db).borrower == ted_id ); + BOOST_CHECK( cd15_id(db).offer_id == co1_id ); + BOOST_CHECK( cd15_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd15_id(db).debt_asset == core_id ); + BOOST_CHECK( cd15_id(db).debt_amount == 50 ); + BOOST_CHECK( cd15_id(db).collateral_asset == cny_id ); + BOOST_CHECK( cd15_id(db).collateral_amount == 100 ); + BOOST_CHECK( cd15_id(db).fee_rate == 500u ); + BOOST_CHECK( cd15_id(db).latest_repay_time == expected_repay_time15 ); + + BOOST_CHECK( co1_id(db).total_balance == 10000 ); + BOOST_CHECK( co1_id(db).current_balance == 9450 ); + + expected_balance_ted_core += 50; + expected_balance_ted_cny -= 100; + check_balances(); + + // Time goes by + generate_blocks( db.head_block_time() + fc::minutes(3) ); // now is 8 minutes after init + set_expiration( db, trx ); + + // Sam withdraw most of funds from the credit offer + update_credit_offer( sam_id, co1_id, asset(-9410), {}, {}, {}, {}, {}, {}, {} ); + BOOST_CHECK( co1_id(db).total_balance == 590 ); + BOOST_CHECK( co1_id(db).current_balance == 40 ); + + expected_balance_sam_core += 9410; + check_balances(); + + // Ted is unable to borrow with EUR because Sam is not authorized by EUR asset + BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(40), asset(499, eur_id) ), + fc::exception ); + + { + // Add Sam to the whitelist of EUR + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Now Ted is able to borrow 40 CORE with EUR + const credit_deal_object& cdo16 = borrow_from_credit_offer( ted_id, co1_id, asset(40), asset(499, eur_id) ); + credit_deal_id_type cd16_id = cdo16.id; + time_point_sec expected_repay_time16 = db.head_block_time() + fc::seconds(600); // 18 minutes after init + + BOOST_CHECK( cd16_id(db).borrower == ted_id ); + BOOST_CHECK( cd16_id(db).offer_id == co1_id ); + BOOST_CHECK( cd16_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd16_id(db).debt_asset == core_id ); + BOOST_CHECK( cd16_id(db).debt_amount == 40 ); + BOOST_CHECK( cd16_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd16_id(db).collateral_amount == 499 ); + BOOST_CHECK( cd16_id(db).fee_rate == 500u ); + BOOST_CHECK( cd16_id(db).latest_repay_time == expected_repay_time16 ); + + BOOST_CHECK( co1_id(db).total_balance == 590 ); + BOOST_CHECK( co1_id(db).current_balance == 0 ); + + expected_balance_ted_core += 40; + expected_balance_ted_eur -= 499; + check_balances(); + + // Ted is unable to borrow 1 more CORE with EUR + BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(1), asset(500, eur_id) ), + fc::exception ); + + // Time goes by + generate_blocks( db.head_block_time() + fc::minutes(4) ); // now is 12 minutes after init + set_expiration( db, trx ); + + // Unable to repay : zero or negative amount + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(0), asset(1) ), + fc::exception ); + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(-1), asset(1) ), + fc::exception ); + // Note: credit fee is allowed to be zero + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(1), asset(-1) ), + fc::exception ); + + // Unable to repay : asset type mismatch + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(1), asset(1, usd_id) ), + fc::exception ); + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(1, usd_id), asset(1, usd_id) ), + fc::exception ); + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(1, usd_id), asset(1) ), + fc::exception ); + + // Unable to repay : credit deal does not belong to the account + BOOST_CHECK_THROW( repay_credit_deal( ted_id, cd13_id, asset(1), asset(1) ), + fc::exception ); + + // Ray partially repays + auto result = repay_credit_deal( ray_id, cd13_id, asset(1), asset(1) ); + BOOST_REQUIRE( result.received.valid() ); + BOOST_REQUIRE( result.received->size() == 1 ); + asset collateral_released = result.received->front(); + + BOOST_CHECK( collateral_released == asset(4, usd_id) ); // round_down(499/100) + + BOOST_CHECK( cd13_id(db).borrower == ray_id ); + BOOST_CHECK( cd13_id(db).offer_id == co1_id ); + BOOST_CHECK( cd13_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd13_id(db).debt_asset == core_id ); + BOOST_CHECK( cd13_id(db).debt_amount == 99 ); + BOOST_CHECK( cd13_id(db).collateral_asset == usd_id ); + BOOST_CHECK( cd13_id(db).collateral_amount == 495 ); + BOOST_CHECK( cd13_id(db).fee_rate == 30000u ); + BOOST_CHECK( cd13_id(db).latest_repay_time == expected_repay_time13 ); + + BOOST_CHECK( co1_id(db).total_balance == 591 ); + BOOST_CHECK( co1_id(db).current_balance == 2 ); + + expected_balance_ray_core -= 2; + expected_balance_ray_usd += 4; + check_balances(); + + // Ted is able to borrow 2 CORE with EUR + const credit_deal_object& cdo17 = borrow_from_credit_offer( ted_id, co1_id, asset(2), asset(49, eur_id) ); + credit_deal_id_type cd17_id = cdo17.id; + time_point_sec expected_repay_time17 = db.head_block_time() + fc::seconds(600); // 22 minutes after init + + BOOST_CHECK( cd17_id(db).borrower == ted_id ); + BOOST_CHECK( cd17_id(db).offer_id == co1_id ); + BOOST_CHECK( cd17_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd17_id(db).debt_asset == core_id ); + BOOST_CHECK( cd17_id(db).debt_amount == 2 ); + BOOST_CHECK( cd17_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd17_id(db).collateral_amount == 49 ); + BOOST_CHECK( cd17_id(db).fee_rate == 500u ); + BOOST_CHECK( cd17_id(db).latest_repay_time == expected_repay_time17 ); + + BOOST_CHECK( co1_id(db).total_balance == 591 ); + BOOST_CHECK( co1_id(db).current_balance == 0 ); + + expected_balance_ted_core += 2; + expected_balance_ted_eur -= 49; + check_balances(); + + // Ray partially repays with more fee than required + result = repay_credit_deal( ray_id, cd13_id, asset(1), asset(2) ); + BOOST_REQUIRE( result.received.valid() ); + BOOST_REQUIRE( result.received->size() == 1 ); + collateral_released = result.received->front(); + + BOOST_CHECK( collateral_released == asset(5, usd_id) ); // round_down(495/99) + + BOOST_CHECK( cd13_id(db).borrower == ray_id ); + BOOST_CHECK( cd13_id(db).offer_id == co1_id ); + BOOST_CHECK( cd13_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd13_id(db).debt_asset == core_id ); + BOOST_CHECK( cd13_id(db).debt_amount == 98 ); + BOOST_CHECK( cd13_id(db).collateral_asset == usd_id ); + BOOST_CHECK( cd13_id(db).collateral_amount == 490 ); + BOOST_CHECK( cd13_id(db).fee_rate == 30000u ); + BOOST_CHECK( cd13_id(db).latest_repay_time == expected_repay_time13 ); + + BOOST_CHECK( co1_id(db).total_balance == 593 ); + BOOST_CHECK( co1_id(db).current_balance == 3 ); + + expected_balance_ray_core -= 3; + expected_balance_ray_usd += 5; + check_balances(); + + // Unable to repay : amount too big + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(99), asset(5) ), + fc::exception ); + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd12_id, asset(101), asset(5) ), + fc::exception ); + // Unable to repay : insufficient credit fee : fee rate = 3% + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(98), asset(2) ), + fc::exception ); + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd12_id, asset(100), asset(2) ), + fc::exception ); + + // Fully repays + result = repay_credit_deal( ray_id, cd12_id, asset(100), asset(3) ); + BOOST_REQUIRE( result.received.valid() ); + BOOST_REQUIRE( result.received->size() == 1 ); + collateral_released = result.received->front(); + + BOOST_CHECK( collateral_released == asset(200, usd_id) ); + + BOOST_REQUIRE( !db.find( cd12_id ) ); + + BOOST_CHECK( co1_id(db).total_balance == 596 ); + BOOST_CHECK( co1_id(db).current_balance == 106 ); + + expected_balance_ray_core -= 103; + expected_balance_ray_usd += 200; + check_balances(); + + // Unable to repay : credit deal does not exist + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd12_id, asset(100), asset(3) ), + fc::exception ); + + // Create another credit offer + auto disable_time2 = db.head_block_time() + fc::minutes(20); // 32 minites after init + + flat_map collateral_map2; + collateral_map2[cny_id] = price( asset(10, usd_id), asset(12, cny_id) ); + collateral_map2[eur_id] = price( asset(10, usd_id), asset(10, eur_id) ); + const credit_offer_object& coo2 = create_credit_offer( sam_id, usd_id, 10000, 70000, 1800, 0, true, + disable_time2, collateral_map2, {} ); + credit_offer_id_type co2_id = coo2.id; + BOOST_CHECK( co2_id(db).owner_account == sam_id ); + BOOST_CHECK( co2_id(db).asset_type == usd_id ); + BOOST_CHECK( co2_id(db).total_balance == 10000 ); + BOOST_CHECK( co2_id(db).current_balance == 10000 ); + BOOST_CHECK( co2_id(db).fee_rate == 70000u ); + BOOST_CHECK( co2_id(db).max_duration_seconds == 1800u ); + BOOST_CHECK( co2_id(db).min_deal_amount == 0 ); + BOOST_CHECK( co2_id(db).enabled == true ); + BOOST_CHECK( co2_id(db).auto_disable_time == disable_time2 ); + BOOST_CHECK( co2_id(db).acceptable_collateral == collateral_map2 ); + BOOST_CHECK( co2_id(db).acceptable_borrowers.empty() ); + + expected_balance_sam_usd -= 10000; + check_balances(); + + // Ray borrows from the new credit offer + const auto& cdo21 = borrow_from_credit_offer( ray_id, co2_id, asset(1000, usd_id), asset(1200, cny_id) ); + credit_deal_id_type cd21_id = cdo21.id; + time_point_sec expected_repay_time21 = db.head_block_time() + fc::seconds(1800); // 42 minutes after init + + BOOST_CHECK( cd21_id(db).borrower == ray_id ); + BOOST_CHECK( cd21_id(db).offer_id == co2_id ); + BOOST_CHECK( cd21_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd21_id(db).debt_asset == usd_id ); + BOOST_CHECK( cd21_id(db).debt_amount == 1000 ); + BOOST_CHECK( cd21_id(db).collateral_asset == cny_id ); + BOOST_CHECK( cd21_id(db).collateral_amount == 1200 ); + BOOST_CHECK( cd21_id(db).fee_rate == 70000u ); + BOOST_CHECK( cd21_id(db).latest_repay_time == expected_repay_time21 ); + + BOOST_CHECK( co2_id(db).total_balance == 10000 ); + BOOST_CHECK( co2_id(db).current_balance == 9000 ); + + expected_balance_ray_usd += 1000; + expected_balance_ray_cny -= 1200; + check_balances(); + + // Ray repays + result = repay_credit_deal( ray_id, cd21_id, asset(100, usd_id), asset(7, usd_id) ); + BOOST_REQUIRE( result.received.valid() ); + BOOST_REQUIRE( result.received->size() == 1 ); + collateral_released = result.received->front(); + + BOOST_CHECK( collateral_released == asset(120, cny_id) ); + + BOOST_CHECK( cd21_id(db).borrower == ray_id ); + BOOST_CHECK( cd21_id(db).offer_id == co2_id ); + BOOST_CHECK( cd21_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd21_id(db).debt_asset == usd_id ); + BOOST_CHECK( cd21_id(db).debt_amount == 900 ); + BOOST_CHECK( cd21_id(db).collateral_asset == cny_id ); + BOOST_CHECK( cd21_id(db).collateral_amount == 1080 ); + BOOST_CHECK( cd21_id(db).fee_rate == 70000u ); + BOOST_CHECK( cd21_id(db).latest_repay_time == expected_repay_time21 ); + + BOOST_CHECK( co2_id(db).total_balance == 10007 ); + BOOST_CHECK( co2_id(db).current_balance == 9107 ); + + expected_balance_ray_usd -= 107; + expected_balance_ray_cny += 120; + check_balances(); + + { + // Remove Ray from the whitelist of USD + account_whitelist_operation wop; + wop.authorizing_account = ted_id; + wop.account_to_list = ray_id; + wop.new_listing = account_whitelist_operation::no_listing; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Ray is no longer able to borrow from co2 + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co2_id, asset(1000, usd_id), asset(1200, cny_id) ), + fc::exception ); + + // Ray is unable to repay the deal with USD + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd21_id, asset(100, usd_id), asset(7, usd_id) ), + fc::exception ); + + // Ray is still able to repay another deal with CORE to get USD + result = repay_credit_deal( ray_id, cd13_id, asset(1), asset(1) ); + BOOST_REQUIRE( result.received.valid() ); + BOOST_REQUIRE( result.received->size() == 1 ); + collateral_released = result.received->front(); + + BOOST_CHECK( collateral_released == asset(5, usd_id) ); // round_down(490/98) + + BOOST_CHECK( cd13_id(db).borrower == ray_id ); + BOOST_CHECK( cd13_id(db).offer_id == co1_id ); + BOOST_CHECK( cd13_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd13_id(db).debt_asset == core_id ); + BOOST_CHECK( cd13_id(db).debt_amount == 97 ); + BOOST_CHECK( cd13_id(db).collateral_asset == usd_id ); + BOOST_CHECK( cd13_id(db).collateral_amount == 485 ); + BOOST_CHECK( cd13_id(db).fee_rate == 30000u ); + BOOST_CHECK( cd13_id(db).latest_repay_time == expected_repay_time13 ); + + BOOST_CHECK( co1_id(db).total_balance == 597 ); + BOOST_CHECK( co1_id(db).current_balance == 108 ); + + expected_balance_ray_core -= 2; + expected_balance_ray_usd += 5; + check_balances(); + + // Ray transfer most of CORE to Sam + transfer( ray_id, sam_id, asset(expected_balance_ray_core - 10) ); + + expected_balance_sam_core += (expected_balance_ray_core - 10); + expected_balance_ray_core = 10; + check_balances(); + + // Unable to repay : insufficient account balance + BOOST_CHECK_THROW( repay_credit_deal( ray_id, cd13_id, asset(10), asset(1) ), + fc::exception ); + + // Time goes by + generate_blocks( db.head_block_time() + fc::minutes(1) ); // now is 13 minutes after init + set_expiration( db, trx ); + + // Ted is unable to borrow from co2 : Ted is not authorized by USD + BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co2_id, asset(1000, usd_id), asset(1100, eur_id) ), + fc::exception ); + + { + // Add Ted to the whitelist of USD + account_whitelist_operation wop; + wop.authorizing_account = ted_id; + wop.account_to_list = ted_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Ted borrows from the new credit offer + const auto& cdo22 = borrow_from_credit_offer( ted_id, co2_id, asset(1000, usd_id), asset(1100, eur_id) ); + credit_deal_id_type cd22_id = cdo22.id; + time_point_sec expected_repay_time22 = db.head_block_time() + fc::seconds(1800); // 43 minutes after init + + BOOST_CHECK( cd22_id(db).borrower == ted_id ); + BOOST_CHECK( cd22_id(db).offer_id == co2_id ); + BOOST_CHECK( cd22_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd22_id(db).debt_asset == usd_id ); + BOOST_CHECK( cd22_id(db).debt_amount == 1000 ); + BOOST_CHECK( cd22_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd22_id(db).collateral_amount == 1100 ); + BOOST_CHECK( cd22_id(db).fee_rate == 70000u ); + BOOST_CHECK( cd22_id(db).latest_repay_time == expected_repay_time22 ); + + BOOST_CHECK( co2_id(db).total_balance == 10007 ); + BOOST_CHECK( co2_id(db).current_balance == 8107 ); + + expected_balance_ted_usd += 1000; + expected_balance_ted_eur -= 1100; + check_balances(); + + // Ted repays + result = repay_credit_deal( ted_id, cd22_id, asset(200, usd_id), asset(15, usd_id) ); + BOOST_REQUIRE( result.received.valid() ); + BOOST_REQUIRE( result.received->size() == 1 ); + collateral_released = result.received->front(); + + BOOST_CHECK( collateral_released == asset(220, eur_id) ); + + BOOST_CHECK( cd22_id(db).borrower == ted_id ); + BOOST_CHECK( cd22_id(db).offer_id == co2_id ); + BOOST_CHECK( cd22_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd22_id(db).debt_asset == usd_id ); + BOOST_CHECK( cd22_id(db).debt_amount == 800 ); + BOOST_CHECK( cd22_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd22_id(db).collateral_amount == 880 ); + BOOST_CHECK( cd22_id(db).fee_rate == 70000u ); + BOOST_CHECK( cd22_id(db).latest_repay_time == expected_repay_time22 ); + + BOOST_CHECK( co2_id(db).total_balance == 10022 ); + BOOST_CHECK( co2_id(db).current_balance == 8322 ); + + expected_balance_ted_usd -= 215; + expected_balance_ted_eur += 220; + check_balances(); + + { + // Remove Sam from the whitelist of USD + account_whitelist_operation wop; + wop.authorizing_account = ted_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::no_listing; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Ted is unable to borrow from co2 : credit offer owner Sam is now not authorized by USD + BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co2_id, asset(1000, usd_id), asset(1100, eur_id) ), + fc::exception ); + + // Ted is unable to repay the co2 deal : credit offer owner Sam is now not authorized by USD + BOOST_CHECK_THROW( repay_credit_deal( ted_id, cd22_id, asset(200, usd_id), asset(15, usd_id) ), + fc::exception ); + + // ===== Time table ========= + // now: 13 + // expected_repay_time14 : 15 + // expected_repay_time15 : 15 + // expected_repay_time16 : 18 + // disable_time1 : 20 + // expected_repay_time17 : 22 + // disable_time2 : 32 + // expected_repay_time21 : 42 + // expected_repay_time22 : 43 + // expected_repay_time11 : 60 + // expected_repay_time12 : 60 // fully repaid already + // expected_repay_time13 : 65 + + // Time goes by + generate_blocks( expected_repay_time14 ); // now is 15 minutes after init + set_expiration( db, trx ); + + // Expiration + BOOST_REQUIRE( !db.find( cd14_id ) ); + BOOST_REQUIRE( !db.find( cd15_id ) ); + + BOOST_CHECK( co1_id(db).total_balance == 347 ); // 597 - 200 - 50 + BOOST_CHECK( co1_id(db).current_balance == 108 ); // unchanged + BOOST_CHECK( co1_id(db).enabled == true ); + BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 ); + + expected_balance_sam_cny += 200; // cd14 + expected_balance_sam_cny += 100; // cd15 + check_balances(); + + BOOST_REQUIRE( db.find( cd16_id ) ); + BOOST_CHECK( cd16_id(db).borrower == ted_id ); + BOOST_CHECK( cd16_id(db).offer_id == co1_id ); + BOOST_CHECK( cd16_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd16_id(db).debt_asset == core_id ); + BOOST_CHECK( cd16_id(db).debt_amount == 40 ); + BOOST_CHECK( cd16_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd16_id(db).collateral_amount == 499 ); + BOOST_CHECK( cd16_id(db).fee_rate == 500u ); + BOOST_CHECK( cd16_id(db).latest_repay_time == expected_repay_time16 ); + + // Time goes by + generate_blocks( expected_repay_time16 ); // now is 18 minutes after init + set_expiration( db, trx ); + + // Expiration + BOOST_REQUIRE( !db.find( cd16_id ) ); + + BOOST_CHECK( co1_id(db).total_balance == 307 ); // 347 - 40 + BOOST_CHECK( co1_id(db).current_balance == 108 ); // unchanged + BOOST_CHECK( co1_id(db).enabled == true ); + BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 ); + + expected_balance_sam_eur += 499; // cd16 + check_balances(); + + BOOST_REQUIRE( db.find( cd17_id ) ); + BOOST_CHECK( cd17_id(db).borrower == ted_id ); + BOOST_CHECK( cd17_id(db).offer_id == co1_id ); + BOOST_CHECK( cd17_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd17_id(db).debt_asset == core_id ); + BOOST_CHECK( cd17_id(db).debt_amount == 2 ); + BOOST_CHECK( cd17_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd17_id(db).collateral_amount == 49 ); + BOOST_CHECK( cd17_id(db).fee_rate == 500u ); + BOOST_CHECK( cd17_id(db).latest_repay_time == expected_repay_time17 ); + + // Ted borrows more + const credit_deal_object& cdo18 = borrow_from_credit_offer( ted_id, co1_id, asset(10), asset(30, eur_id) ); + credit_deal_id_type cd18_id = cdo18.id; + time_point_sec expected_repay_time18 = db.head_block_time() + fc::seconds(600); // 28 minutes after init + + BOOST_CHECK( cd18_id(db).borrower == ted_id ); + BOOST_CHECK( cd18_id(db).offer_id == co1_id ); + BOOST_CHECK( cd18_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd18_id(db).debt_asset == core_id ); + BOOST_CHECK( cd18_id(db).debt_amount == 10 ); + BOOST_CHECK( cd18_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd18_id(db).collateral_amount == 30 ); + BOOST_CHECK( cd18_id(db).fee_rate == 500u ); + BOOST_CHECK( cd18_id(db).latest_repay_time == expected_repay_time18 ); + + BOOST_CHECK( co1_id(db).total_balance == 307 ); + BOOST_CHECK( co1_id(db).current_balance == 98 ); + + expected_balance_ted_core += 10; + expected_balance_ted_eur -= 30; + check_balances(); + + // ===== Time table ========= + // now: 18 + // expected_repay_time14 : 15 // expired + // expected_repay_time15 : 15 // expired + // expected_repay_time16 : 18 // expired + // disable_time1 : 20 + // expected_repay_time17 : 22 + // expected_repay_time18 : 28 + // disable_time2 : 32 + // expected_repay_time21 : 42 + // expected_repay_time22 : 43 + // expected_repay_time11 : 60 + // expected_repay_time12 : 60 // fully repaid already + // expected_repay_time13 : 65 + + // Time goes by + generate_blocks( disable_time1 ); // now is 20 minutes after init + set_expiration( db, trx ); + + // Expiration + BOOST_CHECK( co1_id(db).total_balance == 307 ); + BOOST_CHECK( co1_id(db).current_balance == 98 ); + BOOST_CHECK( co1_id(db).enabled == false ); + BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 ); + + // Unable to borrow from co1 + BOOST_CHECK_THROW( borrow_from_credit_offer( ted_id, co1_id, asset(10), asset(30, eur_id) ), + fc::exception ); + + BOOST_REQUIRE( db.find( cd17_id ) ); + BOOST_CHECK( cd17_id(db).borrower == ted_id ); + BOOST_CHECK( cd17_id(db).offer_id == co1_id ); + BOOST_CHECK( cd17_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd17_id(db).debt_asset == core_id ); + BOOST_CHECK( cd17_id(db).debt_amount == 2 ); + BOOST_CHECK( cd17_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd17_id(db).collateral_amount == 49 ); + BOOST_CHECK( cd17_id(db).fee_rate == 500u ); + BOOST_CHECK( cd17_id(db).latest_repay_time == expected_repay_time17 ); + + // Time goes by + generate_blocks( expected_repay_time17 ); // now is 22 minutes after init + set_expiration( db, trx ); + + // Expiration + BOOST_REQUIRE( !db.find( cd17_id ) ); + + BOOST_CHECK( co1_id(db).total_balance == 305 ); // 307 - 2 + BOOST_CHECK( co1_id(db).current_balance == 98 ); // unchanged + BOOST_CHECK( co1_id(db).enabled == false ); + BOOST_CHECK( co1_id(db).auto_disable_time == disable_time1 ); + + expected_balance_sam_eur += 49; // cd17 + check_balances(); + + BOOST_REQUIRE( db.find( cd18_id ) ); + BOOST_CHECK( cd18_id(db).borrower == ted_id ); + BOOST_CHECK( cd18_id(db).offer_id == co1_id ); + BOOST_CHECK( cd18_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd18_id(db).debt_asset == core_id ); + BOOST_CHECK( cd18_id(db).debt_amount == 10 ); + BOOST_CHECK( cd18_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd18_id(db).collateral_amount == 30 ); + BOOST_CHECK( cd18_id(db).fee_rate == 500u ); + BOOST_CHECK( cd18_id(db).latest_repay_time == expected_repay_time18 ); + + // Time goes by + generate_blocks( expected_repay_time18 ); // now is 28 minutes after init + set_expiration( db, trx ); + + // Expiration + BOOST_REQUIRE( !db.find( cd18_id ) ); + + BOOST_CHECK( co1_id(db).total_balance == 295 ); // 305 - 10 + BOOST_CHECK( co1_id(db).current_balance == 98 ); // unchanged + + expected_balance_sam_eur += 30; // cd18 + check_balances(); + + BOOST_CHECK( co2_id(db).enabled == true ); + BOOST_CHECK( co2_id(db).auto_disable_time == disable_time2 ); + + // ===== Time table ========= + // now: 28 + // expected_repay_time14 : 15 // expired + // expected_repay_time15 : 15 // expired + // expected_repay_time16 : 18 // expired + // disable_time1 : 20 // expired + // expected_repay_time17 : 22 // expired + // expected_repay_time18 : 28 // expired + // disable_time2 : 32 + // expected_repay_time21 : 42 + // expected_repay_time22 : 43 + // expected_repay_time11 : 60 + // expected_repay_time12 : 60 // fully repaid already + // expected_repay_time13 : 65 + + // Time goes by + generate_blocks( disable_time2 ); // now is 32 minutes after init + set_expiration( db, trx ); + + BOOST_CHECK( co2_id(db).enabled == false ); + BOOST_CHECK( co2_id(db).auto_disable_time == disable_time2 ); + + BOOST_REQUIRE( db.find( cd21_id ) ); + BOOST_CHECK( cd21_id(db).borrower == ray_id ); + BOOST_CHECK( cd21_id(db).offer_id == co2_id ); + BOOST_CHECK( cd21_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd21_id(db).debt_asset == usd_id ); + BOOST_CHECK( cd21_id(db).debt_amount == 900 ); + BOOST_CHECK( cd21_id(db).collateral_asset == cny_id ); + BOOST_CHECK( cd21_id(db).collateral_amount == 1080 ); + BOOST_CHECK( cd21_id(db).fee_rate == 70000u ); + BOOST_CHECK( cd21_id(db).latest_repay_time == expected_repay_time21 ); + + // Time goes by + generate_blocks( expected_repay_time21 ); // now is 42 minutes after init + set_expiration( db, trx ); + + // Expiration + BOOST_REQUIRE( !db.find( cd21_id ) ); + + BOOST_CHECK( co2_id(db).total_balance == 9122 ); // 10022 - 900 + BOOST_CHECK( co2_id(db).current_balance == 8322 ); // unchanged + + expected_balance_sam_cny += 1080; // cd21 + check_balances(); + + BOOST_CHECK( cd22_id(db).borrower == ted_id ); + BOOST_CHECK( cd22_id(db).offer_id == co2_id ); + BOOST_CHECK( cd22_id(db).offer_owner == sam_id ); + BOOST_CHECK( cd22_id(db).debt_asset == usd_id ); + BOOST_CHECK( cd22_id(db).debt_amount == 800 ); + BOOST_CHECK( cd22_id(db).collateral_asset == eur_id ); + BOOST_CHECK( cd22_id(db).collateral_amount == 880 ); + BOOST_CHECK( cd22_id(db).fee_rate == 70000u ); + BOOST_CHECK( cd22_id(db).latest_repay_time == expected_repay_time22 ); + + { + // Remove Sam from the whitelist of EUR + account_whitelist_operation wop; + wop.authorizing_account = sam_id; + wop.account_to_list = sam_id; + wop.new_listing = account_whitelist_operation::no_listing; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + } + + // Time goes by + generate_blocks( expected_repay_time22 ); // now is 43 minutes after init + set_expiration( db, trx ); + + // Expiration + BOOST_REQUIRE( !db.find( cd22_id ) ); + + BOOST_CHECK( co2_id(db).total_balance == 8322 ); // 9122 - 800 + BOOST_CHECK( co2_id(db).current_balance == 8322 ); // unchanged + + // Funds go to account balance ignoring asset authorization + expected_balance_sam_eur += 880; // cd22 + check_balances(); + + // Sam delete credit offer + delete_credit_offer( sam_id, co2_id ); + + BOOST_REQUIRE( db.find( co1_id ) ); + BOOST_REQUIRE( !db.find( co2_id ) ); + + expected_balance_sam_usd += 8322; + check_balances(); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/samet_fund_tests.cpp b/tests/tests/samet_fund_tests.cpp index a0d40a0eba..75739f067e 100644 --- a/tests/tests/samet_fund_tests.cpp +++ b/tests/tests/samet_fund_tests.cpp @@ -360,7 +360,7 @@ BOOST_AUTO_TEST_CASE( samet_fund_crud_and_proposal_test ) expected_balance_sam_eur += 9; check_balances(); - // Same is unable to recreate the fund + // Sam is unable to recreate the fund BOOST_CHECK_THROW( create_samet_fund( sam_id, eur.id, 10, 1u ), fc::exception ); check_balances(); From d71638e5792a01c1b5c4302fc3f44856bec398da Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 25 Jun 2021 12:02:50 +0000 Subject: [PATCH 054/258] Add tests for operation results about credit deals --- tests/common/database_fixture.cpp | 7 +++- tests/tests/credit_offer_tests.cpp | 64 +++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 94df251f36..0e9d9bf6a6 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1814,7 +1814,12 @@ const credit_deal_object& database_fixture_base::borrow_from_credit_offer( const operation_result& op_result = ptx.operation_results.front(); trx.operations.clear(); verify_asset_supplies(db); - return db.get( *op_result.get().value.new_objects->begin() ); + const auto& result_dtl = op_result.get().value; + BOOST_REQUIRE( result_dtl.impacted_accounts.valid() ); + BOOST_CHECK( *result_dtl.impacted_accounts == flat_set({ offer_id(db).owner_account }) ); + BOOST_REQUIRE( result_dtl.new_objects.valid() ); + BOOST_REQUIRE_EQUAL( result_dtl.new_objects->size(), 1u ); + return db.get( *result_dtl.new_objects->begin() ); } credit_deal_repay_operation database_fixture_base::make_credit_deal_repay_op( diff --git a/tests/tests/credit_offer_tests.cpp b/tests/tests/credit_offer_tests.cpp index b875105232..5b63c725a7 100644 --- a/tests/tests/credit_offer_tests.cpp +++ b/tests/tests/credit_offer_tests.cpp @@ -1119,6 +1119,16 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) BOOST_CHECK( collateral_released == asset(4, usd_id) ); // round_down(499/100) + BOOST_REQUIRE( result.updated_objects.valid() ); + BOOST_CHECK( result.updated_objects->size() == 2 ); + BOOST_CHECK( *result.updated_objects == flat_set({ co1_id, cd13_id }) ); + + BOOST_CHECK( !result.removed_objects.valid() ); + + BOOST_REQUIRE( result.impacted_accounts.valid() ); + BOOST_CHECK( result.impacted_accounts->size() == 1 ); + BOOST_CHECK( *result.impacted_accounts == flat_set({ sam_id }) ); + BOOST_CHECK( cd13_id(db).borrower == ray_id ); BOOST_CHECK( cd13_id(db).offer_id == co1_id ); BOOST_CHECK( cd13_id(db).offer_owner == sam_id ); @@ -1166,6 +1176,16 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) BOOST_CHECK( collateral_released == asset(5, usd_id) ); // round_down(495/99) + BOOST_REQUIRE( result.updated_objects.valid() ); + BOOST_CHECK( result.updated_objects->size() == 2 ); + BOOST_CHECK( *result.updated_objects == flat_set({ co1_id, cd13_id }) ); + + BOOST_CHECK( !result.removed_objects.valid() ); + + BOOST_REQUIRE( result.impacted_accounts.valid() ); + BOOST_CHECK( result.impacted_accounts->size() == 1 ); + BOOST_CHECK( *result.impacted_accounts == flat_set({ sam_id }) ); + BOOST_CHECK( cd13_id(db).borrower == ray_id ); BOOST_CHECK( cd13_id(db).offer_id == co1_id ); BOOST_CHECK( cd13_id(db).offer_owner == sam_id ); @@ -1202,7 +1222,19 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) BOOST_CHECK( collateral_released == asset(200, usd_id) ); - BOOST_REQUIRE( !db.find( cd12_id ) ); + BOOST_REQUIRE( result.updated_objects.valid() ); + BOOST_REQUIRE( result.updated_objects->size() == 1 ); + BOOST_CHECK( *(result.updated_objects->begin()) == co1_id ); + + BOOST_REQUIRE( result.removed_objects.valid() ); + BOOST_REQUIRE( result.removed_objects->size() == 1 ); + BOOST_CHECK( *(result.removed_objects->begin()) == cd12_id ); + + BOOST_REQUIRE( result.impacted_accounts.valid() ); + BOOST_CHECK( result.impacted_accounts->size() == 1 ); + BOOST_CHECK( *result.impacted_accounts == flat_set({ sam_id }) ); + + BOOST_CHECK( !db.find( cd12_id ) ); BOOST_CHECK( co1_id(db).total_balance == 596 ); BOOST_CHECK( co1_id(db).current_balance == 106 ); @@ -1269,6 +1301,16 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) BOOST_CHECK( collateral_released == asset(120, cny_id) ); + BOOST_REQUIRE( result.updated_objects.valid() ); + BOOST_CHECK( result.updated_objects->size() == 2 ); + BOOST_CHECK( *result.updated_objects == flat_set({ co2_id, cd21_id }) ); + + BOOST_CHECK( !result.removed_objects.valid() ); + + BOOST_REQUIRE( result.impacted_accounts.valid() ); + BOOST_CHECK( result.impacted_accounts->size() == 1 ); + BOOST_CHECK( *result.impacted_accounts == flat_set({ sam_id }) ); + BOOST_CHECK( cd21_id(db).borrower == ray_id ); BOOST_CHECK( cd21_id(db).offer_id == co2_id ); BOOST_CHECK( cd21_id(db).offer_owner == sam_id ); @@ -1313,6 +1355,16 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) BOOST_CHECK( collateral_released == asset(5, usd_id) ); // round_down(490/98) + BOOST_REQUIRE( result.updated_objects.valid() ); + BOOST_CHECK( result.updated_objects->size() == 2 ); + BOOST_CHECK( *result.updated_objects == flat_set({ co1_id, cd13_id }) ); + + BOOST_CHECK( !result.removed_objects.valid() ); + + BOOST_REQUIRE( result.impacted_accounts.valid() ); + BOOST_CHECK( result.impacted_accounts->size() == 1 ); + BOOST_CHECK( *result.impacted_accounts == flat_set({ sam_id }) ); + BOOST_CHECK( cd13_id(db).borrower == ray_id ); BOOST_CHECK( cd13_id(db).offer_id == co1_id ); BOOST_CHECK( cd13_id(db).offer_owner == sam_id ); @@ -1390,6 +1442,16 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) BOOST_CHECK( collateral_released == asset(220, eur_id) ); + BOOST_REQUIRE( result.updated_objects.valid() ); + BOOST_CHECK( result.updated_objects->size() == 2 ); + BOOST_CHECK( *result.updated_objects == flat_set({ co2_id, cd22_id }) ); + + BOOST_CHECK( !result.removed_objects.valid() ); + + BOOST_REQUIRE( result.impacted_accounts.valid() ); + BOOST_CHECK( result.impacted_accounts->size() == 1 ); + BOOST_CHECK( *result.impacted_accounts == flat_set({ sam_id }) ); + BOOST_CHECK( cd22_id(db).borrower == ted_id ); BOOST_CHECK( cd22_id(db).offer_id == co2_id ); BOOST_CHECK( cd22_id(db).offer_owner == sam_id ); From fd4d08b8b0eb0bb13ea86fae7bd69fa716741ea5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 25 Jun 2021 19:39:22 +0000 Subject: [PATCH 055/258] Try to fix OOM for Github CI Ubuntu debug build --- .github/workflows/build-and-test.ubuntu-debug.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-and-test.ubuntu-debug.yml b/.github/workflows/build-and-test.ubuntu-debug.yml index 90927ab047..6689f61c4a 100644 --- a/.github/workflows/build-and-test.ubuntu-debug.yml +++ b/.github/workflows/build-and-test.ubuntu-debug.yml @@ -85,6 +85,8 @@ jobs: df -h make -j 2 -C _build chain_test make -j 2 -C _build cli_test + make -j 2 -C _build app_test + make -j 2 -C _build es_test make -j 2 -C _build cli_wallet make -j 2 -C _build witness_node make -j 2 -C _build From 490b124fe791f12c40a5533993557914d6be58cd Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 26 Jun 2021 00:00:29 +0000 Subject: [PATCH 056/258] Add database APIs for credit offers and deals - list_credit_offers( limit, start_id ) - get_credit_offers_by_owner( account_name_or_id, limit, start_id ) - get_credit_offers_by_asset( asset_symbol_or_id, limit, start_id ) - list_credit_deals( limit, start_id ) - get_credit_deals_by_offer_id( offer_id, limit, start_id ) - get_credit_deals_by_offer_owner( account_name_or_id, limit, start_id ) - get_credit_deals_by_borrower( account_name_or_id, limit, start_id ) - get_credit_deals_by_debt_asset( asset_symbol_or_id, limit, start_id ) - get_credit_deals_by_collateral_asset( asset_symbol_or_id, limit, start_id ) BTW refactored database APIs for SameT Funds. --- libraries/app/application.cpp | 5 + libraries/app/database_api.cpp | 239 ++++++++++++------ libraries/app/database_api_impl.hxx | 78 +++++- .../app/include/graphene/app/application.hpp | 7 + .../app/include/graphene/app/database_api.hpp | 186 +++++++++++++- 5 files changed, 414 insertions(+), 101 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index b3a26bca83..8b4c3028f8 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -397,6 +397,9 @@ void application_impl::set_api_limit() { if(_options->count("api-limit-get-samet-funds") > 0) { _app_options.api_limit_get_samet_funds = _options->at("api-limit-get-samet-funds").as(); } + if(_options->count("api-limit-get-credit-offers") > 0) { + _app_options.api_limit_get_credit_offers = _options->at("api-limit-get-credit-offers").as(); + } } graphene::chain::genesis_state_type application_impl::initialize_genesis_state() const @@ -1193,6 +1196,8 @@ void application::set_program_options(boost::program_options::options_descriptio "Set maximum limit value for APIs which query for history of liquidity pools") ("api-limit-get-samet-funds", boost::program_options::value()->default_value(101), "Set maximum limit value for database APIs which query for SameT Funds") + ("api-limit-get-credit-offers", boost::program_options::value()->default_value(101), + "Set maximum limit value for database APIs which query for credit offers and credit deals") ; command_line_options.add(configuration_file_options); command_line_options.add_options() diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 612af1f5f8..29763c53cc 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -2085,36 +2085,13 @@ vector database_api::list_samet_funds( const optional& limit, const optional& start_id )const { - return my->list_samet_funds( limit, start_id ); -} - -vector database_api_impl::list_samet_funds( - const optional& olimit, - const optional& ostart_id )const -{ - uint32_t limit = olimit.valid() ? *olimit : 101; - - FC_ASSERT( _app_options, "Internal error" ); - const auto configured_limit = _app_options->api_limit_get_samet_funds; - FC_ASSERT( limit <= configured_limit, - "limit can not be greater than ${configured_limit}", - ("configured_limit", configured_limit) ); - - vector results; - - samet_fund_id_type start_id = ostart_id.valid() ? *ostart_id : samet_fund_id_type(); - - const auto& idx = _db.get_index_type().indices().get(); - auto lower_itr = idx.lower_bound( start_id ); - auto upper_itr = idx.end(); - - results.reserve( limit ); - for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) - { - results.emplace_back( *lower_itr ); - } - - return results; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->list_objects< samet_fund_object, + samet_fund_id_type, + decltype(idx), + decltype(static_cast(nullptr)->api_limit_get_samet_funds), + &application_options::api_limit_get_samet_funds + >( idx, limit, start_id ); } vector database_api::get_samet_funds_by_owner( @@ -2122,79 +2099,175 @@ vector database_api::get_samet_funds_by_owner( const optional& limit, const optional& start_id )const { - return my->get_samet_funds_by_owner( account_name_or_id, limit, start_id ); + account_id_type owner = my->get_account_from_string(account_name_or_id)->id; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< samet_fund_object, + samet_fund_id_type, + decltype(idx), + account_id_type, + decltype(static_cast(nullptr)->api_limit_get_samet_funds), + &application_options::api_limit_get_samet_funds + >( idx, owner, limit, start_id ); } -vector database_api_impl::get_samet_funds_by_owner( - const std::string& account_name_or_id, - const optional& olimit, - const optional& ostart_id )const +vector database_api::get_samet_funds_by_asset( + const std::string& asset_symbol_or_id, + const optional& limit, + const optional& start_id )const { - uint32_t limit = olimit.valid() ? *olimit : 101; - - FC_ASSERT( _app_options, "Internal error" ); - const auto configured_limit = _app_options->api_limit_get_samet_funds; - FC_ASSERT( limit <= configured_limit, - "limit can not be greater than ${configured_limit}", - ("configured_limit", configured_limit) ); - - account_id_type owner = get_account_from_string(account_name_or_id)->id; - - vector results; - - samet_fund_id_type start_id = ostart_id.valid() ? *ostart_id : samet_fund_id_type(); + asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->id; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< samet_fund_object, + samet_fund_id_type, + decltype(idx), + asset_id_type, + decltype(static_cast(nullptr)->api_limit_get_samet_funds), + &application_options::api_limit_get_samet_funds + >( idx, asset_type, limit, start_id ); +} - const auto& idx = _db.get_index_type().indices().get(); - auto lower_itr = idx.lower_bound( std::make_tuple( owner, start_id ) ); - auto upper_itr = idx.upper_bound( owner ); - results.reserve( limit ); - for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) - { - results.emplace_back( *lower_itr ); - } +////////////////////////////////////////////////////////////////////// +// // +// Credit offers and credit deals // +// // +////////////////////////////////////////////////////////////////////// - return results; +vector database_api::list_credit_offers( + const optional& limit, + const optional& start_id )const +{ + const auto& idx = my->_db.get_index_type().indices().get(); + return my->list_objects< credit_offer_object, + credit_offer_id_type, + decltype(idx), + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, limit, start_id ); } -vector database_api::get_samet_funds_by_asset( - const std::string& asset_symbol_or_id, +vector database_api::get_credit_offers_by_owner( + const std::string& account_name_or_id, const optional& limit, - const optional& start_id )const + const optional& start_id )const { - return my->get_samet_funds_by_asset( asset_symbol_or_id, limit, start_id ); + account_id_type owner = my->get_account_from_string(account_name_or_id)->id; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< credit_offer_object, + credit_offer_id_type, + decltype(idx), + account_id_type, + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, owner, limit, start_id ); } -vector database_api_impl::get_samet_funds_by_asset( +vector database_api::get_credit_offers_by_asset( const std::string& asset_symbol_or_id, - const optional& olimit, - const optional& ostart_id )const + const optional& limit, + const optional& start_id )const { - uint32_t limit = olimit.valid() ? *olimit : 101; - - FC_ASSERT( _app_options, "Internal error" ); - const auto configured_limit = _app_options->api_limit_get_samet_funds; - FC_ASSERT( limit <= configured_limit, - "limit can not be greater than ${configured_limit}", - ("configured_limit", configured_limit) ); + asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->id; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< credit_offer_object, + credit_offer_id_type, + decltype(idx), + asset_id_type, + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, asset_type, limit, start_id ); +} - asset_id_type asset_type = get_asset_from_string(asset_symbol_or_id)->id; +vector database_api::list_credit_deals( + const optional& limit, + const optional& start_id )const +{ + const auto& idx = my->_db.get_index_type().indices().get(); + return my->list_objects< credit_deal_object, + credit_deal_id_type, + decltype(idx), + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, limit, start_id ); +} - vector results; +vector database_api::get_credit_deals_by_offer_id( + const credit_offer_id_type& offer_id, + const optional& limit, + const optional& start_id )const +{ + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< credit_deal_object, + credit_deal_id_type, + decltype(idx), + credit_offer_id_type, + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, offer_id, limit, start_id ); +} - samet_fund_id_type start_id = ostart_id.valid() ? *ostart_id : samet_fund_id_type(); +vector database_api::get_credit_deals_by_offer_owner( + const std::string& account_name_or_id, + const optional& limit, + const optional& start_id )const +{ + account_id_type owner = my->get_account_from_string(account_name_or_id)->id; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< credit_deal_object, + credit_deal_id_type, + decltype(idx), + account_id_type, + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, owner, limit, start_id ); +} - const auto& idx = _db.get_index_type().indices().get(); - auto lower_itr = idx.lower_bound( std::make_tuple( asset_type, start_id ) ); - auto upper_itr = idx.upper_bound( asset_type ); +vector database_api::get_credit_deals_by_borrower( + const std::string& account_name_or_id, + const optional& limit, + const optional& start_id )const +{ + account_id_type borrower = my->get_account_from_string(account_name_or_id)->id; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< credit_deal_object, + credit_deal_id_type, + decltype(idx), + account_id_type, + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, borrower, limit, start_id ); +} - results.reserve( limit ); - for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) - { - results.emplace_back( *lower_itr ); - } +vector database_api::get_credit_deals_by_debt_asset( + const std::string& asset_symbol_or_id, + const optional& limit, + const optional& start_id )const +{ + asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->id; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< credit_deal_object, + credit_deal_id_type, + decltype(idx), + asset_id_type, + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, asset_type, limit, start_id ); +} - return results; +vector database_api::get_credit_deals_by_collateral_asset( + const std::string& asset_symbol_or_id, + const optional& limit, + const optional& start_id )const +{ + asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->id; + const auto& idx = my->_db.get_index_type().indices().get(); + return my->get_objects_by_x< credit_deal_object, + credit_deal_id_type, + decltype(idx), + asset_id_type, + decltype(static_cast(nullptr)->api_limit_get_credit_offers), + &application_options::api_limit_get_credit_offers + >( idx, asset_type, limit, start_id ); } diff --git a/libraries/app/database_api_impl.hxx b/libraries/app/database_api_impl.hxx index 4f77f3852d..4fe9a9341c 100644 --- a/libraries/app/database_api_impl.hxx +++ b/libraries/app/database_api_impl.hxx @@ -178,19 +178,6 @@ class database_api_impl : public std::enable_shared_from_this optional start_id = optional(), optional with_statistics = false )const; - // SameT Funds - vector list_samet_funds( - const optional& limit = 101, - const optional& start_id = optional() )const; - vector get_samet_funds_by_owner( - const std::string& account_name_or_id, - const optional& limit = 101, - const optional& start_id = optional() )const; - vector get_samet_funds_by_asset( - const std::string& asset_symbol_or_id, - const optional& limit = 101, - const optional& start_id = optional() )const; - // Witnesses vector> get_witnesses(const vector& witness_ids)const; fc::optional get_witness_by_account(const std::string account_id_or_name)const; @@ -357,6 +344,71 @@ class database_api_impl : public std::enable_shared_from_this return results; } + // template function to reduce duplicate code + template + vector list_objects( + INDEX_TYPE idx, + const optional& olimit, + const optional& ostart_id ) const + { + uint64_t limit = olimit.valid() ? *olimit : application_options::get_default().*app_opt_member_ptr; + + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->*app_opt_member_ptr; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + + vector results; + + OBJ_ID_TYPE start_id = ostart_id.valid() ? *ostart_id : OBJ_ID_TYPE(); + + auto lower_itr = idx.lower_bound( start_id ); + auto upper_itr = idx.end(); + + results.reserve( limit ); + for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) + { + results.emplace_back( *lower_itr ); + } + + return results; + } + + // template function to reduce duplicate code + template + vector get_objects_by_x( + INDEX_TYPE idx, + const X& x, + const optional& olimit, + const optional& ostart_id ) const + { + uint64_t limit = olimit.valid() ? *olimit : application_options::get_default().*app_opt_member_ptr; + + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->*app_opt_member_ptr; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + + vector results; + + OBJ_ID_TYPE start_id = ostart_id.valid() ? *ostart_id : OBJ_ID_TYPE(); + + auto lower_itr = idx.lower_bound( std::make_tuple( x, start_id ) ); + auto upper_itr = idx.upper_bound( x ); + + results.reserve( limit ); + for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) + { + results.emplace_back( *lower_itr ); + } + + return results; + } + //////////////////////////////////////////////// // Subscription //////////////////////////////////////////////// diff --git a/libraries/app/include/graphene/app/application.hpp b/libraries/app/include/graphene/app/application.hpp index bacdf7ff3f..32f1b88da3 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -76,6 +76,13 @@ namespace graphene { namespace app { uint64_t api_limit_get_liquidity_pools = 101; uint64_t api_limit_get_liquidity_pool_history = 101; uint64_t api_limit_get_samet_funds = 101; + uint64_t api_limit_get_credit_offers = 101; + + static const application_options& get_default() + { + static const application_options default_options; + return default_options; + } }; class application diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 47d9f2e29d..d62e631184 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -786,9 +787,10 @@ class database_api optional start_id = optional(), optional with_statistics = false )const; + ///////////////////// - // SameT Funds // - ///////////////////// + /// SameT Funds + /// @{ /** * @brief Get a list of SameT Funds @@ -809,7 +811,7 @@ class database_api * @brief Get a list of SameT Funds by the name or ID of the owner account * @param account_name_or_id name or ID of the owner account * @param limit The limitation of items each query can fetch, not greater than a configured value - * @param start_id Start Samet Fund id, fetch items whose IDs are greater than or equal to this ID + * @param start_id Start SameT Fund id, fetch items whose IDs are greater than or equal to this ID * @return The SameT Funds * * @note @@ -824,10 +826,10 @@ class database_api const optional& start_id = optional() )const; /** - * @brief Get a list of SameT Funds by the symbole or ID of the asset type + * @brief Get a list of SameT Funds by the symbol or ID of the asset type * @param asset_symbol_or_id symbol or ID of the asset type * @param limit The limitation of items each query can fetch, not greater than a configured value - * @param start_id Start Samet Fund id, fetch items whose IDs are greater than or equal to this ID + * @param start_id Start SameT Fund id, fetch items whose IDs are greater than or equal to this ID * @return The SameT Funds * * @note @@ -840,6 +842,169 @@ class database_api const std::string& asset_symbol_or_id, const optional& limit = 101, const optional& start_id = optional() )const; + /// @} + + + //////////////////////////////////// + /// Credit offers and credit deals + /// @{ + + /** + * @brief Get a list of credit offers + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit offer id, fetch items whose IDs are greater than or equal to this ID + * @return The credit offers + * + * @note + * 1. @p limit can be omitted or be null, if so the default value 101 will be used + * 2. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 3. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector list_credit_offers( + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of credit offers by the name or ID of the owner account + * @param account_name_or_id name or ID of the owner account + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit offer id, fetch items whose IDs are greater than or equal to this ID + * @return The credit offers + * + * @note + * 1. if @p account_name_or_id cannot be tied to an account, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_credit_offers_by_owner( + const std::string& account_name_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of credit offers by the symbol or ID of the asset type + * @param asset_symbol_or_id symbol or ID of the asset type + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit offer id, fetch items whose IDs are greater than or equal to this ID + * @return The credit offers + * + * @note + * 1. if @p asset_symbol_or_id cannot be tied to an asset, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_credit_offers_by_asset( + const std::string& asset_symbol_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of credit deals + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID + * @return The credit deals + * + * @note + * 1. @p limit can be omitted or be null, if so the default value 101 will be used + * 2. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 3. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector list_credit_deals( + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of credit deals by the ID of a credit offer + * @param offer_id ID of the credit offer + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID + * @return The credit deals + * + * @note + * 1. if @p offer_id cannot be tied to a credit offer, an empty list will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_credit_deals_by_offer_id( + const credit_offer_id_type& offer_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of credit deals by the name or ID of a credit offer owner account + * @param account_name_or_id name or ID of the credit offer owner account + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID + * @return The credit deals + * + * @note + * 1. if @p account_name_or_id cannot be tied to an account, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_credit_deals_by_offer_owner( + const std::string& account_name_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of credit deals by the name or ID of a borrower account + * @param account_name_or_id name or ID of the borrower account + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID + * @return The credit deals + * + * @note + * 1. if @p account_name_or_id cannot be tied to an account, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_credit_deals_by_borrower( + const std::string& account_name_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of credit deals by the symbol or ID of the debt asset type + * @param asset_symbol_or_id symbol or ID of the debt asset type + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID + * @return The credit deals + * + * @note + * 1. if @p asset_symbol_or_id cannot be tied to an asset, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_credit_deals_by_debt_asset( + const std::string& asset_symbol_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + + /** + * @brief Get a list of credit deals by the symbol or ID of the collateral asset type + * @param asset_symbol_or_id symbol or ID of the collateral asset type + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start credit deal id, fetch items whose IDs are greater than or equal to this ID + * @return The credit deals + * + * @note + * 1. if @p asset_symbol_or_id cannot be tied to an asset, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of data + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_credit_deals_by_collateral_asset( + const std::string& asset_symbol_or_id, + const optional& limit = 101, + const optional& start_id = optional() )const; + /// @} /////////////// @@ -1264,6 +1429,17 @@ FC_API(graphene::app::database_api, (get_samet_funds_by_owner) (get_samet_funds_by_asset) + // Credit offers and credit deals + (list_credit_offers) + (get_credit_offers_by_owner) + (get_credit_offers_by_asset) + (list_credit_deals) + (get_credit_deals_by_offer_id) + (get_credit_deals_by_offer_owner) + (get_credit_deals_by_borrower) + (get_credit_deals_by_debt_asset) + (get_credit_deals_by_collateral_asset) + // Witnesses (get_witnesses) (get_witness_by_account) From a2f81009a6f489f94244ce0d9c7ae12f798132f4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 26 Jun 2021 02:50:43 +0000 Subject: [PATCH 057/258] Fix code smell in application::set_program_options Replace magic numbers with more meaningful variables --- libraries/app/application.cpp | 104 +++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 34 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 8b4c3028f8..1b8292bed2 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -1099,6 +1099,7 @@ application::~application() void application::set_program_options(boost::program_options::options_description& command_line_options, boost::program_options::options_description& configuration_file_options) const { + const auto& default_opts = application_options::get_default(); configuration_file_options.add_options() ("enable-p2p-network", bpo::value()->implicit_value(true), "Whether to enable P2P network. Note: if delayed_node plugin is enabled, " @@ -1131,73 +1132,108 @@ void application::set_program_options(boost::program_options::options_descriptio ("enable-standby-votes-tracking", bpo::value()->implicit_value(true), "Whether to enable tracking of votes of standby witnesses and committee members. " "Set it to true to provide accurate data to API clients, set to false for slightly better performance.") - ("api-limit-get-account-history-operations",boost::program_options::value()->default_value(100), + ("api-limit-get-account-history-operations", + bpo::value()->default_value(default_opts.api_limit_get_account_history_operations), "For history_api::get_account_history_operations to set max limit value") - ("api-limit-get-account-history",boost::program_options::value()->default_value(100), + ("api-limit-get-account-history", + bpo::value()->default_value(default_opts.api_limit_get_account_history), "For history_api::get_account_history to set max limit value") - ("api-limit-get-grouped-limit-orders",boost::program_options::value()->default_value(101), + ("api-limit-get-grouped-limit-orders", + bpo::value()->default_value(default_opts.api_limit_get_grouped_limit_orders), "For orders_api::get_grouped_limit_orders to set max limit value") - ("api-limit-get-relative-account-history",boost::program_options::value()->default_value(100), + ("api-limit-get-relative-account-history", + bpo::value()->default_value(default_opts.api_limit_get_relative_account_history), "For history_api::get_relative_account_history to set max limit value") - ("api-limit-get-account-history-by-operations",boost::program_options::value()->default_value(100), + ("api-limit-get-account-history-by-operations", + bpo::value()->default_value(default_opts.api_limit_get_account_history_by_operations), "For history_api::get_account_history_by_operations to set max limit value") - ("api-limit-get-asset-holders",boost::program_options::value()->default_value(100), + ("api-limit-get-asset-holders", + bpo::value()->default_value(default_opts.api_limit_get_asset_holders), "For asset_api::get_asset_holders to set max limit value") - ("api-limit-get-key-references",boost::program_options::value()->default_value(100), + ("api-limit-get-key-references", + bpo::value()->default_value(default_opts.api_limit_get_key_references), "For database_api_impl::get_key_references to set max limit value") - ("api-limit-get-htlc-by",boost::program_options::value()->default_value(100), + ("api-limit-get-htlc-by", + bpo::value()->default_value(default_opts.api_limit_get_htlc_by), "For database_api_impl::get_htlc_by_from and get_htlc_by_to to set max limit value") - ("api-limit-get-full-accounts",boost::program_options::value()->default_value(50), + ("api-limit-get-full-accounts", + bpo::value()->default_value(default_opts.api_limit_get_full_accounts), "For database_api_impl::get_full_accounts to set max accounts to query at once") - ("api-limit-get-full-accounts-lists",boost::program_options::value()->default_value(500), + ("api-limit-get-full-accounts-lists", + bpo::value()->default_value(default_opts.api_limit_get_full_accounts_lists), "For database_api_impl::get_full_accounts to set max items to return in the lists") - ("api-limit-get-top-voters",boost::program_options::value()->default_value(200), + ("api-limit-get-top-voters", + bpo::value()->default_value(default_opts.api_limit_get_top_voters), "For database_api_impl::get_top_voters to set max limit value") - ("api-limit-get-call-orders",boost::program_options::value()->default_value(300), + ("api-limit-get-call-orders", + bpo::value()->default_value(default_opts.api_limit_get_call_orders), "For database_api_impl::get_call_orders and get_call_orders_by_account to set max limit value") - ("api-limit-get-settle-orders",boost::program_options::value()->default_value(300), + ("api-limit-get-settle-orders", + bpo::value()->default_value(default_opts.api_limit_get_settle_orders), "For database_api_impl::get_settle_orders and get_settle_orders_by_account to set max limit value") - ("api-limit-get-assets",boost::program_options::value()->default_value(101), + ("api-limit-get-assets", + bpo::value()->default_value(default_opts.api_limit_get_assets), "For database_api_impl::list_assets and get_assets_by_issuer to set max limit value") - ("api-limit-get-limit-orders",boost::program_options::value()->default_value(300), + ("api-limit-get-limit-orders", + bpo::value()->default_value(default_opts.api_limit_get_limit_orders), "For database_api_impl::get_limit_orders to set max limit value") - ("api-limit-get-limit-orders-by-account",boost::program_options::value()->default_value(101), + ("api-limit-get-limit-orders-by-account", + bpo::value()->default_value(default_opts.api_limit_get_limit_orders_by_account), "For database_api_impl::get_limit_orders_by_account to set max limit value") - ("api-limit-get-order-book",boost::program_options::value()->default_value(50), + ("api-limit-get-order-book", + bpo::value()->default_value(default_opts.api_limit_get_order_book), "For database_api_impl::get_order_book to set max limit value") - ("api-limit-lookup-accounts",boost::program_options::value()->default_value(1000), + ("api-limit-list-htlcs", + bpo::value()->default_value(default_opts.api_limit_list_htlcs), + "For database_api_impl::list_htlcs to set max limit value") + ("api-limit-lookup-accounts", + bpo::value()->default_value(default_opts.api_limit_lookup_accounts), "For database_api_impl::lookup_accounts to set max limit value") - ("api-limit-lookup-witness-accounts",boost::program_options::value()->default_value(1000), + ("api-limit-lookup-witness-accounts", + bpo::value()->default_value(default_opts.api_limit_lookup_witness_accounts), "For database_api_impl::lookup_witness_accounts to set max limit value") - ("api-limit-lookup-committee-member-accounts",boost::program_options::value()->default_value(1000), + ("api-limit-lookup-committee-member-accounts", + bpo::value()->default_value(default_opts.api_limit_lookup_committee_member_accounts), "For database_api_impl::lookup_committee_member_accounts to set max limit value") - ("api-limit-lookup-vote-ids",boost::program_options::value()->default_value(1000), + ("api-limit-lookup-vote-ids", + bpo::value()->default_value(default_opts.api_limit_lookup_vote_ids), "For database_api_impl::lookup_vote_ids to set max limit value") - ("api-limit-get-account-limit-orders",boost::program_options::value()->default_value(101), + ("api-limit-get-account-limit-orders", + bpo::value()->default_value(default_opts.api_limit_get_account_limit_orders), "For database_api_impl::get_account_limit_orders to set max limit value") - ("api-limit-get-collateral-bids",boost::program_options::value()->default_value(100), + ("api-limit-get-collateral-bids", + bpo::value()->default_value(default_opts.api_limit_get_collateral_bids), "For database_api_impl::get_collateral_bids to set max limit value") - ("api-limit-get-top-markets",boost::program_options::value()->default_value(100), + ("api-limit-get-top-markets", + bpo::value()->default_value(default_opts.api_limit_get_top_markets), "For database_api_impl::get_top_markets to set max limit value") - ("api-limit-get-trade-history",boost::program_options::value()->default_value(100), + ("api-limit-get-trade-history", + bpo::value()->default_value(default_opts.api_limit_get_trade_history), "For database_api_impl::get_trade_history to set max limit value") - ("api-limit-get-trade-history-by-sequence",boost::program_options::value()->default_value(100), + ("api-limit-get-trade-history-by-sequence", + bpo::value()->default_value(default_opts.api_limit_get_trade_history_by_sequence), "For database_api_impl::get_trade_history_by_sequence to set max limit value") - ("api-limit-get-withdraw-permissions-by-giver",boost::program_options::value()->default_value(101), + ("api-limit-get-withdraw-permissions-by-giver", + bpo::value()->default_value(default_opts.api_limit_get_withdraw_permissions_by_giver), "For database_api_impl::get_withdraw_permissions_by_giver to set max limit value") ("api-limit-get-withdraw-permissions-by-recipient", - boost::program_options::value()->default_value(101), + bpo::value()->default_value(default_opts.api_limit_get_withdraw_permissions_by_recipient), "For database_api_impl::get_withdraw_permissions_by_recipient to set max limit value") - ("api-limit-get-tickets", boost::program_options::value()->default_value(101), + ("api-limit-get-tickets", + bpo::value()->default_value(default_opts.api_limit_get_tickets), "Set maximum limit value for database APIs which query for tickets") - ("api-limit-get-liquidity-pools", boost::program_options::value()->default_value(101), + ("api-limit-get-liquidity-pools", + bpo::value()->default_value(default_opts.api_limit_get_liquidity_pools), "Set maximum limit value for database APIs which query for liquidity pools") - ("api-limit-get-liquidity-pool-history", boost::program_options::value()->default_value(101), + ("api-limit-get-liquidity-pool-history", + bpo::value()->default_value(default_opts.api_limit_get_liquidity_pool_history), "Set maximum limit value for APIs which query for history of liquidity pools") - ("api-limit-get-samet-funds", boost::program_options::value()->default_value(101), + ("api-limit-get-samet-funds", + bpo::value()->default_value(default_opts.api_limit_get_samet_funds), "Set maximum limit value for database APIs which query for SameT Funds") - ("api-limit-get-credit-offers", boost::program_options::value()->default_value(101), - "Set maximum limit value for database APIs which query for credit offers and credit deals") + ("api-limit-get-credit-offers", + bpo::value()->default_value(default_opts.api_limit_get_credit_offers), + "Set maximum limit value for database APIs which query for credit offers or credit deals") ; command_line_options.add(configuration_file_options); command_line_options.add_options() From c75fcc43326ac85e33782e1489ab4a94a2a33e2c Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 26 Jun 2021 03:12:54 +0000 Subject: [PATCH 058/258] Add tests for database APIs about credit offers --- tests/tests/credit_offer_tests.cpp | 151 +++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/tests/tests/credit_offer_tests.cpp b/tests/tests/credit_offer_tests.cpp index 5b63c725a7..8ff7af050b 100644 --- a/tests/tests/credit_offer_tests.cpp +++ b/tests/tests/credit_offer_tests.cpp @@ -1758,4 +1758,155 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) } } +BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) +{ try { + + // Pass the hard fork time + generate_blocks( HARDFORK_CORE_2362_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(ted)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( ted, asset(init_amount) ); + + asset_id_type core_id; + + const asset_object& usd = create_user_issued_asset( "MYUSD" ); + asset_id_type usd_id = usd.id; + issue_uia( sam, usd.amount(init_amount) ); + issue_uia( ted, usd.amount(init_amount) ); + + const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); + asset_id_type eur_id = eur.id; + issue_uia( sam, eur.amount(init_amount) ); + issue_uia( ted, eur.amount(init_amount) ); + + // create credit offers + flat_map collateral_map_core; + collateral_map_core[usd_id] = price( asset(1), asset(2, usd_id) ); + + flat_map collateral_map_usd; + collateral_map_usd[eur_id] = price( asset(1, usd_id), asset(1, eur_id) ); + + flat_map collateral_map_eur; + collateral_map_eur[core_id] = price( asset(1, eur_id), asset(3) ); + + const credit_offer_object& coo1 = create_credit_offer( sam_id, core_id, 10000, 30000, 3600, 0, false, + time_point_sec(), collateral_map_core, {} ); + credit_offer_id_type co1_id = coo1.id; + + const credit_offer_object& coo2 = create_credit_offer( ted_id, usd_id, 10000, 30000, 3600, 0, false, + time_point_sec(), collateral_map_usd, {} ); + credit_offer_id_type co2_id = coo2.id; + + const credit_offer_object& coo3 = create_credit_offer( sam_id, eur_id, 10000, 30000, 3600, 0, false, + time_point_sec(), collateral_map_eur, {} ); + credit_offer_id_type co3_id = coo3.id; + + const credit_offer_object& coo4 = create_credit_offer( sam_id, eur_id, 10000, 30000, 3600, 0, false, + time_point_sec(), collateral_map_eur, {} ); + credit_offer_id_type co4_id = coo4.id; + + const credit_offer_object& coo5 = create_credit_offer( sam_id, usd_id, 10000, 30000, 3600, 0, false, + time_point_sec(), collateral_map_usd, {} ); + credit_offer_id_type co5_id = coo5.id; + + const credit_offer_object& coo6 = create_credit_offer( ted_id, usd_id, 10000, 30000, 3600, 0, false, + time_point_sec(), collateral_map_usd, {} ); + credit_offer_id_type co6_id = coo6.id; + + generate_block(); + + // Check database API + graphene::app::database_api db_api( db, &( app.get_options() ) ); + + // List all credit offers + auto funds = db_api.list_credit_offers(); + BOOST_REQUIRE_EQUAL( funds.size(), 6u ); + BOOST_CHECK( funds.front().id == co1_id ); + BOOST_CHECK( funds.back().id == co6_id ); + + // Pagination : the first page + funds = db_api.list_credit_offers( 5 ); + BOOST_REQUIRE_EQUAL( funds.size(), 5u ); + BOOST_CHECK( funds.front().id == co1_id ); + BOOST_CHECK( funds.back().id == co5_id ); + + // Pagination : the last page + funds = db_api.list_credit_offers( 5, co3_id ); + BOOST_REQUIRE_EQUAL( funds.size(), 4u ); + BOOST_CHECK( funds.front().id == co3_id ); + BOOST_CHECK( funds.back().id == co6_id ); + + // Limit too large + BOOST_CHECK_THROW( db_api.list_credit_offers( 102 ), fc::exception ); + + // Get all credit offers owned by Sam + funds = db_api.get_credit_offers_by_owner( "sam" ); + BOOST_REQUIRE_EQUAL( funds.size(), 4u ); + BOOST_CHECK( funds.front().id == co1_id ); + BOOST_CHECK( funds.back().id == co5_id ); + + // Pagination : the first page + funds = db_api.get_credit_offers_by_owner( "sam", 3, {} ); + BOOST_REQUIRE_EQUAL( funds.size(), 3u ); + BOOST_CHECK( funds.front().id == co1_id ); + BOOST_CHECK( funds.back().id == co4_id ); + + // Pagination : another page + funds = db_api.get_credit_offers_by_owner( "sam", 3, co2_id ); + BOOST_REQUIRE_EQUAL( funds.size(), 3u ); + BOOST_CHECK( funds.front().id == co3_id ); + BOOST_CHECK( funds.back().id == co5_id ); + + // Pagination : the first page of credit offers owned by Ted + funds = db_api.get_credit_offers_by_owner( string("1.2.")+fc::to_string(ted_id.instance.value), 3 ); + BOOST_REQUIRE_EQUAL( funds.size(), 2u ); + BOOST_CHECK( funds.front().id == co2_id ); + BOOST_CHECK( funds.back().id == co6_id ); + + // Nonexistent account + BOOST_CHECK_THROW( db_api.get_credit_offers_by_owner( "nonexistent-account" ), fc::exception ); + + // Limit too large + BOOST_CHECK_THROW( db_api.get_credit_offers_by_owner( "ted", 102 ), fc::exception ); + + // Get all credit offers whose asset type is USD + funds = db_api.get_credit_offers_by_asset( "MYUSD" ); + BOOST_REQUIRE_EQUAL( funds.size(), 3u ); + BOOST_CHECK( funds.front().id == co2_id ); + BOOST_CHECK( funds.back().id == co6_id ); + + // Pagination : the first page + funds = db_api.get_credit_offers_by_asset( "MYUSD", 2 ); + BOOST_REQUIRE_EQUAL( funds.size(), 2u ); + BOOST_CHECK( funds.front().id == co2_id ); + BOOST_CHECK( funds.back().id == co5_id ); + + // Pagination : another page + funds = db_api.get_credit_offers_by_asset( "MYUSD", 2, co4_id ); + BOOST_REQUIRE_EQUAL( funds.size(), 2u ); + BOOST_CHECK( funds.front().id == co5_id ); + BOOST_CHECK( funds.back().id == co6_id ); + + // Pagination : the first page of credit offers whose asset type is CORE + funds = db_api.get_credit_offers_by_asset( "1.3.0", 2, {} ); + BOOST_REQUIRE_EQUAL( funds.size(), 1u ); + BOOST_CHECK( funds.front().id == co1_id ); + BOOST_CHECK( funds.back().id == co1_id ); + + // Nonexistent asset + BOOST_CHECK_THROW( db_api.get_credit_offers_by_asset( "NOSUCHASSET" ), fc::exception ); + + // Limit too large + BOOST_CHECK_THROW( db_api.get_credit_offers_by_asset( "MYUSD", 102 ), fc::exception ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 4ddd6203ad1a5749f631737e291ecabffdaa6642 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 26 Jun 2021 08:57:36 +0000 Subject: [PATCH 059/258] Refactor simple database query APIs --- libraries/app/database_api.cpp | 111 ++++++++++------------------ libraries/app/database_api_impl.hxx | 58 ++++++--------- 2 files changed, 61 insertions(+), 108 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 29763c53cc..7875320f9c 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -2086,12 +2086,10 @@ vector database_api::list_samet_funds( const optional& start_id )const { const auto& idx = my->_db.get_index_type().indices().get(); - return my->list_objects< samet_fund_object, - samet_fund_id_type, - decltype(idx), - decltype(static_cast(nullptr)->api_limit_get_samet_funds), - &application_options::api_limit_get_samet_funds - >( idx, limit, start_id ); + return my->get_objects_by_x< samet_fund_object, + samet_fund_id_type + >( &application_options::api_limit_get_samet_funds, + idx, limit, start_id ); } vector database_api::get_samet_funds_by_owner( @@ -2102,12 +2100,9 @@ vector database_api::get_samet_funds_by_owner( account_id_type owner = my->get_account_from_string(account_name_or_id)->id; const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< samet_fund_object, - samet_fund_id_type, - decltype(idx), - account_id_type, - decltype(static_cast(nullptr)->api_limit_get_samet_funds), - &application_options::api_limit_get_samet_funds - >( idx, owner, limit, start_id ); + samet_fund_id_type + >( &application_options::api_limit_get_samet_funds, + idx, limit, start_id, owner ); } vector database_api::get_samet_funds_by_asset( @@ -2118,12 +2113,9 @@ vector database_api::get_samet_funds_by_asset( asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->id; const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< samet_fund_object, - samet_fund_id_type, - decltype(idx), - asset_id_type, - decltype(static_cast(nullptr)->api_limit_get_samet_funds), - &application_options::api_limit_get_samet_funds - >( idx, asset_type, limit, start_id ); + samet_fund_id_type + >( &application_options::api_limit_get_samet_funds, + idx, limit, start_id, asset_type ); } @@ -2138,12 +2130,10 @@ vector database_api::list_credit_offers( const optional& start_id )const { const auto& idx = my->_db.get_index_type().indices().get(); - return my->list_objects< credit_offer_object, - credit_offer_id_type, - decltype(idx), - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, limit, start_id ); + return my->get_objects_by_x< credit_offer_object, + credit_offer_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id ); } vector database_api::get_credit_offers_by_owner( @@ -2154,12 +2144,9 @@ vector database_api::get_credit_offers_by_owner( account_id_type owner = my->get_account_from_string(account_name_or_id)->id; const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< credit_offer_object, - credit_offer_id_type, - decltype(idx), - account_id_type, - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, owner, limit, start_id ); + credit_offer_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id, owner ); } vector database_api::get_credit_offers_by_asset( @@ -2170,12 +2157,9 @@ vector database_api::get_credit_offers_by_asset( asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->id; const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< credit_offer_object, - credit_offer_id_type, - decltype(idx), - asset_id_type, - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, asset_type, limit, start_id ); + credit_offer_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id, asset_type ); } vector database_api::list_credit_deals( @@ -2183,12 +2167,10 @@ vector database_api::list_credit_deals( const optional& start_id )const { const auto& idx = my->_db.get_index_type().indices().get(); - return my->list_objects< credit_deal_object, - credit_deal_id_type, - decltype(idx), - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, limit, start_id ); + return my->get_objects_by_x< credit_deal_object, + credit_deal_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id ); } vector database_api::get_credit_deals_by_offer_id( @@ -2198,12 +2180,9 @@ vector database_api::get_credit_deals_by_offer_id( { const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< credit_deal_object, - credit_deal_id_type, - decltype(idx), - credit_offer_id_type, - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, offer_id, limit, start_id ); + credit_deal_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id, offer_id ); } vector database_api::get_credit_deals_by_offer_owner( @@ -2214,12 +2193,9 @@ vector database_api::get_credit_deals_by_offer_owner( account_id_type owner = my->get_account_from_string(account_name_or_id)->id; const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< credit_deal_object, - credit_deal_id_type, - decltype(idx), - account_id_type, - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, owner, limit, start_id ); + credit_deal_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id, owner ); } vector database_api::get_credit_deals_by_borrower( @@ -2230,12 +2206,9 @@ vector database_api::get_credit_deals_by_borrower( account_id_type borrower = my->get_account_from_string(account_name_or_id)->id; const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< credit_deal_object, - credit_deal_id_type, - decltype(idx), - account_id_type, - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, borrower, limit, start_id ); + credit_deal_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id, borrower ); } vector database_api::get_credit_deals_by_debt_asset( @@ -2246,12 +2219,9 @@ vector database_api::get_credit_deals_by_debt_asset( asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->id; const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< credit_deal_object, - credit_deal_id_type, - decltype(idx), - asset_id_type, - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, asset_type, limit, start_id ); + credit_deal_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id, asset_type ); } vector database_api::get_credit_deals_by_collateral_asset( @@ -2262,12 +2232,9 @@ vector database_api::get_credit_deals_by_collateral_asset( asset_id_type asset_type = my->get_asset_from_string(asset_symbol_or_id)->id; const auto& idx = my->_db.get_index_type().indices().get(); return my->get_objects_by_x< credit_deal_object, - credit_deal_id_type, - decltype(idx), - asset_id_type, - decltype(static_cast(nullptr)->api_limit_get_credit_offers), - &application_options::api_limit_get_credit_offers - >( idx, asset_type, limit, start_id ); + credit_deal_id_type + >( &application_options::api_limit_get_credit_offers, + idx, limit, start_id, asset_type ); } diff --git a/libraries/app/database_api_impl.hxx b/libraries/app/database_api_impl.hxx index 4fe9a9341c..54475d579f 100644 --- a/libraries/app/database_api_impl.hxx +++ b/libraries/app/database_api_impl.hxx @@ -344,46 +344,31 @@ class database_api_impl : public std::enable_shared_from_this return results; } - // template function to reduce duplicate code - template - vector list_objects( - INDEX_TYPE idx, - const optional& olimit, - const optional& ostart_id ) const - { - uint64_t limit = olimit.valid() ? *olimit : application_options::get_default().*app_opt_member_ptr; - - FC_ASSERT( _app_options, "Internal error" ); - const auto configured_limit = _app_options->*app_opt_member_ptr; - FC_ASSERT( limit <= configured_limit, - "limit can not be greater than ${configured_limit}", - ("configured_limit", configured_limit) ); - - vector results; + /// Template functions for simple list_X and get_X_by_T APIs, to reduce duplicate code + /// @{ + template + X make_tuple_if_multiple(X x) const + { return x; } - OBJ_ID_TYPE start_id = ostart_id.valid() ? *ostart_id : OBJ_ID_TYPE(); + template + typename std::tuple make_tuple_if_multiple(X... x) const + { return std::make_tuple( x... ); } - auto lower_itr = idx.lower_bound( start_id ); - auto upper_itr = idx.end(); + template + decltype((static_cast(nullptr))->end()) call_end_or_upper_bound( const T& t ) const + { return t.end(); } - results.reserve( limit ); - for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) - { - results.emplace_back( *lower_itr ); - } + template + decltype((static_cast(nullptr))->end()) call_end_or_upper_bound( const T& t, X... x ) const + { return t.upper_bound( make_tuple_if_multiple( x... ) ); } - return results; - } - - // template function to reduce duplicate code - template + template vector get_objects_by_x( - INDEX_TYPE idx, - const X& x, + T application_options::* app_opt_member_ptr, + const INDEX_TYPE& idx, const optional& olimit, - const optional& ostart_id ) const + const optional& ostart_id, + X... x ) const { uint64_t limit = olimit.valid() ? *olimit : application_options::get_default().*app_opt_member_ptr; @@ -397,8 +382,8 @@ class database_api_impl : public std::enable_shared_from_this OBJ_ID_TYPE start_id = ostart_id.valid() ? *ostart_id : OBJ_ID_TYPE(); - auto lower_itr = idx.lower_bound( std::make_tuple( x, start_id ) ); - auto upper_itr = idx.upper_bound( x ); + auto lower_itr = idx.lower_bound( make_tuple_if_multiple( x..., start_id ) ); + auto upper_itr = call_end_or_upper_bound( idx, x... ); results.reserve( limit ); for ( ; lower_itr != upper_itr && results.size() < limit; ++lower_itr ) @@ -408,6 +393,7 @@ class database_api_impl : public std::enable_shared_from_this return results; } + /// @} //////////////////////////////////////////////// // Subscription From 2f1dc7358e0289aadcb12fd82b77f1afcb3f9399 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 26 Jun 2021 21:44:57 +0000 Subject: [PATCH 060/258] Simplify code and fix code smells --- libraries/app/database_api_impl.hxx | 12 +- libraries/chain/credit_offer_evaluator.cpp | 10 +- libraries/chain/db_notify.cpp | 139 +++++++++------------ libraries/protocol/credit_offer.cpp | 4 +- 4 files changed, 75 insertions(+), 90 deletions(-) diff --git a/libraries/app/database_api_impl.hxx b/libraries/app/database_api_impl.hxx index 54475d579f..8bee269c5b 100644 --- a/libraries/app/database_api_impl.hxx +++ b/libraries/app/database_api_impl.hxx @@ -347,19 +347,19 @@ class database_api_impl : public std::enable_shared_from_this /// Template functions for simple list_X and get_X_by_T APIs, to reduce duplicate code /// @{ template - X make_tuple_if_multiple(X x) const + auto make_tuple_if_multiple(X x) const { return x; } template - typename std::tuple make_tuple_if_multiple(X... x) const + auto make_tuple_if_multiple(X... x) const { return std::make_tuple( x... ); } template - decltype((static_cast(nullptr))->end()) call_end_or_upper_bound( const T& t ) const - { return t.end(); } + auto call_end_or_upper_bound( const T& t ) const + { return std::end( t ); } template - decltype((static_cast(nullptr))->end()) call_end_or_upper_bound( const T& t, X... x ) const + auto call_end_or_upper_bound( const T& t, X... x ) const { return t.upper_bound( make_tuple_if_multiple( x... ) ); } template @@ -370,7 +370,7 @@ class database_api_impl : public std::enable_shared_from_this const optional& ostart_id, X... x ) const { - uint64_t limit = olimit.valid() ? *olimit : application_options::get_default().*app_opt_member_ptr; + uint64_t limit = olimit.valid() ? *olimit : ( application_options::get_default().*app_opt_member_ptr ); FC_ASSERT( _app_options, "Internal error" ); const auto configured_limit = _app_options->*app_opt_member_ptr; diff --git a/libraries/chain/credit_offer_evaluator.cpp b/libraries/chain/credit_offer_evaluator.cpp index 720450d6df..8452f8be97 100644 --- a/libraries/chain/credit_offer_evaluator.cpp +++ b/libraries/chain/credit_offer_evaluator.cpp @@ -149,10 +149,10 @@ void_result credit_offer_update_evaluator::do_evaluate(const credit_offer_update } } - bool enabled = ( op.enabled.valid() ? *op.enabled : _offer->enabled ); + bool enabled = op.enabled.valid() ? *op.enabled : _offer->enabled; if( enabled ) { - auto auto_disable_time = ( op.auto_disable_time.valid() ? *op.auto_disable_time : _offer->auto_disable_time ); + auto auto_disable_time = op.auto_disable_time.valid() ? *op.auto_disable_time : _offer->auto_disable_time; FC_ASSERT( auto_disable_time > block_time, "Auto-disable time should be in the future" ); FC_ASSERT( auto_disable_time - block_time <= fc::days(GRAPHENE_MAX_CREDIT_OFFER_DAYS), "Auto-disable time should not be later than ${d} days in the future", @@ -305,9 +305,9 @@ extendable_operation_result credit_offer_accept_evaluator::do_apply( const credi }); const auto block_time = d.head_block_time(); - auto repay_time = ( ( ( fc::time_point_sec::maximum() - block_time ) >= fc::seconds(_offer->max_duration_seconds) ) - ? ( block_time + _offer->max_duration_seconds ) - : fc::time_point_sec::maximum() ); + auto repay_time = ( fc::time_point_sec::maximum() - block_time ) >= fc::seconds(_offer->max_duration_seconds) + ? ( block_time + _offer->max_duration_seconds ) + : fc::time_point_sec::maximum(); const auto& new_deal = d.create([&op,this,&repay_time](credit_deal_object& obj){ obj.borrower = op.borrower; diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 62a6964411..627aff52d2 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -33,8 +33,8 @@ struct get_impacted_account_visitor flat_set& _impacted; bool _ignore_custom_op_reqd_auths; - get_impacted_account_visitor( flat_set& impact, bool ignore_custom_operation_required_auths ) - : _impacted( impact ), _ignore_custom_op_reqd_auths( ignore_custom_operation_required_auths ) + get_impacted_account_visitor( flat_set& impact, bool ignore_custom_op_required_auths ) + : _impacted( impact ), _ignore_custom_op_reqd_auths( ignore_custom_op_required_auths ) {} using result_type = void; @@ -165,8 +165,9 @@ struct get_impacted_account_visitor _impacted.insert( op.fee_payer() ); // fee_paying_account vector other; for( const auto& proposed_op : op.proposed_ops ) - operation_get_required_authorities( proposed_op.op, _impacted, _impacted, other, _ignore_custom_op_reqd_auths ); - for( auto& o : other ) + operation_get_required_authorities( proposed_op.op, _impacted, _impacted, other, + _ignore_custom_op_reqd_auths ); + for( const auto& o : other ) add_authority_accounts( _impacted, o ); } void operator()( const proposal_update_operation& op ) @@ -288,10 +289,10 @@ struct get_impacted_account_visitor } void operator()( const htlc_extend_operation& op ) { - _impacted.insert( op.fee_payer() ); + _impacted.insert( op.fee_payer() ); } - void operator()( const htlc_refund_operation& op ) - { + void operator()( const htlc_refund_operation& op ) + { _impacted.insert( op.fee_payer() ); } void operator()( const custom_authority_create_operation& op ) @@ -386,22 +387,24 @@ struct get_impacted_account_visitor } // namespace detail -void operation_get_impacted_accounts( const operation& op, flat_set& result, - bool ignore_custom_operation_required_auths ) +void operation_get_impacted_accounts( const operation& op, flat_set& result, + bool ignore_custom_op_required_auths ) { - detail::get_impacted_account_visitor vtor = detail::get_impacted_account_visitor( result, - ignore_custom_operation_required_auths ); + detail::get_impacted_account_visitor vtor = detail::get_impacted_account_visitor( result, + ignore_custom_op_required_auths ); op.visit( vtor ); } -void transaction_get_impacted_accounts( const transaction& tx, flat_set& result, - bool ignore_custom_operation_required_auths ) +void transaction_get_impacted_accs( const transaction& tx, flat_set& result, + bool ignore_custom_op_required_auths ) { for( const auto& op : tx.operations ) - operation_get_impacted_accounts( op, result, ignore_custom_operation_required_auths ); + operation_get_impacted_accounts( op, result, ignore_custom_op_required_auths ); } -void get_relevant_accounts( const object* obj, flat_set& accounts, bool ignore_custom_operation_required_auths ) { +void get_relevant_accounts( const object* obj, flat_set& accounts, + bool ignore_custom_op_required_auths ) { + FC_ASSERT( obj != nullptr, "Internal error: get_relevant_accounts called with nullptr" ); // This should not happen if( obj->id.space() == protocol_ids ) { switch( (object_type)obj->id.type() ) @@ -409,109 +412,93 @@ void get_relevant_accounts( const object* obj, flat_set& accoun case null_object_type: case base_object_type: return; - case account_object_type:{ + case account_object_type: accounts.insert( obj->id ); break; - } case asset_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + case asset_object_type:{ + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->issuer ); break; } case force_settlement_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->owner ); break; } case committee_member_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->committee_member_account ); break; } case witness_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->witness_account ); break; } case limit_order_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->seller ); break; } case call_order_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->borrower ); break; - } case custom_object_type:{ + } case custom_object_type: break; - } case proposal_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); - transaction_get_impacted_accounts( aobj->proposed_transaction, accounts, - ignore_custom_operation_required_auths ); + case proposal_object_type:{ + const auto* aobj = dynamic_cast(obj); + transaction_get_impacted_accs( aobj->proposed_transaction, accounts, + ignore_custom_op_required_auths ); break; } case operation_history_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); operation_get_impacted_accounts( aobj->op, accounts, - ignore_custom_operation_required_auths ); + ignore_custom_op_required_auths ); break; } case withdraw_permission_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->withdraw_from_account ); accounts.insert( aobj->authorized_account ); break; } case vesting_balance_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->owner ); break; } case worker_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->worker_account ); break; - } case balance_object_type:{ + } case balance_object_type: /** these are free from any accounts */ break; - } case htlc_object_type:{ - const auto& htlc_obj = dynamic_cast(obj); - FC_ASSERT( htlc_obj != nullptr ); + case htlc_object_type:{ + const auto* htlc_obj = dynamic_cast(obj); accounts.insert( htlc_obj->transfer.from ); accounts.insert( htlc_obj->transfer.to ); break; } case custom_authority_object_type:{ const auto* cust_auth_obj = dynamic_cast( obj ); - FC_ASSERT( cust_auth_obj != nullptr ); accounts.insert( cust_auth_obj->account ); add_authority_accounts( accounts, cust_auth_obj->auth ); break; } case ticket_object_type:{ const auto* aobj = dynamic_cast( obj ); - FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->account ); break; - } case liquidity_pool_object_type:{ + } case liquidity_pool_object_type: // no account info in the object although it does have an owner break; - } case samet_fund_object_type:{ + case samet_fund_object_type:{ const auto* aobj = dynamic_cast( obj ); - FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->owner_account ); break; } case credit_offer_object_type:{ const auto* aobj = dynamic_cast( obj ); - FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->owner_account ); break; } case credit_deal_object_type:{ const auto* aobj = dynamic_cast( obj ); - FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->offer_owner ); accounts.insert( aobj->borrower ); break; } + // Do not have a default fallback so that there will be a compiler warning when a new type is added } } else if( obj->id.space() == implementation_ids ) @@ -529,32 +516,27 @@ void get_relevant_accounts( const object* obj, flat_set& accoun case impl_asset_bitasset_data_object_type: break; case impl_account_balance_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->owner ); break; } case impl_account_statistics_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->owner ); break; } case impl_transaction_history_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); - transaction_get_impacted_accounts( aobj->trx, accounts, - ignore_custom_operation_required_auths ); + const auto* aobj = dynamic_cast(obj); + transaction_get_impacted_accs( aobj->trx, accounts, + ignore_custom_op_required_auths ); break; } case impl_blinded_balance_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); for( const auto& a : aobj->owner.account_auths ) accounts.insert( a.first ); break; } case impl_block_summary_object_type: break; case impl_account_transaction_history_object_type: { - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->account ); break; } case impl_chain_property_object_type: @@ -570,17 +552,16 @@ void get_relevant_accounts( const object* obj, flat_set& accoun case impl_fba_accumulator_object_type: break; case impl_collateral_bid_object_type:{ - const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + const auto* aobj = dynamic_cast(obj); accounts.insert( aobj->bidder ); break; } case impl_credit_deal_summary_object_type:{ const auto* aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->offer_owner ); accounts.insert( aobj->borrower ); break; } + // Do not have a default fallback so that there will be a compiler warning when a new type is added } } } // end get_relevant_accounts( const object* obj, flat_set& accounts ) @@ -597,7 +578,7 @@ void database::notify_on_pending_transaction( const signed_transaction& tx ) void database::notify_changed_objects() { try { - if( _undo_db.enabled() ) + if( _undo_db.enabled() ) { const auto& head_undo = _undo_db.head(); auto chain_time = head_block_time(); @@ -605,12 +586,13 @@ void database::notify_changed_objects() // New if( !new_objects.empty() ) { - vector new_ids; new_ids.reserve(head_undo.new_ids.size()); + vector new_ids; + new_ids.reserve(head_undo.new_ids.size()); flat_set new_accounts_impacted; for( const auto& item : head_undo.new_ids ) { new_ids.push_back(item); - auto obj = find_object(item); + auto* obj = find_object(item); if(obj != nullptr) get_relevant_accounts(obj, new_accounts_impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time)); @@ -623,7 +605,8 @@ void database::notify_changed_objects() // Changed if( !changed_objects.empty() ) { - vector changed_ids; changed_ids.reserve(head_undo.old_values.size()); + vector changed_ids; + changed_ids.reserve(head_undo.old_values.size()); flat_set changed_accounts_impacted; for( const auto& item : head_undo.old_values ) { @@ -639,13 +622,15 @@ void database::notify_changed_objects() // Removed if( !removed_objects.empty() ) { - vector removed_ids; removed_ids.reserve( head_undo.removed.size() ); - vector removed; removed.reserve( head_undo.removed.size() ); + vector removed_ids; + removed_ids.reserve( head_undo.removed.size() ); + vector removed; + removed.reserve( head_undo.removed.size() ); flat_set removed_accounts_impacted; for( const auto& item : head_undo.removed ) { removed_ids.emplace_back( item.first ); - auto obj = item.second.get(); + auto* obj = item.second.get(); removed.emplace_back( obj ); get_relevant_accounts(obj, removed_accounts_impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(chain_time)); diff --git a/libraries/protocol/credit_offer.cpp b/libraries/protocol/credit_offer.cpp index be15d9222f..b8131b3a5f 100644 --- a/libraries/protocol/credit_offer.cpp +++ b/libraries/protocol/credit_offer.cpp @@ -32,8 +32,8 @@ static void validate_acceptable_collateral( const flat_map { FC_ASSERT( !acceptable_collateral.empty(), "Acceptable collateral list should not be empty" ); - asset_id_type asset_type = ( p_asset_type != nullptr ? *p_asset_type - : acceptable_collateral.begin()->second.base.asset_id ); + asset_id_type asset_type = ( p_asset_type != nullptr ) ? *p_asset_type + : acceptable_collateral.begin()->second.base.asset_id; for( const auto& collateral : acceptable_collateral ) { From 67cc36b3996cb113daa481c0684c78eb7d6ee245 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 27 Jun 2021 21:12:42 +0000 Subject: [PATCH 061/258] Reduce default SameT Fund and credit offer fees --- libraries/protocol/include/graphene/protocol/credit_offer.hpp | 4 ++-- libraries/protocol/include/graphene/protocol/samet_fund.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/protocol/include/graphene/protocol/credit_offer.hpp b/libraries/protocol/include/graphene/protocol/credit_offer.hpp index 4496c2224b..5f436938d3 100644 --- a/libraries/protocol/include/graphene/protocol/credit_offer.hpp +++ b/libraries/protocol/include/graphene/protocol/credit_offer.hpp @@ -36,7 +36,7 @@ namespace graphene { namespace protocol { struct credit_offer_create_operation : public base_operation { struct fee_parameters_type { - uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; @@ -88,7 +88,7 @@ namespace graphene { namespace protocol { struct credit_offer_update_operation : public base_operation { struct fee_parameters_type { - uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp index 098fd6774e..68dbc406c3 100644 --- a/libraries/protocol/include/graphene/protocol/samet_fund.hpp +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -35,7 +35,7 @@ namespace graphene { namespace protocol { */ struct samet_fund_create_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type owner_account; ///< Owner of the fund @@ -73,7 +73,7 @@ namespace graphene { namespace protocol { */ struct samet_fund_update_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = 5 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_parameters_type { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< Operation fee account_id_type owner_account; ///< Owner of the fund From 554a16db486842fa5cfa33f1af9cf185bf6eebb4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 27 Jun 2021 18:12:59 -0400 Subject: [PATCH 062/258] Notify all related accounts on account creation --- libraries/chain/hardfork.d/CORE_265.hf | 6 ++++++ .../plugins/account_history/account_history_plugin.cpp | 4 +++- libraries/plugins/elasticsearch/elasticsearch_plugin.cpp | 4 +++- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_265.hf diff --git a/libraries/chain/hardfork.d/CORE_265.hf b/libraries/chain/hardfork.d/CORE_265.hf new file mode 100644 index 0000000000..2c5c684a91 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_265.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #265 Account_history plugin: notify all related accounts after a new account is created +#ifndef HARDFORK_CORE_265_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_265_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_265_PASSED(now) (now >= HARDFORK_CORE_265_TIME) +#endif diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 6b6eadae63..4828f5d7ba 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -134,7 +134,9 @@ void account_history_plugin_impl::update_account_histories( const signed_block& if( op.op.is_type< account_create_operation >() ) impacted.insert( op.result.get() ); - else + + // https://github.com/bitshares/bitshares-core/issues/265 + if( HARDFORK_CORE_265_PASSED(b.timestamp) || !op.op.is_type< account_create_operation >() ) { operation_get_impacted_accounts( op.op, impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); diff --git a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp index fd51bf9eb8..ade7bd7ef0 100644 --- a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp +++ b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp @@ -164,7 +164,9 @@ bool elasticsearch_plugin_impl::update_account_histories( const signed_block& b if( op.op.is_type< account_create_operation >() ) impacted.insert( op.result.get() ); - else + + // https://github.com/bitshares/bitshares-core/issues/265 + if( HARDFORK_CORE_265_PASSED(b.timestamp) || !op.op.is_type< account_create_operation >() ) { operation_get_impacted_accounts( op.op, impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); From 553c814c1a4a0797eb8d9f6ba4d854e01b0f2045 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 27 Jun 2021 21:49:41 +0000 Subject: [PATCH 063/258] Add tests for database APIs about credit deals --- tests/tests/credit_offer_tests.cpp | 189 ++++++++++++++++++++--------- 1 file changed, 132 insertions(+), 57 deletions(-) diff --git a/tests/tests/credit_offer_tests.cpp b/tests/tests/credit_offer_tests.cpp index 8ff7af050b..70d7476a3a 100644 --- a/tests/tests/credit_offer_tests.cpp +++ b/tests/tests/credit_offer_tests.cpp @@ -1765,9 +1765,11 @@ BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) generate_blocks( HARDFORK_CORE_2362_TIME ); set_expiration( db, trx ); - ACTORS((sam)(ted)); + ACTORS((bob)(ray)(sam)(ted)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( bob, asset(init_amount) ); + fund( ray, asset(init_amount) ); fund( sam, asset(init_amount) ); fund( ted, asset(init_amount) ); @@ -1775,17 +1777,22 @@ BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) const asset_object& usd = create_user_issued_asset( "MYUSD" ); asset_id_type usd_id = usd.id; + issue_uia( bob, usd.amount(init_amount) ); + issue_uia( ray, usd.amount(init_amount) ); issue_uia( sam, usd.amount(init_amount) ); issue_uia( ted, usd.amount(init_amount) ); const asset_object& eur = create_user_issued_asset( "MYEUR", sam, white_list ); asset_id_type eur_id = eur.id; + issue_uia( bob, eur.amount(init_amount) ); + issue_uia( ray, eur.amount(init_amount) ); issue_uia( sam, eur.amount(init_amount) ); issue_uia( ted, eur.amount(init_amount) ); // create credit offers flat_map collateral_map_core; collateral_map_core[usd_id] = price( asset(1), asset(2, usd_id) ); + collateral_map_core[eur_id] = price( asset(1), asset(1, eur_id) ); flat_map collateral_map_usd; collateral_map_usd[eur_id] = price( asset(1, usd_id), asset(1, eur_id) ); @@ -1793,28 +1800,28 @@ BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) flat_map collateral_map_eur; collateral_map_eur[core_id] = price( asset(1, eur_id), asset(3) ); - const credit_offer_object& coo1 = create_credit_offer( sam_id, core_id, 10000, 30000, 3600, 0, false, - time_point_sec(), collateral_map_core, {} ); + const credit_offer_object& coo1 = create_credit_offer( sam_id, core_id, 10000, 30000, 3600, 0, true, + db.head_block_time() + fc::days(1), collateral_map_core, {} ); credit_offer_id_type co1_id = coo1.id; - const credit_offer_object& coo2 = create_credit_offer( ted_id, usd_id, 10000, 30000, 3600, 0, false, - time_point_sec(), collateral_map_usd, {} ); + const credit_offer_object& coo2 = create_credit_offer( ted_id, usd_id, 10000, 30000, 3600, 0, true, + db.head_block_time() + fc::days(1), collateral_map_usd, {} ); credit_offer_id_type co2_id = coo2.id; - const credit_offer_object& coo3 = create_credit_offer( sam_id, eur_id, 10000, 30000, 3600, 0, false, - time_point_sec(), collateral_map_eur, {} ); + const credit_offer_object& coo3 = create_credit_offer( sam_id, eur_id, 10000, 30000, 3600, 0, true, + db.head_block_time() + fc::days(1), collateral_map_eur, {} ); credit_offer_id_type co3_id = coo3.id; - const credit_offer_object& coo4 = create_credit_offer( sam_id, eur_id, 10000, 30000, 3600, 0, false, - time_point_sec(), collateral_map_eur, {} ); + const credit_offer_object& coo4 = create_credit_offer( sam_id, eur_id, 10000, 30000, 3600, 0, true, + db.head_block_time() + fc::days(1), collateral_map_eur, {} ); credit_offer_id_type co4_id = coo4.id; - const credit_offer_object& coo5 = create_credit_offer( sam_id, usd_id, 10000, 30000, 3600, 0, false, - time_point_sec(), collateral_map_usd, {} ); + const credit_offer_object& coo5 = create_credit_offer( sam_id, usd_id, 10000, 30000, 3600, 0, true, + db.head_block_time() + fc::days(1), collateral_map_usd, {} ); credit_offer_id_type co5_id = coo5.id; - const credit_offer_object& coo6 = create_credit_offer( ted_id, usd_id, 10000, 30000, 3600, 0, false, - time_point_sec(), collateral_map_usd, {} ); + const credit_offer_object& coo6 = create_credit_offer( ted_id, usd_id, 10000, 30000, 3600, 0, true, + db.head_block_time() + fc::days(1), collateral_map_usd, {} ); credit_offer_id_type co6_id = coo6.id; generate_block(); @@ -1823,49 +1830,49 @@ BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) graphene::app::database_api db_api( db, &( app.get_options() ) ); // List all credit offers - auto funds = db_api.list_credit_offers(); - BOOST_REQUIRE_EQUAL( funds.size(), 6u ); - BOOST_CHECK( funds.front().id == co1_id ); - BOOST_CHECK( funds.back().id == co6_id ); + auto offers = db_api.list_credit_offers(); + BOOST_REQUIRE_EQUAL( offers.size(), 6u ); + BOOST_CHECK( offers.front().id == co1_id ); + BOOST_CHECK( offers.back().id == co6_id ); // Pagination : the first page - funds = db_api.list_credit_offers( 5 ); - BOOST_REQUIRE_EQUAL( funds.size(), 5u ); - BOOST_CHECK( funds.front().id == co1_id ); - BOOST_CHECK( funds.back().id == co5_id ); + offers = db_api.list_credit_offers( 5 ); + BOOST_REQUIRE_EQUAL( offers.size(), 5u ); + BOOST_CHECK( offers.front().id == co1_id ); + BOOST_CHECK( offers.back().id == co5_id ); // Pagination : the last page - funds = db_api.list_credit_offers( 5, co3_id ); - BOOST_REQUIRE_EQUAL( funds.size(), 4u ); - BOOST_CHECK( funds.front().id == co3_id ); - BOOST_CHECK( funds.back().id == co6_id ); + offers = db_api.list_credit_offers( 5, co3_id ); + BOOST_REQUIRE_EQUAL( offers.size(), 4u ); + BOOST_CHECK( offers.front().id == co3_id ); + BOOST_CHECK( offers.back().id == co6_id ); // Limit too large BOOST_CHECK_THROW( db_api.list_credit_offers( 102 ), fc::exception ); // Get all credit offers owned by Sam - funds = db_api.get_credit_offers_by_owner( "sam" ); - BOOST_REQUIRE_EQUAL( funds.size(), 4u ); - BOOST_CHECK( funds.front().id == co1_id ); - BOOST_CHECK( funds.back().id == co5_id ); + offers = db_api.get_credit_offers_by_owner( "sam" ); + BOOST_REQUIRE_EQUAL( offers.size(), 4u ); + BOOST_CHECK( offers.front().id == co1_id ); + BOOST_CHECK( offers.back().id == co5_id ); // Pagination : the first page - funds = db_api.get_credit_offers_by_owner( "sam", 3, {} ); - BOOST_REQUIRE_EQUAL( funds.size(), 3u ); - BOOST_CHECK( funds.front().id == co1_id ); - BOOST_CHECK( funds.back().id == co4_id ); + offers = db_api.get_credit_offers_by_owner( "sam", 3, {} ); + BOOST_REQUIRE_EQUAL( offers.size(), 3u ); + BOOST_CHECK( offers.front().id == co1_id ); + BOOST_CHECK( offers.back().id == co4_id ); // Pagination : another page - funds = db_api.get_credit_offers_by_owner( "sam", 3, co2_id ); - BOOST_REQUIRE_EQUAL( funds.size(), 3u ); - BOOST_CHECK( funds.front().id == co3_id ); - BOOST_CHECK( funds.back().id == co5_id ); + offers = db_api.get_credit_offers_by_owner( "sam", 3, co2_id ); + BOOST_REQUIRE_EQUAL( offers.size(), 3u ); + BOOST_CHECK( offers.front().id == co3_id ); + BOOST_CHECK( offers.back().id == co5_id ); // Pagination : the first page of credit offers owned by Ted - funds = db_api.get_credit_offers_by_owner( string("1.2.")+fc::to_string(ted_id.instance.value), 3 ); - BOOST_REQUIRE_EQUAL( funds.size(), 2u ); - BOOST_CHECK( funds.front().id == co2_id ); - BOOST_CHECK( funds.back().id == co6_id ); + offers = db_api.get_credit_offers_by_owner( string("1.2.")+fc::to_string(ted_id.instance.value), 3 ); + BOOST_REQUIRE_EQUAL( offers.size(), 2u ); + BOOST_CHECK( offers.front().id == co2_id ); + BOOST_CHECK( offers.back().id == co6_id ); // Nonexistent account BOOST_CHECK_THROW( db_api.get_credit_offers_by_owner( "nonexistent-account" ), fc::exception ); @@ -1874,28 +1881,28 @@ BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) BOOST_CHECK_THROW( db_api.get_credit_offers_by_owner( "ted", 102 ), fc::exception ); // Get all credit offers whose asset type is USD - funds = db_api.get_credit_offers_by_asset( "MYUSD" ); - BOOST_REQUIRE_EQUAL( funds.size(), 3u ); - BOOST_CHECK( funds.front().id == co2_id ); - BOOST_CHECK( funds.back().id == co6_id ); + offers = db_api.get_credit_offers_by_asset( "MYUSD" ); + BOOST_REQUIRE_EQUAL( offers.size(), 3u ); + BOOST_CHECK( offers.front().id == co2_id ); + BOOST_CHECK( offers.back().id == co6_id ); // Pagination : the first page - funds = db_api.get_credit_offers_by_asset( "MYUSD", 2 ); - BOOST_REQUIRE_EQUAL( funds.size(), 2u ); - BOOST_CHECK( funds.front().id == co2_id ); - BOOST_CHECK( funds.back().id == co5_id ); + offers = db_api.get_credit_offers_by_asset( "MYUSD", 2 ); + BOOST_REQUIRE_EQUAL( offers.size(), 2u ); + BOOST_CHECK( offers.front().id == co2_id ); + BOOST_CHECK( offers.back().id == co5_id ); // Pagination : another page - funds = db_api.get_credit_offers_by_asset( "MYUSD", 2, co4_id ); - BOOST_REQUIRE_EQUAL( funds.size(), 2u ); - BOOST_CHECK( funds.front().id == co5_id ); - BOOST_CHECK( funds.back().id == co6_id ); + offers = db_api.get_credit_offers_by_asset( "MYUSD", 2, co4_id ); + BOOST_REQUIRE_EQUAL( offers.size(), 2u ); + BOOST_CHECK( offers.front().id == co5_id ); + BOOST_CHECK( offers.back().id == co6_id ); // Pagination : the first page of credit offers whose asset type is CORE - funds = db_api.get_credit_offers_by_asset( "1.3.0", 2, {} ); - BOOST_REQUIRE_EQUAL( funds.size(), 1u ); - BOOST_CHECK( funds.front().id == co1_id ); - BOOST_CHECK( funds.back().id == co1_id ); + offers = db_api.get_credit_offers_by_asset( "1.3.0", 2, {} ); + BOOST_REQUIRE_EQUAL( offers.size(), 1u ); + BOOST_CHECK( offers.front().id == co1_id ); + BOOST_CHECK( offers.back().id == co1_id ); // Nonexistent asset BOOST_CHECK_THROW( db_api.get_credit_offers_by_asset( "NOSUCHASSET" ), fc::exception ); @@ -1903,6 +1910,74 @@ BOOST_AUTO_TEST_CASE( credit_offer_apis_test ) // Limit too large BOOST_CHECK_THROW( db_api.get_credit_offers_by_asset( "MYUSD", 102 ), fc::exception ); + // Create credit deals + // Offer owner : sam + const credit_deal_object& cdo11 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id) ); + credit_deal_id_type cd11_id = cdo11.id; + + // Offer owner : sam + const credit_deal_object& cdo12 = borrow_from_credit_offer( ray_id, co1_id, asset(150), asset(400, eur_id) ); + credit_deal_id_type cd12_id = cdo12.id; + + // Offer owner : sam + const credit_deal_object& cdo13 = borrow_from_credit_offer( bob_id, co1_id, asset(200), asset(600, eur_id) ); + credit_deal_id_type cd13_id = cdo13.id; + + // Offer owner : ted + const credit_deal_object& cdo21 = borrow_from_credit_offer( bob_id, co2_id, asset(500, usd_id), + asset(500, eur_id) ); + credit_deal_id_type cd21_id = cdo21.id; + + // Offer owner : sam + const credit_deal_object& cdo31 = borrow_from_credit_offer( bob_id, co3_id, asset(500, eur_id), asset(5000) ); + credit_deal_id_type cd31_id = cdo31.id; + + // Offer owner : sam + const credit_deal_object& cdo51 = borrow_from_credit_offer( ray_id, co5_id, asset(400, usd_id), + asset(800, eur_id) ); + credit_deal_id_type cd51_id = cdo51.id; + + generate_block(); + + // Since all APIs are now calling the same template function, + // no need to to test with many cases only for pagination. + auto deals = db_api.list_credit_deals(); + BOOST_REQUIRE_EQUAL( deals.size(), 6u ); + BOOST_CHECK( deals.front().id == cd11_id ); + BOOST_CHECK( deals.back().id == cd51_id ); + + deals = db_api.get_credit_deals_by_offer_id( co1_id ); + BOOST_REQUIRE_EQUAL( deals.size(), 3u ); + BOOST_CHECK( deals[0].id == cd11_id ); + BOOST_CHECK( deals[1].id == cd12_id ); + BOOST_CHECK( deals[2].id == cd13_id ); + + deals = db_api.get_credit_deals_by_offer_owner( "sam" ); + BOOST_REQUIRE_EQUAL( deals.size(), 5u ); + BOOST_CHECK( deals[0].id == cd11_id ); + BOOST_CHECK( deals[1].id == cd12_id ); + BOOST_CHECK( deals[2].id == cd13_id ); + BOOST_CHECK( deals[3].id == cd31_id ); + BOOST_CHECK( deals[4].id == cd51_id ); + + deals = db_api.get_credit_deals_by_borrower( "bob" ); + BOOST_REQUIRE_EQUAL( deals.size(), 3u ); + BOOST_CHECK( deals[0].id == cd13_id ); + BOOST_CHECK( deals[1].id == cd21_id ); + BOOST_CHECK( deals[2].id == cd31_id ); + + deals = db_api.get_credit_deals_by_debt_asset( "MYUSD" ); + BOOST_REQUIRE_EQUAL( deals.size(), 2u ); + BOOST_CHECK( deals[0].id == cd21_id ); + BOOST_CHECK( deals[1].id == cd51_id ); + + deals = db_api.get_credit_deals_by_collateral_asset( "MYEUR" ); + BOOST_REQUIRE_EQUAL( deals.size(), 4u ); + BOOST_CHECK( deals[0].id == cd12_id ); + BOOST_CHECK( deals[1].id == cd13_id ); + BOOST_CHECK( deals[2].id == cd21_id ); + BOOST_CHECK( deals[3].id == cd51_id ); + } catch (fc::exception& e) { edump((e.to_detail_string())); throw; From 1ad4cf32615ed9a1a2d1e65768d76bf8deeb11db Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 27 Jun 2021 23:57:35 +0000 Subject: [PATCH 064/258] Change return types of SameT Fund borrow and repay Return extendable_operation_result --- .../graphene/chain/samet_fund_evaluator.hpp | 4 ++-- libraries/chain/samet_fund_evaluator.cpp | 14 ++++++++++---- .../account_history/account_history_plugin.cpp | 3 --- .../plugins/elasticsearch/elasticsearch_plugin.cpp | 3 --- .../include/graphene/protocol/samet_fund.hpp | 2 -- tests/common/database_fixture.cpp | 12 ++++++++++-- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp index 931a34b7cf..ae23c80b46 100644 --- a/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/samet_fund_evaluator.hpp @@ -67,7 +67,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_borrow_operation; void_result do_evaluate( const samet_fund_borrow_operation& op ); - object_id_type do_apply( const samet_fund_borrow_operation& op ) const; + extendable_operation_result do_apply( const samet_fund_borrow_operation& op ) const; const samet_fund_object* _fund = nullptr; }; @@ -78,7 +78,7 @@ namespace graphene { namespace chain { using operation_type = samet_fund_repay_operation; void_result do_evaluate( const samet_fund_repay_operation& op ); - object_id_type do_apply( const samet_fund_repay_operation& op ) const; + extendable_operation_result do_apply( const samet_fund_repay_operation& op ) const; const samet_fund_object* _fund = nullptr; }; diff --git a/libraries/chain/samet_fund_evaluator.cpp b/libraries/chain/samet_fund_evaluator.cpp index b244a15908..f9ef0ca3ea 100644 --- a/libraries/chain/samet_fund_evaluator.cpp +++ b/libraries/chain/samet_fund_evaluator.cpp @@ -164,7 +164,7 @@ void_result samet_fund_borrow_evaluator::do_evaluate(const samet_fund_borrow_ope return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -object_id_type samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_operation& op) const +extendable_operation_result samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_operation& op) const { try { database& d = db(); @@ -177,7 +177,10 @@ object_id_type samet_fund_borrow_evaluator::do_apply( const samet_fund_borrow_op // Defensive check FC_ASSERT( _fund->balance >= _fund->unpaid_amount, "Should not borrow more than available" ); - return _fund->owner_account; + extendable_operation_result result; + result.value.impacted_accounts = flat_set({ _fund->owner_account }); + + return result; } FC_CAPTURE_AND_RETHROW( (op) ) } void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_operation& op) @@ -205,7 +208,7 @@ void_result samet_fund_repay_evaluator::do_evaluate(const samet_fund_repay_opera return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -object_id_type samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operation& op) const +extendable_operation_result samet_fund_repay_evaluator::do_apply( const samet_fund_repay_operation& op) const { try { database& d = db(); @@ -216,7 +219,10 @@ object_id_type samet_fund_repay_evaluator::do_apply( const samet_fund_repay_oper sfo.unpaid_amount -= op.repay_amount.amount; }); - return _fund->owner_account; + extendable_operation_result result; + result.value.impacted_accounts = flat_set({ _fund->owner_account }); + + return result; } FC_CAPTURE_AND_RETHROW( (op) ) } } } // graphene::chain diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 062e999052..18c9a7e113 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -138,9 +138,6 @@ void account_history_plugin_impl::update_account_histories( const signed_block& { operation_get_impacted_accounts( op.op, impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); - // TODO change return type - if( op.op.is_type< samet_fund_borrow_operation >() || op.op.is_type< samet_fund_repay_operation >() ) - impacted.insert( op.result.get() ); } if( op.result.is_type() ) diff --git a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp index 49fbc7119a..a7f12721c3 100644 --- a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp +++ b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp @@ -168,9 +168,6 @@ bool elasticsearch_plugin_impl::update_account_histories( const signed_block& b { operation_get_impacted_accounts( op.op, impacted, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ) ); - // TODO change return type - if( op.op.is_type< samet_fund_borrow_operation >() || op.op.is_type< samet_fund_repay_operation >() ) - impacted.insert( op.result.get() ); } if( op.result.is_type() ) diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp index 68dbc406c3..6b19fc670f 100644 --- a/libraries/protocol/include/graphene/protocol/samet_fund.hpp +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -90,7 +90,6 @@ namespace graphene { namespace protocol { /** * @brief Borrow from a SameT Fund * @ingroup operations - * @note The result of this operation is the ID of the owner account of the SameT Fund */ struct samet_fund_borrow_operation : public base_operation { @@ -110,7 +109,6 @@ namespace graphene { namespace protocol { /** * @brief Repay to a SameT Fund * @ingroup operations - * @note The result of this operation is the ID of the owner account of the SameT Fund */ struct samet_fund_repay_operation : public base_operation { diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 0e9d9bf6a6..2c04862c0f 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1636,9 +1636,13 @@ void database_fixture_base::borrow_from_samet_fund( account_id_type account, sam for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); trx.validate(); set_expiration( db, trx ); - PUSH_TX(db, trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); trx.operations.clear(); verify_asset_supplies(db); + const auto& result_dtl = op_result.get().value; + BOOST_REQUIRE( result_dtl.impacted_accounts.valid() ); + BOOST_CHECK( *result_dtl.impacted_accounts == flat_set({ fund_id(db).owner_account }) ); } samet_fund_repay_operation database_fixture_base::make_samet_fund_repay_op( @@ -1663,9 +1667,13 @@ void database_fixture_base::repay_to_samet_fund( account_id_type account, samet_ for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); trx.validate(); set_expiration( db, trx ); - PUSH_TX(db, trx, ~0); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); trx.operations.clear(); verify_asset_supplies(db); + const auto& result_dtl = op_result.get().value; + BOOST_REQUIRE( result_dtl.impacted_accounts.valid() ); + BOOST_CHECK( *result_dtl.impacted_accounts == flat_set({ fund_id(db).owner_account }) ); } credit_offer_create_operation database_fixture_base::make_credit_offer_create_op( From 71d16e99b37ca7ddf4709afc66f072d37ed47f90 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 27 Jun 2021 18:34:28 -0400 Subject: [PATCH 065/258] Add tests for account creation notification --- tests/tests/history_api_tests.cpp | 84 ++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/tests/tests/history_api_tests.cpp b/tests/tests/history_api_tests.cpp index 877762bc3b..deab20400d 100644 --- a/tests/tests/history_api_tests.cpp +++ b/tests/tests/history_api_tests.cpp @@ -26,6 +26,8 @@ #include +#include + #include #include @@ -43,8 +45,66 @@ BOOST_AUTO_TEST_CASE(get_account_history) { //account_id_type() do 3 ops create_bitasset("USD", account_id_type()); - create_account("dan"); - create_account("bob"); + create_account( "dan", account_id_type()(db), GRAPHENE_WITNESS_ACCOUNT(db) ); + create_account( "bob", account_id_type()(db), GRAPHENE_TEMP_ACCOUNT(db) ); + + generate_block(); + fc::usleep(fc::milliseconds(2000)); + + int asset_create_op_id = operation::tag::value; + int account_create_op_id = operation::tag::value; + + //account_id_type() did 3 ops and includes id0 + vector histories = hist_api.get_account_history("1.2.0", operation_history_id_type(), + 100, operation_history_id_type()); + + BOOST_CHECK_EQUAL(histories.size(), 3u); + BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u); + BOOST_CHECK_EQUAL(histories[2].op.which(), asset_create_op_id); + + // 1 account_create op larger than id1 + histories = hist_api.get_account_history("1.2.0", operation_history_id_type(1), + 100, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK(histories[0].id.instance() != 0); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + // Limit 2 returns 2 result + histories = hist_api.get_account_history("1.2.0", operation_history_id_type(), + 2, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 2u); + BOOST_CHECK(histories[1].id.instance() != 0); + BOOST_CHECK_EQUAL(histories[1].op.which(), account_create_op_id); + + // bob has 1 op + histories = hist_api.get_account_history("bob", operation_history_id_type(), + 100, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + // witness-account has 0 op + histories = hist_api.get_account_history("witness-account", operation_history_id_type(), + 100, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 0u); + + } catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(get_account_history_notify_all_on_creation) { + try { + // Pass hard fork time + generate_blocks(HARDFORK_CORE_265_TIME); + set_expiration( db, trx ); + + graphene::app::history_api hist_api(app); + + //account_id_type() do 3 ops + create_bitasset("USD", account_id_type()); + create_account( "dan", account_id_type()(db), GRAPHENE_WITNESS_ACCOUNT(db) ); + create_account( "bob", account_id_type()(db), GRAPHENE_TEMP_ACCOUNT(db) ); generate_block(); fc::usleep(fc::milliseconds(2000)); @@ -53,25 +113,36 @@ BOOST_AUTO_TEST_CASE(get_account_history) { int account_create_op_id = operation::tag::value; //account_id_type() did 3 ops and includes id0 - vector histories = hist_api.get_account_history("1.2.0", operation_history_id_type(), 100, operation_history_id_type()); + vector histories = hist_api.get_account_history("1.2.0", operation_history_id_type(), + 100, operation_history_id_type()); BOOST_CHECK_EQUAL(histories.size(), 3u); BOOST_CHECK_EQUAL(histories[2].id.instance(), 0u); BOOST_CHECK_EQUAL(histories[2].op.which(), asset_create_op_id); // 1 account_create op larger than id1 - histories = hist_api.get_account_history("1.2.0", operation_history_id_type(1), 100, operation_history_id_type()); + histories = hist_api.get_account_history("1.2.0", operation_history_id_type(1), + 100, operation_history_id_type()); BOOST_CHECK_EQUAL(histories.size(), 1u); BOOST_CHECK(histories[0].id.instance() != 0); BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); // Limit 2 returns 2 result - histories = hist_api.get_account_history("1.2.0", operation_history_id_type(), 2, operation_history_id_type()); + histories = hist_api.get_account_history("1.2.0", operation_history_id_type(), + 2, operation_history_id_type()); BOOST_CHECK_EQUAL(histories.size(), 2u); BOOST_CHECK(histories[1].id.instance() != 0); BOOST_CHECK_EQUAL(histories[1].op.which(), account_create_op_id); + // bob has 1 op - histories = hist_api.get_account_history("bob", operation_history_id_type(), 100, operation_history_id_type()); + histories = hist_api.get_account_history("bob", operation_history_id_type(), + 100, operation_history_id_type()); + BOOST_CHECK_EQUAL(histories.size(), 1u); + BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); + + // witness-account has 1 op + histories = hist_api.get_account_history("witness-account", operation_history_id_type(), + 100, operation_history_id_type()); BOOST_CHECK_EQUAL(histories.size(), 1u); BOOST_CHECK_EQUAL(histories[0].op.which(), account_create_op_id); @@ -80,6 +151,7 @@ BOOST_AUTO_TEST_CASE(get_account_history) { throw; } } + BOOST_AUTO_TEST_CASE(get_account_history_additional) { try { graphene::app::history_api hist_api(app); From 2e9a09a35c746d35b5468e1094abda0632bb67e5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 28 Jun 2021 09:07:29 +0000 Subject: [PATCH 066/258] Move fee_schedule::set_fee(...) to a new file Fix MinGW64 build --- libraries/protocol/CMakeLists.txt | 1 + libraries/protocol/fee_schedule.cpp | 58 ++++------------- libraries/protocol/fee_schedule_calc.cpp | 11 ++-- libraries/protocol/fee_schedule_set_fee.cpp | 63 +++++++++++++++++++ .../include/graphene/protocol/config.hpp | 3 + .../graphene/protocol/fee_schedule.hpp | 12 ++-- 6 files changed, 90 insertions(+), 58 deletions(-) create mode 100644 libraries/protocol/fee_schedule_set_fee.cpp diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index da2ffa9adc..e57907e91b 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -10,6 +10,7 @@ list(APPEND SOURCES account.cpp fee_schedule_calc.cpp fee_schedule_serialization_pack.cpp fee_schedule_serialization_variant.cpp + fee_schedule_set_fee.cpp memo.cpp proposal.cpp transfer.cpp diff --git a/libraries/protocol/fee_schedule.cpp b/libraries/protocol/fee_schedule.cpp index a67f285625..929fa555bc 100644 --- a/libraries/protocol/fee_schedule.cpp +++ b/libraries/protocol/fee_schedule.cpp @@ -21,47 +21,33 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include #include -#include - -#define MAX_FEE_STABILIZATION_ITERATION 4 - namespace graphene { namespace protocol { - fee_schedule::fee_schedule() - { - } - - fee_schedule fee_schedule::get_default() + const fee_schedule fee_schedule::get_default_impl() { fee_schedule result; - for( size_t i = 0; i < fee_parameters().count(); ++i ) + const auto count = fee_parameters::count(); + result.parameters.reserve(count); + for( size_t i = 0; i < count; ++i ) { - fee_parameters x; x.set_which(i); + fee_parameters x; + x.set_which(i); result.parameters.insert(x); } return result; } - struct set_fee_visitor + const fee_schedule& fee_schedule::get_default() { - typedef void result_type; - asset _fee; - - set_fee_visitor( asset f ):_fee(f){} - - template - void operator()( OpType& op )const - { - op.fee = _fee; - } - }; + static const auto result = get_default_impl(); + return result; + } struct zero_fee_visitor { - typedef void result_type; + using result_type = void; template result_type operator()( ParamType& op )const @@ -78,26 +64,4 @@ namespace graphene { namespace protocol { this->scale = 0; } - asset fee_schedule::set_fee( operation& op, const price& core_exchange_rate )const - { - auto f = calculate_fee( op, core_exchange_rate ); - auto f_max = f; - for( size_t i=0; i result_type operator()( const OpType& op )const @@ -42,9 +43,11 @@ namespace graphene { namespace protocol { try { return op.calculate_fee( param.get() ).value; } catch (fc::assert_exception& e) { - fee_parameters params; params.set_which(current_op); + fee_parameters params; + params.set_which(current_op); auto itr = param.parameters.find(params); - if( itr != param.parameters.end() ) params = *itr; + if( itr != param.parameters.end() ) + params = *itr; return op.calculate_fee( params.get() ).value; } } diff --git a/libraries/protocol/fee_schedule_set_fee.cpp b/libraries/protocol/fee_schedule_set_fee.cpp new file mode 100644 index 0000000000..29616888e9 --- /dev/null +++ b/libraries/protocol/fee_schedule_set_fee.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., 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 + +namespace graphene { namespace protocol { + + struct set_fee_visitor + { + using result_type = void; + + asset _fee; + + set_fee_visitor( const asset& f ):_fee(f){} + + template + void operator()( OpType& op )const + { + op.fee = _fee; + } + }; + + asset fee_schedule::set_fee( operation& op, const price& core_exchange_rate )const + { + auto f = calculate_fee( op, core_exchange_rate ); + for( size_t i=0; i= f2 ) + break; + f = f2; + if( i == 0 ) + { + // no need for warnings on later iterations + wlog( "set_fee requires multiple iterations to stabilize with core_exchange_rate ${p} on operation ${op}", + ("p", core_exchange_rate) ("op", op) ); + } + } + return f; + } + +} } // graphene::protocol diff --git a/libraries/protocol/include/graphene/protocol/config.hpp b/libraries/protocol/include/graphene/protocol/config.hpp index 64379ff427..c03ca39c3d 100644 --- a/libraries/protocol/include/graphene/protocol/config.hpp +++ b/libraries/protocol/include/graphene/protocol/config.hpp @@ -129,6 +129,9 @@ constexpr int64_t GRAPHENE_MAX_CREDIT_DEAL_DAYS = 380; /// How long a credit deal will be kept, in seconds constexpr int64_t GRAPHENE_MAX_CREDIT_DEAL_SECS = GRAPHENE_MAX_CREDIT_DEAL_DAYS * 86400; +/// How many iterations to run in @c fee_schedule::set_fee() +constexpr size_t MAX_FEE_STABILIZATION_ITERATION = 4; + /** * Reserved Account IDs with special meaning */ diff --git a/libraries/protocol/include/graphene/protocol/fee_schedule.hpp b/libraries/protocol/include/graphene/protocol/fee_schedule.hpp index ab57661efa..30f615d7e6 100644 --- a/libraries/protocol/include/graphene/protocol/fee_schedule.hpp +++ b/libraries/protocol/include/graphene/protocol/fee_schedule.hpp @@ -172,9 +172,7 @@ namespace graphene { namespace protocol { */ struct fee_schedule { - fee_schedule(); - - static fee_schedule get_default(); + static const fee_schedule& get_default(); /** * Finds the appropriate fee parameter struct for the operation @@ -220,12 +218,12 @@ namespace graphene { namespace protocol { * @note must be sorted by fee_parameters.which() and have no duplicates */ fee_parameters::flat_set_type parameters; - uint32_t scale = GRAPHENE_100_PERCENT; ///< fee * scale / GRAPHENE_100_PERCENT - private: - static void set_fee_parameters(fee_schedule& sched); + uint32_t scale = GRAPHENE_100_PERCENT; ///< fee * scale / GRAPHENE_100_PERCENT + private: + static const fee_schedule get_default_impl(); }; - typedef fee_schedule fee_schedule_type; + using fee_schedule_type = fee_schedule; } } // graphene::protocol From cd6ffa1ad5a1c18ac66ca8e65a2e4087f22a3f03 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 28 Jun 2021 12:35:49 +0000 Subject: [PATCH 067/258] Fix code smells --- libraries/protocol/fee_schedule.cpp | 2 +- libraries/protocol/fee_schedule_set_fee.cpp | 6 +++--- .../protocol/include/graphene/protocol/fee_schedule.hpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/protocol/fee_schedule.cpp b/libraries/protocol/fee_schedule.cpp index 929fa555bc..9416ae0fe3 100644 --- a/libraries/protocol/fee_schedule.cpp +++ b/libraries/protocol/fee_schedule.cpp @@ -25,7 +25,7 @@ namespace graphene { namespace protocol { - const fee_schedule fee_schedule::get_default_impl() + fee_schedule fee_schedule::get_default_impl() { fee_schedule result; const auto count = fee_parameters::count(); diff --git a/libraries/protocol/fee_schedule_set_fee.cpp b/libraries/protocol/fee_schedule_set_fee.cpp index 29616888e9..503ee89aa8 100644 --- a/libraries/protocol/fee_schedule_set_fee.cpp +++ b/libraries/protocol/fee_schedule_set_fee.cpp @@ -31,7 +31,7 @@ namespace graphene { namespace protocol { asset _fee; - set_fee_visitor( const asset& f ):_fee(f){} + explicit set_fee_visitor( const asset& f ):_fee(f){} template void operator()( OpType& op )const @@ -43,14 +43,14 @@ namespace graphene { namespace protocol { asset fee_schedule::set_fee( operation& op, const price& core_exchange_rate )const { auto f = calculate_fee( op, core_exchange_rate ); - for( size_t i=0; i= f2 ) break; f = f2; - if( i == 0 ) + if( 0 == i ) { // no need for warnings on later iterations wlog( "set_fee requires multiple iterations to stabilize with core_exchange_rate ${p} on operation ${op}", diff --git a/libraries/protocol/include/graphene/protocol/fee_schedule.hpp b/libraries/protocol/include/graphene/protocol/fee_schedule.hpp index 30f615d7e6..f3adc6840c 100644 --- a/libraries/protocol/include/graphene/protocol/fee_schedule.hpp +++ b/libraries/protocol/include/graphene/protocol/fee_schedule.hpp @@ -220,7 +220,7 @@ namespace graphene { namespace protocol { fee_parameters::flat_set_type parameters; uint32_t scale = GRAPHENE_100_PERCENT; ///< fee * scale / GRAPHENE_100_PERCENT private: - static const fee_schedule get_default_impl(); + static fee_schedule get_default_impl(); }; using fee_schedule_type = fee_schedule; From a3273901802fb74fb0b1ad19d9509f2f3debbc9a Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 29 Jun 2021 09:27:28 +0000 Subject: [PATCH 068/258] Remove BSIP-40 support for some operations Since BSIP-40 is not to be enabled on the BitShares Mainnet any time soon, to reduce the compilation time and the size of binaries, we temporarily remove support for unused operations. New operations will be added when we decide to continue BSIP-40 development. --- libraries/protocol/CMakeLists.txt | 5 +-- .../protocol/custom_authorities/list_12.cpp | 42 ------------------- .../protocol/custom_authorities/list_13.cpp | 42 ------------------- .../protocol/custom_authorities/list_14.cpp | 42 ------------------- .../restriction_predicate.cpp | 15 ++----- .../custom_authorities/sliced_lists.hxx | 22 +++++----- 6 files changed, 16 insertions(+), 152 deletions(-) delete mode 100644 libraries/protocol/custom_authorities/list_12.cpp delete mode 100644 libraries/protocol/custom_authorities/list_13.cpp delete mode 100644 libraries/protocol/custom_authorities/list_14.cpp diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index e57907e91b..21d619bcad 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -54,10 +54,7 @@ list(APPEND CUSTOM_AUTHS_FILES custom_authorities/list_8.cpp custom_authorities/list_9.cpp custom_authorities/list_10.cpp - custom_authorities/list_11.cpp - custom_authorities/list_12.cpp - custom_authorities/list_13.cpp - custom_authorities/list_14.cpp) + custom_authorities/list_11.cpp) file(GLOB CUSTOM_AUTHS_HEADERS "custom_authorities/*.hxx") diff --git a/libraries/protocol/custom_authorities/list_12.cpp b/libraries/protocol/custom_authorities/list_12.cpp deleted file mode 100644 index 8311b66c0d..0000000000 --- a/libraries/protocol/custom_authorities/list_12.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2019 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 "restriction_predicate.hxx" -#include "sliced_lists.hxx" - -namespace graphene { namespace protocol { -using result_type = object_restriction_predicate; - -result_type get_restriction_pred_list_12(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_12::list(), idx, [&rs] (auto t) { - using Op = typename decltype(t)::type; - result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { - FC_ASSERT(op.which() == operation::tag::value, - "Supplied operation is incorrect type for restriction predicate"); - return p(op.get()); - }; - return to_return; - }); -} -} } diff --git a/libraries/protocol/custom_authorities/list_13.cpp b/libraries/protocol/custom_authorities/list_13.cpp deleted file mode 100644 index 8c4f74c508..0000000000 --- a/libraries/protocol/custom_authorities/list_13.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2019 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 "restriction_predicate.hxx" -#include "sliced_lists.hxx" - -namespace graphene { namespace protocol { -using result_type = object_restriction_predicate; - -result_type get_restriction_pred_list_13(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_13::list(), idx, [&rs] (auto t) { - using Op = typename decltype(t)::type; - result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { - FC_ASSERT(op.which() == operation::tag::value, - "Supplied operation is incorrect type for restriction predicate"); - return p(op.get()); - }; - return to_return; - }); -} -} } diff --git a/libraries/protocol/custom_authorities/list_14.cpp b/libraries/protocol/custom_authorities/list_14.cpp deleted file mode 100644 index 2560321b49..0000000000 --- a/libraries/protocol/custom_authorities/list_14.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2019 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 "restriction_predicate.hxx" -#include "sliced_lists.hxx" - -namespace graphene { namespace protocol { -using result_type = object_restriction_predicate; - -result_type get_restriction_pred_list_14(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_14::list(), idx, [&rs] (auto t) { - using Op = typename decltype(t)::type; - result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { - FC_ASSERT(op.which() == operation::tag::value, - "Supplied operation is incorrect type for restriction predicate"); - return p(op.get()); - }; - return to_return; - }); -} -} } diff --git a/libraries/protocol/custom_authorities/restriction_predicate.cpp b/libraries/protocol/custom_authorities/restriction_predicate.cpp index f42ad6a016..13c67e72f9 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.cpp +++ b/libraries/protocol/custom_authorities/restriction_predicate.cpp @@ -54,14 +54,8 @@ restriction_predicate_function get_restriction_predicate(vector rs, return get_restriction_pred_list_10(typelist::index_of(), std::move(rs)); if (typelist::contains()) return get_restriction_pred_list_11(typelist::index_of(), std::move(rs)); - if (typelist::contains()) - return get_restriction_pred_list_12(typelist::index_of(), std::move(rs)); - if (typelist::contains()) - return get_restriction_pred_list_13(typelist::index_of(), std::move(rs)); - if (typelist::contains()) - return get_restriction_pred_list_14(typelist::index_of(), std::move(rs)); - if (typelist::contains()) - FC_THROW_EXCEPTION( fc::assert_exception, "Virtual operations not allowed!" ); + if (typelist::contains()) + FC_THROW_EXCEPTION( fc::assert_exception, "Unsupported operation detected!" ); // Compile time check that we'll never get to the exception below static_assert(typelist::contains rs, operation_list_5::list, operation_list_6::list, operation_list_7::list, operation_list_8::list, operation_list_9::list, operation_list_10::list, - operation_list_11::list, operation_list_12::list, - operation_list_13::list, operation_list_14::list, - virtual_operations_list::list>, + operation_list_11::list, + unsupported_operations_list::list>, Op>(), ""); FC_THROW_EXCEPTION(fc::assert_exception, "LOGIC ERROR: Operation type not handled by custom authorities implementation. " diff --git a/libraries/protocol/custom_authorities/sliced_lists.hxx b/libraries/protocol/custom_authorities/sliced_lists.hxx index 923f489179..7975813894 100644 --- a/libraries/protocol/custom_authorities/sliced_lists.hxx +++ b/libraries/protocol/custom_authorities/sliced_lists.hxx @@ -32,7 +32,7 @@ namespace typelist = fc::typelist; // To make the build gentler on RAM, break the operation list into several pieces to build over several files using operation_list_1 = static_variant>; using operation_list_2 = static_variant>; -using operation_list_3 = static_variant>; +using operation_list_3 = static_variant>; using operation_list_4 = static_variant>; using operation_list_5 = static_variant>; using operation_list_6 = static_variant>; @@ -46,19 +46,22 @@ using operation_list_11 = static_variant ::add_list> ::add // 52 ::finalize>; -using operation_list_12 = static_variant>; -using operation_list_13 = static_variant>; -using operation_list_14 = static_variant>; -// Note: supported list ends at 64 so far, new operations are added to virtual_operations_list -// TODO support new operations -using virtual_operations_list = static_variant +// Note: Since BSIP-40 is not to be enabled on the BitShares Mainnet any time soon, +// by now, the list of supported operations ends here. +// New operations are added to unsupported_operations_list automatically. +// This is to reduce the compilation time and the size of binaries. +// In addition, consider removing more operations from the list which did not appear in the BitShares Testnet +// nor in unit tests. +// TODO support new operations when we decide to continue BSIP-40 development. +using unsupported_operations_list = static_variant ::add // 4 + ::add // 9 ::add // 42 ::add // 44 ::add // 46 ::add // 51 ::add // 53 - ::add_list> // Unsupported ops + ::add_list> // Unsupported ops ::finalize>; object_restriction_predicate get_restriction_pred_list_1(size_t idx, vector rs); @@ -72,8 +75,5 @@ object_restriction_predicate get_restriction_pred_list_8(size_t idx, object_restriction_predicate get_restriction_pred_list_9(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_10(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_11(size_t idx, vector rs); -object_restriction_predicate get_restriction_pred_list_12(size_t idx, vector rs); -object_restriction_predicate get_restriction_pred_list_13(size_t idx, vector rs); -object_restriction_predicate get_restriction_pred_list_14(size_t idx, vector rs); } } // namespace graphene::protocol From 9fbc3c8b967d7873171d6ad5efeef6fae93af347 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 29 Jun 2021 18:52:52 +0000 Subject: [PATCH 069/258] Remove BSIP-40 support for more operations --- libraries/protocol/CMakeLists.txt | 3 -- .../protocol/custom_authorities/list_4.cpp | 43 ------------------- .../protocol/custom_authorities/list_7.cpp | 43 ------------------- .../protocol/custom_authorities/list_8.cpp | 43 ------------------- .../restriction_predicate.cpp | 9 +--- .../custom_authorities/sliced_lists.hxx | 43 +++++++++++-------- 6 files changed, 25 insertions(+), 159 deletions(-) delete mode 100644 libraries/protocol/custom_authorities/list_4.cpp delete mode 100644 libraries/protocol/custom_authorities/list_7.cpp delete mode 100644 libraries/protocol/custom_authorities/list_8.cpp diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 21d619bcad..fd0d12e041 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -47,11 +47,8 @@ list(APPEND CUSTOM_AUTHS_FILES custom_authorities/list_1.cpp custom_authorities/list_2.cpp custom_authorities/list_3.cpp - custom_authorities/list_4.cpp custom_authorities/list_5.cpp custom_authorities/list_6.cpp - custom_authorities/list_7.cpp - custom_authorities/list_8.cpp custom_authorities/list_9.cpp custom_authorities/list_10.cpp custom_authorities/list_11.cpp) diff --git a/libraries/protocol/custom_authorities/list_4.cpp b/libraries/protocol/custom_authorities/list_4.cpp deleted file mode 100644 index 2453728c83..0000000000 --- a/libraries/protocol/custom_authorities/list_4.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2019 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 "restriction_predicate.hxx" -#include "sliced_lists.hxx" - -namespace graphene { namespace protocol { - -using result_type = object_restriction_predicate; - -result_type get_restriction_pred_list_4(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_4::list(), idx, [&rs] (auto t) { - using Op = typename decltype(t)::type; - result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { - FC_ASSERT(op.which() == operation::tag::value, - "Supplied operation is incorrect type for restriction predicate"); - return p(op.get()); - }; - return to_return; - }); -} -} } diff --git a/libraries/protocol/custom_authorities/list_7.cpp b/libraries/protocol/custom_authorities/list_7.cpp deleted file mode 100644 index 4bb31c9315..0000000000 --- a/libraries/protocol/custom_authorities/list_7.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2019 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 "restriction_predicate.hxx" -#include "sliced_lists.hxx" - -namespace graphene { namespace protocol { - -using result_type = object_restriction_predicate; - -result_type get_restriction_pred_list_7(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_7::list(), idx, [&rs] (auto t) { - using Op = typename decltype(t)::type; - result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { - FC_ASSERT(op.which() == operation::tag::value, - "Supplied operation is incorrect type for restriction predicate"); - return p(op.get()); - }; - return to_return; - }); -} -} } diff --git a/libraries/protocol/custom_authorities/list_8.cpp b/libraries/protocol/custom_authorities/list_8.cpp deleted file mode 100644 index 9d576305bd..0000000000 --- a/libraries/protocol/custom_authorities/list_8.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2019 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 "restriction_predicate.hxx" -#include "sliced_lists.hxx" - -namespace graphene { namespace protocol { - -using result_type = object_restriction_predicate; - -result_type get_restriction_pred_list_8(size_t idx, vector rs) { - return typelist::runtime::dispatch(operation_list_8::list(), idx, [&rs] (auto t) { - using Op = typename decltype(t)::type; - result_type to_return = [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { - FC_ASSERT(op.which() == operation::tag::value, - "Supplied operation is incorrect type for restriction predicate"); - return p(op.get()); - }; - return to_return; - }); -} -} } diff --git a/libraries/protocol/custom_authorities/restriction_predicate.cpp b/libraries/protocol/custom_authorities/restriction_predicate.cpp index 13c67e72f9..646b160ad4 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.cpp +++ b/libraries/protocol/custom_authorities/restriction_predicate.cpp @@ -38,16 +38,10 @@ restriction_predicate_function get_restriction_predicate(vector rs, return get_restriction_pred_list_2(typelist::index_of(), std::move(rs)); if (typelist::contains()) return get_restriction_pred_list_3(typelist::index_of(), std::move(rs)); - if (typelist::contains()) - return get_restriction_pred_list_4(typelist::index_of(), std::move(rs)); if (typelist::contains()) return get_restriction_pred_list_5(typelist::index_of(), std::move(rs)); if (typelist::contains()) return get_restriction_pred_list_6(typelist::index_of(), std::move(rs)); - if (typelist::contains()) - return get_restriction_pred_list_7(typelist::index_of(), std::move(rs)); - if (typelist::contains()) - return get_restriction_pred_list_8(typelist::index_of(), std::move(rs)); if (typelist::contains()) return get_restriction_pred_list_9(typelist::index_of(), std::move(rs)); if (typelist::contains()) @@ -59,9 +53,8 @@ restriction_predicate_function get_restriction_predicate(vector rs, // Compile time check that we'll never get to the exception below static_assert(typelist::contains, diff --git a/libraries/protocol/custom_authorities/sliced_lists.hxx b/libraries/protocol/custom_authorities/sliced_lists.hxx index 7975813894..1e1209a845 100644 --- a/libraries/protocol/custom_authorities/sliced_lists.hxx +++ b/libraries/protocol/custom_authorities/sliced_lists.hxx @@ -31,47 +31,52 @@ namespace typelist = fc::typelist; // To make the build gentler on RAM, break the operation list into several pieces to build over several files using operation_list_1 = static_variant>; -using operation_list_2 = static_variant>; +using operation_list_2 = static_variant>; using operation_list_3 = static_variant>; -using operation_list_4 = static_variant>; -using operation_list_5 = static_variant>; -using operation_list_6 = static_variant>; -using operation_list_7 = static_variant>; -using operation_list_8 = static_variant>; -using operation_list_9 = static_variant>; -using operation_list_10 = static_variant>; +using operation_list_5 = static_variant>; +using operation_list_6 = static_variant>; +using operation_list_9 = static_variant>; +using operation_list_10 = static_variant>; using operation_list_11 = static_variant - ::add // 43 - ::add // 45 - ::add_list> + ::add_list> ::add // 52 ::finalize>; // Note: Since BSIP-40 is not to be enabled on the BitShares Mainnet any time soon, // by now, the list of supported operations ends here. -// New operations are added to unsupported_operations_list automatically. +// These operations are used in unit tests. +// As of writing, transfer_operation and limit_order_create_operation appeared on the public testnet +// between block #31808000 and #31811000. +// Other operations are added to the unsupported_operations_list with a comment "Unsupported". +// The operations in the unsupported_operations_list without the comment are virtual operations or +// unimplemented, so should be kept there anyway. // This is to reduce the compilation time and the size of binaries. -// In addition, consider removing more operations from the list which did not appear in the BitShares Testnet -// nor in unit tests. -// TODO support new operations when we decide to continue BSIP-40 development. +// TODO support more operations when we decide to continue BSIP-40 development. using unsupported_operations_list = static_variant ::add // 4 + ::add_list> // Unsupported ::add // 9 + ::add_list> // Unsupported + ::add_list> // Unsupported + ::add_list> // Unsupported + ::add_list> // Unsupported + ::add_list> // Unsupported ::add // 42 + ::add_list> // Unsupported ::add // 44 + ::add_list> // Unsupported ::add // 46 + ::add_list> // Unsupported ::add // 51 ::add // 53 - ::add_list> // Unsupported ops + // New operations are added here + ::add_list> // Unsupported ::finalize>; object_restriction_predicate get_restriction_pred_list_1(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_2(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_3(size_t idx, vector rs); -object_restriction_predicate get_restriction_pred_list_4(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_5(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_6(size_t idx, vector rs); -object_restriction_predicate get_restriction_pred_list_7(size_t idx, vector rs); -object_restriction_predicate get_restriction_pred_list_8(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_9(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_10(size_t idx, vector rs); object_restriction_predicate get_restriction_pred_list_11(size_t idx, vector rs); From 648465306931dddb60380c35ea32ad8d4f817ab4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 29 Jun 2021 19:42:01 +0000 Subject: [PATCH 070/258] Add tests for BSIP-40 unsupported operation check --- tests/tests/custom_authority_tests.cpp | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 475aaa0c27..fc0867434e 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -603,6 +603,43 @@ BOOST_AUTO_TEST_CASE(container_in_not_in_checks) { try { } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(custom_auths_unsupported_operations_test) { try { + ////// + // Initialize the test + ////// + generate_blocks(HARDFORK_BSIP_40_TIME); + generate_blocks(5); + db.modify(global_property_id_type()(db), [](global_property_object& gpo) { + gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type(); + }); + set_expiration(db, trx); + ACTORS((alice)(bob)) + fund(alice, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION)); + fund(bob, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION)); + + ////// + // Create a custom authority where Bob is authorized to do "fill_order_operation" with Alice's account, + // This should fail since "fill_order_operation" is a virtual operation thus is not supported. + ////// + custom_authority_create_operation op; + op.account = alice.get_id(); + op.auth.add_authority(bob.get_id(), 1); + op.auth.weight_threshold = 1; + op.enabled = true; + op.valid_to = db.head_block_time() + 1000; + op.operation_type = operation::tag::value; + auto account_index = member_index("account_id"); + op.restrictions = { restriction(account_index, restriction::func_eq, account_id_type(0))}; + + ////// + // Alice publishes the custom authority + ////// + trx.clear(); + trx.operations = {op}; + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE(custom_auths) { try { ////// From 7db58f82ba9084f1790c01ff272e41197dab28a1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 30 Jun 2021 01:58:54 -0400 Subject: [PATCH 071/258] Replace magic numbers with named constants --- .../custom_authorities/sliced_lists.hxx | 97 +++++++++++++------ 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/libraries/protocol/custom_authorities/sliced_lists.hxx b/libraries/protocol/custom_authorities/sliced_lists.hxx index 1e1209a845..4734a126e4 100644 --- a/libraries/protocol/custom_authorities/sliced_lists.hxx +++ b/libraries/protocol/custom_authorities/sliced_lists.hxx @@ -30,16 +30,39 @@ namespace graphene { namespace protocol { namespace typelist = fc::typelist; // To make the build gentler on RAM, break the operation list into several pieces to build over several files -using operation_list_1 = static_variant>; -using operation_list_2 = static_variant>; -using operation_list_3 = static_variant>; -using operation_list_5 = static_variant>; -using operation_list_6 = static_variant>; -using operation_list_9 = static_variant>; -using operation_list_10 = static_variant>; +using operation_list_1 = static_variant + ::add // 0 + ::add // 1 + ::add // 2 + ::add // 3 + ::finalize>; +using operation_list_2 = static_variant + ::add // 5 + ::add // 6 + ::finalize>; +using operation_list_3 = static_variant + ::add // 10 + ::finalize>; +using operation_list_5 = static_variant + ::add // 13 + ::add // 14 + ::add // 15 + ::finalize>; +using operation_list_6 = static_variant + ::add // 19 + ::add // 21 + ::finalize>; +using operation_list_9 = static_variant + ::add // 32 + ::add // 33 + ::finalize>; +using operation_list_10 = static_variant + ::add // 38 + ::finalize>; using operation_list_11 = static_variant - ::add_list> - ::add // 52 + ::add // 49 + ::add // 50 + ::add // 52 ::finalize>; // Note: Since BSIP-40 is not to be enabled on the BitShares Mainnet any time soon, // by now, the list of supported operations ends here. @@ -47,29 +70,47 @@ using operation_list_11 = static_variant // As of writing, transfer_operation and limit_order_create_operation appeared on the public testnet // between block #31808000 and #31811000. // Other operations are added to the unsupported_operations_list with a comment "Unsupported". -// The operations in the unsupported_operations_list without the comment are virtual operations or +// The operations in the unsupported_operations_list with other comments are virtual operations or // unimplemented, so should be kept there anyway. // This is to reduce the compilation time and the size of binaries. // TODO support more operations when we decide to continue BSIP-40 development. using unsupported_operations_list = static_variant - ::add // 4 - ::add_list> // Unsupported - ::add // 9 - ::add_list> // Unsupported - ::add_list> // Unsupported - ::add_list> // Unsupported - ::add_list> // Unsupported - ::add_list> // Unsupported - ::add // 42 - ::add_list> // Unsupported - ::add // 44 - ::add_list> // Unsupported - ::add // 46 - ::add_list> // Unsupported - ::add // 51 - ::add // 53 - // New operations are added here - ::add_list> // Unsupported + ::add // 4 // VIRTUAL + ::add // 7 // Unsupported + ::add // 8 // Unsupported + ::add // 9 // Unimplemented + ::add // 11 // Unsupported + ::add // 12 // Unsupported + ::add // 16 // Unsupported + ::add // 17 // Unsupported + ::add // 18 // Unsupported + ::add // 20 // Unsupported + // [22, 32) // Unsupported + ::add_list(), + typelist::index_of< operation::list, + vesting_balance_create_operation >() >> + ::add // 34 // Unsupported + ::add // 35 // Unsupported + ::add // 36 // Unsupported + ::add // 37 // Unsupported + ::add // 39 // Unsupported + ::add // 40 // Unsupported + ::add // 41 // Unsupported + ::add // 42 // VIRTUAL + ::add // 43 // Unsupported + ::add // 44 // VIRTUAL + ::add // 45 // Unsupported + ::add // 46 // VIRTUAL + ::add // 47 // Unsupported + ::add // 48 // Unsupported + ::add // 51 // VIRTUAL + ::add // 53 // VIRTUAL + // New operations are added here // Unsupported + ::add_list() >> ::finalize>; object_restriction_predicate get_restriction_pred_list_1(size_t idx, vector rs); From dd0b761b550e1946594a080ae013f5222f83061a Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 30 Jun 2021 03:26:28 -0400 Subject: [PATCH 072/258] Fix code smells --- .../protocol/custom_authorities/restriction_predicate.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/protocol/custom_authorities/restriction_predicate.cpp b/libraries/protocol/custom_authorities/restriction_predicate.cpp index 646b160ad4..30cc16a8d9 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.cpp +++ b/libraries/protocol/custom_authorities/restriction_predicate.cpp @@ -70,12 +70,13 @@ restriction_predicate_function get_restriction_predicate(vector rs, } predicate_result& predicate_result::reverse_path() { - if (success == true) + if (success) return *this; - auto reverse_subpaths = [](rejection_indicator& indicator) { + const auto reverse_subpaths = [](rejection_indicator& indicator) { if (indicator.is_type>()) { auto& results = indicator.get>(); - for (predicate_result& result : results) result.reverse_path(); + for (predicate_result& result : results) + result.reverse_path(); } }; std::reverse(rejection_path.begin(), rejection_path.end()); From 407461176fdc680b38ddf4188c4b4c145a7d197c Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 5 Jul 2021 07:56:36 +0000 Subject: [PATCH 073/258] Check and remove unneeded dir when flushing db and fix some code smells --- libraries/chain/db_management.cpp | 7 ++- libraries/db/object_database.cpp | 95 +++++++++++++++++++++---------- 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index c71d85d3d9..7a7e8f2723 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -142,9 +142,9 @@ void database::reindex( fc::path data_dir ) } if( i == undo_point ) { - ilog( "Writing database to disk at block ${i}", ("i",i) ); + ilog( "Writing object database to disk at block ${i}, please DO NOT kill the program", ("i", i) ); flush(); - ilog( "Done" ); + ilog( "Done writing object database to disk" ); } if( i < undo_point ) apply_block( block, skip ); @@ -262,7 +262,10 @@ void database::close(bool rewind) // DB state (issue #336). clear_pending(); + ilog( "Writing object database to disk at block ${i}, please DO NOT kill the program", ("i", head_block_num()) ); object_database::flush(); + ilog( "Done writing object database to disk" ); + object_database::close(); if( _block_id_to_block.is_open() ) diff --git a/libraries/db/object_database.cpp b/libraries/db/object_database.cpp index 6767051264..5e91c7c87d 100644 --- a/libraries/db/object_database.cpp +++ b/libraries/db/object_database.cpp @@ -53,44 +53,72 @@ const object& object_database::get_object( object_id_type id )const const index& object_database::get_index(uint8_t space_id, uint8_t type_id)const { - FC_ASSERT( _index.size() > space_id, "", ("space_id",space_id)("type_id",type_id)("index.size",_index.size()) ); - FC_ASSERT( _index[space_id].size() > type_id, "", ("space_id",space_id)("type_id",type_id)("index[space_id].size",_index[space_id].size()) ); - const auto& tmp = _index[space_id][type_id]; - FC_ASSERT( tmp ); + FC_ASSERT( _index.size() > space_id, + "Database index ${space_id}.${type_id} does not exist, index size is ${index.size}", + ("space_id",space_id)("type_id",type_id)("index.size",_index.size()) ); + FC_ASSERT( _index[space_id].size() > type_id, + "Database index ${space_id}.${type_id} does not exist, space size is ${index[space_id].size}", + ("space_id",space_id)("type_id",type_id)("index[space_id].size",_index[space_id].size()) ); + const auto& tmp = _index[space_id][type_id]; // it is a unique_ptr + FC_ASSERT( tmp != nullptr, + "Database index ${space_id}.${type_id} has not been initialized", + ("space_id",space_id)("type_id",type_id) ); return *tmp; } index& object_database::get_mutable_index(uint8_t space_id, uint8_t type_id) { - FC_ASSERT( _index.size() > space_id, "", ("space_id",space_id)("type_id",type_id)("index.size",_index.size()) ); - FC_ASSERT( _index[space_id].size() > type_id , "", ("space_id",space_id)("type_id",type_id)("index[space_id].size",_index[space_id].size()) ); - const auto& idx = _index[space_id][type_id]; - FC_ASSERT( idx, "", ("space",space_id)("type",type_id) ); + FC_ASSERT( _index.size() > space_id, + "Database index ${space_id}.${type_id} does not exist, index size is ${index.size}", + ("space_id",space_id)("type_id",type_id)("index.size",_index.size()) ); + FC_ASSERT( _index[space_id].size() > type_id , + "Database index ${space_id}.${type_id} does not exist, space size is ${index[space_id].size}", + ("space_id",space_id)("type_id",type_id)("index[space_id].size",_index[space_id].size()) ); + const auto& idx = _index[space_id][type_id]; // it is a unique_ptr + FC_ASSERT( idx != nullptr, + "Database index ${space_id}.${type_id} has not been initialized", + ("space_id",space_id)("type_id",type_id) ); return *idx; } void object_database::flush() { -// ilog("Save object_database in ${d}", ("d", _data_dir)); - fc::create_directories( _data_dir / "object_database.tmp" / "lock" ); + const auto tmp_dir = _data_dir / "object_database.tmp"; + const auto old_dir = _data_dir / "object_database.old"; + const auto target_dir = _data_dir / "object_database"; + + if( fc::exists( tmp_dir ) ) + fc::remove_all( tmp_dir ); + fc::create_directories( tmp_dir / "lock" ); std::vector> tasks; - tasks.reserve(200); - for( uint32_t space = 0; space < _index.size(); ++space ) + constexpr size_t max_tasks = 200; + tasks.reserve(max_tasks); + + auto push_task = [this,&tasks,&tmp_dir]( size_t space, size_t type ) { + if( _index[space][type] ) + tasks.push_back( fc::do_parallel( [this,space,type,&tmp_dir] () { + _index[space][type]->save( tmp_dir / fc::to_string(space) / fc::to_string(type) ); + } ) ); + }; + + const auto spaces = _index.size(); + for( size_t space = 0; space < spaces; ++space ) { - fc::create_directories( _data_dir / "object_database.tmp" / fc::to_string(space) ); + fc::create_directories( tmp_dir / fc::to_string(space) ); const auto types = _index[space].size(); - for( uint32_t type = 0; type < types; ++type ) - if( _index[space][type] ) - tasks.push_back( fc::do_parallel( [this,space,type] () { - _index[space][type]->save( _data_dir / "object_database.tmp" / fc::to_string(space)/fc::to_string(type) ); - } ) ); + for( size_t type = 0; type < types; ++type ) + push_task( space, type ); } for( auto& task : tasks ) task.wait(); - fc::remove_all( _data_dir / "object_database.tmp" / "lock" ); - if( fc::exists( _data_dir / "object_database" ) ) - fc::rename( _data_dir / "object_database", _data_dir / "object_database.old" ); - fc::rename( _data_dir / "object_database.tmp", _data_dir / "object_database" ); - fc::remove_all( _data_dir / "object_database.old" ); + fc::remove_all( tmp_dir / "lock" ); + if( fc::exists( target_dir ) ) + { + if( fc::exists( old_dir ) ) + fc::remove_all( old_dir ); + fc::rename( target_dir, old_dir ); + } + fc::rename( tmp_dir, target_dir ); + fc::remove_all( old_dir ); } void object_database::wipe(const fc::path& data_dir) @@ -111,13 +139,22 @@ void object_database::open(const fc::path& data_dir) } std::vector> tasks; tasks.reserve(200); + + auto push_task = [this,&tasks]( size_t space, size_t type ) { + if( _index[space][type] ) + tasks.push_back( fc::do_parallel( [this,space,type] () { + _index[space][type]->open( _data_dir / "object_database" / fc::to_string(space) / fc::to_string(type) ); + } ) ); + }; + ilog("Opening object database from ${d} ...", ("d", data_dir)); - for( uint32_t space = 0; space < _index.size(); ++space ) - for( uint32_t type = 0; type < _index[space].size(); ++type ) - if( _index[space][type] ) - tasks.push_back( fc::do_parallel( [this,space,type] () { - _index[space][type]->open( _data_dir / "object_database" / fc::to_string(space)/fc::to_string(type) ); - } ) ); + const auto spaces = _index.size(); + for( size_t space = 0; space < spaces; ++space ) + { + const auto types = _index[space].size(); + for( size_t type = 0; type < types; ++type ) + push_task( space, type ); + } for( auto& task : tasks ) task.wait(); ilog( "Done opening object database." ); From e42df9c1201d5cb780ca5dd18c881a40b8931fc1 Mon Sep 17 00:00:00 2001 From: Abit Date: Mon, 5 Jul 2021 10:43:26 +0200 Subject: [PATCH 074/258] Add OS version to cache key in sonar-scan workflow This fixes an issue when Github changes `ubuntu-latest` environment from `18.04` to `20.04`, and avoids similar issues in the future. The fix is inspired by https://github.com/neutrinolabs/xrdp/pull/1821 . --- .github/workflows/sonar-scan.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index cda85540b1..2d0bbb1859 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -92,15 +92,19 @@ jobs: -D Boost_USE_STATIC_LIBS=OFF \ .. popd + # Get OS version to be used in cache key - see https://github.com/actions/cache/issues/543 + - run: | + echo "OS_VERSION=`lsb_release -sr`" >> $GITHUB_ENV - name: Load Cache uses: actions/cache@v2 with: path: | ccache sonar_cache - key: sonar-${{ github.ref }}-${{ github.sha }} + key: sonar-${{ env.OS_VERSION }}-${{ github.ref }}-${{ github.sha }} restore-keys: | - sonar-${{ github.ref }}- + sonar-${{ env.OS_VERSION }}-${{ github.ref }}- + sonar-${{ env.OS_VERSION }}- sonar- - name: Build run: | From bc841d95c111f500eafb2790e8d608ae8972258e Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 5 Jul 2021 12:49:55 +0000 Subject: [PATCH 075/258] Add more tests about margin call fee --- tests/tests/market_tests.cpp | 193 +++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index f71c56a1fb..e7d60a0aa0 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1210,6 +1210,199 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) } FC_LOG_AND_RETHROW() } +/*** + * Tests a scenario that GS may occur when there is no sufficient collateral to pay margin call fee, + * but GS won't occur if no need to pay margin call fee. + */ +BOOST_AUTO_TEST_CASE(mcfr_blackswan_test) +{ try { + // Proceeds to the bsip-74 hard fork time + generate_blocks(HARDFORK_CORE_BSIP74_TIME); + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 80; + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 400% collateral, call price is 20/1.75 CORE/USD = 80/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(20000)); + call_order_id_type call2_id = call2.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 20000, call2.collateral.value ); + BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // No margin call at this moment + + // This order is sufficient to close the first debt position and no GS if margin call fee ratio is 0 + limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(1000), core.amount(14900))->id; + + BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value ); + + BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 20000, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to get call_order into black swan territory + BOOST_MESSAGE( "Trying to trigger GS" ); + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(18); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/18, mssp = 10/198 + + // GS occurs even when there is a good sell order + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + // GS price is 1/18, but the first call order has only 15000 thus capped + BOOST_CHECK_EQUAL( 15000 + 18000, usd_id(db).bitasset_data(db).settlement_fund.value ); + + // the sell order does not change + BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value ); + + // generate a block to include operations above + BOOST_MESSAGE( "Generating a new block" ); + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Tests a scenario about rounding errors related to margin call fee + */ +BOOST_AUTO_TEST_CASE(mcfr_rounding_test) +{ try { + // Proceeds to the bsip-74 hard fork time + generate_blocks(HARDFORK_CORE_BSIP74_TIME); + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)(feeder2)(feeder3)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 70; + uop.new_options.feed_lifetime_sec = 86400; + uop.new_options.minimum_feeds = 1; + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + update_feed_producers( bitusd, {feedproducer_id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 800% collateral, call price is 40/1.75 CORE/USD = 160/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(40000)); + call_order_id_type call2_id = call2.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 40000, call2.collateral.value ); + BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // No margin call at this moment + + // This order would be matched later + limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(1100), core.amount(15451))->id; + // call_pays_price = (15451 / 1100) * 1100 / (1100-70) = 15451 / 1030 + // debt * call_pays_price = 1000 * 15451 / 1030 = 15000.9 + + BOOST_CHECK_EQUAL( 1100, sell_mid(db).for_sale.value ); + + BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // Tring to adjust price feed to get call_order into margin call territory + BOOST_MESSAGE( "Trying to trigger a margin call" ); + auto feed2 = current_feed; + feed2.settlement_price = bitusd.amount( 1 ) / core.amount(18); + publish_feed( bitusd, feedproducer, feed2 ); + + // The first call order should have been filled + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_REQUIRE( db.find( call2_id ) ); + + BOOST_CHECK_EQUAL( 100, sell_mid(db).for_sale.value ); + + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 14047, get_balance(seller, core) ); + + // generate a block to include operations above + BOOST_MESSAGE( "Generating a new block" ); + generate_block(); + +} FC_LOG_AND_RETHROW() } + /*** * BSIP38 "target_collateral_ratio" test: matching a taker limit order with multiple maker call orders */ From aea36e1238b34fa110720294ef3e75ea62e82fe1 Mon Sep 17 00:00:00 2001 From: Abit Date: Mon, 5 Jul 2021 20:31:14 +0200 Subject: [PATCH 076/258] Require OS version in cache key for sonar-scan --- .github/workflows/sonar-scan.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index 2d0bbb1859..2bf94d5c2d 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -105,7 +105,6 @@ jobs: restore-keys: | sonar-${{ env.OS_VERSION }}-${{ github.ref }}- sonar-${{ env.OS_VERSION }}- - sonar- - name: Build run: | export CCACHE_DIR="$GITHUB_WORKSPACE/ccache" From 2500b8b089b3828aeb783e5dd04118009a85356c Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 5 Jul 2021 14:02:10 +0000 Subject: [PATCH 077/258] Match force-settlements with margin calls --- libraries/chain/asset_evaluator.cpp | 32 +- libraries/chain/db_maint.cpp | 16 +- libraries/chain/db_market.cpp | 345 ++++++++++++++---- libraries/chain/db_update.cpp | 85 ++++- libraries/chain/hardfork.d/CORE_2481.hf | 6 + .../graphene/chain/asset_evaluator.hpp | 1 + .../chain/include/graphene/chain/database.hpp | 98 ++++- libraries/chain/market_object.cpp | 11 +- .../include/graphene/protocol/asset_ops.hpp | 7 +- 9 files changed, 489 insertions(+), 112 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_2481.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index bf26a7865d..e71ae325fd 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -999,11 +999,14 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op { try { const database& d = db(); asset_to_settle = &op.amount.asset_id(d); - FC_ASSERT(asset_to_settle->is_market_issued()); + FC_ASSERT( asset_to_settle->is_market_issued(), + "Can only force settle a predition market or a market issued asset" ); const auto& bitasset = asset_to_settle->bitasset_data(d); - FC_ASSERT(asset_to_settle->can_force_settle() || bitasset.has_settlement() ); + FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.has_settlement(), + "Either the asset need to have the force_settle flag enabled, or it need to be globally settled" ); if( bitasset.is_prediction_market ) - FC_ASSERT( bitasset.has_settlement(), "global settlement must occur before force settling a prediction market" ); + FC_ASSERT( bitasset.has_settlement(), + "Global settlement must occur before force settling a prediction market" ); else if( bitasset.current_feed.settlement_price.is_null() && ( d.head_block_time() <= HARDFORK_CORE_216_TIME // TODO check whether the HF check can be removed || !bitasset.has_settlement() ) ) @@ -1019,14 +1022,17 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op "The account is not allowed to receive the backing asset" ); } + bitasset_ptr = &bitasset; + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) { try { database& d = db(); + const auto head_time = d.head_block_time(); - const auto& bitasset = asset_to_settle->bitasset_data(d); + const auto& bitasset = *bitasset_ptr; if( bitasset.has_settlement() ) { const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); @@ -1035,7 +1041,8 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( op.amount.amount == mia_dyn.current_supply ) settled_amount.amount = bitasset.settlement_fund; // avoid rounding problems else - FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund ); // should be strictly < except for PM with zero outcome + // should be strictly < except for PM with zero outcome + FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund ); if( settled_amount.amount == 0 && !bitasset.is_prediction_market ) { @@ -1065,7 +1072,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: // TODO Check whether the HF check can be removed after the HF. // Note: even if logically it can be removed, perhaps the removal will lead to a small // performance loss. Needs testing. - if( d.head_block_time() >= HARDFORK_CORE_1780_TIME ) + if( head_time >= HARDFORK_CORE_1780_TIME ) { auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); @@ -1085,11 +1092,18 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: else { d.adjust_balance( op.account, -op.amount ); - return d.create([&](force_settlement_object& s) { + const auto& settle = d.create([&op,&head_time,&bitasset](force_settlement_object& s) { s.owner = op.account; s.balance = op.amount; - s.settlement_date = d.head_block_time() + asset_to_settle->bitasset_data(d).options.force_settlement_delay_sec; - }).id; + s.settlement_date = head_time + bitasset.options.force_settlement_delay_sec; + }); + auto id = settle.id; + auto maint_time = d.get_dynamic_global_properties().next_maintenance_time; + if( HARDFORK_CORE_2481_PASSED( maint_time ) ) + { + d.apply_force_settlement( settle, bitasset ); + } + return id; } } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 8e7de32c85..96464c4476 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -898,12 +898,13 @@ void match_call_orders( database& db ) wlog( "Matching call orders at block ${n}", ("n",db.head_block_num()) ); const auto& asset_idx = db.get_index_type().indices().get(); auto itr = asset_idx.lower_bound( true /** market issued */ ); - while( itr != asset_idx.end() ) + auto itr_end = asset_idx.end(); + while( itr != itr_end ) { const asset_object& a = *itr; ++itr; // be here, next_maintenance_time should have been updated already - db.check_call_orders( a, true, false ); // allow black swan, and call orders are taker + db.check_call_orders( a ); // allow black swan, and call orders are taker } wlog( "Done matching call orders at block ${n}", ("n",db.head_block_num()) ); } @@ -1474,6 +1475,11 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if ( dgpo.next_maintenance_time <= HARDFORK_CORE_2262_TIME && next_maintenance_time > HARDFORK_CORE_2262_TIME ) process_hf_2262(*this); + // To check call orders and potential match them with force settlements, for hard fork core-2481 + bool match_call_orders_for_hf_2481 = false; + if( (dgpo.next_maintenance_time <= HARDFORK_CORE_2481_TIME) && (next_maintenance_time > HARDFORK_CORE_2481_TIME) ) + match_call_orders_for_hf_2481 = true; + modify(dgpo, [last_vote_tally_time, next_maintenance_time](dynamic_global_property_object& d) { d.next_maintenance_time = next_maintenance_time; d.last_vote_tally_time = last_vote_tally_time; @@ -1495,6 +1501,12 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g match_call_orders(*this); } + // We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-2481 + if( match_call_orders_for_hf_2481 ) + { + match_call_orders(*this); + } + process_bitassets(); delete_expired_custom_authorities(*this); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f0c07435c1..51defd5433 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -54,7 +54,8 @@ namespace detail { * Force settlement happens without delay at the swan price, deducting from force-settlement fund * No more asset updates may be issued. */ -void database::globally_settle_asset( const asset_object& mia, const price& settlement_price ) +void database::globally_settle_asset( const asset_object& mia, const price& settlement_price, + bool check_margin_calls ) { auto maint_time = get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_1669 = ( maint_time <= HARDFORK_CORE_1669_TIME ); // whether to use call_price @@ -62,25 +63,27 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett if( before_core_hardfork_1669 ) { globally_settle_asset_impl( mia, settlement_price, - get_index_type().indices().get() ); + get_index_type().indices().get(), + check_margin_calls ); } else { globally_settle_asset_impl( mia, settlement_price, - get_index_type().indices().get() ); + get_index_type().indices().get(), + check_margin_calls ); } } template void database::globally_settle_asset_impl( const asset_object& mia, const price& settlement_price, - const IndexType& call_index ) + const IndexType& call_index, + bool check_margin_calls ) { try { const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); FC_ASSERT( !bitasset.has_settlement(), "black swan already occurred, it should not happen again" ); - const asset_object& backing_asset = bitasset.options.short_backing_asset(*this); - asset collateral_gathered = backing_asset.amount(0); + asset collateral_gathered( 0, bitasset.options.short_backing_asset ); const asset_dynamic_data_object& mia_dyn = mia.dynamic_asset_data_id(*this); auto original_mia_supply = mia_dyn.current_supply; @@ -89,26 +92,62 @@ void database::globally_settle_asset_impl( const asset_object& mia, bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding // cancel all call orders and accumulate it into collateral_gathered - auto call_itr = call_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) ); - auto call_end = call_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) ); + auto call_itr = call_index.lower_bound( price::min( bitasset.options.short_backing_asset, bitasset.asset_id ) ); + auto call_end = call_index.upper_bound( price::max( bitasset.options.short_backing_asset, bitasset.asset_id ) ); + + auto margin_end = call_end; + bool is_margin_call = false; + price call_pays_price = settlement_price; + price fund_receives_price = settlement_price; + if( check_margin_calls ) + { + margin_end = call_index.upper_bound( bitasset.current_maintenance_collateralization ); + // Note: settlement_price is in debt / collateral, here the fund gets less collateral + fund_receives_price = settlement_price * ratio_type( bitasset.current_feed.maximum_short_squeeze_ratio, + GRAPHENE_COLLATERAL_RATIO_DENOM ); + if( call_itr != margin_end ) + is_margin_call = true; + } + asset margin_call_fee( 0, bitasset.options.short_backing_asset ); asset pays; while( call_itr != call_end ) { + if( is_margin_call && call_itr == margin_end ) + { + is_margin_call = false; + call_pays_price = fund_receives_price; + } + const call_order_object& order = *call_itr; ++call_itr; + auto order_debt = order.get_debt(); if( before_core_hardfork_342 ) - pays = order.get_debt() * settlement_price; // round down, in favor of call order + pays = order_debt * call_pays_price; // round down, in favor of call order else - pays = order.get_debt().multiply_and_round_up( settlement_price ); // round up in favor of global-settle fund + pays = order_debt.multiply_and_round_up( call_pays_price ); // round up in favor of global-settle fund if( pays > order.get_collateral() ) pays = order.get_collateral(); - collateral_gathered += pays; + if( is_margin_call ) + { + auto fund_receives = order_debt.multiply_and_round_up( fund_receives_price ); + if( fund_receives > pays ) + fund_receives = pays; + margin_call_fee = pays - fund_receives; + collateral_gathered += fund_receives; + } + else + { + margin_call_fee.amount = 0; + collateral_gathered += pays; + } - FC_ASSERT( fill_call_order( order, pays, order.get_debt(), settlement_price, true ) ); // call order is maker + // call order is maker + FC_ASSERT( fill_call_order( order, pays, order_debt, fund_receives_price, true, margin_call_fee ), + "Internal error: unable to close margin call" ); } modify( bitasset, [&mia,original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ @@ -220,18 +259,12 @@ void database::execute_bid( const collateral_bid_object& bid, share_type debt_co remove(bid); } -void database::cancel_settle_order(const force_settlement_object& order, bool create_virtual_op) +void database::cancel_settle_order( const force_settlement_object& order ) { adjust_balance(order.owner, order.balance); - if( create_virtual_op ) - { - asset_settle_cancel_operation vop; - vop.settlement = order.id; - vop.account = order.owner; - vop.amount = order.balance; - push_applied_operation( vop ); - } + push_applied_operation( asset_settle_cancel_operation( order.id, order.owner, order.balance ) ); + remove(order); } @@ -356,7 +389,7 @@ bool maybe_cull_small_order( database& db, const limit_order_object& order ) return false; } -bool database::apply_order_before_hardfork_625(const limit_order_object& new_order_object, bool allow_black_swan) +bool database::apply_order_before_hardfork_625(const limit_order_object& new_order_object) { auto order_id = new_order_object.id; const asset_object& sell_asset = get(new_order_object.amount_for_sale().asset_id); @@ -365,8 +398,8 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord // Possible optimization: We only need to check calls if both are true: // - The new order is at the front of the book // - The new order is below the call limit price - bool called_some = check_call_orders(sell_asset, allow_black_swan, true); // the first time when checking, call order is maker - called_some |= check_call_orders(receive_asset, allow_black_swan, true); // the other side, same as above + bool called_some = check_call_orders(sell_asset, true, true); // the first time when checking, call order is maker + called_some |= check_call_orders(receive_asset, true, true); // the other side, same as above if( called_some && !find_object(order_id) ) // then we were filled by call order return true; @@ -390,11 +423,11 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord finished = (match(new_order_object, *old_limit_itr, old_limit_itr->sell_price) != 2); } - //Possible optimization: only check calls if the new order completely filled some old order - //Do I need to check both assets? - check_call_orders(sell_asset, allow_black_swan); // after the new limit order filled some orders on the book, - // if a call order matches another order, the call order is taker - check_call_orders(receive_asset, allow_black_swan); // the other side, same as above + // TODO Possible optimization: only check calls if the new order completely filled some old order. + // Do I need to check both assets? + check_call_orders(sell_asset); // after the new limit order filled some orders on the book, + // if a call order matches another order, the call order is taker + check_call_orders(receive_asset); // the other side, same as above const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); if( updated_order_object == nullptr ) @@ -421,10 +454,8 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord * remains on the book. * @param new_order_object the new limit order (read only ref, though the corresponding db * object is modified as we match and deleted if filled completely) - * @param allow_black_swan ignored, defaulted to true (is used in the _before_hardfork_625 - * variant of this function, but not this variant) */ -bool database::apply_order(const limit_order_object& new_order_object, bool allow_black_swan) +bool database::apply_order(const limit_order_object& new_order_object) { auto order_id = new_order_object.id; asset_id_type sell_asset_id = new_order_object.sell_asset_id(); @@ -592,6 +623,60 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo return maybe_cull_small_order( *this, *updated_order_object ); } +void database::apply_force_settlement( const force_settlement_object& new_settlement, + const asset_bitasset_data_object& bitasset ) +{ + // Defensive checks + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + FC_ASSERT( HARDFORK_CORE_2481_PASSED( maint_time ), "Internal error: hard fork core-2481 not passed" ); + FC_ASSERT( new_settlement.balance.asset_id == bitasset.asset_id, "Internal error: asset type mismatch" ); + FC_ASSERT( !bitasset.is_prediction_market, "Internal error: asset is a prediction market" ); + FC_ASSERT( !bitasset.has_settlement(), "Internal error: asset is globally settled already" ); + FC_ASSERT( !bitasset.current_feed.settlement_price.is_null(), "Internal error: no sufficient price feeds" ); + + auto new_obj_id = new_settlement.id; + + // Price at which margin calls sit on the books. + // It is the MCOP, which may deviate from MSSP due to MCFR. + price call_match_price = bitasset.current_feed. + margin_call_order_price(bitasset.options.extensions.value.margin_call_fee_ratio); + // Price margin call actually relinquishes collateral at. Equals the MSSP and it may + // differ from call_match_price if there is a Margin Call Fee. + price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + + bool finished = false; // whether the new order is gone + + // check if there are margin calls + const auto& call_collateral_idx = get_index_type().indices().get(); + auto call_min = price::min( bitasset.options.short_backing_asset, new_settlement.balance.asset_id ); + while( !finished ) + { + // always check call order with the least collateral ratio + auto call_itr = call_collateral_idx.lower_bound( call_min ); + // Note: we don't precalculate an iterator with upper_bound() before entering the loop, + // because the upper bound can change after a call order got filled + if( call_itr == call_collateral_idx.end() + || call_itr->debt_type() != new_settlement.balance.asset_id + // feed protected https://github.com/cryptonomex/graphene/issues/436 + || call_itr->collateralization() > bitasset.current_maintenance_collateralization ) + break; + // TCR applies here + asset max_debt_to_cover( call_itr->get_max_debt_to_cover( call_pays_price, + bitasset.current_feed.settlement_price, + bitasset.current_feed.maintenance_collateral_ratio, + bitasset.current_maintenance_collateralization ), + new_settlement.balance.asset_id ); + + // Note: the call order should be able to pay at call_pays_price, + // thus no need to pass in margin_call_pays_ratio + match( new_settlement, *call_itr, call_pays_price, max_debt_to_cover, call_match_price, true ); + + // Check whether the new order is gone + finished = ( find_object( new_obj_id ) == nullptr ); + } + +} + /** * Matches the two orders, the first parameter is taker, the second is maker. * @@ -725,11 +810,32 @@ int database::match( const limit_order_object& bid, const call_order_object& ask } -asset database::match( const call_order_object& call, - const force_settlement_object& settle, +asset database::match( const force_settlement_object& settle, + const call_order_object& call, const price& match_price, - asset max_settlement, + const asset& max_settlement, + const price& fill_price, + bool is_margin_call ) +{ + return match_impl( settle, call, match_price, max_settlement, fill_price, is_margin_call, true ); +} + +asset database::match( const call_order_object& call, + const force_settlement_object& settle, + const price& match_price, + const asset& max_settlement, const price& fill_price ) +{ + return match_impl( settle, call, match_price, max_settlement, fill_price, true, false ); +} + +asset database::match_impl( const force_settlement_object& settle, + const call_order_object& call, + const price& match_price, + const asset& max_settlement, + const price& fill_price, + bool is_margin_call, + bool settle_is_taker ) { try { FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id ); FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0); @@ -744,6 +850,13 @@ asset database::match( const call_order_object& call, asset call_pays = call_receives * match_price; // round down here, in favor of call order, for first check // TODO possible optimization: check need to round up or down first + // Note: when is_margin_call == true, the call order is being margin called, + // match_price is the price that the call order pays, + // fill_price is the price that the settle order receives, + // the difference is the margin-call fee + + asset settle_receives = is_margin_call ? ( call_receives * fill_price ) : call_pays; + // Be here, the call order may be paying nothing. bool cull_settle_order = false; // whether need to cancel dust settle order if( call_pays.amount == 0 ) @@ -753,16 +866,30 @@ asset database::match( const call_order_object& call, if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order { call_pays.amount = 1; + settle_receives.amount = 1; // Note: no margin-call fee in this case even if is_margin_call } else { if( call_receives == settle.balance ) // the settle order is smaller { cancel_settle_order( settle ); + // If the settle order is canceled, we just return, since nothing else can be done + return asset( 0, settle.balance.asset_id ); } - // else do nothing: neither order will be completely filled, perhaps due to max_settlement too small - - return asset( 0, settle.balance.asset_id ); + // else : neither order will be completely filled, perhaps due to max_settlement too small + + // If the call order is not being margin called, we simply return and continue outside + if( !is_margin_call ) + return asset( 0, settle.balance.asset_id ); + + // Be here, the call order is being margin called, and it is not being fully covered due to TCR, + // and the settle order is big enough. + // So the call order is considered as the smaller one, and we should round up call_pays. + // We have ( call_receives == max_settlement == call_order.get_max_debt_to_cover() ). + // It is guaranteed by call_order.get_max_debt_to_cover() that rounding up call_pays + // would not reduce CR of the call order, but would push it to be above MCR. + call_pays.amount = 1; + settle_receives.amount = 1; // Note: no margin-call fee in this case } } @@ -775,6 +902,10 @@ asset database::match( const call_order_object& call, { call_pays = call_receives.multiply_and_round_up( match_price ); // round up here, in favor of settle order // be here, we should have: call_pays <= call_collateral + if( is_margin_call ) + settle_receives = call_receives.multiply_and_round_up( fill_price ); + else + settle_receives = call_pays; // Note: fill_price is not used in calculation when is_margin_call is false } else { @@ -799,12 +930,11 @@ asset database::match( const call_order_object& call, } asset settle_pays = call_receives; - asset settle_receives = call_pays; /** * If the least collateralized call position lacks sufficient - * collateral to cover at the match price then this indicates a black - * swan event according to the price feed, but only the market + * collateral to cover at the match price then this indicates a black + * swan event according to the price feed, but only the market * can trigger a black swan. So now we must cancel the forced settlement * object. */ @@ -817,8 +947,11 @@ asset database::match( const call_order_object& call, } // else do nothing, since black swan event won't happen, and the assertion is no longer true - fill_call_order( call, call_pays, call_receives, fill_price, true ); // call order is maker - fill_settle_order( settle, settle_pays, settle_receives, fill_price, false ); // force settlement order is taker + asset margin_call_fee = call_pays - settle_receives; + + fill_call_order( call, call_pays, call_receives, fill_price, settle_is_taker, margin_call_fee ); + // do not pay force-settlement fee if the call is being margin called + fill_settle_order( settle, settle_pays, settle_receives, fill_price, !settle_is_taker, !is_margin_call ); if( cull_settle_order ) cancel_settle_order( settle ); @@ -1032,7 +1165,7 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay * @returns TRUE if the settle order was completely filled, FALSE if only partially filled */ bool database::fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker ) + const price& fill_price, bool is_maker, bool pay_force_settle_fee ) { try { bool filled = false; @@ -1054,7 +1187,9 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a // debt asset is passed to the pay function so it knows where to put the fee. Note that // amount of collateral asset upon which fee is assessed is reduced by market_fees already // paid to prevent the total fee exceeding total collateral. - asset force_settle_fees = pay_force_settle_fees( get(pays.asset_id), receives - market_fees ); + asset force_settle_fees = pay_force_settle_fee + ? pay_force_settle_fees( get(pays.asset_id), receives - market_fees ) + : asset( 0, receives.asset_id ); auto total_collateral_denominated_fees = market_fees + force_settle_fees; @@ -1108,7 +1243,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( !mia.is_market_issued() ) return false; const asset_bitasset_data_object& bitasset = ( bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this) ); - + // price feeds can cause black swans in prediction markets // The hardfork check may be able to be removed after the hardfork date // if check_for_blackswan never triggered a black swan on a prediction market. @@ -1129,7 +1264,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue // Looking for limit orders selling the most USD for the least CORE. - auto max_price = price::max( mia.id, bitasset.options.short_backing_asset ); + auto max_price = price::max( bitasset.asset_id, bitasset.options.short_backing_asset ); // Stop when limit orders are selling too little USD for too much CORE. // Note that since BSIP74, margin calls offer somewhat less CORE per USD // if the issuer claims a Margin Call Fee. @@ -1150,8 +1285,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const auto& call_price_index = call_index.indices().get(); const auto& call_collateral_index = call_index.indices().get(); - auto call_min = price::min( bitasset.options.short_backing_asset, mia.id ); - auto call_max = price::max( bitasset.options.short_backing_asset, mia.id ); + auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); + auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_price_itr = call_price_index.begin(); auto call_price_end = call_price_itr; @@ -1179,12 +1314,15 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool after_hardfork_436 = ( head_time > HARDFORK_436_TIME ); bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding - bool before_core_hardfork_343 = ( maint_time <= HARDFORK_CORE_343_TIME ); // update call_price after partially filled + bool before_core_hardfork_343 = ( maint_time <= HARDFORK_CORE_343_TIME ); // update call_price on partial fill bool before_core_hardfork_453 = ( maint_time <= HARDFORK_CORE_453_TIME ); // multiple matching issue bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option - while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance by passing in iterators + bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls + + while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance + // by passing in iterators && limit_itr != limit_end && ( ( !before_core_hardfork_1270 && call_collateral_itr != call_collateral_end ) || ( before_core_hardfork_1270 && call_price_itr != call_price_end ) ) ) @@ -1194,7 +1332,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - if( ( !before_core_hardfork_1270 && bitasset.current_maintenance_collateralization < call_order.collateralization() ) + if( ( !before_core_hardfork_1270 + && bitasset.current_maintenance_collateralization < call_order.collateralization() ) || ( before_core_hardfork_1270 && after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) ) return margin_called; @@ -1218,15 +1357,15 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa margin_called = true; // Although we checked for black swan above, we do one more check to ensure the call order can - // pay the amount of collateral which we intend to take from it (including margin call fee). I - // guess this is just a sanity check, as, I'm not sure how we'd get here without it being - // detected in the prior swan check, aside perhaps for rounding errors. Or maybe there was some - // way prior to hf_1270. + // pay the amount of collateral which we intend to take from it (including margin call fee). + // TODO refactor code for better performance and readability, perhaps extract the new logic to a new + // function and call it after hf_1270, hf_bsip74 or hf_2481. auto usd_to_buy = call_order.get_debt(); - if( usd_to_buy * call_pays_price > call_order.get_collateral() ) + if( !after_core_hardfork_2481 && usd_to_buy * call_pays_price > call_order.get_collateral() ) { + // Trigger black swan elog( "black swan detected on asset ${symbol} (${id}) at block ${b}", - ("id",mia.id)("symbol",mia.symbol)("b",head_num) ); + ("id",bitasset.asset_id)("symbol",mia.symbol)("b",head_num) ); edump((enable_black_swan)); FC_ASSERT( enable_black_swan ); globally_settle_asset(mia, bitasset.current_feed.settlement_price ); @@ -1271,9 +1410,19 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // so calling maybe_cull_small() will always cull it. call_receives = limit_receives.multiply_and_round_up( match_price ); + if( after_core_hardfork_2481 ) + { + if( call_pays.amount >= call_order.collateral ) + break; + auto new_collateral = call_order.get_collateral() - call_pays; + auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math + if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease + break; + } + filled_limit = true; - } else { // fill call + } else { // fill call, could be partial fill due to TCR call_receives = usd_to_buy; if( before_core_hardfork_342 ) @@ -1281,10 +1430,21 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa limit_receives = usd_to_buy * match_price; // round down, in favor of call order call_pays = limit_receives; } else { - limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. - // Note: TODO: Due to different rounding, couldn't this potentialy be - // one satoshi more than the blackswan check above? Can this bite us? + // Note: Due to different rounding, this could potentialy be + // one satoshi more than the blackswan check above + if( call_pays.amount > call_order.collateral ) + { + if( after_core_hardfork_2481 ) + break; + call_pays.amount = call_order.collateral; + } + // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher + // than the old CR, so no additional check for potential blackswan here + + limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order + if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 + limit_receives.amount = call_order.collateral; } filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hard fork core-343) @@ -1324,9 +1484,68 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } // while call_itr != call_end + // Check margin calls against force settlements + if( after_core_hardfork_2481 && !bitasset.has_settlement() ) + { + // Be here, there exists at least one margin call not processed + match_force_settlements( bitasset ); + // At last, check for blackswan + check_for_blackswan( mia, enable_black_swan, &bitasset ); // TODO perhaps improve performance by passing in iterators + } + return margin_called; } FC_CAPTURE_AND_RETHROW() } +void database::match_force_settlements( const asset_bitasset_data_object& bitasset ) +{ + // Defensive checks + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + FC_ASSERT( HARDFORK_CORE_2481_PASSED( maint_time ), "Internal error: hard fork core-2481 not passed" ); + FC_ASSERT( !bitasset.is_prediction_market, "Internal error: asset is a prediction market" ); + FC_ASSERT( !bitasset.has_settlement(), "Internal error: asset is globally settled already" ); + FC_ASSERT( !bitasset.current_feed.settlement_price.is_null(), "Internal error: no sufficient price feeds" ); + + const auto& settlement_index = get_index_type().indices().get(); + auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); + auto settle_end = settlement_index.upper_bound( bitasset.asset_id ); + + const auto& call_collateral_index = get_index_type().indices().get(); + auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); + auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id ); + auto call_itr = call_collateral_index.lower_bound( call_min ); + auto call_end = call_collateral_index.upper_bound( call_max ); + + // Price at which margin calls sit on the books. + // It is the MCOP, which may deviate from MSSP due to MCFR. + price call_match_price = bitasset.current_feed. + margin_call_order_price(bitasset.options.extensions.value.margin_call_fee_ratio); + // Price margin call actually relinquishes collateral at. Equals the MSSP and it may + // differ from call_match_price if there is a Margin Call Fee. + price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + + while( settle_itr != settle_end && call_itr != call_end ) + { + const force_settlement_object& settle_order = *settle_itr; + const call_order_object& call_order = *call_itr; + + // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 + if( bitasset.current_maintenance_collateralization < call_order.collateralization() ) + return; + + // TCR applies here + asset max_debt_to_cover( call_order.get_max_debt_to_cover( call_pays_price, + bitasset.current_feed.settlement_price, + bitasset.current_feed.maintenance_collateral_ratio, + bitasset.current_maintenance_collateralization ), + bitasset.asset_id ); + + match( call_order, settle_order, call_pays_price, max_debt_to_cover, call_match_price ); + + settle_itr = settlement_index.lower_bound( bitasset.asset_id ); + call_itr = call_collateral_index.lower_bound( call_min ); + } +} + void database::pay_order( const account_object& receiver, const asset& receives, const asset& pays ) { const auto& balances = receiver.statistics(*this); @@ -1410,7 +1629,7 @@ asset database::pay_market_fees(const account_object* seller, const asset_object if (seller == nullptr) return false; const auto &white_list = recv_asset.options.extensions.value.whitelist_market_fee_sharing; - return ( !white_list || (*white_list).empty() + return ( !white_list || (*white_list).empty() || ( (*white_list).find(seller->registrar) != (*white_list).end() ) ); }; diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index b1b09c6c01..38b72a60f3 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -188,18 +188,29 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s { if( !mia.is_market_issued() ) return false; - const asset_bitasset_data_object& bitasset = ( bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this) ); + const asset_bitasset_data_object& bitasset = bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this); if( bitasset.has_settlement() ) return true; // already force settled auto settle_price = bitasset.current_feed.settlement_price; if( settle_price.is_null() ) return false; // no feed - const call_order_object* call_ptr = nullptr; // place holder for the call order with least collateral ratio - - asset_id_type debt_asset_id = mia.id; - auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); + asset_id_type debt_asset_id = bitasset.asset_id; auto maint_time = get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls + + // After core-2481 hard fork, if there are force-settlements, match call orders with them first + if( after_core_hardfork_2481 ) + { + const auto& settlement_index = get_index_type().indices().get(); + auto lower_itr = settlement_index.lower_bound( debt_asset_id ); + if( lower_itr != settlement_index.end() && lower_itr->balance.asset_id == debt_asset_id ) + return false; + } + + const call_order_object* call_ptr = nullptr; // place holder for the call order with least collateral ratio + + auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price { @@ -221,7 +232,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s return false; price highest = settle_price; - if( maint_time > HARDFORK_CORE_1270_TIME ) + if( !before_core_hardfork_1270 ) // due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here highest = bitasset.current_feed.max_short_squeeze_price(); else if( maint_time > HARDFORK_CORE_338_TIME ) @@ -232,20 +243,41 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s const auto& limit_price_index = limit_index.indices().get(); // looking for limit orders selling the most USD for the least CORE - auto highest_possible_bid = price::max( mia.id, bitasset.options.short_backing_asset ); + auto highest_possible_bid = price::max( debt_asset_id, bitasset.options.short_backing_asset ); // stop when limit orders are selling too little USD for too much CORE - auto lowest_possible_bid = price::min( mia.id, bitasset.options.short_backing_asset ); + auto lowest_possible_bid = price::min( debt_asset_id, bitasset.options.short_backing_asset ); FC_ASSERT( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id ); // NOTE limit_price_index is sorted from greatest to least auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); - if( limit_itr != limit_end ) { + if( limit_itr != limit_end ) + { FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); - highest = std::max( limit_itr->sell_price, highest ); + auto call_pays_price = limit_itr->sell_price; + if( after_core_hardfork_2481 ) + { + // due to margin call fee, we check with MCPP (margin call pays price) here + call_pays_price = call_pays_price * bitasset.current_feed.margin_call_pays_ratio( + bitasset.options.extensions.value.margin_call_fee_ratio ); + } + highest = std::max( call_pays_price, highest ); } + // The variable `highest` after hf_338: + // * if no limit order, it is expected to be the black swan price; if the call order with the least CR + // has CR below or equal to the black swan price, we trigger GS; + // * if there exists at least one limit order and the price is higher, we use the limit order's price, + // which means we will match the margin call orders with the limit order first. + // + // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered + // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got + // caught by an additional check in check_call_orders(). + // This bug is fixed in hf_2481. Actually, after hf_2481, + // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(); + // * if there is no force settlement, we check here with margin call fee in consideration. + auto least_collateral = call_ptr->collateralization(); if( ~least_collateral >= highest ) { @@ -261,8 +293,27 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s ("sp",settle_price.to_real())("~sp",(~settle_price).to_real()) ("h",highest.to_real())("~h",(~highest).to_real()) ); edump((enable_black_swan)); - FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price ) + FC_ASSERT( enable_black_swan, + "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); + if( after_core_hardfork_2481 ) + { + // After hf_2481, when a global settlement occurs, + // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and + // they are closed at the same price; + // * the debt positions with CR > MCR do not pay premium or margin call fee, and they are closed at a same + // price too. + // * The GS price would close the position with the least CR with no collateral left for the owner, + // but would close other positions with some collateral left (if any) for their owners. + // * Both the premium and the margin call fee paid by the margin calls go to the asset owner, none will go + // to the global settlement fund, because + // - if a part of the premium or fees goes to the global settlement fund, it means there would be a + // difference in settlement prices, so traders are incentivized to create new debt in the last minute + // then settle after GS to earn free money; + // - if no premium or fees goes to the global settlement fund, it means debt asset holders would only + // settle for less after GS, so they are incentivized to settle before GS which helps avoid GS. + globally_settle_asset(mia, ~least_collateral, true ); + } + else if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price ) // global settle at feed price if possible globally_settle_asset(mia, settle_price ); else @@ -301,7 +352,13 @@ void database::clear_expired_orders() } } - //Process expired force settlement orders + // Process expired force settlement orders + // TODO Possible performance optimization. Looping through all assets is not ideal. + // - One idea is to check time first, if any expired settlement found, check asset. + // However, due to max_settlement_volume, this does not work, i.e. time meets but have to + // skip due to volume limit. + // - Instead, maintain some data e.g. (whether_force_settle_volome_meets, first_settle_time) + // in bitasset_data object and index by them, then we could process here faster. auto& settlement_index = get_index_type().indices().get(); if( !settlement_index.empty() ) { @@ -446,7 +503,7 @@ void database::clear_expired_orders() break; } try { - asset new_settled = match(*itr, order, settlement_price, max_settlement, settlement_fill_price); + asset new_settled = match(order, *itr, settlement_price, max_settlement, settlement_fill_price); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { if( find_object( order_id ) ) // the settle order hasn't been cancelled diff --git a/libraries/chain/hardfork.d/CORE_2481.hf b/libraries/chain/hardfork.d/CORE_2481.hf new file mode 100644 index 0000000000..c42f1bccb5 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2481.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2481 Match force-settlements with margin calls at normal margin call fill price +#ifndef HARDFORK_CORE_2481_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2481_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2481_PASSED(now) (now > HARDFORK_CORE_2481_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 579ff94a0f..ede754db74 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -146,6 +146,7 @@ namespace graphene { namespace chain { operation_result do_apply(const operation_type& op); const asset_object* asset_to_settle = nullptr; + const asset_bitasset_data_object* bitasset_ptr = nullptr; }; class asset_publish_feeds_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 300010d127..3b8815893f 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -374,34 +374,72 @@ namespace graphene { namespace chain { /// @ingroup Market Helpers /// @{ - void globally_settle_asset( const asset_object& bitasset, const price& settle_price ); - void cancel_settle_order(const force_settlement_object& order, bool create_virtual_op = true); - void cancel_limit_order(const limit_order_object& order, bool create_virtual_op = true, bool skip_cancel_fee = false); + + /// Globally settle @p bitasset at @p settle_price, let margin calls pay a premium and margin call fee if + /// @p check_margin_calls is @c true (in this case others would be closed not at @p settle_price but at a + /// price better for their owners). + void globally_settle_asset( const asset_object& bitasset, const price& settle_price, + bool check_margin_calls = false ); + void cancel_settle_order( const force_settlement_object& order ); + void cancel_limit_order( const limit_order_object& order, + bool create_virtual_op = true, + bool skip_cancel_fee = false ); void revive_bitasset( const asset_object& bitasset ); void cancel_bid(const collateral_bid_object& bid, bool create_virtual_op = true); - void execute_bid( const collateral_bid_object& bid, share_type debt_covered, share_type collateral_from_fund, const price_feed& current_feed ); + void execute_bid( const collateral_bid_object& bid, share_type debt_covered, + share_type collateral_from_fund, const price_feed& current_feed ); private: template void globally_settle_asset_impl( const asset_object& bitasset, const price& settle_price, - const IndexType& call_index ); + const IndexType& call_index, + bool check_margin_calls = false ); + void match_force_settlements( const asset_bitasset_data_object& bitasset ); + /// Matches the two orders + /// @param settle the force-settlement order + /// @param call the call order + /// @param match_price the price to calculate how much the call order pays + /// @param max_settlement the maximum debt amount to be filled during this match + /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate + /// how much the settle order receives when the call order is being margin called + /// @param is_margin_call whether the call order is being margin called + /// @param settle_order_is_taker whether the settle_order is the taker + /// @return the amount of asset settled + asset match_impl( const force_settlement_object& settle, + const call_order_object& call, + const price& match_price, + const asset& max_settlement, + const price& fill_price, + bool is_margin_call = false, + bool settle_order_is_taker = true ); + public: /** * @brief Process a new limit order through the markets * @param new_order_object The new order to process - * @param allow_black_swan whether to allow a black swan event * @return true if order was completely filled; false otherwise * * This function takes a new limit order, and runs the markets attempting to match it with existing orders * already on the books. */ ///@{ - bool apply_order_before_hardfork_625(const limit_order_object& new_order_object, bool allow_black_swan = true); - bool apply_order(const limit_order_object& new_order_object, bool allow_black_swan = true); + bool apply_order_before_hardfork_625( const limit_order_object& new_order_object ); + bool apply_order( const limit_order_object& new_order_object ); ///@} + /** + * @brief Process a new force-settlement request + * @param new_settlment The new force-settlement request + * @param bitasset The bitasset data object + * + * Since the core-2481 hard fork, this function is called after a new force-settlement object is created + * to check if there are margin calls to be matched instantly. + */ + void apply_force_settlement( const force_settlement_object& new_settlement, + const asset_bitasset_data_object& bitasset ); + /** * Matches the two orders, the first parameter is taker, the second is maker. * @@ -430,7 +468,7 @@ namespace graphene { namespace chain { const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization, const price& call_pays_price); - // If separate call_pays_price not provided, assume call pays at trade_price: + /// If separate call_pays_price not provided, assume call pays at trade_price: int match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization) { @@ -441,12 +479,34 @@ namespace graphene { namespace chain { ///@} /// Matches the two orders, the first parameter is taker, the second is maker. + /// @param settle the force-settlement order + /// @param call the call order + /// @param match_price the price to calculate how much the call order pays + /// @param max_settlement the maximum debt amount to be filled during this match + /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate + /// how much the settle order receives when the call order is being margin called + /// @param is_margin_call whether the call order is being margin called + /// @return the amount of asset settled + asset match( const force_settlement_object& settle, + const call_order_object& call, + const price& match_price, + const asset& max_settlement, + const price& fill_price, + bool is_margin_call = false ); + + /// Matches the two orders, the first parameter is taker, the second is maker. + /// @param call the call order being margin called + /// @param settle the force-settlement order + /// @param match_price the price to calculate how much the call order pays + /// @param max_settlement the maximum debt amount to be filled during this match + /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate + /// how much the settle order receives. /// @return the amount of asset settled - asset match(const call_order_object& call, - const force_settlement_object& settle, - const price& match_price, - asset max_settlement, - const price& fill_price); + asset match( const call_order_object& call, + const force_settlement_object& settle, + const price& match_price, + const asset& max_settlement, + const price& fill_price ); /** * @brief fills limit order @@ -481,9 +541,10 @@ namespace graphene { namespace chain { } bool fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker ); + const price& fill_price, bool is_maker, bool pay_force_settle_fee = true ); - bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, bool for_new_limit_order = false, + bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, + bool for_new_limit_order = false, const asset_bitasset_data_object* bitasset_ptr = nullptr ); // helpers to fill_order @@ -570,7 +631,8 @@ namespace graphene { namespace chain { private: void _apply_block( const signed_block& next_block ); processed_transaction _apply_transaction( const signed_transaction& trx ); - void _cancel_bids_and_revive_mpa( const asset_object& bitasset, const asset_bitasset_data_object& bad ); + void _cancel_bids_and_revive_mpa( const asset_object& bitasset, + const asset_bitasset_data_object& bad ); ///Steps involved in applying a new block ///@{ @@ -601,7 +663,7 @@ namespace graphene { namespace chain { void update_withdraw_permissions(); void update_credit_offers_and_deals(); bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, - const asset_bitasset_data_object* bitasset_ptr = nullptr ); + const asset_bitasset_data_object* bitasset_ptr = nullptr); void clear_expired_htlcs(); ///Steps performed only at maintenance intervals diff --git a/libraries/chain/market_object.cpp b/libraries/chain/market_object.cpp index 0cd19b63f5..99d9110fe2 100644 --- a/libraries/chain/market_object.cpp +++ b/libraries/chain/market_object.cpp @@ -80,7 +80,8 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, } // According to the feed protection rule (https://github.com/cryptonomex/graphene/issues/436), - // a call order should only be called when its collateral ratio is not higher than required maintenance collateral ratio. + // a call order should only be called when its collateral ratio is not higher than required + // maintenance collateral ratio. // Although this should be guaranteed by the caller of this function, we still check here to be defensive. // Theoretically this check can be skipped for better performance. // @@ -92,7 +93,8 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, if( !target_collateral_ratio.valid() ) // target cr is not set return debt; - uint16_t tcr = std::max( *target_collateral_ratio, maintenance_collateral_ratio ); // use mcr if target cr is too small + // use mcr if target cr is too small + uint16_t tcr = std::max( *target_collateral_ratio, maintenance_collateral_ratio ); price target_collateralization = ( after_core_hardfork_1270 ? feed_price * ratio_type( tcr, GRAPHENE_COLLATERAL_RATIO_DENOM ) : @@ -105,7 +107,7 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, FC_ASSERT( match_price.base.asset_id == call_price.base.asset_id && match_price.quote.asset_id == call_price.quote.asset_id ); - typedef boost::multiprecision::int256_t i256; + using i256 = boost::multiprecision::int256_t; i256 mp_debt_amt = match_price.quote.amount.value; i256 mp_coll_amt = match_price.base.amount.value; i256 fp_debt_amt = feed_price.quote.amount.value; @@ -175,7 +177,8 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, max_to_cover.amount = debt; } - if( max_to_pay <= to_pay || max_to_cover <= to_cover ) // strange data. should skip binary search and go on, but doesn't help much + if( max_to_pay <= to_pay || max_to_cover <= to_cover ) // strange data. should skip binary search and go on, + // but doesn't help much return debt; FC_ASSERT( max_to_pay > to_pay && max_to_cover > to_cover ); diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 8ee46ac572..386dd73917 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -252,13 +252,16 @@ namespace graphene { namespace protocol { { struct fee_parameters_type { }; + asset_settle_cancel_operation() = default; + asset_settle_cancel_operation( const force_settlement_id_type& fsid, const account_id_type& aid, + const asset& a ) : settlement(fsid), account(aid), amount(a) {} + asset fee; force_settlement_id_type settlement; /// Account requesting the force settlement. This account pays the fee account_id_type account; /// Amount of asset to force settle. This must be a market-issued asset asset amount; - extensions_type extensions; account_id_type fee_payer()const { return account; } /*** @@ -670,7 +673,7 @@ FC_REFLECT( graphene::protocol::asset_update_feed_producers_operation, FC_REFLECT( graphene::protocol::asset_publish_feed_operation, (fee)(publisher)(asset_id)(feed)(extensions) ) FC_REFLECT( graphene::protocol::asset_settle_operation, (fee)(account)(amount)(extensions) ) -FC_REFLECT( graphene::protocol::asset_settle_cancel_operation, (fee)(settlement)(account)(amount)(extensions) ) +FC_REFLECT( graphene::protocol::asset_settle_cancel_operation, (fee)(settlement)(account)(amount) ) FC_REFLECT( graphene::protocol::asset_global_settle_operation, (fee)(issuer)(asset_to_settle)(settle_price)(extensions) ) FC_REFLECT( graphene::protocol::asset_issue_operation, (fee)(issuer)(asset_to_issue)(issue_to_account)(memo)(extensions) ) From ddd26f851fe5bc6612a6952d9ff5431903bef6ed Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 5 Jul 2021 17:17:21 +0000 Subject: [PATCH 078/258] Add tests for GS after core-2481 hard fork --- tests/tests/market_tests.cpp | 120 +++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index e7d60a0aa0..2fb95e66fa 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1304,6 +1304,126 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test) } FC_LOG_AND_RETHROW() } +/*** + * Tests a scenario after the core-2481 hard fork that GS may occur when there is no sufficient collateral + * to pay margin call fee, but GS won't occur if no need to pay margin call fee. The amount gathered to the + * global settlement fund will be different than the case before the hard fork. + */ +BOOST_AUTO_TEST_CASE(mcfr_blackswan_test_after_hf_core_2481) +{ try { + // Proceeds to the core-2481 hard fork time + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(borrower3)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 80; + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 400% collateral, call price is 20/1.75 CORE/USD = 80/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(20000)); + call_order_id_type call2_id = call2.id; + // create yet another position with 800% collateral, call price is 40/1.75 CORE/USD = 160/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(40000)); + call_order_id_type call3_id = call3.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 20000, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 40000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // No margin call at this moment + + // This order is sufficient to close the first debt position and no GS if margin call fee ratio is 0 + limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(1000), core.amount(14900))->id; + + BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value ); + + BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 20000, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call3_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to get call_order into black swan territory + BOOST_MESSAGE( "Trying to trigger GS" ); + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(18); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/18, mssp = 10/198 + + // GS occurs even when there is a good sell order + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + + // after the core-2481 hard fork, GS price is not 1/18. + // * the first call order would pay all collateral. + // due to margin call fee, not all collateral enters global settlement fund, but + // fund_receives = round_up(15000 / 1.1) = 13637 + // fees = 15000 - 13637 = 1363 + // * the second call order was in margin call territory too, so it would pay a premium and margin call fee. + // fund_receives = 13637 + // fees = 15000 - 13637 = 1363 + // the rest ( 20000 - 15000 = 5000 ) returns to borrower2 + // * the third call order was not in margin call territory, so no premium or margin call fee. + // fund_receives = round_up(15000 / 1.1) = 13637 + // GS price is 1/18, but the first call order has only 15000 thus capped + BOOST_CHECK_EQUAL( 13637 * 3, usd_id(db).bitasset_data(db).settlement_fund.value ); + BOOST_CHECK_EQUAL( 1363 * 2, usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value ); + + // the sell order does not change + BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value ); + + // generate a block to include operations above + BOOST_MESSAGE( "Generating a new block" ); + generate_block(); + +} FC_LOG_AND_RETHROW() } + /*** * Tests a scenario about rounding errors related to margin call fee */ From 564de55a07cbed0fa6987b826a0bc6536df989d3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 6 Jul 2021 14:24:19 +0000 Subject: [PATCH 079/258] Fix code smells --- libraries/protocol/include/graphene/protocol/base.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/protocol/include/graphene/protocol/base.hpp b/libraries/protocol/include/graphene/protocol/base.hpp index edc03ab5f0..d7b533d0d3 100644 --- a/libraries/protocol/include/graphene/protocol/base.hpp +++ b/libraries/protocol/include/graphene/protocol/base.hpp @@ -128,10 +128,10 @@ namespace graphene { namespace protocol { { return params.fee; } - virtual void get_required_authorities( vector& )const{} - virtual void get_required_active_authorities( flat_set& )const{} - virtual void get_required_owner_authorities( flat_set& )const{} - virtual void validate()const{} + virtual void get_required_authorities( vector& )const{ /* do nothing by default */ } + virtual void get_required_active_authorities( flat_set& )const{ /* do nothing by default */ } + virtual void get_required_owner_authorities( flat_set& )const{ /* do nothing by default */ } + virtual void validate()const{ /* do nothing by default */ } fc::optional< fc::future > validate_parallel( uint32_t skip )const; static uint64_t calculate_data_fee( uint64_t bytes, uint64_t price_per_kbyte ); From 337fccb4e2c6359ad5bb923b9c446e6e6aa960af Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 9 Jul 2021 20:49:45 +0000 Subject: [PATCH 080/258] Handle blackswan when matching calls with settles --- libraries/chain/db_market.cpp | 196 ++++++++++++------ .../chain/include/graphene/chain/database.hpp | 12 +- 2 files changed, 139 insertions(+), 69 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 51defd5433..d547f6a6c7 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -815,27 +815,32 @@ asset database::match( const force_settlement_object& settle, const price& match_price, const asset& max_settlement, const price& fill_price, - bool is_margin_call ) + bool is_margin_call, + const ratio_type* margin_call_pays_ratio ) { - return match_impl( settle, call, match_price, max_settlement, fill_price, is_margin_call, true ); + return match_impl( settle, call, match_price, max_settlement, fill_price, is_margin_call, true, + margin_call_pays_ratio ); } asset database::match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, const asset& max_settlement, - const price& fill_price ) + const price& fill_price, + const ratio_type* margin_call_pays_ratio ) { - return match_impl( settle, call, match_price, max_settlement, fill_price, true, false ); + return match_impl( settle, call, match_price, max_settlement, fill_price, true, false, + margin_call_pays_ratio ); } asset database::match_impl( const force_settlement_object& settle, const call_order_object& call, - const price& match_price, + const price& p_match_price, const asset& max_settlement, - const price& fill_price, + const price& p_fill_price, bool is_margin_call, - bool settle_is_taker ) + bool settle_is_taker, + const ratio_type* margin_call_pays_ratio ) { try { FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id ); FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0); @@ -845,6 +850,10 @@ asset database::match_impl( const force_settlement_object& settle, auto settle_for_sale = std::min(settle.balance, max_settlement); auto call_debt = call.get_debt(); + auto call_collateral = call.get_collateral(); + + price match_price = p_match_price; + price fill_price = p_fill_price; asset call_receives = std::min(settle_for_sale, call_debt); asset call_pays = call_receives * match_price; // round down here, in favor of call order, for first check @@ -859,75 +868,122 @@ asset database::match_impl( const force_settlement_object& settle, // Be here, the call order may be paying nothing. bool cull_settle_order = false; // whether need to cancel dust settle order - if( call_pays.amount == 0 ) + if( maint_time > HARDFORK_CORE_184_TIME && call_pays.amount == 0 ) { - if( maint_time > HARDFORK_CORE_184_TIME ) + if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order { - if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order - { - call_pays.amount = 1; - settle_receives.amount = 1; // Note: no margin-call fee in this case even if is_margin_call - } - else + call_pays.amount = 1; + settle_receives.amount = 1; // Note: no margin-call fee in this case even if is_margin_call + } + else if( call_receives == settle.balance ) // the settle order is smaller + { + cancel_settle_order( settle ); + // If the settle order is canceled, we just return, since nothing else can be done + return asset( 0, call_debt.asset_id ); + } + // be here, neither order will be completely filled, perhaps due to max_settlement too small + else if( !is_margin_call ) + { + // If the call order is not being margin called, we simply return and continue outside + return asset( 0, call_debt.asset_id ); + } + else + { + // Be here, the call order is being margin called, and it is not being fully covered due to TCR, + // and the settle order is big enough. + // So the call order is considered as the smaller one, and we should round up call_pays. + // We have ( call_receives == max_settlement == call_order.get_max_debt_to_cover() ). + // It is guaranteed by call_order.get_max_debt_to_cover() that rounding up call_pays + // would not reduce CR of the call order, but would push it to be above MCR. + call_pays.amount = 1; + settle_receives.amount = 1; // Note: no margin-call fee in this case + } + } + else if( !before_core_hardfork_342 && call_pays.amount != 0 ) + { + // be here, the call order is not paying nothing, + // but it is still possible that the settle order is paying more than minimum required due to rounding + if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order + { + call_pays = call_receives.multiply_and_round_up( match_price ); // round up here, in favor of settle order + if( is_margin_call ) // implies hf core-2481 { - if( call_receives == settle.balance ) // the settle order is smaller + if( call_pays.amount > call.collateral ) // CR too low { - cancel_settle_order( settle ); - // If the settle order is canceled, we just return, since nothing else can be done - return asset( 0, settle.balance.asset_id ); + call_pays.amount = call.collateral; + match_price = call_debt / call_collateral; + fill_price = ( margin_call_pays_ratio != nullptr ) ? ( match_price / (*margin_call_pays_ratio) ) + : match_price; } - // else : neither order will be completely filled, perhaps due to max_settlement too small - - // If the call order is not being margin called, we simply return and continue outside - if( !is_margin_call ) - return asset( 0, settle.balance.asset_id ); - - // Be here, the call order is being margin called, and it is not being fully covered due to TCR, - // and the settle order is big enough. - // So the call order is considered as the smaller one, and we should round up call_pays. - // We have ( call_receives == max_settlement == call_order.get_max_debt_to_cover() ). - // It is guaranteed by call_order.get_max_debt_to_cover() that rounding up call_pays - // would not reduce CR of the call order, but would push it to be above MCR. - call_pays.amount = 1; - settle_receives.amount = 1; // Note: no margin-call fee in this case + settle_receives = call_receives.multiply_and_round_up( fill_price ); } + else // be here, we should have: call_pays <= call_collateral + settle_receives = call_pays; // Note: fill_price is not used in calculation when is_margin_call is false } - - } - else // the call order is not paying nothing, but still possible it's paying more than minimum required due to rounding - { - if( !before_core_hardfork_342 ) + else // the call order is not completely filled, due to max_settlement too small or settle order too small { - if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order + // be here, call_pays has been rounded down + + if( is_margin_call ) // implies hf core-2481 { - call_pays = call_receives.multiply_and_round_up( match_price ); // round up here, in favor of settle order - // be here, we should have: call_pays <= call_collateral - if( is_margin_call ) - settle_receives = call_receives.multiply_and_round_up( fill_price ); + bool cap_price = false; + if( call_pays.amount >= call.collateral ) // CR too low + cap_price = true; else - settle_receives = call_pays; // Note: fill_price is not used in calculation when is_margin_call is false - } - else - { - // be here, call_pays has been rounded down + { + auto new_collateral = call_collateral - call_pays; + auto new_debt = call_debt - call_receives; // the result is positive due to math + if( ( new_collateral / new_debt ) < call.collateralization() ) // if CR would decrease + cap_price = true; + } + if( cap_price ) + { + match_price = call_debt / call_collateral; + call_pays = call_receives * match_price; // round down here, in favor of call order + // price changed, check if it is something-for-nothing again + if( call_pays.amount == 0 ) + { + // Note: when it is a margin call, max_settlement is max_debt_to_cover. + // if need to cap price here, max_debt_to_cover should be equal to call_debt. + // if call pays 0, it means the settle order is really small. + cancel_settle_order( settle ); + // If the settle order is canceled, we just return, since nothing else can be done + return asset( 0, call_debt.asset_id ); + } + // update fill price and settle_receives + if( margin_call_pays_ratio != nullptr ) + { + fill_price = match_price / (*margin_call_pays_ratio); + settle_receives = call_receives * fill_price; // round down here, in favor of call order + } + else + { + fill_price = match_price; + settle_receives = call_pays; + } + } + if( settle_receives.amount == 0 ) + settle_receives.amount = 1; // reduce margin-call fee in this case. Note: here call_pays >= 1 + } // end : if is_margin_call - // be here, we should have: call_pays <= call_collateral + // be here, we should have: call_pays <= call_collateral - if( call_receives == settle.balance ) // the settle order will be completely filled, assuming we need to cull it - cull_settle_order = true; - // else do nothing, since we can't cull the settle order + // if the settle order will be completely filled, assuming we need to cull it + if( call_receives == settle.balance ) + cull_settle_order = true; + // else do nothing, since we can't cull the settle order - call_receives = call_pays.multiply_and_round_up( match_price ); // round up here to mitigate rounding issue (core-342). - // It is important to understand here that the newly - // rounded up call_receives won't be greater than the - // old call_receives. + // round up here to mitigate rounding issue (core-342). + // It is important to understand the math that the newly rounded up call_receives won't be greater than the + // old call_receives. + call_receives = call_pays.multiply_and_round_up( match_price ); - if( call_receives == settle.balance ) // the settle order will be completely filled, no need to cull - cull_settle_order = false; - // else do nothing, since we still need to cull the settle order or still can't cull the settle order - } + if( call_receives == settle.balance ) // the settle order will be completely filled, no need to cull + cull_settle_order = false; + // else do nothing, since we still need to cull the settle order or still can't cull the settle order } } + // else : before the core-184 hf or the core-342 hf, do nothing asset settle_pays = call_receives; @@ -940,7 +996,6 @@ asset database::match_impl( const force_settlement_object& settle, */ if( before_core_hardfork_342 ) { - auto call_collateral = call.get_collateral(); GRAPHENE_ASSERT( call_pays < call_collateral, black_swan_exception, "" ); assert( settle_pays == settle_for_sale || call_receives == call.get_debt() ); @@ -957,7 +1012,7 @@ asset database::match_impl( const force_settlement_object& settle, cancel_settle_order( settle ); return call_receives; -} FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) } +} FC_CAPTURE_AND_RETHROW( (p_match_price)(max_settlement)(p_fill_price)(is_margin_call)(settle_is_taker) ) } bool database::fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small, const price& fill_price, const bool is_maker) @@ -1061,7 +1116,7 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p return maybe_cull_small_order( *this, order ); return false; } -} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) } +} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } /*** * @brief fill a call order in the specified amounts @@ -1141,7 +1196,7 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay remove( order ); return collateral_freed.valid(); -} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) } +} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } /*** * @brief fullfill a settle order in the specified amounts @@ -1214,7 +1269,7 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a return filled; -} FC_CAPTURE_AND_RETHROW( (settle)(pays)(receives) ) } +} FC_CAPTURE_AND_RETHROW( (pays)(receives) ) } /** * Starting with the least collateralized orders, fill them if their @@ -1517,12 +1572,17 @@ void database::match_force_settlements( const asset_bitasset_data_object& bitass // Price at which margin calls sit on the books. // It is the MCOP, which may deviate from MSSP due to MCFR. + // It is in debt/collateral . price call_match_price = bitasset.current_feed. margin_call_order_price(bitasset.options.extensions.value.margin_call_fee_ratio); // Price margin call actually relinquishes collateral at. Equals the MSSP and it may // differ from call_match_price if there is a Margin Call Fee. + // It is in debt/collateral . price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + auto margin_call_pays_ratio = bitasset.current_feed.margin_call_pays_ratio( + bitasset.options.extensions.value.margin_call_fee_ratio); + while( settle_itr != settle_end && call_itr != call_end ) { const force_settlement_object& settle_order = *settle_itr; @@ -1539,7 +1599,11 @@ void database::match_force_settlements( const asset_bitasset_data_object& bitass bitasset.current_maintenance_collateralization ), bitasset.asset_id ); - match( call_order, settle_order, call_pays_price, max_debt_to_cover, call_match_price ); + // Note: if the call order's CR is too low, it is probably unable to fill at call_pays_price. + // In this case, the call order pays at its CR, the settle order may receive less due to margin call fee. + // It is processed inside the function. + match( call_order, settle_order, call_pays_price, max_debt_to_cover, call_match_price, + &margin_call_pays_ratio ); settle_itr = settlement_index.lower_bound( bitasset.asset_id ); call_itr = call_collateral_index.lower_bound( call_min ); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 3b8815893f..888be6e292 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -405,6 +405,7 @@ namespace graphene { namespace chain { /// how much the settle order receives when the call order is being margin called /// @param is_margin_call whether the call order is being margin called /// @param settle_order_is_taker whether the settle_order is the taker + /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match_impl( const force_settlement_object& settle, const call_order_object& call, @@ -412,7 +413,8 @@ namespace graphene { namespace chain { const asset& max_settlement, const price& fill_price, bool is_margin_call = false, - bool settle_order_is_taker = true ); + bool settle_order_is_taker = true, + const ratio_type* margin_call_pays_ratio = nullptr ); public: @@ -486,13 +488,15 @@ namespace graphene { namespace chain { /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives when the call order is being margin called /// @param is_margin_call whether the call order is being margin called + /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match( const force_settlement_object& settle, const call_order_object& call, const price& match_price, const asset& max_settlement, const price& fill_price, - bool is_margin_call = false ); + bool is_margin_call = false, + const ratio_type* margin_call_pays_ratio = nullptr ); /// Matches the two orders, the first parameter is taker, the second is maker. /// @param call the call order being margin called @@ -501,12 +505,14 @@ namespace graphene { namespace chain { /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives. + /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, const asset& max_settlement, - const price& fill_price ); + const price& fill_price, + const ratio_type* margin_call_pays_ratio = nullptr ); /** * @brief fills limit order From bec187ac59682cbb9d2f9d56216928560cfadaa1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 10 Jul 2021 16:59:57 +0000 Subject: [PATCH 081/258] Fix potential undercollateralization issue --- libraries/chain/db_market.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index d547f6a6c7..1ce37717d9 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -865,6 +865,7 @@ asset database::match_impl( const force_settlement_object& settle, // the difference is the margin-call fee asset settle_receives = is_margin_call ? ( call_receives * fill_price ) : call_pays; + asset settle_pays = call_receives; // Be here, the call order may be paying nothing. bool cull_settle_order = false; // whether need to cancel dust settle order @@ -924,6 +925,11 @@ asset database::match_impl( const force_settlement_object& settle, { // be here, call_pays has been rounded down + // round up here to mitigate rounding issues (hf core-342). + // It is important to understand the math that the newly rounded up call_receives won't be greater than the + // old call_receives. + call_receives = call_pays.multiply_and_round_up( match_price ); + if( is_margin_call ) // implies hf core-2481 { bool cap_price = false; @@ -939,7 +945,7 @@ asset database::match_impl( const force_settlement_object& settle, if( cap_price ) { match_price = call_debt / call_collateral; - call_pays = call_receives * match_price; // round down here, in favor of call order + call_pays = settle_pays * match_price; // round down here, in favor of call order // price changed, check if it is something-for-nothing again if( call_pays.amount == 0 ) { @@ -950,6 +956,8 @@ asset database::match_impl( const force_settlement_object& settle, // If the settle order is canceled, we just return, since nothing else can be done return asset( 0, call_debt.asset_id ); } + // price changed, update call_receives // round up to mitigate rounding issues (hf core-342) + call_receives = call_pays.multiply_and_round_up( match_price ); // update fill price and settle_receives if( margin_call_pays_ratio != nullptr ) { @@ -968,25 +976,16 @@ asset database::match_impl( const force_settlement_object& settle, // be here, we should have: call_pays <= call_collateral - // if the settle order will be completely filled, assuming we need to cull it - if( call_receives == settle.balance ) + // if the settle order is too small, mark it to be culled + if( settle_pays == settle.balance && call_receives != settle.balance ) cull_settle_order = true; - // else do nothing, since we can't cull the settle order + // else do nothing, since we can't cull the settle order, or it is already fully filled - // round up here to mitigate rounding issue (core-342). - // It is important to understand the math that the newly rounded up call_receives won't be greater than the - // old call_receives. - call_receives = call_pays.multiply_and_round_up( match_price ); - - if( call_receives == settle.balance ) // the settle order will be completely filled, no need to cull - cull_settle_order = false; - // else do nothing, since we still need to cull the settle order or still can't cull the settle order + settle_pays = call_receives; } } // else : before the core-184 hf or the core-342 hf, do nothing - asset settle_pays = call_receives; - /** * If the least collateralized call position lacks sufficient * collateral to cover at the match price then this indicates a black From b8153865d18462ecfa155edc56e1a333160af1b0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 10 Jul 2021 17:23:55 +0000 Subject: [PATCH 082/258] Wrap long lines --- libraries/chain/db_market.cpp | 65 +++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 1ce37717d9..df2eb0478c 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -229,8 +229,8 @@ void database::cancel_bid(const collateral_bid_object& bid, bool create_virtual_ remove(bid); } -void database::execute_bid( const collateral_bid_object& bid, share_type debt_covered, share_type collateral_from_fund, - const price_feed& current_feed ) +void database::execute_bid( const collateral_bid_object& bid, share_type debt_covered, + share_type collateral_from_fund, const price_feed& current_feed ) { const call_order_object& call_obj = create( [&](call_order_object& call ){ call.borrower = bid.bidder; @@ -253,7 +253,8 @@ void database::execute_bid( const collateral_bid_object& bid, share_type debt_co stats.total_core_in_orders += call_obj.collateral; }); - push_applied_operation( execute_bid_operation( bid.bidder, asset( debt_covered, bid.inv_swan_price.quote.asset_id ), + push_applied_operation( execute_bid_operation( bid.bidder, + asset( debt_covered, bid.inv_swan_price.quote.asset_id ), asset( call_obj.collateral, bid.inv_swan_price.base.asset_id ) ) ); remove(bid); @@ -434,8 +435,10 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord return true; if( head_block_time() <= HARDFORK_555_TIME ) return false; - // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() being called by match() above - // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that was done too eagerly before, and + // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() + // being called by match() above + // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that + // was done too eagerly before, and // this is the point it's deferred to. return maybe_cull_small_order( *this, *updated_order_object ); } @@ -546,11 +549,12 @@ bool database::apply_order(const limit_order_object& new_order_object) { auto old_limit_itr = limit_itr; ++limit_itr; - // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. + // match returns 2 when only the old order was fully filled. + // In this case, we keep matching; otherwise, we stop. finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); } - if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hard fork + if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls const auto& call_collateral_idx = get_index_type().indices().get(); @@ -571,8 +575,9 @@ bool database::apply_order(const limit_order_object& new_order_object) sell_abd->current_feed.maintenance_collateral_ratio, sell_abd->current_maintenance_collateralization, call_pays_price); - // match returns 1 or 3 when the new order was fully filled. In this case, we stop matching; otherwise keep matching. - // since match can return 0 due to BSIP38 (hard fork core-834), we no longer only check if the result is 2. + // match returns 1 or 3 when the new order was fully filled. + // In this case, we stop matching; otherwise keep matching. + // since match can return 0 due to BSIP38 (hf core-834), we no longer only check if the result is 2. if( match_result == 1 || match_result == 3 ) finished = true; } @@ -584,20 +589,24 @@ bool database::apply_order(const limit_order_object& new_order_object) auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) { - // assume hard fork core-343 and core-625 will take place at same time, always check call order with least call_price + // assume hard fork core-343 and core-625 will take place at same time, + // always check call order with least call_price auto call_itr = call_price_idx.lower_bound( call_min ); if( call_itr == call_price_idx.end() || call_itr->debt_type() != sell_asset_id // feed protected https://github.com/cryptonomex/graphene/issues/436 || call_itr->call_price > ~sell_abd->current_feed.settlement_price ) break; - // assume hard fork core-338 and core-625 will take place at same time, not checking HARDFORK_CORE_338_TIME here. + // assume hard fork core-338 and core-625 will take place at same time, + // not checking HARDFORK_CORE_338_TIME here. int match_result = match( new_order_object, *call_itr, call_match_price, sell_abd->current_feed.settlement_price, sell_abd->current_feed.maintenance_collateral_ratio, optional() ); - // match returns 1 or 3 when the new order was fully filled. In this case, we stop matching; otherwise keep matching. - // since match can return 0 due to BSIP38 (hard fork core-834), we no longer only check if the result is 2. + // match returns 1 or 3 when the new order was fully filled. + // In this case, we stop matching; otherwise keep matching. + // since match can return 0 due to BSIP38 (hard fork core-834), + // we no longer only check if the result is 2. if( match_result == 1 || match_result == 3 ) finished = true; } @@ -617,8 +626,10 @@ bool database::apply_order(const limit_order_object& new_order_object) if( updated_order_object == nullptr ) return true; - // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() being called by match() above - // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that was done too eagerly before, and + // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() + // being called by match() above + // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that + // was done too eagerly before, and // this is the point it's deferred to. return maybe_cull_small_order( *this, *updated_order_object ); } @@ -748,8 +759,10 @@ int database::match( const limit_order_object& usd, const limit_order_object& co core_pays == core.amount_for_sale() ); int result = 0; - result |= fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ); // the first param is taker - result |= fill_limit_order( core, core_pays, core_receives, true, match_price, true ) << 1; // the second param is maker + // the first param of match() is taker + result |= fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ); + // the second param of match() is maker + result |= fill_limit_order( core, core_pays, core_receives, true, match_price, true ) << 1; FC_ASSERT( result != 0 ); return result; } @@ -857,7 +870,8 @@ asset database::match_impl( const force_settlement_object& settle, asset call_receives = std::min(settle_for_sale, call_debt); asset call_pays = call_receives * match_price; // round down here, in favor of call order, for first check - // TODO possible optimization: check need to round up or down first + // TODO possible optimization: check need to round up + // or down first // Note: when is_margin_call == true, the call order is being margin called, // match_price is the price that the call order pays, @@ -1013,8 +1027,8 @@ asset database::match_impl( const force_settlement_object& settle, return call_receives; } FC_CAPTURE_AND_RETHROW( (p_match_price)(max_settlement)(p_fill_price)(is_margin_call)(settle_is_taker) ) } -bool database::fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small, - const price& fill_price, const bool is_maker) +bool database::fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives, + bool cull_if_small, const price& fill_price, const bool is_maker) { try { cull_if_small |= (head_block_time() < HARDFORK_555_TIME); @@ -1028,7 +1042,8 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p pay_order( seller, receives - issuer_fees, pays ); assert( pays.asset_id != receives.asset_id ); - push_applied_operation( fill_order_operation( order.id, order.seller, pays, receives, issuer_fees, fill_price, is_maker ) ); + push_applied_operation( fill_order_operation( order.id, order.seller, pays, receives, + issuer_fees, fill_price, is_maker ) ); // BSIP85: Maker order creation fee discount, https://github.com/bitshares/bsips/blob/master/bsip-0085.md // if the order creation fee was paid in BTS, @@ -1501,7 +1516,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa limit_receives.amount = call_order.collateral; } - filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hard fork core-343) + filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) if( usd_to_buy == usd_for_sale ) filled_limit = true; @@ -1543,8 +1558,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa { // Be here, there exists at least one margin call not processed match_force_settlements( bitasset ); - // At last, check for blackswan - check_for_blackswan( mia, enable_black_swan, &bitasset ); // TODO perhaps improve performance by passing in iterators + // At last, check for blackswan // TODO perhaps improve performance by passing in iterators + check_for_blackswan( mia, enable_black_swan, &bitasset ); } return margin_called; @@ -1730,7 +1745,7 @@ asset database::pay_market_fees(const account_object* seller, const asset_object if( referrer != registrar ) { const auto referrer_rewards_value = detail::calculate_percent( reward.amount, - seller->referrer_rewards_percentage ); + seller->referrer_rewards_percentage ); if ( referrer_rewards_value > 0 && is_authorized_asset(*this, referrer(*this), recv_asset) ) { From a4e81a055c24bfd7b922740b2ec6c2a6b9296418 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 10 Jul 2021 20:36:45 +0000 Subject: [PATCH 083/258] Split clear_expired_orders() into 2 functions --- libraries/chain/db_block.cpp | 1 + libraries/chain/db_update.cpp | 13 ++++++++++--- libraries/chain/include/graphene/chain/database.hpp | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 520f0614a0..97b4bee493 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -644,6 +644,7 @@ void database::_apply_block( const signed_block& next_block ) clear_expired_transactions(); clear_expired_proposals(); clear_expired_orders(); + clear_expired_force_settlements(); clear_expired_htlcs(); update_expired_feeds(); // this will update expired feeds and some core exchange rates update_core_exchange_rates(); // this will update remaining core exchange rates diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 38b72a60f3..38ffbaae93 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -329,8 +329,6 @@ void database::clear_expired_orders() auto head_time = head_block_time(); auto maint_time = get_dynamic_global_properties().next_maintenance_time; - bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing - bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call auto& limit_index = get_index_type().indices().get(); @@ -351,7 +349,16 @@ void database::clear_expired_orders() check_call_orders( quote_asset( *this ) ); } } +} FC_CAPTURE_AND_RETHROW() } + +void database::clear_expired_force_settlements() +{ try { + //Cancel expired limit orders + auto head_time = head_block_time(); + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing + bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding // Process expired force settlement orders // TODO Possible performance optimization. Looping through all assets is not ideal. // - One idea is to check time first, if any expired settlement found, check asset. @@ -531,7 +538,7 @@ void database::clear_expired_orders() } if( mia.force_settled_volume != settled.amount ) { - modify(mia, [settled](asset_bitasset_data_object& b) { + modify(mia, [&settled](asset_bitasset_data_object& b) { b.force_settled_volume = settled.amount; }); } diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 888be6e292..3274cbe1e1 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -663,6 +663,7 @@ namespace graphene { namespace chain { void clear_expired_transactions(); void clear_expired_proposals(); void clear_expired_orders(); + void clear_expired_force_settlements(); void update_expired_feeds(); void update_core_exchange_rates(); void update_maintenance_flag( bool new_maintenance_flag ); From 66c3382fe908b6d8db693ec6a27818b4d878074b Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 10 Jul 2021 22:21:39 +0000 Subject: [PATCH 084/258] Add some regression tests for hf core-2481 --- tests/common/database_fixture.hpp | 1 + tests/tests/bitasset_tests.cpp | 18 ++- tests/tests/market_tests.cpp | 180 +++++++++++++++++++++++++----- tests/tests/swan_tests.cpp | 88 +++++++++++++-- 4 files changed, 251 insertions(+), 36 deletions(-) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index e562633e9c..45c1c0dc58 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -219,6 +219,7 @@ struct database_fixture_base { bool skip_key_index_test = false; uint32_t anon_acct_count; bool hf1270 = false; + bool hf2481 = false; bool bsip77 = false; string es_index_prefix; ///< Index prefix for elasticsearch plugin diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 9af6fa5e71..96ac4555f2 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -454,7 +454,9 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); auto hf_time = HARDFORK_CORE_868_890_TIME; - if(hf1270) + if(hf2481) + hf_time = HARDFORK_CORE_2481_TIME; + else if(hf1270) hf_time = HARDFORK_CORE_1270_TIME; for( int i=0; i<2; ++i ) @@ -927,7 +929,7 @@ BOOST_AUTO_TEST_CASE( hf_1270_test ) generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); generate_block( skip ); - for( int i = 0; i < 10; ++i ) + for( int i = 0; i < 12; ++i ) { idump( (i) ); int blocks = 0; @@ -947,6 +949,11 @@ BOOST_AUTO_TEST_CASE( hf_1270_test ) { generate_blocks( HARDFORK_BSIP_77_TIME, true, skip ); } + else if( i == 10 ) // go beyond hard fork 2481 + { + generate_blocks( HARDFORK_CORE_2481_TIME - mi, true, skip ); + generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); + } set_expiration( db, trx ); ACTORS( (seller)(borrower)(feedproducer)(feedproducer2)(feedproducer3) ); @@ -1405,6 +1412,13 @@ BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) hf1270 = true; INVOKE(hf_890_test); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hf_890_test_hf2481) +{ try { + hf2481 = true; + INVOKE(hf_890_test); + } FC_LOG_AND_RETHROW() } diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 2fb95e66fa..58c0a7adaf 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -235,7 +235,9 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) auto mi = db.get_global_properties().parameters.maintenance_interval; - if(hf1270) + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else if(hf1270) generate_blocks(HARDFORK_CORE_1270_TIME - mi); else generate_blocks(HARDFORK_CORE_343_TIME - mi); @@ -332,7 +334,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) // call's call_price will be updated after the match, to 741/31/1.75 CORE/USD = 2964/217 // it's above settlement price (10/1) so won't be margin called again - if(!hf1270) // can use call price only if we are before hf1270 + if(!hf1270 && !hf2481) // can use call price only if we are before hf1270 BOOST_CHECK( price(asset(2964),asset(217,usd_id)) == call.call_price ); // This would match with call before, but would match with call2 after #343 fixed @@ -350,7 +352,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) BOOST_CHECK_EQUAL( 1000, call3.debt.value ); BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); // call2's call_price will be updated after the match, to 78/3/1.75 CORE/USD = 312/21 - if(!hf1270) // can use call price only if we are before hf1270 + if(!hf1270 && !hf2481) // can use call price only if we are before hf1270 BOOST_CHECK( price(asset(312),asset(21,usd_id)) == call2.call_price ); // it's above settlement price (10/1) so won't be margin called @@ -362,27 +364,45 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) force_settle( seller, bitusd.amount(10) ); BOOST_CHECK_EQUAL( 1583, get_balance(seller, bitusd) ); - BOOST_CHECK_EQUAL( 15401, get_balance(seller, core) ); + if( hf2481 ) // force settle matches with margin calls, at mssp 1/11 + BOOST_CHECK_EQUAL( 15511, get_balance(seller, core) ); // 15401 + 10 * 11 + else + BOOST_CHECK_EQUAL( 15401, get_balance(seller, core) ); BOOST_CHECK_EQUAL( 310, call.debt.value ); BOOST_CHECK_EQUAL( 7410, call.collateral.value ); BOOST_CHECK_EQUAL( 300, call2.debt.value ); BOOST_CHECK_EQUAL( 7800, call2.collateral.value ); - BOOST_CHECK_EQUAL( 1000, call3.debt.value ); - BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + if( hf2481 ) // force settle matches with margin calls, at mssp 1/11 + { + BOOST_CHECK_EQUAL( 990, call3.debt.value ); // 1000 - 10 + BOOST_CHECK_EQUAL( 15890, call3.collateral.value ); // 16000 - 10 * 11 + } + else + { + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + } - // generate blocks to let the settle order execute (price feed will expire after it) + // generate blocks to let the settle order execute (only before hf2481) (price feed will expire after it) generate_block(); generate_blocks( db.head_block_time() + fc::hours(24) ); - // call3 get settled, at settlement price 1/10: #343 fixed + // if before hf2481, call3 get settled, at settlement price 1/10: #343 fixed + // else matched at above step already BOOST_CHECK_EQUAL( 1583, get_balance(seller_id, usd_id) ); - BOOST_CHECK_EQUAL( 15501, get_balance(seller_id, core_id) ); + if( hf2481 ) + BOOST_CHECK_EQUAL( 15511, get_balance(seller_id, core_id) ); // no change + else + BOOST_CHECK_EQUAL( 15501, get_balance(seller_id, core_id) ); // 15401 + 10 * 10 BOOST_CHECK_EQUAL( 310, call_id(db).debt.value ); BOOST_CHECK_EQUAL( 7410, call_id(db).collateral.value ); BOOST_CHECK_EQUAL( 300, call2_id(db).debt.value ); BOOST_CHECK_EQUAL( 7800, call2_id(db).collateral.value ); BOOST_CHECK_EQUAL( 990, call3_id(db).debt.value ); - BOOST_CHECK_EQUAL( 15900, call3_id(db).collateral.value ); + if( hf2481 ) + BOOST_CHECK_EQUAL( 15890, call3_id(db).collateral.value ); + else + BOOST_CHECK_EQUAL( 15900, call3_id(db).collateral.value ); // 16000 - 10 * 10 set_expiration( db, trx ); update_feed_producers( usd_id(db), {feedproducer_id} ); @@ -418,7 +438,9 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) auto mi = db.get_global_properties().parameters.maintenance_interval; - if(hf1270) + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else if(hf1270) generate_blocks(HARDFORK_CORE_1270_TIME - mi); else generate_blocks(HARDFORK_CORE_343_TIME - mi); @@ -503,7 +525,9 @@ BOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test) auto mi = db.get_global_properties().parameters.maintenance_interval; - if(hf1270) + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else if(hf1270) generate_blocks(HARDFORK_CORE_1270_TIME - mi); else generate_blocks(HARDFORK_CORE_625_TIME - mi); @@ -1429,8 +1453,18 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test_after_hf_core_2481) */ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) { try { - // Proceeds to the bsip-74 hard fork time - generate_blocks(HARDFORK_CORE_BSIP74_TIME); + + if(hf2481) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + { + // Proceeds to the bsip-74 hard fork time + generate_blocks(HARDFORK_CORE_BSIP74_TIME); + } set_expiration( db, trx ); ACTORS((seller)(borrower)(borrower2)(feedproducer)(feeder2)(feeder3)); @@ -1482,6 +1516,8 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) BOOST_CHECK_EQUAL( 40000, call2.collateral.value ); BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower2, core) ); // No margin call at this moment @@ -1498,6 +1534,8 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower2, core) ); // Tring to adjust price feed to get call_order into margin call territory BOOST_MESSAGE( "Trying to trigger a margin call" ); @@ -1505,17 +1543,40 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) feed2.settlement_price = bitusd.amount( 1 ) / core.amount(18); publish_feed( bitusd, feedproducer, feed2 ); - // The first call order should have been filled - BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find( call_id ) ); - BOOST_REQUIRE( db.find( call2_id ) ); + if(hf2481) + { + // blackswan + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + int64_t call_pays_to_fund = (15000 * 10 + 10) / 11; + BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, + call_pays_to_fund * 2 ); + BOOST_CHECK_EQUAL( usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value, + 15000 - call_pays_to_fund ); + + // sell order doesn't change + BOOST_CHECK_EQUAL( 1100, sell_mid(db).for_sale.value ); + // seller balance doesn't change + BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - call_pays_to_fund, get_balance(borrower2, core) ); + } + else + { + // The first call order should have been filled + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_REQUIRE( db.find( call2_id ) ); - BOOST_CHECK_EQUAL( 100, sell_mid(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100, sell_mid(db).for_sale.value ); - BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); - BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); - BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) ); - BOOST_CHECK_EQUAL( 14047, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 14047, get_balance(seller, core) ); + } // generate a block to include operations above BOOST_MESSAGE( "Generating a new block" ); @@ -1531,7 +1592,9 @@ BOOST_AUTO_TEST_CASE(target_cr_test_limit_call) auto mi = db.get_global_properties().parameters.maintenance_interval; - if(hf1270) + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else if(hf1270) generate_blocks(HARDFORK_CORE_1270_TIME - mi); else generate_blocks(HARDFORK_CORE_834_TIME - mi); @@ -1715,7 +1778,9 @@ BOOST_AUTO_TEST_CASE(target_cr_test_call_limit) auto mi = db.get_global_properties().parameters.maintenance_interval; - if(hf1270) + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else if(hf1270) generate_blocks(HARDFORK_CORE_1270_TIME - mi); else generate_blocks(HARDFORK_CORE_834_TIME - mi); @@ -1921,7 +1986,10 @@ BOOST_AUTO_TEST_CASE(mcr_bug_increase_after1270) { try { auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_1270_TIME - mi); + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else + generate_blocks(HARDFORK_CORE_1270_TIME - mi); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); @@ -2057,7 +2125,10 @@ BOOST_AUTO_TEST_CASE(mcr_bug_decrease_after1270) { try { auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_1270_TIME - mi); + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else + generate_blocks(HARDFORK_CORE_1270_TIME - mi); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); @@ -2202,5 +2273,60 @@ BOOST_AUTO_TEST_CASE(target_cr_test_call_limit_after_hf1270) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(hardfork_core_338_test_after_hf2481) +{ try { + hf2481 = true; + INVOKE(hardfork_core_338_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hardfork_core_453_test_after_hf2481) +{ try { + hf2481 = true; + INVOKE(hardfork_core_453_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test_after_hf2481) +{ try { + hf2481 = true; + INVOKE(hardfork_core_625_big_limit_order_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(target_cr_test_limit_call_after_hf2481) +{ try { + hf2481 = true; + INVOKE(target_cr_test_limit_call); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(target_cr_test_call_limit_after_hf2481) +{ try { + hf2481 = true; + INVOKE(target_cr_test_call_limit); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_decrease_after2481) +{ try { + hf2481 = true; + INVOKE(mcr_bug_decrease_after1270); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_increase_after2481) +{ try { + hf2481 = true; + INVOKE(mcr_bug_increase_after1270); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcfr_rounding_test_after2481) +{ try { + hf2481 = true; + INVOKE(mcfr_rounding_test); + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index e99c0607fb..92999679b6 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -90,7 +90,14 @@ struct swan_fixture : database_fixture { FC_ASSERT( get_balance(borrower(), swan()) == amount1 ); FC_ASSERT( get_balance(borrower2(), swan()) == amount2 - 1 ); FC_ASSERT( get_balance(borrower() , back()) == init_balance - 2*amount1 ); - FC_ASSERT( get_balance(borrower2(), back()) == init_balance - 2*amount2 ); + if( !hf_core_2481_passed() ) + FC_ASSERT( get_balance(borrower2(), back()) == init_balance - 2*amount2 ); + else + { + auto mssr = swan().bitasset_data(db).current_feed.maximum_short_squeeze_ratio; + auto denom = GRAPHENE_COLLATERAL_RATIO_DENOM; + FC_ASSERT( get_balance(borrower2(), back()) == init_balance - (2*amount2*denom+mssr-1)/mssr); + } BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); @@ -119,6 +126,17 @@ struct swan_fixture : database_fixture { generate_blocks(HARDFORK_CORE_1270_TIME - mi); wait_for_maintenance(); } + void wait_for_hf_core_2481() { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + wait_for_maintenance(); + } + + bool hf_core_2481_passed() { + if( !hf2481 ) return false; + auto maint_time = db.get_dynamic_global_properties().next_maintenance_time; + return HARDFORK_CORE_2481_PASSED( maint_time ); + } void wait_for_maintenance() { generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); @@ -146,7 +164,9 @@ BOOST_FIXTURE_TEST_SUITE( swan_tests, swan_fixture ) */ BOOST_AUTO_TEST_CASE( black_swan ) { try { - if(hf1270) + if(hf2481) + wait_for_hf_core_2481(); + else if(hf1270) wait_for_hf_core_1270(); init_standard_swan(); @@ -291,7 +311,9 @@ BOOST_AUTO_TEST_CASE( revive_recovered ) { try { init_standard_swan( 700 ); - if(hf1270) + if(hf2481) + wait_for_hf_core_2481(); + else if(hf1270) wait_for_hf_core_1270(); else wait_for_hf_core_216(); @@ -316,7 +338,9 @@ BOOST_AUTO_TEST_CASE( recollateralize ) // no hardfork yet GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(1000), swan().amount(100) ), fc::exception ); - if(hf1270) + if(hf2481) + wait_for_hf_core_2481(); + else if(hf1270) wait_for_hf_core_1270(); else wait_for_hf_core_216(); @@ -436,7 +460,9 @@ BOOST_AUTO_TEST_CASE( revive_empty_recovered ) { try { limit_order_id_type oid = init_standard_swan( 1000 ); - if(hf1270) + if(hf2481) + wait_for_hf_core_2481(); + else if(hf1270) wait_for_hf_core_1270(); else wait_for_hf_core_216(); @@ -466,7 +492,9 @@ BOOST_AUTO_TEST_CASE( revive_empty_recovered ) */ BOOST_AUTO_TEST_CASE( revive_empty ) { try { - if(hf1270) + if(hf2481) + wait_for_hf_core_2481(); + else if(hf1270) wait_for_hf_core_1270(); else wait_for_hf_core_216(); @@ -493,7 +521,9 @@ BOOST_AUTO_TEST_CASE( revive_empty ) */ BOOST_AUTO_TEST_CASE( revive_empty_with_bid ) { try { - if(hf1270) + if(hf2481) + wait_for_hf_core_2481(); + else if(hf1270) wait_for_hf_core_1270(); else wait_for_hf_core_216(); @@ -585,6 +615,50 @@ BOOST_AUTO_TEST_CASE(revive_empty_with_bid_hf1270) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(black_swan_after_hf2481) +{ try { + hf2481 = true; + INVOKE(black_swan); + +} FC_LOG_AND_RETHROW() } + +// black_swan_issue_346_hf2481 is skipped as it is already failing with HARDFORK_CORE_834_TIME + +BOOST_AUTO_TEST_CASE(revive_recovered_hf2481) +{ try { + hf2481 = true; + INVOKE(revive_recovered); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(recollateralize_hf2481) +{ try { + hf2481 = true; + INVOKE(recollateralize); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_recovered_hf2481) +{ try { + hf2481 = true; + INVOKE(revive_empty_recovered); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_hf2481) +{ try { + hf2481 = true; + INVOKE(revive_empty); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_with_bid_hf2481) +{ try { + hf2481 = true; + INVOKE(revive_empty_with_bid); + +} FC_LOG_AND_RETHROW() } + /** Creates a black swan, bids on more than outstanding debt */ BOOST_AUTO_TEST_CASE( overflow ) From e1f00990f76b2d57535c2015077623b8837f3f0c Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 11 Jul 2021 01:05:34 +0000 Subject: [PATCH 085/258] Add tests for matching settlements with calls --- tests/tests/force_settle_match_tests.cpp | 409 +++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 tests/tests/force_settle_match_tests.cpp diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp new file mode 100644 index 0000000000..1e760c43a6 --- /dev/null +++ b/tests/tests/force_settle_match_tests.cpp @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include + +#include +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(force_settle_match_tests, database_fixture) + +/*** + * BSIP38 "target_collateral_ratio" test after hf core-2481: + * matching a taker settle order with multiple maker call orders + */ +BOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS((buyer)(buyer2)(buyer3)(seller)(borrower)(borrower2)(borrower3)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3% + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, buyer2_id, asset(init_balance)); + transfer(committee_account, buyer3_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175% + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% > 175% + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000); + call_order_id_type call2_id = call2.id; + // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000)); + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); + + // adjust price feed to get call and call2 (but not call3) into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11 + + // This sell order above MSSP will not be matched with a call + limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + + BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // This buy order is too low will not be matched with a sell order + limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->id; + // This buy order at MSSP will be matched only if no margin call (margin call takes precedence) + limit_order_id_type buy_med = create_sell_order(buyer2, asset(33000), bitusd.amount(3000))->id; + // This buy order above MSSP will be matched with a sell order (limit order with better price takes precedence) + limit_order_id_type buy_high = create_sell_order(buyer3, asset(111), bitusd.amount(10))->id; + + BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(buyer2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(buyer3, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) ); + BOOST_CHECK_EQUAL( init_balance - 33000, get_balance(buyer2, core) ); + BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) ); + + // call and call2's CR is quite high, and debt amount is quite a lot, + // assume neither of them will be completely filled + price match_price( bitusd.amount(1) / core.amount(11) ); + share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value ); + BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value ); + // even though call2 has a higher CR, since call's TCR is less than call2's TCR, + // so we expect call will cover less when called + BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value ); + + // Create a force settlement, will be matched with several call orders + auto result = force_settle( seller, bitusd.amount(700*4) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle_id = result.get(); + BOOST_CHECK( db.find( settle_id ) != nullptr ); + + // buy orders won't change + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 33000 ); + BOOST_CHECK_EQUAL( db.find( buy_high )->for_sale.value, 111 ); + + // the settle order will match with call, at mssp: 1/11 = 1000/11000 + const call_order_object* tmp_call = db.find( call_id ); + BOOST_CHECK( tmp_call != nullptr ); + + // call will receive call_to_cover, pay 11*call_to_cover + share_type call_to_pay = call_to_cover * 11; + share_type call_to_settler = (call_to_cover * 10 * 107 + 99) / 100; // round up, favors settle order + BOOST_CHECK_EQUAL( 1000 - call_to_cover.value, call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value ); + // new collateral ratio should be higher than mcr as well as tcr + BOOST_CHECK( call.debt.value * 10 * 1750 < call.collateral.value * 1000 ); + idump( (call) ); + // borrower's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + + // the settle order then will match with call2, at mssp: 1/11 = 1000/11000 + const call_order_object* tmp_call2 = db.find( call2_id ); + BOOST_CHECK( tmp_call2 != nullptr ); + + // call2 will receive call2_to_cover, pay 11*call2_to_cover + share_type call2_to_pay = call2_to_cover * 11; + share_type call2_to_settler = (call2_to_cover * 10 * 107 + 99) / 100; // round up, favors settle order + BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value, call2.collateral.value ); + // new collateral ratio should be higher than mcr as well as tcr + BOOST_CHECK( call2.debt.value * 10 * 2000 < call2.collateral.value * 1000 ); + idump( (call2) ); + // borrower2's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + + // call3 is not in margin call territory so won't be matched + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + + // check the settle order's balance + BOOST_CHECK_EQUAL( 700 * 4 - call2_to_cover.value - call_to_cover.value, + settle_id(db).balance.amount.value ); + + // check seller balance + BOOST_CHECK_EQUAL( 193, get_balance(seller, bitusd) ); // 3000 - 7 - 700*4 + int64_t expected_seller_core_balance = call_to_settler.value + call2_to_settler.value; + BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); + + // asset's force_settled_volume does not change + BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value ); + + // generate a block + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * BSIP38 "target_collateral_ratio" test after hf core-2481: + * matching taker call orders with maker settle orders + */ +BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3% + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + transfer(committee_account, borrower4_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175% + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% > 175% + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000); + call_order_id_type call2_id = call2.id; + // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000)); + // create a small position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7, no tcr + const call_order_object& call4 = *borrow( borrower4, bitusd.amount(10), asset(160) ); + call_order_id_type call4_id = call4.id; + + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); + BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) ); + + // This sell order above MSSP will not be matched with a call + limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + + BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // This buy order is too low will not be matched with a sell order + limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->id; + + BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) ); + + // Create a sell order which will be matched with several call orders later, price 1/9 + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(500), core.amount(4500) )->id; + BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 500 ); + + // Create a force settlement, will be matched with several call orders later + auto result = force_settle( seller, bitusd.amount(2400) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle_id = result.get(); + BOOST_CHECK( db.find( settle_id ) != nullptr ); + + // prepare price feed to get call and call2 (but not call3) into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + + // call and call2's CR is quite high, and debt amount is quite a lot, + // assume neither of them will be completely filled + price match_price = sell_id(db).sell_price * ratio_type(107,110); + share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value ); + BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value ); + // even though call2 has a higher CR, since call's TCR is less than call2's TCR, + // so we expect call will cover less when called + BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value ); + + call_order_object call2_copy = call2; + + // adjust price feed to get call and call2 (but not call3) into margin call territory + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11, mcop = 10/107, mcpr = 110/107 + + // firstly the limit order will match with call, at limit order's price: 1/9 + const call_order_object* tmp_call = db.find( call_id ); + BOOST_CHECK( tmp_call != nullptr ); + + // call will receive call_to_cover, pay 9*call_to_cover + share_type call_to_pay = (call_to_cover * 9 * 110 + 106) / 107; // round up since it's smaller + BOOST_CHECK_EQUAL( 1000 - call_to_cover.value, call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value ); + // new collateral ratio should be higher than mcr as well as tcr + BOOST_CHECK( call.debt.value * 10 * 1750 < call.collateral.value * 1000 ); + idump( (call_to_pay)(call_to_cover)(call) ); + // borrower's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + + // the limit order then will match with call2, at limit order's price: 1/9 + const call_order_object* tmp_call2 = db.find( call2_id ); + BOOST_CHECK( tmp_call2 != nullptr ); + + // if the limit is big enough, call2 will receive call2_to_cover, pay 9*call2_to_cover + // however it's not the case, so call2 will receive less + call2_to_cover = 500 - call_to_cover; + share_type call2_to_pay = call2_to_cover * 9 * 110 / 107; // round down since it's larger + + // borrower2's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + + // call4 will match with the settle order, since it has no tcr, it will be fully closed + // match price is 1/11 + const call_order_object* tmp_call4 = db.find( call4_id ); + BOOST_CHECK( tmp_call4 == nullptr ); + + // borrower4 balance changes + BOOST_CHECK_EQUAL( init_balance - 110, get_balance(borrower4, core) ); + BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) ); + + // call2 is still in margin call territory after matched with limit order, now it matches with settle order + price call_pays_price( asset(1, usd_id), asset(11) ); + call2_copy.debt -= call2_to_cover; + call2_copy.collateral -= call2_to_pay; + auto call2_to_cover2 = call2_copy.get_max_debt_to_cover(call_pays_price,current_feed.settlement_price,1750); + BOOST_CHECK_GT( call2_to_cover2.value, 0 ); + share_type call2_to_pay2 = call2_to_cover2 * 11; + BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value - call2_to_cover2.value, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value - call2_to_pay2.value, call2.collateral.value ); + idump( (call2_to_pay)(call2_to_cover)(call2_to_pay2)(call2_to_cover2)(call2) ); + + // call3 is not in margin call territory so won't be matched + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + + // sell_id is completely filled + BOOST_CHECK( !db.find( sell_id ) ); + + // settle order is not fully filled + BOOST_CHECK( db.find( settle_id ) != nullptr ); + + // check seller balance + BOOST_CHECK_EQUAL( 93, get_balance(seller, bitusd) ); // 3000 - 7 - 500 - 2400 + BOOST_CHECK_EQUAL( 4500 + 107 + (call2_to_cover2.value * 107 + 9) / 10, // round up + get_balance(seller, core) ); // 500*9 + 10*10.7 + call2_cover2 * 10.7 + + // buy_low's price is too low that won't be matched + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + + // generate a block + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() From 4ca64760373af25f63a1d2dc5704670ddac4fcb1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 11 Jul 2021 17:46:17 +0000 Subject: [PATCH 086/258] Fix rounding issue due to TCR on settle/call match --- libraries/chain/db_market.cpp | 68 ++++++++++++++----- .../include/graphene/chain/market_object.hpp | 3 +- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index df2eb0478c..353b2ff850 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -805,6 +805,12 @@ int database::match( const limit_order_object& bid, const call_order_object& ask call_receives = usd_to_buy; order_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up here, in favor of limit order call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); + // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more + // debt asset, it means the call order could be receiving a bit too much less than its value. + // It is a sad thing for the call order, but it is the rule -- when a call order is margin called, + // it does not get more than it borrowed. + // On the other hand, if the call order is not being closed (due to TCR), + // it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it. } order_pays = call_receives; @@ -878,7 +884,7 @@ asset database::match_impl( const force_settlement_object& settle, // fill_price is the price that the settle order receives, // the difference is the margin-call fee - asset settle_receives = is_margin_call ? ( call_receives * fill_price ) : call_pays; + asset settle_receives = call_pays; asset settle_pays = call_receives; // Be here, the call order may be paying nothing. @@ -913,7 +919,7 @@ asset database::match_impl( const force_settlement_object& settle, call_pays.amount = 1; settle_receives.amount = 1; // Note: no margin-call fee in this case } - } + } // end : if after the core-184 hf and call_pays.amount == 0 else if( !before_core_hardfork_342 && call_pays.amount != 0 ) { // be here, the call order is not paying nothing, @@ -933,33 +939,56 @@ asset database::match_impl( const force_settlement_object& settle, settle_receives = call_receives.multiply_and_round_up( fill_price ); } else // be here, we should have: call_pays <= call_collateral + { settle_receives = call_pays; // Note: fill_price is not used in calculation when is_margin_call is false + } } else // the call order is not completely filled, due to max_settlement too small or settle order too small { // be here, call_pays has been rounded down - - // round up here to mitigate rounding issues (hf core-342). - // It is important to understand the math that the newly rounded up call_receives won't be greater than the - // old call_receives. - call_receives = call_pays.multiply_and_round_up( match_price ); - - if( is_margin_call ) // implies hf core-2481 + if( !is_margin_call ) + { + // it was correct to round down call_pays. + // round up here to mitigate rounding issues (hf core-342). + // It is important to understand the math that the newly rounded-up call_receives won't be greater than + // the old call_receives. And rounding up here would NOT make CR lower. + call_receives = call_pays.multiply_and_round_up( match_price ); + } + // the call order is a margin call, implies hf core-2481 + else if( settle_pays == max_settlement ) // the settle order is larger, but the call order has TCR + { + call_pays = call_receives.multiply_and_round_up( match_price ); // round up, in favor of settle order + settle_receives = call_receives.multiply_and_round_up( fill_price ); // round up + // Note: here we do NOT stabilize call_receives since it is done in get_max_debt_to_cover(), + // and it is already the maximum value + } + else // the call order is a margin call, and the settle order is smaller { + // it was correct to round down call_pays. + + // check whether the call order can be filled at match_price bool cap_price = false; if( call_pays.amount >= call.collateral ) // CR too low cap_price = true; else { + // round up to mitigate rounding issues (hf core-342) + call_receives = call_pays.multiply_and_round_up( match_price ); + auto new_collateral = call_collateral - call_pays; auto new_debt = call_debt - call_receives; // the result is positive due to math if( ( new_collateral / new_debt ) < call.collateralization() ) // if CR would decrease cap_price = true; } - if( cap_price ) + + if( !cap_price ) // match_price is good + { + settle_receives = call_receives * fill_price; // round down + } + else // match_price is not good { match_price = call_debt / call_collateral; - call_pays = settle_pays * match_price; // round down here, in favor of call order + call_pays = settle_pays * match_price; // round down here, in favor of call order // price changed, check if it is something-for-nothing again if( call_pays.amount == 0 ) { @@ -971,22 +1000,23 @@ asset database::match_impl( const force_settlement_object& settle, return asset( 0, call_debt.asset_id ); } // price changed, update call_receives // round up to mitigate rounding issues (hf core-342) - call_receives = call_pays.multiply_and_round_up( match_price ); + call_receives = call_pays.multiply_and_round_up( match_price ); // round up // update fill price and settle_receives - if( margin_call_pays_ratio != nullptr ) + if( margin_call_pays_ratio != nullptr ) // check to be defensive { fill_price = match_price / (*margin_call_pays_ratio); settle_receives = call_receives * fill_price; // round down here, in favor of call order } - else + else // normally this code won't be reached. be defensive here { + wlog( "Unexpected: margin_call_pays_ratio == nullptr" ); fill_price = match_price; settle_receives = call_pays; } } if( settle_receives.amount == 0 ) settle_receives.amount = 1; // reduce margin-call fee in this case. Note: here call_pays >= 1 - } // end : if is_margin_call + } // end : if is_margin_call, else ... // be here, we should have: call_pays <= call_collateral @@ -997,7 +1027,7 @@ asset database::match_impl( const force_settlement_object& settle, settle_pays = call_receives; } - } + } // end : if after the core-342 hf and call_pays.amount != 0 // else : before the core-184 hf or the core-342 hf, do nothing /** @@ -1514,6 +1544,12 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 limit_receives.amount = call_order.collateral; + // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more + // debt asset, it means the call order could be receiving a bit too much less than its value. + // It is a sad thing for the call order, but it is the rule -- when a call order is margin called, + // it does not get more than it borrowed. + // On the other hand, if the call order is not being closed (due to TCR), + // it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it. } filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 71e9f108a1..d2ce79df0e 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -156,7 +156,8 @@ class call_order_object : public abstract_object share_type get_max_debt_to_cover( price match_price, price feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization = optional() )const; + const optional& maintenance_collateralization = optional() + )const; }; /** From b1ff84e776fd5cc8f49b2d802aa52e170f746003 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 12 Jul 2021 21:26:09 +0000 Subject: [PATCH 087/258] Add tests about GS when matching settlements --- tests/tests/force_settle_match_tests.cpp | 298 +++++++++++++++++++++++ 1 file changed, 298 insertions(+) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 1e760c43a6..e018ab8ac8 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -406,4 +406,302 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) } FC_LOG_AND_RETHROW() } +/*** + * Matching taker call orders with maker settle orders and triggers blackswan event + */ +BOOST_AUTO_TEST_CASE(call_settle_blackswan) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3% + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + transfer(committee_account, borrower4_id, asset(init_balance)); + transfer(committee_account, borrower5_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175% + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% > 175% + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000); + call_order_id_type call2_id = call2.id; + // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000)); + call_order_id_type call3_id = call3.id; + // create a small position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7, no tcr + const call_order_object& call4 = *borrow( borrower4, bitusd.amount(10), asset(160) ); + call_order_id_type call4_id = call4.id; + // create yet another position with 900% collateral, call price is 45/1.75 CORE/USD = 180/7, no tcr + const call_order_object& call5 = *borrow( borrower5, bitusd.amount(1000), asset(45000)); + call_order_id_type call5_id = call5.id; + + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) ); + BOOST_CHECK_EQUAL( init_balance - 45000, get_balance(borrower5, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); + BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) ); + BOOST_CHECK_EQUAL( 1000, get_balance(borrower5, bitusd) ); + + // This sell order above MCOP will not be matched with a call + limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(150))->id; + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + + BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // This buy order is too low will not be matched with a sell order + limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->id; + + BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) ); + + // Create a sell order which will be matched with several call orders later, price 1/9 + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(1000), core.amount(9000) )->id; + BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 1000 ); + + // Create a force settlement, will be matched with several call orders later + auto result = force_settle( seller, bitusd.amount(400) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle_id = result.get(); + BOOST_CHECK( db.find( settle_id ) != nullptr ); + + // Create another force settlement + result = force_settle( seller, bitusd.amount(100) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle2_id = result.get(); + BOOST_CHECK( db.find( settle2_id ) != nullptr ); + + // prepare price feed to get call, call2, call3 and call4 (but not call5) into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(20); + + // since the sell limit order's price is low, and TCR is set for both call and call2, + // call and call2 will match with the sell limit order + price match_price = sell_id(db).sell_price * ratio_type(107,110); + share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value ); + BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value ); + // even though call2 has a higher CR, since call's TCR is less than call2's TCR, + // so we expect call will cover less when called + BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value ); + + call_order_object call_copy = call; + call_order_object call2_copy = call2; + call_order_object call3_copy = call3; + call_order_object call4_copy = call4; + call_order_object call5_copy = call5; + + // adjust price feed to get call, call2, call3 and call4 (but not call5) into margin call territory + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/20, mssp = 1/22, mcop = 20/107, mcpr = 110/107 + + share_type expected_margin_call_fees = 0; + + // firstly the sell limit order will match with call, at limit order's price: 1/9 + // call will receive call_to_cover, pay 9*call_to_cover + share_type call_to_pay = (call_to_cover * 9 * 110 + 106) / 107; // round up since it's smaller + call_copy.debt -= call_to_cover; + call_copy.collateral -= call_to_pay; + + // the limit order then will match with call2, at limit order's price: 1/9 + // if the limit is big enough, call2 will receive call2_to_cover, pay 9*call2_to_cover + // however it's not the case, so call2 will receive less + call2_to_cover = 1000 - call_to_cover; + share_type call2_to_pay = call2_to_cover * 9 * 110 / 107; // round down since it's larger + call2_copy.debt -= call2_to_cover; + call2_copy.collateral -= call2_to_pay; + + // sell_id is completely filled + BOOST_CHECK( !db.find( sell_id ) ); + share_type margin_call_fee_limit = call_to_pay + call2_to_pay - 9000; + expected_margin_call_fees += margin_call_fee_limit; + + // now call4 has the lowest CR + // call4 will match with the settle order, since it is small and has too few collateral, it will be fully closed + // and it will lose all collateral, 160 + // call_pays_price is 1/16, settle_receives_price is (1/16)*(110/107) = 55/856 + share_type settle_receives4 = 157; // (10 * 856 + 54) / 55; // round up + share_type margin_call_fee_settle_4 = 3; // 160 - 157 + expected_margin_call_fees += margin_call_fee_settle_4; + // borrower4 balance does not change + BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) ); + BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) ); + + // now call2 has the lowest CR + // call2 is still in margin call territory after matched with limit order, now it matches with settle orders + // the settle orders are too small to fill call2 + share_type call2_to_cover1 = 390; // 400 - 10 + share_type call2_to_pay1 = call2_to_cover1 * call2_copy.collateral / call2_copy.debt; // round down + + share_type settle_receives2 = call2_to_pay1 * 107 / 110; // round down + share_type margin_call_fee_settle_2 = call2_to_pay1 - settle_receives2; + expected_margin_call_fees += margin_call_fee_settle_2; + + call2_copy.debt -= call2_to_cover1; + call2_copy.collateral -= call2_to_pay1; + + // call2 matches with the other settle order + share_type call2_to_cover2 = 100; // 400 - 10 + share_type call2_to_pay2 = call2_to_cover2 * call2_copy.collateral / call2_copy.debt; // round down + + share_type settle2_receives2 = call2_to_pay2 * 107 / 110; // round down + share_type margin_call_fee_settle2_2 = call2_to_pay2 - settle2_receives2; + expected_margin_call_fees += margin_call_fee_settle2_2; + + call2_copy.debt -= call2_to_cover2; + call2_copy.collateral -= call2_to_pay2; + + // settle order is fully filled + BOOST_CHECK( db.find( settle_id ) == nullptr ); + + // blackswan event occurs + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( db.find( call_id ) == nullptr ); + BOOST_CHECK( db.find( call2_id ) == nullptr ); + BOOST_CHECK( db.find( call3_id ) == nullptr ); + BOOST_CHECK( db.find( call4_id ) == nullptr ); + BOOST_CHECK( db.find( call5_id ) == nullptr ); + + share_type expected_gs_fund = 0; + + idump( (call2_copy) ); + + // call2 has the lowest CR below required + share_type call2_to_gs_fund = (call2_copy.collateral * 10 + 10) / 11; // round up + share_type margin_call_fee_gs_2 = call2_copy.collateral - call2_to_gs_fund; + expected_margin_call_fees += margin_call_fee_gs_2; + expected_gs_fund += call2_to_gs_fund; + // GS price (margin calls to pay) = call2_copy.collateral / call2_copy.debt + // GS price (all positions to fund) = (call2_copy.collateral * 10) / (call2_copy.debt * 11) + + // borrower2 balance does not change + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + + // call3 is in margin call territory + share_type call3_to_pay_gs = ( call3_copy.debt * call2_copy.collateral + call2_copy.debt - 1 ) / call2_copy.debt; + share_type call3_to_gs_fund = ( call3_copy.debt * call2_copy.collateral * 10 + call2_copy.debt * 11 - 1 ) + / (call2_copy.debt * 11); + share_type margin_call_fee_gs_3 = call3_to_pay_gs - call3_to_gs_fund; + expected_margin_call_fees += margin_call_fee_gs_3; + expected_gs_fund += call3_to_gs_fund; + + // borrower3 balance changes -- some collateral returned + BOOST_CHECK_EQUAL( init_balance - call3_to_pay_gs.value, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); + + // call is not in margin call territory + share_type call_to_gs_fund = ( call_copy.debt * call2_copy.collateral * 10 + call2_copy.debt * 11 - 1 ) + / (call2_copy.debt * 11); + share_type call_to_pay_gs = call_to_gs_fund; + expected_gs_fund += call_to_gs_fund; + // no fee + + // borrower balance changes -- some collateral returned + BOOST_CHECK_EQUAL( init_balance - call_to_pay.value - call_to_pay_gs.value, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + + // call5 is not in margin call territory + share_type call5_to_gs_fund = ( call5_copy.debt * call2_copy.collateral * 10 + call2_copy.debt * 11 - 1 ) + / (call2_copy.debt * 11); + share_type call5_to_pay_gs = call5_to_gs_fund; + expected_gs_fund += call5_to_gs_fund; + // no fee + + // borrower5 balance changes -- some collateral returned + BOOST_CHECK_EQUAL( init_balance - call5_to_pay_gs.value, get_balance(borrower5, core) ); + BOOST_CHECK_EQUAL( 1000, get_balance(borrower5, bitusd) ); + + // check seller balance + BOOST_CHECK_EQUAL( 1493, get_balance(seller, bitusd) ); // 3000 - 7 - 1000 - 400 - 100 + BOOST_CHECK_EQUAL( 9000 + settle_receives4.value + settle_receives2.value + settle2_receives2.value, + get_balance(seller, core) ); // 1000*9 + 160*107/110 + 490 * call2_cr * 107/110 + + // buy_low's price is too low that won't be matched + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + + // sell_high is not matched + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + + // check gs fund + BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, expected_gs_fund.value ); + // force_settled_volume is 0 + BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).force_settled_volume.value, 0 ); + + // check margin call fees + BOOST_CHECK_EQUAL( usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value, + expected_margin_call_fees.value ); + + // generate a block + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + BOOST_TEST_MESSAGE( "Check again" ); + + // buy_low's price is too low that won't be matched + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + + // sell_high is not matched + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + + // check gs fund + BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, expected_gs_fund.value ); + // force_settled_volume is 0 + BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).force_settled_volume.value, 0 ); + + // check margin call fees + BOOST_CHECK_EQUAL( usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value, + expected_margin_call_fees.value ); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 975dda93850305a4b6675a2f950ff149dcf7c66b Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 12 Jul 2021 21:49:34 +0000 Subject: [PATCH 088/258] Add more regression tests for hf core-2481 --- tests/tests/settle_tests.cpp | 85 +++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 51e4c24036..5d3312c089 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -644,7 +644,14 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) { try { auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_184_TIME - mi); + + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -1379,7 +1386,14 @@ BOOST_AUTO_TEST_CASE( global_settle_rounding_test_after_hf_184 ) { try { auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_184_TIME - mi); // assume that hard fork core-184 and core-342 happen at same time + + if(hf2481) + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + else if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_184_TIME - mi); // assume that hf core-184 and core-342 happen at same time + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -1671,7 +1685,15 @@ BOOST_AUTO_TEST_CASE( market_fee_of_settle_order_after_hardfork_1780 ) try { INVOKE(create_bitassets); - generate_blocks( HARDFORK_CORE_1780_TIME ); + if(hf2481) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + generate_blocks( HARDFORK_CORE_1780_TIME ); + set_expiration( db, trx ); GET_ACTOR(paul); @@ -1759,7 +1781,15 @@ BOOST_AUTO_TEST_CASE( market_fee_of_instant_settle_order_after_hardfork_1780 ) try { INVOKE(create_bitassets); - generate_blocks( HARDFORK_CORE_1780_TIME ); + if(hf2481) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + generate_blocks( HARDFORK_CORE_1780_TIME ); + set_expiration( db, trx ); GET_ACTOR(paul); @@ -1820,8 +1850,9 @@ BOOST_AUTO_TEST_CASE( market_fee_of_instant_settle_order_after_hardfork_1780 ) int64_t biteur_accumulated_fee = 0; int64_t bitusd_accumulated_fee = 0; { - // 1000 biteur = 10000 bitusd in settlement fund - const auto biteur_expected_result = rachel_bitusd_count/10; + // before hf2481: 1000 biteur = 10000 bitusd in settlement fund + // after hf2481: round_up(10000/11) in settlement fund + const auto biteur_expected_result = hf2481 ? (rachel_bitusd_count+10)/11 : rachel_bitusd_count/10; const auto biteur_market_fee = biteur_expected_result / 2; // market fee percent = 50% biteur_balance += biteur_expected_result - biteur_market_fee; @@ -1967,4 +1998,46 @@ BOOST_AUTO_TEST_CASE( global_settle_ticker_test ) } } +BOOST_AUTO_TEST_CASE(settle_rounding_test_after_hf_1270) +{ try { + hf1270 = true; + INVOKE(settle_rounding_test_after_hf_184); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(settle_rounding_test_after_hf_2481) +{ try { + hf2481 = true; + INVOKE(settle_rounding_test_after_hf_184); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(global_settle_rounding_test_after_hf_1270) +{ try { + hf1270 = true; + INVOKE(global_settle_rounding_test_after_hf_184); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(global_settle_rounding_test_after_hf_2481) +{ try { + hf2481 = true; + INVOKE(global_settle_rounding_test_after_hf_184); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(market_fee_of_settle_order_after_hardfork_2481) +{ try { + hf2481 = true; + INVOKE(market_fee_of_settle_order_after_hardfork_1780); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(market_fee_of_instant_settle_order_after_hardfork_2481) +{ try { + hf2481 = true; + INVOKE(market_fee_of_instant_settle_order_after_hardfork_1780); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 54ad498aaadc613381400b8dd7dd1d10ce29ca07 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 12 Jul 2021 22:40:42 +0000 Subject: [PATCH 089/258] Update comments and code style --- libraries/chain/db_update.cpp | 4 ++-- libraries/chain/include/graphene/chain/database.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 38ffbaae93..c7b303aa09 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -353,13 +353,13 @@ void database::clear_expired_orders() void database::clear_expired_force_settlements() { try { - //Cancel expired limit orders + // Process expired force settlement orders auto head_time = head_block_time(); auto maint_time = get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding - // Process expired force settlement orders + // TODO Possible performance optimization. Looping through all assets is not ideal. // - One idea is to check time first, if any expired settlement found, check asset. // However, due to max_settlement_volume, this does not work, i.e. time meets but have to diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 3274cbe1e1..9b64d7a31f 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -670,7 +670,7 @@ namespace graphene { namespace chain { void update_withdraw_permissions(); void update_credit_offers_and_deals(); bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, - const asset_bitasset_data_object* bitasset_ptr = nullptr); + const asset_bitasset_data_object* bitasset_ptr = nullptr ); void clear_expired_htlcs(); ///Steps performed only at maintenance intervals From e2695bf054f2d06ba30476ad0ec244f105fd2511 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 12 Jul 2021 22:41:22 +0000 Subject: [PATCH 090/258] Bump DB version --- libraries/chain/include/graphene/chain/config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 372f3a74dc..dc0c0a2351 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -32,7 +32,7 @@ #define GRAPHENE_MAX_NESTED_OBJECTS (200) -const std::string GRAPHENE_CURRENT_DB_VERSION = "20210609"; +const std::string GRAPHENE_CURRENT_DB_VERSION = "20210713"; #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 From 4c50abada0596385ff744635d05ffa4509660c11 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 12 Jul 2021 23:10:33 +0000 Subject: [PATCH 091/258] Avoid unnecessary database update --- libraries/chain/db_market.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 353b2ff850..a4678eda5b 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1662,13 +1662,13 @@ void database::match_force_settlements( const asset_bitasset_data_object& bitass void database::pay_order( const account_object& receiver, const asset& receives, const asset& pays ) { - const auto& balances = receiver.statistics(*this); - modify( balances, [&]( account_statistics_object& b ){ - if( pays.asset_id == asset_id_type() ) - { - b.total_core_in_orders -= pays.amount; - } - }); + if( pays.asset_id == asset_id_type() ) + { + const auto& stats = receiver.statistics(*this); + modify( stats, [&pays]( account_statistics_object& b ){ + b.total_core_in_orders -= pays.amount; + }); + } adjust_balance(receiver.get_id(), receives); } From b9ebbf5b1da963b19616aab03634e70263848e37 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 12 Jul 2021 23:31:10 +0000 Subject: [PATCH 092/258] Replace BOOST_MESSAGE with BOOST_TEST_MESSAGE --- tests/tests/market_tests.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 58c0a7adaf..18d9d90df2 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1307,7 +1307,7 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test) BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // adjust price feed to get call_order into black swan territory - BOOST_MESSAGE( "Trying to trigger GS" ); + BOOST_TEST_MESSAGE( "Trying to trigger GS" ); current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(18); publish_feed( bitusd, feedproducer, current_feed ); // settlement price = 1/18, mssp = 10/198 @@ -1323,7 +1323,7 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test) BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value ); // generate a block to include operations above - BOOST_MESSAGE( "Generating a new block" ); + BOOST_TEST_MESSAGE( "Generating a new block" ); generate_block(); } FC_LOG_AND_RETHROW() } @@ -1413,7 +1413,7 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test_after_hf_core_2481) BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // adjust price feed to get call_order into black swan territory - BOOST_MESSAGE( "Trying to trigger GS" ); + BOOST_TEST_MESSAGE( "Trying to trigger GS" ); current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(18); publish_feed( bitusd, feedproducer, current_feed ); // settlement price = 1/18, mssp = 10/198 @@ -1443,7 +1443,7 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test_after_hf_core_2481) BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value ); // generate a block to include operations above - BOOST_MESSAGE( "Generating a new block" ); + BOOST_TEST_MESSAGE( "Generating a new block" ); generate_block(); } FC_LOG_AND_RETHROW() } @@ -1538,7 +1538,7 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower2, core) ); // Tring to adjust price feed to get call_order into margin call territory - BOOST_MESSAGE( "Trying to trigger a margin call" ); + BOOST_TEST_MESSAGE( "Trying to trigger a margin call" ); auto feed2 = current_feed; feed2.settlement_price = bitusd.amount( 1 ) / core.amount(18); publish_feed( bitusd, feedproducer, feed2 ); @@ -1579,7 +1579,7 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) } // generate a block to include operations above - BOOST_MESSAGE( "Generating a new block" ); + BOOST_TEST_MESSAGE( "Generating a new block" ); generate_block(); } FC_LOG_AND_RETHROW() } From 57782520213f1123058cf33d120dd3b51b06d7e7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 13 Jul 2021 09:07:01 +0000 Subject: [PATCH 093/258] Fix code smells --- libraries/chain/db_market.cpp | 21 ++++++++++----------- libraries/chain/db_update.cpp | 8 ++++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index a4678eda5b..00135925aa 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -400,8 +400,8 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord // - The new order is at the front of the book // - The new order is below the call limit price bool called_some = check_call_orders(sell_asset, true, true); // the first time when checking, call order is maker - called_some |= check_call_orders(receive_asset, true, true); // the other side, same as above - if( called_some && !find_object(order_id) ) // then we were filled by call order + bool called_some_else = check_call_orders(receive_asset, true, true); // the other side, same as above + if( ( called_some || called_some_else ) && !find_object(order_id) ) // then we were filled by call order return true; const auto& limit_price_idx = get_index_type().indices().get(); @@ -683,7 +683,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle match( new_settlement, *call_itr, call_pays_price, max_debt_to_cover, call_match_price, true ); // Check whether the new order is gone - finished = ( find_object( new_obj_id ) == nullptr ); + finished = ( nullptr == find_object( new_obj_id ) ); } } @@ -758,11 +758,10 @@ int database::match( const limit_order_object& usd, const limit_order_object& co FC_ASSERT( usd_pays == usd.amount_for_sale() || core_pays == core.amount_for_sale() ); - int result = 0; // the first param of match() is taker - result |= fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ); + int result = fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ) ? 1 : 0; // the second param of match() is maker - result |= fill_limit_order( core, core_pays, core_receives, true, match_price, true ) << 1; + result += fill_limit_order( core, core_pays, core_receives, true, match_price, true ) ? 2 : 0; FC_ASSERT( result != 0 ); return result; } @@ -820,9 +819,8 @@ int database::match( const limit_order_object& bid, const call_order_object& ask FC_ASSERT(call_pays >= order_receives); const asset margin_call_fee = call_pays - order_receives; - int result = 0; - result |= fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false ); // taker - result |= fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee ) << 1; // maker + int result = fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false ) ? 1 : 0; // taker + result += fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee ) ? 2 : 0; // maker // result can be 0 when call order has target_collateral_ratio option set. return result; @@ -1060,7 +1058,8 @@ asset database::match_impl( const force_settlement_object& settle, bool database::fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small, const price& fill_price, const bool is_maker) { try { - cull_if_small |= (head_block_time() < HARDFORK_555_TIME); + if( head_block_time() < HARDFORK_555_TIME ) + cull_if_small = true; FC_ASSERT( order.amount_for_sale().asset_id == pays.asset_id ); FC_ASSERT( pays.asset_id != receives.asset_id ); @@ -1460,7 +1459,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // TODO refactor code for better performance and readability, perhaps extract the new logic to a new // function and call it after hf_1270, hf_bsip74 or hf_2481. auto usd_to_buy = call_order.get_debt(); - if( !after_core_hardfork_2481 && usd_to_buy * call_pays_price > call_order.get_collateral() ) + if( !after_core_hardfork_2481 && ( usd_to_buy * call_pays_price ) > call_order.get_collateral() ) { // Trigger black swan elog( "black swan detected on asset ${symbol} (${id}) at block ${b}", diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index c7b303aa09..a4e199c56c 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -267,7 +267,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // The variable `highest` after hf_338: // * if no limit order, it is expected to be the black swan price; if the call order with the least CR - // has CR below or equal to the black swan price, we trigger GS; + // has CR below or equal to the black swan price, we trigger GS, // * if there exists at least one limit order and the price is higher, we use the limit order's price, // which means we will match the margin call orders with the limit order first. // @@ -275,7 +275,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got // caught by an additional check in check_call_orders(). // This bug is fixed in hf_2481. Actually, after hf_2481, - // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(); + // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), // * if there is no force settlement, we check here with margin call fee in consideration. auto least_collateral = call_ptr->collateralization(); @@ -299,7 +299,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s { // After hf_2481, when a global settlement occurs, // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and - // they are closed at the same price; + // they are closed at the same price, // * the debt positions with CR > MCR do not pay premium or margin call fee, and they are closed at a same // price too. // * The GS price would close the position with the least CR with no collateral left for the owner, @@ -308,7 +308,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // to the global settlement fund, because // - if a part of the premium or fees goes to the global settlement fund, it means there would be a // difference in settlement prices, so traders are incentivized to create new debt in the last minute - // then settle after GS to earn free money; + // then settle after GS to earn free money, // - if no premium or fees goes to the global settlement fund, it means debt asset holders would only // settle for less after GS, so they are incentivized to settle before GS which helps avoid GS. globally_settle_asset(mia, ~least_collateral, true ); From 2045d7c7f409ffae5b83ee5c0558b01b3fa90992 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 13 Jul 2021 09:29:39 +0000 Subject: [PATCH 094/258] Add comment and logging --- libraries/chain/db_market.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 00135925aa..e95ff7c01d 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1534,7 +1534,11 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( call_pays.amount > call_order.collateral ) { if( after_core_hardfork_2481 ) + { + // Normally this code won't be reached. be defensive here + wlog( "Unexpected: (call_pays.amount > call_order.collateral) and after_core_hardfork_2481" ); break; + } call_pays.amount = call_order.collateral; } // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher From cec23780f98cf369ade1b2ff555f9e6c60c315fc Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 16 Jul 2021 20:04:55 +0000 Subject: [PATCH 095/258] Add tests for matching call orders at hf 2481 --- tests/tests/force_settle_match_tests.cpp | 233 +++++++++++++++++++++++ 1 file changed, 233 insertions(+) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index e018ab8ac8..b5f908440e 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -406,6 +406,239 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) } FC_LOG_AND_RETHROW() } +/*** + * Request force settlement before hard fork, match taker call orders with maker settle orders at hard fork time + */ +BOOST_AUTO_TEST_CASE(hf2481_cross_test) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.feed_lifetime_sec = mi * 10; + uop.new_options.force_settlement_delay_sec = mi * 10; + uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3% + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + transfer(committee_account, borrower4_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175% + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% > 175% + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000); + call_order_id_type call2_id = call2.id; + // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000)); + call_order_id_type call3_id = call3.id; + // create a small position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7, no tcr + const call_order_object& call4 = *borrow( borrower4, bitusd.amount(10), asset(160) ); + call_order_id_type call4_id = call4.id; + + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); + BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) ); + + // This sell order above MSSP will not be matched with a call + limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + + BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // This buy order is too low will not be matched with a sell order + limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->id; + + BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) ); + + // Create a sell order which will be matched with several call orders later, price 1/9 + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(500), core.amount(4500) )->id; + BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 500 ); + + // Create a force settlement, will be matched with several call orders later + auto result = force_settle( seller, bitusd.amount(2400) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle_id = result.get(); + BOOST_CHECK( db.find( settle_id ) != nullptr ); + + BOOST_CHECK_EQUAL( 2400, settle_id(db).balance.amount.value ); + + // prepare price feed to get call and call2 (but not call3) into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + + // call and call2's CR is quite high, and debt amount is quite a lot, + // assume neither of them will be completely filled + price match_price = sell_id(db).sell_price * ratio_type(107,110); + share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value ); + BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value ); + // even though call2 has a higher CR, since call's TCR is less than call2's TCR, + // so we expect call will cover less when called + BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value ); + + call_order_object call2_copy = call2; + + // adjust price feed to get call and call2 (but not call3) into margin call territory + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11, mcop = 10/107, mcpr = 110/107 + + generate_block(); + + // firstly the limit order will match with call, at limit order's price: 1/9 + const call_order_object* tmp_call = db.find( call_id ); + BOOST_CHECK( tmp_call != nullptr ); + + // call will receive call_to_cover, pay 9*call_to_cover + share_type call_to_pay = (call_to_cover * 9 * 110 + 106) / 107; // round up since it's smaller + BOOST_CHECK_EQUAL( 1000 - call_to_cover.value, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call_id(db).collateral.value ); + // new collateral ratio should be higher than mcr as well as tcr + BOOST_CHECK( call_id(db).debt.value * 10 * 1750 < call_id(db).collateral.value * 1000 ); + idump( (call_to_pay)(call_to_cover)(call_id(db)) ); + // borrower's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower_id, asset_id_type()) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, usd_id) ); + + // the limit order then will match with call2, at limit order's price: 1/9 + const call_order_object* tmp_call2 = db.find( call2_id ); + BOOST_CHECK( tmp_call2 != nullptr ); + + // if the limit is big enough, call2 will receive call2_to_cover, pay 9*call2_to_cover + // however it's not the case, so call2 will receive less + call2_to_cover = 500 - call_to_cover; + share_type call2_to_pay = call2_to_cover * 9 * 110 / 107; // round down since it's larger + + // borrower2's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2_id, asset_id_type()) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2_id, usd_id) ); + + // sell_id is completely filled + BOOST_CHECK( !db.find( sell_id ) ); + + // all call orders are still there + BOOST_CHECK( db.find( call_id ) != nullptr ); + BOOST_CHECK( db.find( call2_id ) != nullptr ); + BOOST_CHECK( db.find( call3_id ) != nullptr ); + BOOST_CHECK( db.find( call4_id ) != nullptr ); + + BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value, call2_id(db).collateral.value ); + + idump( (call2_to_pay)(call2_to_cover)(call2_id(db)) ); + + // settle order does not change + BOOST_CHECK( db.find( settle_id ) != nullptr ); + BOOST_CHECK_EQUAL( 2400, settle_id(db).balance.amount.value ); + + // check borrower4's balances + BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4_id, asset_id_type()) ); + BOOST_CHECK_EQUAL( 10, get_balance(borrower4_id, usd_id) ); + + // check seller balance + BOOST_CHECK_EQUAL( 93, get_balance(seller_id, usd_id) ); // 3000 - 7 - 500 - 2400 + BOOST_CHECK_EQUAL( 4500, get_balance(seller_id, asset_id_type()) ); // 500*9 + + // call3 is not in margin call territory so won't be matched + BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 25000, call3_id(db).collateral.value ); + + // buy_low's price is too low that won't be matched + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + + // pass the hard fork time + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // call4 will match with the settle order, since it has no tcr, it will be fully closed + // match price is 1/11 + const call_order_object* tmp_call4 = db.find( call4_id ); + BOOST_CHECK( tmp_call4 == nullptr ); + + // borrower4 balance changes + BOOST_CHECK_EQUAL( init_balance - 110, get_balance(borrower4_id, asset_id_type()) ); + BOOST_CHECK_EQUAL( 10, get_balance(borrower4_id, usd_id) ); + + // call2 is still in margin call territory after matched with limit order, now it matches with settle order + price call_pays_price( asset(1, usd_id), asset(11) ); + call2_copy.debt -= call2_to_cover; + call2_copy.collateral -= call2_to_pay; + auto call2_to_cover2 = call2_copy.get_max_debt_to_cover(call_pays_price,current_feed.settlement_price,1750); + BOOST_CHECK_GT( call2_to_cover2.value, 0 ); + share_type call2_to_pay2 = call2_to_cover2 * 11; + BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value - call2_to_cover2.value, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value - call2_to_pay2.value, call2_id(db).collateral.value ); + idump( (call2_to_pay)(call2_to_cover)(call2_to_pay2)(call2_to_cover2)(call2_id(db)) ); + + // call3 is not in margin call territory so won't be matched + BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 25000, call3_id(db).collateral.value ); + + // settle order is not fully filled + BOOST_CHECK( db.find( settle_id ) != nullptr ); + BOOST_CHECK_EQUAL( 2400 - 10 - call2_to_cover2.value, settle_id(db).balance.amount.value ); // call4, call2 + + // check seller balance + BOOST_CHECK_EQUAL( 93, get_balance(seller_id, usd_id) ); // 3000 - 7 - 500 - 2400 + BOOST_CHECK_EQUAL( 4500 + 107 + (call2_to_cover2.value * 107 + 9) / 10, // round up + get_balance(seller_id, asset_id_type()) ); // 500*9 + 10*10.7 + call2_cover2 * 10.7 + + // buy_low's price is too low that won't be matched + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + + // generate a block + generate_block(); + +} FC_LOG_AND_RETHROW() } + /*** * Matching taker call orders with maker settle orders and triggers blackswan event */ From 498c674687073f4727d26a5faaf91074ddbb1853 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 16 Jul 2021 20:06:02 +0000 Subject: [PATCH 096/258] Add tests about reducing CR to trigger margin call ... which is not allowed --- tests/tests/force_settle_match_tests.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index b5f908440e..92149af1ff 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -401,6 +401,9 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // buy_low's price is too low that won't be matched BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + // Can not reduce CR of a call order to trigger a margin call but not get fully filled + BOOST_CHECK_THROW( borrow( borrower_id(db), asset(10, usd_id), asset(0), 1700), fc::exception ); + // generate a block generate_block(); From 68d323a041d48ce17aeb1fee1ce191022afe1048 Mon Sep 17 00:00:00 2001 From: Abit Date: Sat, 17 Jul 2021 00:03:55 +0200 Subject: [PATCH 097/258] Add tests about new instantly-called call order --- tests/tests/force_settle_match_tests.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 92149af1ff..cbcb2b7b37 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -404,6 +404,9 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // Can not reduce CR of a call order to trigger a margin call but not get fully filled BOOST_CHECK_THROW( borrow( borrower_id(db), asset(10, usd_id), asset(0), 1700), fc::exception ); + // Can not create a new call order that is partially called instantly + BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10, usd_id), asset(160), 1700), fc::exception ); + // generate a block generate_block(); From efcc1478826054fd4ba10c479059ca64f1ff1aab Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 16 Jul 2021 20:11:28 +0000 Subject: [PATCH 098/258] Fix issues about matching taker calls with settles - scenario 1: there is at least a settle order, but no limit order that could be matched with a call order, the settle order should get matched - scenario 2: when creating a new call order, if it would trigger a margin call but the call order is not fully filled, should throw - scenario 3: when reducing CR of an existing call order, if it would trigger a margin call but the call order is not fully filled, should throw --- libraries/chain/db_market.cpp | 25 ++++++++++++------- .../chain/include/graphene/chain/database.hpp | 5 +++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index e95ff7c01d..171d6c2828 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -966,7 +966,7 @@ asset database::match_impl( const force_settlement_object& settle, // check whether the call order can be filled at match_price bool cap_price = false; - if( call_pays.amount >= call.collateral ) // CR too low + if( call_pays.amount >= call.collateral ) // CR too low, normally won't be true, just be defensive here cap_price = true; else { @@ -1360,6 +1360,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const auto& limit_price_index = limit_index.indices().get(); bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls // Looking for limit orders selling the most USD for the least CORE. auto max_price = price::max( bitasset.asset_id, bitasset.options.short_backing_asset ); @@ -1376,7 +1377,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa auto limit_itr = limit_price_index.lower_bound( max_price ); auto limit_end = limit_price_index.upper_bound( min_price ); - if( limit_itr == limit_end ) + // Before the core-2481 hf, only check limit orders + if( !after_core_hardfork_2481 && limit_itr == limit_end ) return false; const call_order_index& call_index = get_index_type(); @@ -1417,8 +1419,6 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option - bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls - while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators && limit_itr != limit_end @@ -1596,7 +1596,9 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( after_core_hardfork_2481 && !bitasset.has_settlement() ) { // Be here, there exists at least one margin call not processed - match_force_settlements( bitasset ); + bool called_some = match_force_settlements( bitasset ); + if( called_some ) + margin_called = true; // At last, check for blackswan // TODO perhaps improve performance by passing in iterators check_for_blackswan( mia, enable_black_swan, &bitasset ); } @@ -1604,7 +1606,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa return margin_called; } FC_CAPTURE_AND_RETHROW() } -void database::match_force_settlements( const asset_bitasset_data_object& bitasset ) +bool database::match_force_settlements( const asset_bitasset_data_object& bitasset ) { // Defensive checks auto maint_time = get_dynamic_global_properties().next_maintenance_time; @@ -1636,6 +1638,7 @@ void database::match_force_settlements( const asset_bitasset_data_object& bitass auto margin_call_pays_ratio = bitasset.current_feed.margin_call_pays_ratio( bitasset.options.extensions.value.margin_call_fee_ratio); + bool margin_called = false; while( settle_itr != settle_end && call_itr != call_end ) { const force_settlement_object& settle_order = *settle_itr; @@ -1643,7 +1646,7 @@ void database::match_force_settlements( const asset_bitasset_data_object& bitass // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 if( bitasset.current_maintenance_collateralization < call_order.collateralization() ) - return; + return margin_called; // TCR applies here asset max_debt_to_cover( call_order.get_max_debt_to_cover( call_pays_price, @@ -1655,12 +1658,16 @@ void database::match_force_settlements( const asset_bitasset_data_object& bitass // Note: if the call order's CR is too low, it is probably unable to fill at call_pays_price. // In this case, the call order pays at its CR, the settle order may receive less due to margin call fee. // It is processed inside the function. - match( call_order, settle_order, call_pays_price, max_debt_to_cover, call_match_price, - &margin_call_pays_ratio ); + auto result = match( call_order, settle_order, call_pays_price, max_debt_to_cover, call_match_price, + &margin_call_pays_ratio ); + + if( !margin_called && result.amount > 0 ) + margin_called = true; settle_itr = settlement_index.lower_bound( bitasset.asset_id ); call_itr = call_collateral_index.lower_bound( call_min ); } + return margin_called; } void database::pay_order( const account_object& receiver, const asset& receives, const asset& pays ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 9b64d7a31f..c3e70841cf 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -395,7 +395,10 @@ namespace graphene { namespace chain { const price& settle_price, const IndexType& call_index, bool check_margin_calls = false ); - void match_force_settlements( const asset_bitasset_data_object& bitasset ); + /// Match force settlements with margin calls + /// @param bitasset the asset that to be checked + /// @return true if matched at least one margin call order + bool match_force_settlements( const asset_bitasset_data_object& bitasset ); /// Matches the two orders /// @param settle the force-settlement order /// @param call the call order From 58b4e49683bd074c4adc15b9e5098926df946a10 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 16 Jul 2021 20:51:23 +0000 Subject: [PATCH 099/258] Add tests about matching smaller settle with call --- tests/tests/force_settle_match_tests.cpp | 206 ++++++++++++++++++++++- 1 file changed, 200 insertions(+), 6 deletions(-) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index cbcb2b7b37..85e547bb17 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -103,7 +103,6 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call) BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); - BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); @@ -212,6 +211,205 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call) } FC_LOG_AND_RETHROW() } +/*** + * After hf core-2481, matching small taker settle orders with a big maker call order + */ +BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3% + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/175 CORE/USD = 6/70, tcr 170% is lower than 175% + const call_order_object& call = *borrow( borrower, bitusd.amount(100000), asset(15000), 1700); + call_order_id_type call_id = call.id; + transfer(borrower, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 100000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + + // adjust price feed to get call into margin call territory + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 10/1, mssp = 100/11, mcop = 1000/107, mcpr = 110/107 + + BOOST_CHECK_EQUAL( 100000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // Create a force settlement, will be matched with the call order + share_type amount_to_settle = 101; + auto result = force_settle( seller, bitusd.amount(amount_to_settle) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle_id = result.get(); + BOOST_CHECK( db.find( settle_id ) == nullptr ); + + // the settle order will match with call, at mssp: 100/11 + BOOST_CHECK( db.find( call_id ) != nullptr ); + + // check + share_type call_to_pay = amount_to_settle * 11 / 100; // round down, favors call order + share_type call_to_cover = (call_to_pay * 100 + 10 ) / 11; // stablize : 101 -> 100 + share_type call_to_settler = (call_to_cover * 107) / 1000; // round down, favors call order + BOOST_CHECK_EQUAL( 100000 - call_to_cover.value, call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value ); + idump( (call) ); + // borrower's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + + // check seller balance + BOOST_CHECK_EQUAL( 99900, get_balance(seller, bitusd) ); // 100000 - 100, the rest 1 be canceled + int64_t expected_seller_core_balance = call_to_settler.value; + BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); + + // asset's force_settled_volume does not change + BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value ); + + // Settle again + share_type amount_to_settle2 = 100; + result = force_settle( seller, bitusd.amount(amount_to_settle2) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle2_id = result.get(); + BOOST_CHECK( db.find( settle2_id ) == nullptr ); + + // the settle order will match with call, at mssp: 100/11 + BOOST_CHECK( db.find( call_id ) != nullptr ); + + // check + share_type call_to_pay2 = amount_to_settle2 * 11 / 100; // round down, favors call order + share_type call_to_cover2 = (call_to_pay2 * 100 + 10 ) / 11; // stablize : 100 -> 100 (no change) + share_type call_to_settler2 = (call_to_cover2 * 107) / 1000; // round down, favors call order + BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value ); + idump( (call) ); + // borrower's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + + // check seller balance + BOOST_CHECK_EQUAL( 99800, get_balance(seller, bitusd) ); // 100000 - 100 - 100 + expected_seller_core_balance = call_to_settler.value + call_to_settler2.value; + BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); + + // asset's force_settled_volume does not change + BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value ); + + { + // increase mssr and mcfr + // settlement price = 10/1, mssp = 100/13, mcop = 1000/103, mcpr = 130/127 + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; // 130% + uop.new_options.extensions.value.margin_call_fee_ratio = 270; // 27% + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + // Settle again with a much smaller amount + share_type amount_to_settle3 = 9; + result = force_settle( seller, bitusd.amount(amount_to_settle3) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle3_id = result.get(); + BOOST_CHECK( db.find( settle3_id ) == nullptr ); + + // the settle order will match with call, at mssp + BOOST_CHECK( db.find( call_id ) != nullptr ); + + // check + share_type call_to_pay3 = amount_to_settle3 * 13 / 100; // round down, favors call order + share_type call_to_cover3 = (call_to_pay3 * 100 + 10 ) / 13; // stablize : 9 -> 8 + share_type call_to_settler3 = (call_to_cover3 * 103) / 1000; // round down, favors call order + BOOST_CHECK_EQUAL( 0, call_to_settler3.value ); + call_to_settler3 = 1; // 0 -> 1 + BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value - call_to_cover3.value, + call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value - call_to_pay3.value, + call.collateral.value ); + idump( (call) ); + // borrower's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + + // check seller balance + BOOST_CHECK_EQUAL( 99792, get_balance(seller, bitusd) ); // 100000 - 100 - 100 - 8 + expected_seller_core_balance = call_to_settler.value + call_to_settler2.value + call_to_settler3.value; + BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); + + // asset's force_settled_volume does not change + BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value ); + + // Settle again with a tiny amount that would receive nothing + share_type amount_to_settle4 = 5; + result = force_settle( seller, bitusd.amount(amount_to_settle4) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle4_id = result.get(); + BOOST_CHECK( db.find( settle4_id ) == nullptr ); + + // the settle order will match with call, at mssp + BOOST_CHECK( db.find( call_id ) != nullptr ); + + // no data change + BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value - call_to_cover3.value, + call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value - call_to_pay3.value, + call.collateral.value ); + idump( (call) ); + // borrower's balance doesn't change + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + // check seller balance + BOOST_CHECK_EQUAL( 99792, get_balance(seller, bitusd) ); // 100000 - 100 - 100 - 8 + expected_seller_core_balance = call_to_settler.value + call_to_settler2.value + call_to_settler3.value; + BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); + + // asset's force_settled_volume does not change + BOOST_CHECK_EQUAL( 0, usd_id(db).bitasset_data(db).force_settled_volume.value ); + + // generate a block + generate_block(); + +} FC_LOG_AND_RETHROW() } + /*** * BSIP38 "target_collateral_ratio" test after hf core-2481: * matching taker call orders with maker settle orders @@ -282,7 +480,6 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); - BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); @@ -336,8 +533,7 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // settlement price = 1/10, mssp = 1/11, mcop = 10/107, mcpr = 110/107 // firstly the limit order will match with call, at limit order's price: 1/9 - const call_order_object* tmp_call = db.find( call_id ); - BOOST_CHECK( tmp_call != nullptr ); + BOOST_CHECK( db.find( call_id ) != nullptr ); // call will receive call_to_cover, pay 9*call_to_cover share_type call_to_pay = (call_to_cover * 9 * 110 + 106) / 107; // round up since it's smaller @@ -483,7 +679,6 @@ BOOST_AUTO_TEST_CASE(hf2481_cross_test) BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); - BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); @@ -719,7 +914,6 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); - BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); From 2c998a09dd0e77c354347e3a58b0b1ba74a50483 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 17 Jul 2021 20:14:29 +0000 Subject: [PATCH 100/258] Remove wrong comment and related logging --- libraries/chain/db_market.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 171d6c2828..f10ad6f8a2 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1534,11 +1534,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( call_pays.amount > call_order.collateral ) { if( after_core_hardfork_2481 ) - { - // Normally this code won't be reached. be defensive here - wlog( "Unexpected: (call_pays.amount > call_order.collateral) and after_core_hardfork_2481" ); break; - } call_pays.amount = call_order.collateral; } // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher From 1111348afd1b4fe3f484315a7afad5e65cbc9153 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 17 Jul 2021 20:14:42 +0000 Subject: [PATCH 101/258] Update tests about GS when matching settlements for better coverage --- tests/tests/force_settle_match_tests.cpp | 42 +++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 85e547bb17..3ed8e2a620 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -850,6 +850,10 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) generate_blocks(HARDFORK_CORE_2481_TIME - mi); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + // 3 passes. With no matching limit order, or with a small or big matching limit order. + for( int i = 0; i < 3; ++ i ) + { + set_expiration( db, trx ); ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(feedproducer)); @@ -942,6 +946,19 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(1000), core.amount(9000) )->id; BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 1000 ); + // Create another sell order which will trigger a blackswan event if matched, price 1/21 + limit_order_id_type sell_swan; + if( i == 1 ) + { + sell_swan = create_sell_order(seller, bitusd.amount(1), core.amount(21) )->id; + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 1 ); + } + else if( i == 2 ) + { + sell_swan = create_sell_order(seller, bitusd.amount(100), core.amount(2100) )->id; + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 ); + } + // Create a force settlement, will be matched with several call orders later auto result = force_settle( seller, bitusd.amount(400) ); BOOST_REQUIRE( result.is_type() ); @@ -1096,7 +1113,13 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( 1000, get_balance(borrower5, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 1493, get_balance(seller, bitusd) ); // 3000 - 7 - 1000 - 400 - 100 + int expected_seller_usd_balance = 1493; // 3000 - 7 - 1000 - 400 - 100 + if ( i == 1 ) + expected_seller_usd_balance -= 1; // - sell_swan + else if ( i == 2 ) + expected_seller_usd_balance -= 100; // - sell_swan + + BOOST_CHECK_EQUAL( expected_seller_usd_balance, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 9000 + settle_receives4.value + settle_receives2.value + settle2_receives2.value, get_balance(seller, core) ); // 1000*9 + 160*107/110 + 490 * call2_cr * 107/110 @@ -1106,6 +1129,12 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // sell_high is not matched BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + // sell_swan is not matched + if( i == 1 ) + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 1 ); + else if( i == 2 ) + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 ); + // check gs fund BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, expected_gs_fund.value ); // force_settled_volume is 0 @@ -1126,6 +1155,12 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // sell_high is not matched BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + // sell_swan is not matched + if( i == 1 ) + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 1 ); + else if( i == 2 ) + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 ); + // check gs fund BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, expected_gs_fund.value ); // force_settled_volume is 0 @@ -1135,6 +1170,11 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( usd_id(db).dynamic_asset_data_id(db).accumulated_collateral_fees.value, expected_margin_call_fees.value ); + // reset + db.pop_block(); + + } // for + } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() From a38fed079d846cf62fa46f67b86e61213c31e38a Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 17 Jul 2021 20:53:48 +0000 Subject: [PATCH 102/258] Update return type of database::match() to enum --- libraries/chain/db_market.cpp | 69 ++++++++++++------- .../chain/include/graphene/chain/database.hpp | 26 +++++-- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f10ad6f8a2..22aa425913 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -421,7 +421,8 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord auto old_limit_itr = limit_itr; ++limit_itr; // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. - finished = (match(new_order_object, *old_limit_itr, old_limit_itr->sell_price) != 2); + finished = ( match(new_order_object, *old_limit_itr, old_limit_itr->sell_price) + != match_result_type::only_maker_filled ); } // TODO Possible optimization: only check calls if the new order completely filled some old order. @@ -551,7 +552,8 @@ bool database::apply_order(const limit_order_object& new_order_object) ++limit_itr; // match returns 2 when only the old order was fully filled. // In this case, we keep matching; otherwise, we stop. - finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); + finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) + != match_result_type::only_maker_filled ); } if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf @@ -570,15 +572,16 @@ bool database::apply_order(const limit_order_object& new_order_object) || call_itr->collateralization() > sell_abd->current_maintenance_collateralization ) break; // hard fork core-338 and core-625 took place at same time, not checking HARDFORK_CORE_338_TIME here. - int match_result = match( new_order_object, *call_itr, call_match_price, - sell_abd->current_feed.settlement_price, - sell_abd->current_feed.maintenance_collateral_ratio, - sell_abd->current_maintenance_collateralization, - call_pays_price); + const auto match_result = match( new_order_object, *call_itr, call_match_price, + sell_abd->current_feed.settlement_price, + sell_abd->current_feed.maintenance_collateral_ratio, + sell_abd->current_maintenance_collateralization, + call_pays_price); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hf core-834), we no longer only check if the result is 2. - if( match_result == 1 || match_result == 3 ) + if( match_result_type::only_taker_filled == match_result + || match_result_type::both_filled == match_result ) finished = true; } } @@ -599,15 +602,16 @@ bool database::apply_order(const limit_order_object& new_order_object) break; // assume hard fork core-338 and core-625 will take place at same time, // not checking HARDFORK_CORE_338_TIME here. - int match_result = match( new_order_object, *call_itr, call_match_price, - sell_abd->current_feed.settlement_price, - sell_abd->current_feed.maintenance_collateral_ratio, - optional() ); + const auto match_result = match( new_order_object, *call_itr, call_match_price, + sell_abd->current_feed.settlement_price, + sell_abd->current_feed.maintenance_collateral_ratio, + optional() ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hard fork core-834), // we no longer only check if the result is 2. - if( match_result == 1 || match_result == 3 ) + if( match_result_type::only_taker_filled == match_result + || match_result_type::both_filled == match_result ) finished = true; } } @@ -619,7 +623,8 @@ bool database::apply_order(const limit_order_object& new_order_object) auto old_limit_itr = limit_itr; ++limit_itr; // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. - finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); + finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) + != match_result_type::only_maker_filled ); } const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); @@ -688,6 +693,17 @@ void database::apply_force_settlement( const force_settlement_object& new_settle } +/// Helper function +static database::match_result_type get_match_result( bool taker_filled, bool maker_filled ) +{ + int8_t result = 0; + if( maker_filled ) + result += static_cast( database::match_result_type::only_maker_filled ); + if( taker_filled ) + result += static_cast( database::match_result_type::only_taker_filled ); + return static_cast( result ); +} + /** * Matches the two orders, the first parameter is taker, the second is maker. * @@ -698,7 +714,8 @@ void database::apply_force_settlement( const force_settlement_object& new_settle * 2 - maker was filled * 3 - both were filled */ -int database::match( const limit_order_object& usd, const limit_order_object& core, const price& match_price ) +database::match_result_type database::match( const limit_order_object& usd, const limit_order_object& core, + const price& match_price ) { FC_ASSERT( usd.sell_price.quote.asset_id == core.sell_price.base.asset_id ); FC_ASSERT( usd.sell_price.base.asset_id == core.sell_price.quote.asset_id ); @@ -720,7 +737,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later if( usd_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) - return 1; + return match_result_type::only_taker_filled; if( before_core_hardfork_342 ) core_receives = usd_for_sale; @@ -759,14 +776,17 @@ int database::match( const limit_order_object& usd, const limit_order_object& co core_pays == core.amount_for_sale() ); // the first param of match() is taker - int result = fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ) ? 1 : 0; + bool taker_filled = fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ); // the second param of match() is maker - result += fill_limit_order( core, core_pays, core_receives, true, match_price, true ) ? 2 : 0; - FC_ASSERT( result != 0 ); + bool maker_filled = fill_limit_order( core, core_pays, core_receives, true, match_price, true ); + + match_result_type result = get_match_result( taker_filled, maker_filled ); + FC_ASSERT( result != match_result_type::none_filled ); return result; } -int database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price, +database::match_result_type database::match( const limit_order_object& bid, const call_order_object& ask, + const price& match_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization, const price& call_pays_price ) @@ -790,7 +810,7 @@ int database::match( const limit_order_object& bid, const call_order_object& ask // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later if( order_receives.amount == 0 ) - return 1; + return match_result_type::only_taker_filled; // The remaining amount in the limit order would be too small, // so we should cull the order in fill_limit_order() below. @@ -819,10 +839,11 @@ int database::match( const limit_order_object& bid, const call_order_object& ask FC_ASSERT(call_pays >= order_receives); const asset margin_call_fee = call_pays - order_receives; - int result = fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false ) ? 1 : 0; // taker - result += fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee ) ? 2 : 0; // maker - // result can be 0 when call order has target_collateral_ratio option set. + bool taker_filled = fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false ); + bool maker_filled = fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee ); + // Note: result can be none_filled when call order has target_collateral_ratio option set. + match_result_type result = get_match_result( taker_filled, maker_filled ); return result; } diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index c3e70841cf..8296990b6f 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -451,12 +451,20 @@ namespace graphene { namespace chain { * @return a bit field indicating which orders were filled (and thus removed) * * 0 - no orders were matched - * 1 - taker was filled - * 2 - maker was filled + * 1 - only taker was filled + * 2 - only maker was filled * 3 - both were filled */ ///@{ - int match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price ); + enum class match_result_type + { + none_filled = 0, + only_taker_filled = 1, + only_maker_filled = 2, + both_filled = 3 + }; + match_result_type match( const limit_order_object& taker, const limit_order_object& maker, + const price& trade_price ); /*** * @brief Match limit order as taker to a call order as maker * @param taker the order that is removing liquidity from the book @@ -469,12 +477,14 @@ namespace graphene { namespace chain { * than limit order takes if call order subject to a margin call fee. * @returns 0 if no orders were matched, 1 if taker was filled, 2 if maker was filled, 3 if both were filled */ - int match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, + match_result_type match( const limit_order_object& taker, const call_order_object& maker, + const price& trade_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization, const price& call_pays_price); /// If separate call_pays_price not provided, assume call pays at trade_price: - int match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, + match_result_type match( const limit_order_object& taker, const call_order_object& maker, + const price& trade_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization) { return match(taker, maker, trade_price, feed_price, maintenance_collateral_ratio, @@ -542,7 +552,7 @@ namespace graphene { namespace chain { bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, const price& fill_price, const bool is_maker, const asset& margin_fee ); - // Overload provides compatible default value for margin_fee: (margin_fee.asset_id == pays.asset_id) + /// Overload provides compatible default value for margin_fee: (margin_fee.asset_id == pays.asset_id) bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, const price& fill_price, const bool is_maker ) { @@ -556,7 +566,8 @@ namespace graphene { namespace chain { bool for_new_limit_order = false, const asset_bitasset_data_object* bitasset_ptr = nullptr ); - // helpers to fill_order + /// helpers to fill_order + /// @{ void pay_order( const account_object& receiver, const asset& receives, const asset& pays ); /** @@ -570,6 +581,7 @@ namespace graphene { namespace chain { asset pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives, const bool& is_maker, const optional& calculated_market_fees = {}); asset pay_force_settle_fees(const asset_object& collecting_asset, const asset& collat_receives); + /// @} ///@} From 1209eb6584b464a4f34170b6a2832b172e53aa50 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 18 Jul 2021 23:29:05 +0000 Subject: [PATCH 103/258] Fix rounding issues --- libraries/chain/db_block.cpp | 18 ++ libraries/chain/db_market.cpp | 7 +- .../chain/include/graphene/chain/database.hpp | 2 + tests/tests/force_settle_match_tests.cpp | 223 +++++++++++------- tests/tests/market_tests.cpp | 37 ++- 5 files changed, 191 insertions(+), 96 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 97b4bee493..213343fbd9 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -580,6 +580,21 @@ void database::apply_block( const signed_block& next_block, uint32_t skip ) return; } +class exception_muting_guard { +public: + explicit exception_muting_guard(database& d) : db(d) + { + db._mute_exceptions = true; + } + ~exception_muting_guard() noexcept + { + db._mute_exceptions = false; + } + exception_muting_guard(const exception_muting_guard&) = delete; +private: + database& db; +}; + void database::_apply_block( const signed_block& next_block ) { try { uint32_t next_block_num = next_block.block_num(); @@ -646,6 +661,9 @@ void database::_apply_block( const signed_block& next_block ) clear_expired_orders(); clear_expired_force_settlements(); clear_expired_htlcs(); + + exception_muting_guard guard(*this); + update_expired_feeds(); // this will update expired feeds and some core exchange rates update_core_exchange_rates(); // this will update remaining core exchange rates update_withdraw_permissions(); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 22aa425913..17e4af82ef 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1510,7 +1510,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( usd_to_buy > usd_for_sale ) { // fill order limit_receives = usd_for_sale * match_price; // round down, in favor of call order - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + if( !after_core_hardfork_2481 ) + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) // Be here, the limit order won't be paying something for nothing, since if it would, it would have // been cancelled elsewhere already (a maker limit order won't be paying something for nothing): @@ -1531,6 +1532,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( after_core_hardfork_2481 ) { + call_pays = call_receives * call_pays_price; // calculate with updated call_receives if( call_pays.amount >= call_order.collateral ) break; auto new_collateral = call_order.get_collateral() - call_pays; @@ -1556,7 +1558,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa { if( after_core_hardfork_2481 ) break; - call_pays.amount = call_order.collateral; + if( _mute_exceptions ) + call_pays.amount = call_order.collateral; } // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher // than the old CR, so no additional check for potential blackswan here diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 8296990b6f..7707afeb69 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -773,6 +773,8 @@ namespace graphene { namespace chain { const chain_property_object* _p_chain_property_obj = nullptr; const witness_schedule_object* _p_witness_schedule_obj = nullptr; ///@} + public: + bool _mute_exceptions = false; }; } } diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 3ed8e2a620..406966d0f3 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -114,6 +114,7 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call) current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); publish_feed( bitusd, feedproducer, current_feed ); // settlement price = 1/10, mssp = 1/11 + price mc( asset(10*175), asset(1*100, usd_id) ); // This sell order above MSSP will not be matched with a call limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; @@ -139,8 +140,10 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call) // call and call2's CR is quite high, and debt amount is quite a lot, // assume neither of them will be completely filled price match_price( bitusd.amount(1) / core.amount(11) ); - share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); - share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc); + share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc); + BOOST_CHECK_GT( call_to_cover.value, 0 ); + BOOST_CHECK_GT( call2_to_cover.value, 0 ); BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value ); BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value ); // even though call2 has a higher CR, since call's TCR is less than call2's TCR, @@ -284,7 +287,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // check share_type call_to_pay = amount_to_settle * 11 / 100; // round down, favors call order - share_type call_to_cover = (call_to_pay * 100 + 10 ) / 11; // stablize : 101 -> 100 + share_type call_to_cover = (call_to_pay * 100 + 10 ) / 11; // stabilize : 101 -> 100 share_type call_to_settler = (call_to_cover * 107) / 1000; // round down, favors call order BOOST_CHECK_EQUAL( 100000 - call_to_cover.value, call.debt.value ); BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value ); @@ -313,7 +316,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // check share_type call_to_pay2 = amount_to_settle2 * 11 / 100; // round down, favors call order - share_type call_to_cover2 = (call_to_pay2 * 100 + 10 ) / 11; // stablize : 100 -> 100 (no change) + share_type call_to_cover2 = (call_to_pay2 * 100 + 10 ) / 11; // stabilize : 100 -> 100 (no change) share_type call_to_settler2 = (call_to_cover2 * 107) / 1000; // round down, favors call order BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value ); BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value ); @@ -332,7 +335,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) { // increase mssr and mcfr - // settlement price = 10/1, mssp = 100/13, mcop = 1000/103, mcpr = 130/127 + // settlement price = 10/1, mssp = 100/13, mcop = 1000/103, mcpr = 130/103 asset_update_bitasset_operation uop; uop.issuer = usd_id(db).issuer; uop.asset_to_update = usd_id; @@ -357,7 +360,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // check share_type call_to_pay3 = amount_to_settle3 * 13 / 100; // round down, favors call order - share_type call_to_cover3 = (call_to_pay3 * 100 + 10 ) / 13; // stablize : 9 -> 8 + share_type call_to_cover3 = (call_to_pay3 * 100 + 10 ) / 13; // stabilize : 9 -> 8 share_type call_to_settler3 = (call_to_cover3 * 103) / 1000; // round down, favors call order BOOST_CHECK_EQUAL( 0, call_to_settler3.value ); call_to_settler3 = 1; // 0 -> 1 @@ -514,12 +517,15 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // prepare price feed to get call and call2 (but not call3) into margin call territory current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + price mc( asset(10*175), asset(1*100, usd_id) ); // call and call2's CR is quite high, and debt amount is quite a lot, // assume neither of them will be completely filled price match_price = sell_id(db).sell_price * ratio_type(107,110); - share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); - share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc); + share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc); + BOOST_CHECK_GT( call_to_cover.value, 0 ); + BOOST_CHECK_GT( call2_to_cover.value, 0 ); BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value ); BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value ); // even though call2 has a higher CR, since call's TCR is less than call2's TCR, @@ -572,7 +578,7 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) price call_pays_price( asset(1, usd_id), asset(11) ); call2_copy.debt -= call2_to_cover; call2_copy.collateral -= call2_to_pay; - auto call2_to_cover2 = call2_copy.get_max_debt_to_cover(call_pays_price,current_feed.settlement_price,1750); + auto call2_to_cover2 = call2_copy.get_max_debt_to_cover(call_pays_price,current_feed.settlement_price,1750,mc); BOOST_CHECK_GT( call2_to_cover2.value, 0 ); share_type call2_to_pay2 = call2_to_cover2 * 11; BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value - call2_to_cover2.value, call2.debt.value ); @@ -715,12 +721,15 @@ BOOST_AUTO_TEST_CASE(hf2481_cross_test) // prepare price feed to get call and call2 (but not call3) into margin call territory current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + price mc( asset(10*175), asset(1*100, usd_id) ); // call and call2's CR is quite high, and debt amount is quite a lot, // assume neither of them will be completely filled price match_price = sell_id(db).sell_price * ratio_type(107,110); - share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); - share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc); + share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc); + BOOST_CHECK_GT( call_to_cover.value, 0 ); + BOOST_CHECK_GT( call2_to_cover.value, 0 ); BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value ); BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value ); // even though call2 has a higher CR, since call's TCR is less than call2's TCR, @@ -812,7 +821,7 @@ BOOST_AUTO_TEST_CASE(hf2481_cross_test) price call_pays_price( asset(1, usd_id), asset(11) ); call2_copy.debt -= call2_to_cover; call2_copy.collateral -= call2_to_pay; - auto call2_to_cover2 = call2_copy.get_max_debt_to_cover(call_pays_price,current_feed.settlement_price,1750); + auto call2_to_cover2 = call2_copy.get_max_debt_to_cover(call_pays_price,current_feed.settlement_price,1750,mc); BOOST_CHECK_GT( call2_to_cover2.value, 0 ); share_type call2_to_pay2 = call2_to_cover2 * 11; BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value - call2_to_cover2.value, call2_id(db).debt.value ); @@ -853,6 +862,7 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // 3 passes. With no matching limit order, or with a small or big matching limit order. for( int i = 0; i < 3; ++ i ) { + idump( (i) ); set_expiration( db, trx ); @@ -888,35 +898,35 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) price_feed current_feed; current_feed.maintenance_collateral_ratio = 1750; current_feed.maximum_short_squeeze_ratio = 1100; - current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5); publish_feed( bitusd, feedproducer, current_feed ); - // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7, tcr 170% is lower than 175% - const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000), 1700); + // start out with 300% collateral, call price is 15/175 CORE/USD = 60/700, tcr 170% is lower than 175% + const call_order_object& call = *borrow( borrower, bitusd.amount(100000), asset(15000), 1700); call_order_id_type call_id = call.id; - // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7, tcr 200% > 175% - const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500), 2000); + // create another position with 310% collateral, call price is 15.5/175 CORE/USD = 62/700, tcr 200% > 175% + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(15500), 2000); call_order_id_type call2_id = call2.id; - // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7, no tcr - const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000)); + // create yet another position with 500% collateral, call price is 25/175 CORE/USD = 100/700, no tcr + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(25000)); call_order_id_type call3_id = call3.id; - // create a small position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7, no tcr - const call_order_object& call4 = *borrow( borrower4, bitusd.amount(10), asset(160) ); + // create a small position with 320% collateral, call price is 16/175 CORE/USD = 64/700, no tcr + const call_order_object& call4 = *borrow( borrower4, bitusd.amount(1000), asset(160) ); call_order_id_type call4_id = call4.id; - // create yet another position with 900% collateral, call price is 45/1.75 CORE/USD = 180/7, no tcr - const call_order_object& call5 = *borrow( borrower5, bitusd.amount(1000), asset(45000)); + // create yet another position with 900% collateral, call price is 45/175 CORE/USD = 180/700, no tcr + const call_order_object& call5 = *borrow( borrower5, bitusd.amount(100000), asset(45000)); call_order_id_type call5_id = call5.id; - transfer(borrower, seller, bitusd.amount(1000)); - transfer(borrower2, seller, bitusd.amount(1000)); - transfer(borrower3, seller, bitusd.amount(1000)); + transfer(borrower, seller, bitusd.amount(100000)); + transfer(borrower2, seller, bitusd.amount(100000)); + transfer(borrower3, seller, bitusd.amount(100000)); - BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 100000, call.debt.value ); BOOST_CHECK_EQUAL( 15000, call.collateral.value ); - BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 100000, call2.debt.value ); BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); - BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); - BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 300000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); @@ -926,133 +936,164 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); - BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) ); - BOOST_CHECK_EQUAL( 1000, get_balance(borrower5, bitusd) ); + BOOST_CHECK_EQUAL( 1000, get_balance(borrower4, bitusd) ); + BOOST_CHECK_EQUAL( 100000, get_balance(borrower5, bitusd) ); // This sell order above MCOP will not be matched with a call - limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(150))->id; - BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(700), core.amount(150))->id; + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 700 ); - BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 299300, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // This buy order is too low will not be matched with a sell order - limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->id; + limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(1000))->id; BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) ); - // Create a sell order which will be matched with several call orders later, price 1/9 - limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(1000), core.amount(9000) )->id; - BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 1000 ); + // Create a sell order which will be matched with several call orders later, price 100/9 + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(100000), core.amount(9000) )->id; + BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 100000 ); - // Create another sell order which will trigger a blackswan event if matched, price 1/21 + // Create another sell order which will trigger a blackswan event if matched, price 100/21 limit_order_id_type sell_swan; if( i == 1 ) { - sell_swan = create_sell_order(seller, bitusd.amount(1), core.amount(21) )->id; - BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 1 ); + sell_swan = create_sell_order(seller, bitusd.amount(100), core.amount(21) )->id; + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 ); } else if( i == 2 ) { - sell_swan = create_sell_order(seller, bitusd.amount(100), core.amount(2100) )->id; - BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 ); + sell_swan = create_sell_order(seller, bitusd.amount(10000), core.amount(2100) )->id; + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 ); } // Create a force settlement, will be matched with several call orders later - auto result = force_settle( seller, bitusd.amount(400) ); + auto result = force_settle( seller, bitusd.amount(40000) ); BOOST_REQUIRE( result.is_type() ); force_settlement_id_type settle_id = result.get(); BOOST_CHECK( db.find( settle_id ) != nullptr ); // Create another force settlement - result = force_settle( seller, bitusd.amount(100) ); + result = force_settle( seller, bitusd.amount(10000) ); BOOST_REQUIRE( result.is_type() ); force_settlement_id_type settle2_id = result.get(); BOOST_CHECK( db.find( settle2_id ) != nullptr ); + call_order_object call_copy = call; + call_order_object call2_copy = call2; + call_order_object call3_copy = call3; + call_order_object call4_copy = call4; + call_order_object call5_copy = call5; + // prepare price feed to get call, call2, call3 and call4 (but not call5) into margin call territory - current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(20); + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(20); + price mc( asset(20*175), asset(100*100, usd_id) ); // since the sell limit order's price is low, and TCR is set for both call and call2, // call and call2 will match with the sell limit order price match_price = sell_id(db).sell_price * ratio_type(107,110); - share_type call_to_cover = call_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); - share_type call2_to_cover = call2_id(db).get_max_debt_to_cover(match_price,current_feed.settlement_price,1750); + share_type call_to_cover = call_copy.get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc); + share_type call2_to_cover = call2_copy.get_max_debt_to_cover(match_price,current_feed.settlement_price,1750,mc); + BOOST_CHECK_GT( call_to_cover.value, 0 ); + BOOST_CHECK_GT( call2_to_cover.value, 0 ); BOOST_CHECK_LT( call_to_cover.value, call_id(db).debt.value ); BOOST_CHECK_LT( call2_to_cover.value, call2_id(db).debt.value ); // even though call2 has a higher CR, since call's TCR is less than call2's TCR, // so we expect call will cover less when called BOOST_CHECK_LT( call_to_cover.value, call2_to_cover.value ); - call_order_object call_copy = call; - call_order_object call2_copy = call2; - call_order_object call3_copy = call3; - call_order_object call4_copy = call4; - call_order_object call5_copy = call5; - // adjust price feed to get call, call2, call3 and call4 (but not call5) into margin call territory publish_feed( bitusd, feedproducer, current_feed ); - // settlement price = 1/20, mssp = 1/22, mcop = 20/107, mcpr = 110/107 + // settlement price = 100/20, mssp = 100/22, mcop = 500/107, mcpr = 110/107 share_type expected_margin_call_fees = 0; - // firstly the sell limit order will match with call, at limit order's price: 1/9 - // call will receive call_to_cover, pay 9*call_to_cover - share_type call_to_pay = (call_to_cover * 9 * 110 + 106) / 107; // round up since it's smaller + // firstly the sell limit order will match with call, at limit order's price: 100/9 + // call will receive call_to_cover, limit order gets call_to_cover*9/100, + // call pays call_to_cover*9*110/100/107 = call_to_cover * 99 / 1070 + share_type call_to_pay = (call_to_cover * 99 + 1069) / 1070; // round up since it's smaller + // Note: no stabilization here + call_copy.debt -= call_to_cover; call_copy.collateral -= call_to_pay; - // the limit order then will match with call2, at limit order's price: 1/9 - // if the limit is big enough, call2 will receive call2_to_cover, pay 9*call2_to_cover + share_type sell_receives1 = (call_to_cover * 9 + 99 ) / 100; // round up since the call order is smaller + share_type margin_call_fee_limit_1 = call_to_pay - sell_receives1; + expected_margin_call_fees += margin_call_fee_limit_1; + + // the limit order then will match with call2, at limit order's price: 100/9 + // if the limit is big enough, call2 will receive call2_to_cover, // however it's not the case, so call2 will receive less - call2_to_cover = 1000 - call_to_cover; - share_type call2_to_pay = call2_to_cover * 9 * 110 / 107; // round down since it's larger + call2_to_cover = 100000 - call_to_cover; + share_type sell_receives2 = call2_to_cover * 9 / 100; // round down since the call order is larger + share_type call2_to_cover_old = call2_to_cover; + call2_to_cover = (sell_receives2 * 100 + 8) / 9; // stabilize. Note: from sell_receives2 but not call2_to_pay + share_type call2_to_pay = call2_to_cover * 99 / 1070; // round down since it's larger + share_type sell_refund = call2_to_cover_old - call2_to_cover; + call2_copy.debt -= call2_to_cover; call2_copy.collateral -= call2_to_pay; + share_type margin_call_fee_limit_2 = call2_to_pay - sell_receives2; + expected_margin_call_fees += margin_call_fee_limit_2; + // sell_id is completely filled BOOST_CHECK( !db.find( sell_id ) ); - share_type margin_call_fee_limit = call_to_pay + call2_to_pay - 9000; - expected_margin_call_fees += margin_call_fee_limit; // now call4 has the lowest CR // call4 will match with the settle order, since it is small and has too few collateral, it will be fully closed // and it will lose all collateral, 160 - // call_pays_price is 1/16, settle_receives_price is (1/16)*(110/107) = 55/856 - share_type settle_receives4 = 157; // (10 * 856 + 54) / 55; // round up - share_type margin_call_fee_settle_4 = 3; // 160 - 157 + // call_pays_price is 100/16, settle_receives_price is (100/16)*(110/107) = 1375/214 + share_type settle_receives4 = 156; // round_up( 1000 * 214 / 1375 ) + share_type margin_call_fee_settle_4 = 4; // 160 - 157 expected_margin_call_fees += margin_call_fee_settle_4; // borrower4 balance does not change BOOST_CHECK_EQUAL( init_balance - 160, get_balance(borrower4, core) ); - BOOST_CHECK_EQUAL( 10, get_balance(borrower4, bitusd) ); + BOOST_CHECK_EQUAL( 1000, get_balance(borrower4, bitusd) ); // now call2 has the lowest CR // call2 is still in margin call territory after matched with limit order, now it matches with settle orders // the settle orders are too small to fill call2 - share_type call2_to_cover1 = 390; // 400 - 10 + share_type call2_to_cover1 = 39000; // 40000 - 1000 share_type call2_to_pay1 = call2_to_cover1 * call2_copy.collateral / call2_copy.debt; // round down + share_type call2_to_cover1_old = call2_to_cover1; + // stabilize + call2_to_cover1 = (call2_to_pay1 * call2_copy.debt + call2_copy.collateral - 1) / call2_copy.collateral; + share_type settle_refund = call2_to_cover1_old - call2_to_cover1; - share_type settle_receives2 = call2_to_pay1 * 107 / 110; // round down + share_type settle_receives2 = call2_to_cover1 * call2_copy.collateral * 107 + / (call2_copy.debt * 110); // round down share_type margin_call_fee_settle_2 = call2_to_pay1 - settle_receives2; expected_margin_call_fees += margin_call_fee_settle_2; + idump( ("before_match_settle_call2")(call2_copy) ); + call2_copy.debt -= call2_to_cover1; call2_copy.collateral -= call2_to_pay1; + idump( ("after_match_settle_call2")(call2_copy) ); + // call2 matches with the other settle order - share_type call2_to_cover2 = 100; // 400 - 10 + share_type call2_to_cover2 = 10000; share_type call2_to_pay2 = call2_to_cover2 * call2_copy.collateral / call2_copy.debt; // round down + share_type call2_to_cover2_old = call2_to_cover2; + // stabilize + call2_to_cover2 = (call2_to_pay2 * call2_copy.debt + call2_copy.collateral - 1) / call2_copy.collateral; + share_type settle2_refund = call2_to_cover2_old - call2_to_cover2; - share_type settle2_receives2 = call2_to_pay2 * 107 / 110; // round down + share_type settle2_receives2 = call2_to_cover2 * call2_copy.collateral * 107 + / (call2_copy.debt * 110); // round down share_type margin_call_fee_settle2_2 = call2_to_pay2 - settle2_receives2; expected_margin_call_fees += margin_call_fee_settle2_2; call2_copy.debt -= call2_to_cover2; call2_copy.collateral -= call2_to_pay2; - // settle order is fully filled + // settle orders are fully filled BOOST_CHECK( db.find( settle_id ) == nullptr ); + BOOST_CHECK( db.find( settle2_id ) == nullptr ); // blackswan event occurs BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); @@ -1067,7 +1108,7 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) idump( (call2_copy) ); // call2 has the lowest CR below required - share_type call2_to_gs_fund = (call2_copy.collateral * 10 + 10) / 11; // round up + share_type call2_to_gs_fund = (call2_copy.collateral * 10 + 10) / 11; // MSSR = 11/10, round up here share_type margin_call_fee_gs_2 = call2_copy.collateral - call2_to_gs_fund; expected_margin_call_fees += margin_call_fee_gs_2; expected_gs_fund += call2_to_gs_fund; @@ -1110,30 +1151,32 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // borrower5 balance changes -- some collateral returned BOOST_CHECK_EQUAL( init_balance - call5_to_pay_gs.value, get_balance(borrower5, core) ); - BOOST_CHECK_EQUAL( 1000, get_balance(borrower5, bitusd) ); + BOOST_CHECK_EQUAL( 100000, get_balance(borrower5, bitusd) ); - // check seller balance - int expected_seller_usd_balance = 1493; // 3000 - 7 - 1000 - 400 - 100 + // check seller balance // 149300 == 300000 - 700 - 100000 - 40000 - 10000 + share_type expected_seller_usd_balance = 149300 + sell_refund + settle_refund + settle2_refund; if ( i == 1 ) - expected_seller_usd_balance -= 1; // - sell_swan - else if ( i == 2 ) expected_seller_usd_balance -= 100; // - sell_swan + else if ( i == 2 ) + expected_seller_usd_balance -= 10000; // - sell_swan + // 1000*9 + 160*107/110 + 49000 * call2_cr * 107/110 + share_type expected_seller_core_balance = sell_receives1 + sell_receives2 + settle_receives4 + + settle_receives2 + settle2_receives2; - BOOST_CHECK_EQUAL( expected_seller_usd_balance, get_balance(seller, bitusd) ); - BOOST_CHECK_EQUAL( 9000 + settle_receives4.value + settle_receives2.value + settle2_receives2.value, - get_balance(seller, core) ); // 1000*9 + 160*107/110 + 490 * call2_cr * 107/110 + BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( expected_seller_core_balance.value, get_balance(seller, core) ); // buy_low's price is too low that won't be matched BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); // sell_high is not matched - BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 700 ); // sell_swan is not matched if( i == 1 ) - BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 1 ); - else if( i == 2 ) BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 ); + else if( i == 2 ) + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 ); // check gs fund BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, expected_gs_fund.value ); @@ -1153,13 +1196,13 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); // sell_high is not matched - BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 700 ); // sell_swan is not matched if( i == 1 ) - BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 1 ); - else if( i == 2 ) BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 ); + else if( i == 2 ) + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 ); // check gs fund BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).settlement_fund.value, expected_gs_fund.value ); @@ -1173,7 +1216,7 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // reset db.pop_block(); - } // for + } // for i } FC_LOG_AND_RETHROW() } diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 18d9d90df2..5d227fdd13 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1493,7 +1493,7 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) PUSH_TX(db, trx, ~0); } - update_feed_producers( bitusd, {feedproducer_id} ); + update_feed_producers( bitusd, {feedproducer_id, feeder2_id, feeder3_id} ); price_feed current_feed; current_feed.maintenance_collateral_ratio = 1750; @@ -1541,10 +1541,11 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) BOOST_TEST_MESSAGE( "Trying to trigger a margin call" ); auto feed2 = current_feed; feed2.settlement_price = bitusd.amount( 1 ) / core.amount(18); - publish_feed( bitusd, feedproducer, feed2 ); if(hf2481) { + publish_feed( bitusd, feedproducer, feed2 ); + // blackswan BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !db.find( call_id ) ); @@ -1565,6 +1566,34 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) } else { + BOOST_REQUIRE_THROW( publish_feed( bitusd, feedproducer, feed2 ), fc::exception ); + + publish_feed( bitusd, feeder2, current_feed ); + publish_feed( bitusd, feeder3, current_feed ); + + // No change + BOOST_CHECK_EQUAL( 1100, sell_mid(db).for_sale.value ); + + BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + + generate_blocks( db.head_block_time() + fc::seconds(43200) ); + set_expiration( db, trx ); + + publish_feed( usd_id(db), feedproducer_id(db), feed2 ); + + // No change + BOOST_CHECK_EQUAL( 1100, sell_mid(db).for_sale.value ); + + BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + + generate_blocks( db.head_block_time() + fc::seconds(43200) ); + // The first call order should have been filled BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !db.find( call_id ) ); @@ -1574,8 +1603,8 @@ BOOST_AUTO_TEST_CASE(mcfr_rounding_test) BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); - BOOST_CHECK_EQUAL( 900, get_balance(seller, bitusd) ); - BOOST_CHECK_EQUAL( 14047, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 900, get_balance(seller_id(db), usd_id(db)) ); + BOOST_CHECK_EQUAL( 14047, get_balance(seller_id(db), core) ); } // generate a block to include operations above From 7a2afc78cf709e89555a7163ed4f30db7ee88fdc Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 19 Jul 2021 16:02:24 +0000 Subject: [PATCH 104/258] Add tests for matching tiny settle order with call --- tests/tests/force_settle_match_tests.cpp | 32 ++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 406966d0f3..d9a114c2fe 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -939,11 +939,14 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( 1000, get_balance(borrower4, bitusd) ); BOOST_CHECK_EQUAL( 100000, get_balance(borrower5, bitusd) ); + share_type expected_seller_usd_balance = 300000; + // This sell order above MCOP will not be matched with a call limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(700), core.amount(150))->id; BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 700 ); + expected_seller_usd_balance -= 700; - BOOST_CHECK_EQUAL( 299300, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // This buy order is too low will not be matched with a sell order @@ -955,6 +958,7 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // Create a sell order which will be matched with several call orders later, price 100/9 limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(100000), core.amount(9000) )->id; BOOST_CHECK_EQUAL( db.find( sell_id )->for_sale.value, 100000 ); + expected_seller_usd_balance -= 100000; // Create another sell order which will trigger a blackswan event if matched, price 100/21 limit_order_id_type sell_swan; @@ -962,11 +966,13 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) { sell_swan = create_sell_order(seller, bitusd.amount(100), core.amount(21) )->id; BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 100 ); + expected_seller_usd_balance -= 100; } else if( i == 2 ) { sell_swan = create_sell_order(seller, bitusd.amount(10000), core.amount(2100) )->id; BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 ); + expected_seller_usd_balance -= 10000; } // Create a force settlement, will be matched with several call orders later @@ -974,12 +980,25 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_REQUIRE( result.is_type() ); force_settlement_id_type settle_id = result.get(); BOOST_CHECK( db.find( settle_id ) != nullptr ); + expected_seller_usd_balance -= 40000; // Create another force settlement result = force_settle( seller, bitusd.amount(10000) ); BOOST_REQUIRE( result.is_type() ); force_settlement_id_type settle2_id = result.get(); BOOST_CHECK( db.find( settle2_id ) != nullptr ); + expected_seller_usd_balance -= 10000; + + // Create the third force settlement which is small + result = force_settle( seller, bitusd.amount(3) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle3_id = result.get(); + BOOST_CHECK( db.find( settle3_id ) != nullptr ); + expected_seller_usd_balance -= 3; + + // Check seller balance + BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); call_order_object call_copy = call; call_order_object call2_copy = call2; @@ -1094,6 +1113,9 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // settle orders are fully filled BOOST_CHECK( db.find( settle_id ) == nullptr ); BOOST_CHECK( db.find( settle2_id ) == nullptr ); + // settle3 is canceled + BOOST_CHECK( db.find( settle3_id ) == nullptr ); + share_type settle3_refund = 3; // blackswan event occurs BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); @@ -1153,12 +1175,8 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( init_balance - call5_to_pay_gs.value, get_balance(borrower5, core) ); BOOST_CHECK_EQUAL( 100000, get_balance(borrower5, bitusd) ); - // check seller balance // 149300 == 300000 - 700 - 100000 - 40000 - 10000 - share_type expected_seller_usd_balance = 149300 + sell_refund + settle_refund + settle2_refund; - if ( i == 1 ) - expected_seller_usd_balance -= 100; // - sell_swan - else if ( i == 2 ) - expected_seller_usd_balance -= 10000; // - sell_swan + // check seller balance + expected_seller_usd_balance += (sell_refund + settle_refund + settle2_refund + settle3_refund); // 1000*9 + 160*107/110 + 49000 * call2_cr * 107/110 share_type expected_seller_core_balance = sell_receives1 + sell_receives2 + settle_receives4 + settle_receives2 + settle2_receives2; From 27249c15588246bcb532e553fb73d26982fe6e05 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 19 Jul 2021 20:24:32 +0000 Subject: [PATCH 105/258] Improve performance --- libraries/chain/db_block.cpp | 18 ------------------ libraries/chain/db_market.cpp | 5 +++-- libraries/chain/db_update.cpp | 2 +- .../chain/include/graphene/chain/database.hpp | 5 ++--- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 213343fbd9..97b4bee493 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -580,21 +580,6 @@ void database::apply_block( const signed_block& next_block, uint32_t skip ) return; } -class exception_muting_guard { -public: - explicit exception_muting_guard(database& d) : db(d) - { - db._mute_exceptions = true; - } - ~exception_muting_guard() noexcept - { - db._mute_exceptions = false; - } - exception_muting_guard(const exception_muting_guard&) = delete; -private: - database& db; -}; - void database::_apply_block( const signed_block& next_block ) { try { uint32_t next_block_num = next_block.block_num(); @@ -661,9 +646,6 @@ void database::_apply_block( const signed_block& next_block ) clear_expired_orders(); clear_expired_force_settlements(); clear_expired_htlcs(); - - exception_muting_guard guard(*this); - update_expired_feeds(); // this will update expired feeds and some core exchange rates update_core_exchange_rates(); // this will update remaining core exchange rates update_withdraw_permissions(); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 17e4af82ef..87e221e38d 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1348,11 +1348,12 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a * limit order. (Only relevent before hardfork 625. apply_order_before_hardfork_625() is only * function that calls this with for_new_limit_order true.) * @param bitasset_ptr - an optional pointer to the bitasset_data object of the asset + * @param mute_exceptions - whether to mute exceptions in a special case * * @return true if a margin call was executed. */ bool database::check_call_orders( const asset_object& mia, bool enable_black_swan, bool for_new_limit_order, - const asset_bitasset_data_object* bitasset_ptr ) + const asset_bitasset_data_object* bitasset_ptr, bool mute_exceptions ) { try { const auto& dyn_prop = get_dynamic_global_properties(); auto maint_time = dyn_prop.next_maintenance_time; @@ -1558,7 +1559,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa { if( after_core_hardfork_2481 ) break; - if( _mute_exceptions ) + if( mute_exceptions ) call_pays.amount = call_order.collateral; } // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index a4e199c56c..9bd8aeb4fd 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -578,7 +578,7 @@ void database::update_expired_feeds() && !b.current_feed.margin_call_params_equal( old_median_feed ) ) { asset_ptr = &b.asset_id( *this ); - check_call_orders( *asset_ptr, true, false, &b ); + check_call_orders( *asset_ptr, true, false, &b, true ); } } // update CER diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 7707afeb69..224c6e909f 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -564,7 +564,8 @@ namespace graphene { namespace chain { bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, bool for_new_limit_order = false, - const asset_bitasset_data_object* bitasset_ptr = nullptr ); + const asset_bitasset_data_object* bitasset_ptr = nullptr, + bool mute_exceptions = false ); /// helpers to fill_order /// @{ @@ -773,8 +774,6 @@ namespace graphene { namespace chain { const chain_property_object* _p_chain_property_obj = nullptr; const witness_schedule_object* _p_witness_schedule_obj = nullptr; ///@} - public: - bool _mute_exceptions = false; }; } } From 390ca3d752e09fa245f1f7a2a72a7adea02b08fe Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 19 Jul 2021 22:12:15 +0000 Subject: [PATCH 106/258] Add tests for matching tiny call order with settle --- libraries/chain/db_market.cpp | 1 + tests/tests/force_settle_match_tests.cpp | 65 +++++++++++++++++------- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 87e221e38d..32538f7d0e 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1512,6 +1512,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa { // fill order limit_receives = usd_for_sale * match_price; // round down, in favor of call order if( !after_core_hardfork_2481 ) + // TODO add tests about CR change call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) // Be here, the limit order won't be paying something for nothing, since if it would, it would have diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index d9a114c2fe..f9de563e46 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -215,7 +215,8 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call) } FC_LOG_AND_RETHROW() } /*** - * After hf core-2481, matching small taker settle orders with a big maker call order + * After hf core-2481, matching small taker settle orders with a big maker call order. + * Also tests tiny call orders. */ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) { try { @@ -226,7 +227,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) set_expiration( db, trx ); - ACTORS((seller)(borrower)(feedproducer)); + ACTORS((seller)(borrower)(borrower2)(borrower3)(feedproducer)); const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); const auto& core = asset_id_type()(db); @@ -248,6 +249,8 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) int64_t init_balance(1000000); transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); update_feed_producers( bitusd, {feedproducer.id} ); price_feed current_feed; @@ -258,35 +261,63 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // start out with 300% collateral, call price is 15/175 CORE/USD = 6/70, tcr 170% is lower than 175% const call_order_object& call = *borrow( borrower, bitusd.amount(100000), asset(15000), 1700); call_order_id_type call_id = call.id; + // create another position with 285% collateral + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(7), asset(1), 1700); + call_order_id_type call2_id = call2.id; + // create yet another position with 285% collateral + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(14), asset(2), 1700); + call_order_id_type call3_id = call3.id; transfer(borrower, seller, bitusd.amount(100000)); + // adjust price feed to get call orders into margin call territory + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 10/1, mssp = 100/11, mcop = 1000/107, mcpr = 110/107 + BOOST_CHECK_EQUAL( 100000, call.debt.value ); BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 7, call2.debt.value ); + BOOST_CHECK_EQUAL( 1, call2.collateral.value ); + BOOST_CHECK_EQUAL( 14, call3.debt.value ); + BOOST_CHECK_EQUAL( 2, call3.collateral.value ); BOOST_CHECK_EQUAL( 100000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - - // adjust price feed to get call into margin call territory - current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(10); - publish_feed( bitusd, feedproducer, current_feed ); - // settlement price = 10/1, mssp = 100/11, mcop = 1000/107, mcpr = 110/107 + BOOST_CHECK_EQUAL( init_balance - 1, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( 7, get_balance(borrower2, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 2, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( 14, get_balance(borrower3, bitusd) ); BOOST_CHECK_EQUAL( 100000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // Create a force settlement, will be matched with the call order - share_type amount_to_settle = 101; + share_type amount_to_settle = 117; auto result = force_settle( seller, bitusd.amount(amount_to_settle) ); BOOST_REQUIRE( result.is_type() ); force_settlement_id_type settle_id = result.get(); BOOST_CHECK( db.find( settle_id ) == nullptr ); + // the settle order will match with call2, at mssp: 100/11, + // since call2 is too small, so it pays all + BOOST_CHECK( db.find( call2_id ) == nullptr ); + share_type expected_amount_to_settle = 110; // 117 - 7 + + // the settle order will match with call3, at mssp: 100/11, + // since call3 has TCR, it pays some collateral and stays there + BOOST_REQUIRE( db.find( call3_id ) != nullptr ); + + BOOST_CHECK_EQUAL( 5, call3.debt.value ); + BOOST_CHECK_EQUAL( 1, call3.collateral.value ); + + expected_amount_to_settle = 101; // 117 - 7 - 9 + // the settle order will match with call, at mssp: 100/11 BOOST_CHECK( db.find( call_id ) != nullptr ); // check - share_type call_to_pay = amount_to_settle * 11 / 100; // round down, favors call order + share_type call_to_pay = expected_amount_to_settle * 11 / 100; // round down, favors call order share_type call_to_cover = (call_to_pay * 100 + 10 ) / 11; // stabilize : 101 -> 100 share_type call_to_settler = (call_to_cover * 107) / 1000; // round down, favors call order BOOST_CHECK_EQUAL( 100000 - call_to_cover.value, call.debt.value ); @@ -297,8 +328,8 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99900, get_balance(seller, bitusd) ); // 100000 - 100, the rest 1 be canceled - int64_t expected_seller_core_balance = call_to_settler.value; + BOOST_CHECK_EQUAL( 99884, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100, the rest 1 be canceled + int64_t expected_seller_core_balance = 1 + 1 + call_to_settler.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); // asset's force_settled_volume does not change @@ -326,8 +357,8 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99800, get_balance(seller, bitusd) ); // 100000 - 100 - 100 - expected_seller_core_balance = call_to_settler.value + call_to_settler2.value; + BOOST_CHECK_EQUAL( 99784, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 + expected_seller_core_balance += call_to_settler2.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); // asset's force_settled_volume does not change @@ -374,8 +405,8 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99792, get_balance(seller, bitusd) ); // 100000 - 100 - 100 - 8 - expected_seller_core_balance = call_to_settler.value + call_to_settler2.value + call_to_settler3.value; + BOOST_CHECK_EQUAL( 99776, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 - 8 + expected_seller_core_balance += call_to_settler3.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); // asset's force_settled_volume does not change @@ -401,8 +432,8 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99792, get_balance(seller, bitusd) ); // 100000 - 100 - 100 - 8 - expected_seller_core_balance = call_to_settler.value + call_to_settler2.value + call_to_settler3.value; + BOOST_CHECK_EQUAL( 99776, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 - 8 + // expected_seller_core_balance does not change BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); // asset's force_settled_volume does not change From b3de6a7d1c55c6ed94586764353daa68d2a6c878 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 19 Jul 2021 22:15:55 +0000 Subject: [PATCH 107/258] Allow longer time for node-test --- programs/build_helpers/run-node-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/build_helpers/run-node-test b/programs/build_helpers/run-node-test index d85c3c5105..5628b4fe57 100755 --- a/programs/build_helpers/run-node-test +++ b/programs/build_helpers/run-node-test @@ -38,7 +38,7 @@ fi echo "Waiting for head_block 131071..." 1>&2 touch "$DATA_DIR"/info.json _START="`date +%s`" -while [ $(( `date +%s` - $_START )) -lt 180 ]; do +while [ $(( `date +%s` - $_START )) -lt 600 ]; do sleep 2 curl --silent -o "$DATA_DIR"/info.json --data '{"id":0,"method":"info","params":[]}' \ http://127.0.0.1:8091/rpc From 3d7592993fa609ac78e37e675e341302c215d3c9 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 20 Jul 2021 10:44:52 +0000 Subject: [PATCH 108/258] Add tests for matching small settle with call for better coverage --- tests/tests/force_settle_match_tests.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index f9de563e46..69d76242c4 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -1031,6 +1031,18 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + // Create the fourth force settlement which is a little bigger but still small + // Note: different execution path than settle3 + result = force_settle( seller, bitusd.amount(5) ); + BOOST_REQUIRE( result.is_type() ); + force_settlement_id_type settle4_id = result.get(); + BOOST_CHECK( db.find( settle4_id ) != nullptr ); + expected_seller_usd_balance -= 5; + + // Check seller balance + BOOST_CHECK_EQUAL( expected_seller_usd_balance.value, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + call_order_object call_copy = call; call_order_object call2_copy = call2; call_order_object call3_copy = call3; @@ -1147,6 +1159,9 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // settle3 is canceled BOOST_CHECK( db.find( settle3_id ) == nullptr ); share_type settle3_refund = 3; + // settle4 is canceled + BOOST_CHECK( db.find( settle4_id ) == nullptr ); + share_type settle4_refund = 5; // blackswan event occurs BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); @@ -1207,7 +1222,7 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) BOOST_CHECK_EQUAL( 100000, get_balance(borrower5, bitusd) ); // check seller balance - expected_seller_usd_balance += (sell_refund + settle_refund + settle2_refund + settle3_refund); + expected_seller_usd_balance += (sell_refund + settle_refund + settle2_refund + settle3_refund + settle4_refund); // 1000*9 + 160*107/110 + 49000 * call2_cr * 107/110 share_type expected_seller_core_balance = sell_receives1 + sell_receives2 + settle_receives4 + settle_receives2 + settle2_receives2; From 441f54ddea407fc3b7dd949420b51474aefe0c06 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 21 Jul 2021 23:33:33 +0000 Subject: [PATCH 109/258] Fix typo in docs --- libraries/chain/include/graphene/chain/database.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 224c6e909f..a082ffab7b 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -436,7 +436,7 @@ namespace graphene { namespace chain { /** * @brief Process a new force-settlement request - * @param new_settlment The new force-settlement request + * @param new_settlement The new force-settlement request * @param bitasset The bitasset data object * * Since the core-2481 hard fork, this function is called after a new force-settlement object is created From d9fa5bebe38326664ad2373095204cb2a0a488c2 Mon Sep 17 00:00:00 2001 From: Abit Date: Sun, 25 Jul 2021 02:56:15 +0200 Subject: [PATCH 110/258] Reset cURL options for different request type --- libraries/utilities/elasticsearch.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libraries/utilities/elasticsearch.cpp b/libraries/utilities/elasticsearch.cpp index 27f3b186c0..ce4228f43a 100644 --- a/libraries/utilities/elasticsearch.cpp +++ b/libraries/utilities/elasticsearch.cpp @@ -170,14 +170,23 @@ const std::string doCurl(CurlRequest& curl) struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "Content-Type: application/json"); + // Note: the variable curl.handler has a long lifetime, it only gets initialized once, then be used many times, + // thus we need to clear old data curl_easy_setopt(curl.handler, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl.handler, CURLOPT_URL, curl.url.c_str()); - curl_easy_setopt(curl.handler, CURLOPT_CUSTOMREQUEST, curl.type.c_str()); + curl_easy_setopt(curl.handler, CURLOPT_CUSTOMREQUEST, curl.type.c_str()); // this is OK if(curl.type == "POST") { + curl_easy_setopt(curl.handler, CURLOPT_HTTPGET, false); curl_easy_setopt(curl.handler, CURLOPT_POST, true); curl_easy_setopt(curl.handler, CURLOPT_POSTFIELDS, curl.query.c_str()); } + else // GET or DELETE (only these are used in this file) + { + curl_easy_setopt(curl.handler, CURLOPT_POSTFIELDS, NULL); + curl_easy_setopt(curl.handler, CURLOPT_POST, false); + curl_easy_setopt(curl.handler, CURLOPT_HTTPGET, true); + } curl_easy_setopt(curl.handler, CURLOPT_WRITEFUNCTION, WriteCallback); curl_easy_setopt(curl.handler, CURLOPT_WRITEDATA, (void *)&CurlReadBuffer); curl_easy_setopt(curl.handler, CURLOPT_USERAGENT, "libcrp/0.1"); From 5d8bfac11b4f8ce875b13dfe1c93d0aba6418405 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 27 Jul 2021 17:06:24 +0000 Subject: [PATCH 111/258] Avoid too low CR for new or updated debt positions --- libraries/chain/market_evaluator.cpp | 12 +++++++++++- tests/tests/force_settle_match_tests.cpp | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 007a411739..bdce36ff2b 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -327,11 +327,21 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // then we must check for margin calls and other issues if( !_bitasset_data->is_prediction_market ) { + // After hf core-2481, we do not allow new position's CR to be <= ~max_short_squeeze_price, because + // * if there is no force settlement order, it would trigger a blackswan event instantly, + // * if there is a force settlement order, they will match at the call order's CR, but it is not fair for the + // force settlement order. + if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) + { + FC_ASSERT( call_obj->collateralization() > ~( _bitasset_data->current_feed.max_short_squeeze_price() ), + "Could not create a debt position which would trigger a blackswan event instantly" ); + } // check to see if the order needs to be margin called now, but don't allow black swans and require there to be // limit orders available that could be used to fill the order. // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork, // the first call order may be unable to be updated if the second one is undercollateralized. - if( d.check_call_orders( *_debt_asset, false, false, _bitasset_data ) ) // don't allow black swan, not for new limit order + if( d.check_call_orders( *_debt_asset, false, false, _bitasset_data ) ) // don't allow black swan, + // not for new limit order { call_obj = d.find(call_order_id); // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 69d76242c4..4df32e3686 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -640,6 +640,21 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // Can not create a new call order that is partially called instantly BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10, usd_id), asset(160), 1700), fc::exception ); + idump( (settle_id(db))(get_balance(seller, core)) ); + + // Can not create a new call order that is undercollateralized + BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10, usd_id), asset(10) ), fc::exception ); + + idump( (settle_id(db))(get_balance(seller, core)) ); + + // Can not reduce CR of a call order to make it undercollateralized + BOOST_CHECK_THROW( borrow( borrower3_id(db), asset(0, usd_id), asset(-24000) ), fc::exception ); + + idump( (settle_id(db))(get_balance(seller, core)) ); + + // Can not create a new call order that would trigger a black swan event + BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10000, usd_id), asset(10000) ), fc::exception ); + // generate a block generate_block(); From 97a775252cff74dabeb3e4da79888b4d3b197114 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 6 Aug 2021 22:00:23 +0000 Subject: [PATCH 112/258] Add alternative bad debt settlement methods --- libraries/chain/asset_evaluator.cpp | 12 +++++++++ libraries/chain/asset_object.cpp | 2 ++ libraries/chain/hardfork.d/CORE_2467.hf | 6 +++++ .../include/graphene/chain/asset_object.hpp | 20 ++++++++++----- .../chain/include/graphene/chain/config.hpp | 2 +- .../include/graphene/chain/market_object.hpp | 5 ++-- libraries/chain/market_object.cpp | 1 + libraries/chain/proposal_evaluator.cpp | 4 +++ .../include/graphene/protocol/asset_ops.hpp | 25 +++++++++++++++++++ 9 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_2467.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index e71ae325fd..60c59e787c 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -144,6 +144,14 @@ namespace detail { "Collateral-denominated fees are not yet active and therefore cannot be claimed." ); } + void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options) + { + // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: + FC_ASSERT( !options.extensions.value.bad_debt_settlement_method.valid() + || HARDFORK_CORE_2467_PASSED(next_maint_time), + "A BitAsset's bad debt settlement method cannot be set before Hardfork core-2467" ); + } + } // graphene::chain::detail void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) @@ -151,6 +159,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o const database& d = db(); const time_point_sec now = d.head_block_time(); + const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; // Hardfork Checks: detail::check_asset_options_hf_1774(now, op.common_options); @@ -161,6 +170,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o detail::check_bitasset_options_hf_bsip74( now, *op.bitasset_opts ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip77( now, *op.bitasset_opts ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip87( now, *op.bitasset_opts ); // HF_REMOVABLE + detail::check_bitasset_opts_hf_core2467( next_maint_time, *op.bitasset_opts ); // HF_REMOVABLE } // TODO move as many validations as possible to validate() if not triggered before hardfork @@ -628,12 +638,14 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita { try { const database& d = db(); const time_point_sec now = d.head_block_time(); + const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; // Hardfork Checks: detail::check_bitasset_options_hf_bsip_48_75( now, op.new_options ); detail::check_bitasset_options_hf_bsip74( now, op.new_options ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip77( now, op.new_options ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip87( now, op.new_options ); // HF_REMOVABLE + detail::check_bitasset_opts_hf_core2467( next_maint_time, op.new_options ); // HF_REMOVABLE const asset_object& asset_obj = op.asset_to_update(d); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index e85a951a4e..8fe311cecc 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -232,6 +232,8 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (gr (is_prediction_market) (settlement_price) (settlement_fund) + (individual_settlement_debt) + (individual_settlement_fund) (asset_cer_updated) (feed_cer_updated) ) diff --git a/libraries/chain/hardfork.d/CORE_2467.hf b/libraries/chain/hardfork.d/CORE_2467.hf new file mode 100644 index 0000000000..8d575bb5d0 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2467.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2467 Alternative bad debt settlement methods +#ifndef HARDFORK_CORE_2467_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2467_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2467_PASSED(now) (now > HARDFORK_CORE_2467_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index eee7def65f..8a670dcb2c 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -299,18 +299,26 @@ namespace graphene { namespace chain { bool has_settlement()const { return !settlement_price.is_null(); } /** - * In the event of a black swan, the swan price is saved in the settlement price, and all margin positions - * are settled at the same price with the siezed collateral being moved into the settlement fund. From this - * point on no further updates to the asset are permitted (no feeds, etc) and forced settlement occurs - * immediately when requested, using the settlement price and fund. + * In the event of global settlement, all margin positions + * are settled with the siezed collateral being moved into the settlement fund. From this + * point on forced settlement occurs immediately when requested, using the settlement price and fund. */ ///@{ - /// Price at which force settlements of a black swanned asset will occur + /// Price at which force settlements of a globally settled asset will occur price settlement_price; - /// Amount of collateral which is available for force settlement + /// Amount of collateral which is available for force settlement due to global settlement share_type settlement_fund; ///@} + /// In the event of individual settlements, debt and collateral of the margin positions which got settled + /// are moved here. + ///@{ + /// Amount of debt due to individual settlements + share_type individual_settlement_debt; + /// Amount of collateral which is available for force settlement due to individual settlements + share_type individual_settlement_fund; + ///@} + /// Track whether core_exchange_rate in corresponding asset_object has updated bool asset_cer_updated = false; diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index dc0c0a2351..99b7da3599 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -32,7 +32,7 @@ #define GRAPHENE_MAX_NESTED_OBJECTS (200) -const std::string GRAPHENE_CURRENT_DB_VERSION = "20210713"; +const std::string GRAPHENE_CURRENT_DB_VERSION = "20210806"; #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index d2ce79df0e..62876889f6 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -34,12 +34,12 @@ namespace graphene { namespace chain { using namespace graphene::db; /** - * @brief an offer to sell a amount of a asset at a specified exchange rate by a certain time + * @brief an offer to sell an amount of an asset at a specified exchange rate by a certain time * @ingroup object * @ingroup protocol * @ingroup market * - * This limit_order_objects are indexed by @ref expiration and is automatically deleted on the first block after expiration. + * The objects are indexed by @ref expiration and are automatically deleted on the first block after expiration. */ class limit_order_object : public abstract_object { @@ -53,6 +53,7 @@ class limit_order_object : public abstract_object price sell_price; share_type deferred_fee; ///< fee converted to CORE asset deferred_paid_fee; ///< originally paid fee + bool is_settled_debt = false; ///< Whether this order is a bad-debt settlement fund pair get_market()const { diff --git a/libraries/chain/market_object.cpp b/libraries/chain/market_object.cpp index 99d9110fe2..42ec31d5d0 100644 --- a/libraries/chain/market_object.cpp +++ b/libraries/chain/market_object.cpp @@ -309,6 +309,7 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::limit_order_object, (graphene::db::object), (expiration)(seller)(for_sale)(sell_price)(deferred_fee)(deferred_paid_fee) + (is_settled_debt) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::call_order_object, (graphene::db::object), diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 1b23666dfc..b4a537e32d 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -50,6 +50,8 @@ namespace detail { void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op); // HF_REMOVABLE + + void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options); } struct proposal_operation_hardfork_visitor @@ -74,6 +76,7 @@ struct proposal_operation_hardfork_visitor detail::check_bitasset_options_hf_bsip74( block_time, *v.bitasset_opts ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip77( block_time, *v.bitasset_opts ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip87( block_time, *v.bitasset_opts ); // HF_REMOVABLE + detail::check_bitasset_opts_hf_core2467( next_maintenance_time, *v.bitasset_opts ); // HF_REMOVABLE } // TODO move as many validations as possible to validate() if not triggered before hardfork @@ -103,6 +106,7 @@ struct proposal_operation_hardfork_visitor detail::check_bitasset_options_hf_bsip74( block_time, v.new_options ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip77( block_time, v.new_options ); // HF_REMOVABLE detail::check_bitasset_options_hf_bsip87( block_time, v.new_options ); // HF_REMOVABLE + detail::check_bitasset_opts_hf_core2467( next_maintenance_time, v.new_options ); // HF_REMOVABLE } void operator()(const graphene::chain::asset_claim_fees_operation &v) const { diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 386dd73917..a7e535ad1f 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -108,6 +108,28 @@ namespace graphene { namespace protocol { */ struct bitasset_options { + /// Defines what will happen when bad debt appears + enum class bad_debt_settlement_type + { + /// All debt positions are closed, all or some collateral is moved to a global-settlement fund. + /// Debt asset holders can claim collateral via force-settlement. + /// It is not allowed to create new debt positions when the fund is not empty. + global_settlement = 0, + /// No debt position is closed, and the derived settlement price is dynamically capped at the collateral + /// ratio of the debt position with the least collateral ratio so that all debt positions are able to pay + /// off their debt when being margin called or force-settled. + /// Also known as "Global Settlement Protection". + no_settlement = 1, + /// Only the undercollateralized debt positions are closed and their collateral is moved to a fund which + /// can be claimed via force-settlement. The derived settlement price is capped at the fund's collateral + /// ratio so that remaining debt positions will not be margin called or force-settled at a worse price. + individual_settlement_to_fund = 2, + /// Only the undercollateralized debt positions are closed and their collateral is moved to a limit order + /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining + /// debt positions could be margin called at a worse price. + individual_settlement_to_order = 3 + }; + struct ext { /// After BSIP77, when creating a new debt position or updating an existing position, @@ -120,6 +142,8 @@ namespace graphene { namespace protocol { fc::optional maximum_short_squeeze_ratio; // BSIP-75 fc::optional margin_call_fee_ratio; // BSIP 74 fc::optional force_settle_fee_percent; // BSIP-87 + // https://github.com/bitshares/bitshares-core/issues/2467 + fc::optional bad_debt_settlement_method; }; /// Time before a price feed expires @@ -601,6 +625,7 @@ FC_REFLECT( graphene::protocol::bitasset_options::ext, (maximum_short_squeeze_ratio) (margin_call_fee_ratio) (force_settle_fee_percent) + (bad_debt_settlement_method) ) FC_REFLECT( graphene::protocol::bitasset_options, From b177cbcbcf61df9559ac328f2efd3a36ea674095 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 7 Aug 2021 17:04:57 +0000 Subject: [PATCH 113/258] Add disable_bdsm_update bit in issuer permission --- libraries/chain/asset_evaluator.cpp | 22 ++++++++++++++++--- .../include/graphene/chain/asset_object.hpp | 2 ++ libraries/chain/proposal_evaluator.cpp | 3 +++ libraries/protocol/asset_ops.cpp | 2 ++ .../include/graphene/protocol/types.hpp | 13 +++++++---- tests/common/database_fixture.hpp | 1 + tests/tests/bsip48_75_tests.cpp | 21 +++++++++++++++--- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 60c59e787c..7e6c3675f5 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -144,12 +144,25 @@ namespace detail { "Collateral-denominated fees are not yet active and therefore cannot be claimed." ); } + void check_asset_options_hf_core2467(const fc::time_point_sec& next_maint_time, const asset_options& options) + { + // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: + if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) ) + { + // new issuer permissions should not be set until activation of the hardfork + FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_bdsm_update), + "New asset issuer permission bits should not be set before Hardfork core-2467" ); + } + } + void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options) { // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: - FC_ASSERT( !options.extensions.value.bad_debt_settlement_method.valid() - || HARDFORK_CORE_2467_PASSED(next_maint_time), - "A BitAsset's bad debt settlement method cannot be set before Hardfork core-2467" ); + if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) ) + { + FC_ASSERT( !options.extensions.value.bad_debt_settlement_method.valid(), + "A BitAsset's bad debt settlement method cannot be set before Hardfork core-2467" ); + } } } // graphene::chain::detail @@ -165,6 +178,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o detail::check_asset_options_hf_1774(now, op.common_options); detail::check_asset_options_hf_bsip_48_75(now, op.common_options); detail::check_asset_options_hf_bsip81(now, op.common_options); + detail::check_asset_options_hf_core2467( next_maint_time, op.common_options ); // HF_REMOVABLE if( op.bitasset_opts ) { detail::check_bitasset_options_hf_bsip_48_75( now, *op.bitasset_opts ); detail::check_bitasset_options_hf_bsip74( now, *op.bitasset_opts ); // HF_REMOVABLE @@ -409,11 +423,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) { try { const database& d = db(); const time_point_sec now = d.head_block_time(); + const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; // Hardfork Checks: detail::check_asset_options_hf_1774(now, o.new_options); detail::check_asset_options_hf_bsip_48_75(now, o.new_options); detail::check_asset_options_hf_bsip81(now, o.new_options); + detail::check_asset_options_hf_core2467( next_maint_time, o.new_options ); // HF_REMOVABLE detail::check_asset_update_extensions_hf_bsip_48_75( now, o.extensions.value ); bool hf_bsip_48_75_passed = ( HARDFORK_BSIP_48_75_PASSED( now ) ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 8a670dcb2c..9bd265fb1f 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -111,6 +111,8 @@ namespace graphene { namespace chain { bool can_owner_update_icr()const { return !(options.issuer_permissions & disable_icr_update); } /// @return true if the asset owner can update MSSR directly bool can_owner_update_mssr()const { return !(options.issuer_permissions & disable_mssr_update); } + /// @return true if the asset owner can change bad debt settlement method + bool can_owner_update_bdsm()const { return !(options.issuer_permissions & disable_bdsm_update); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index b4a537e32d..da4e959e63 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -51,6 +51,7 @@ namespace detail { void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op); // HF_REMOVABLE + void check_asset_options_hf_core2467(const fc::time_point_sec& next_maint_time, const asset_options& options); void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options); } @@ -71,6 +72,7 @@ struct proposal_operation_hardfork_visitor detail::check_asset_options_hf_1774(block_time, v.common_options); detail::check_asset_options_hf_bsip_48_75(block_time, v.common_options); detail::check_asset_options_hf_bsip81(block_time, v.common_options); + detail::check_asset_options_hf_core2467( next_maintenance_time, v.common_options ); // HF_REMOVABLE if( v.bitasset_opts.valid() ) { detail::check_bitasset_options_hf_bsip_48_75( block_time, *v.bitasset_opts ); detail::check_bitasset_options_hf_bsip74( block_time, *v.bitasset_opts ); // HF_REMOVABLE @@ -90,6 +92,7 @@ struct proposal_operation_hardfork_visitor detail::check_asset_options_hf_1774(block_time, v.new_options); detail::check_asset_options_hf_bsip_48_75(block_time, v.new_options); detail::check_asset_options_hf_bsip81(block_time, v.new_options); + detail::check_asset_options_hf_core2467( next_maintenance_time, v.new_options ); // HF_REMOVABLE detail::check_asset_update_extensions_hf_bsip_48_75( block_time, v.extensions.value ); diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 5b719efdf6..5dc5f04323 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -309,6 +309,8 @@ void asset_options::validate_flags( bool is_market_issued )const "Can not set disable_icr_update flag, it is for issuer permission only" ); FC_ASSERT( !(flags & disable_mssr_update), "Can not set disable_mssr_update flag, it is for issuer permission only" ); + FC_ASSERT( !(flags & disable_bdsm_update), + "Can not set disable_mssr_update flag, it is for issuer permission only" ); if( !is_market_issued ) { FC_ASSERT( !(flags & ~UIA_ASSET_ISSUER_PERMISSION_MASK), diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 1cbbd25285..b98c0eccdf 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -179,7 +179,8 @@ enum asset_issuer_permission_flags { ///@{ disable_mcr_update = 0x800, ///< the bitasset owner can not update MCR, permisison only disable_icr_update = 0x1000, ///< the bitasset owner can not update ICR, permisison only - disable_mssr_update = 0x2000 ///< the bitasset owner can not update MSSR, permisison only + disable_mssr_update = 0x2000, ///< the bitasset owner can not update MSSR, permisison only + disable_bdsm_update = 0x4000 ///< the bitasset owner can not update BDSM, permission only ///@} ///@} }; @@ -199,7 +200,8 @@ const static uint16_t ASSET_ISSUER_PERMISSION_MASK = | disable_new_supply | disable_mcr_update | disable_icr_update - | disable_mssr_update; + | disable_mssr_update + | disable_bdsm_update; // The "enable" bits for non-UIA assets const static uint16_t ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK = charge_market_fee @@ -217,7 +219,8 @@ const static uint16_t ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK = | disable_new_supply | disable_mcr_update | disable_icr_update - | disable_mssr_update; + | disable_mssr_update + | disable_bdsm_update; // The bits that can be used in asset issuer permissions for UIA assets const static uint16_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee @@ -242,7 +245,8 @@ const static uint16_t PERMISSION_ONLY_MASK = global_settle | disable_mcr_update | disable_icr_update - | disable_mssr_update; + | disable_mssr_update + | disable_bdsm_update; // The bits that can be used in flags for non-UIA assets const static uint16_t VALID_FLAGS_MASK = ASSET_ISSUER_PERMISSION_MASK & ~PERMISSION_ONLY_MASK; // the bits that can be used in flags for UIA assets @@ -360,6 +364,7 @@ FC_REFLECT_ENUM(graphene::protocol::asset_issuer_permission_flags, (disable_mcr_update) (disable_icr_update) (disable_mssr_update) + (disable_bdsm_update) ) namespace fc { namespace raw { diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 45c1c0dc58..445520d913 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -219,6 +219,7 @@ struct database_fixture_base { bool skip_key_index_test = false; uint32_t anon_acct_count; bool hf1270 = false; + bool hf2467 = false; bool hf2481 = false; bool bsip77 = false; diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 8fda0a6d87..6721fd0837 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK & ~disable_bdsm_update; uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set @@ -992,7 +992,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK & ~disable_bdsm_update; uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set @@ -1062,6 +1062,8 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // advance to bsip48/75 hard fork generate_blocks( HARDFORK_BSIP_48_75_TIME ); + if( hf2467 ) + generate_blocks( HARDFORK_CORE_2467_TIME ); set_expiration( db, trx ); // take a look at flags of UIA @@ -1071,6 +1073,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) auop.new_options = samcoin_id(db).options; for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { + idump( (bit) ); auop.new_options.flags = UIA_VALID_FLAGS_MASK | bit; if( auop.new_options.flags == UIA_VALID_FLAGS_MASK ) continue; @@ -1102,6 +1105,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) auop2.new_options = sambit_id(db).options; for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { + idump( (bit) ); auop2.new_options.flags = valid_bitflag | bit; if( auop2.new_options.flags == valid_bitflag ) continue; @@ -1155,7 +1159,8 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // Unable to create a new MPA with an unknown bit in flags acop2.symbol = "NEWSAMBIT"; // With all possible bits in permissions set to 1 - acop2.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_MASK; + acop2.common_options.issuer_permissions = hf2467 ? ASSET_ISSUER_PERMISSION_MASK + : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update ); for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { acop2.common_options.flags = valid_bitflag | bit; @@ -1181,6 +1186,10 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_CHECK( !newsambit_id(db).can_owner_update_icr() ); BOOST_CHECK( !newsambit_id(db).can_owner_update_mcr() ); BOOST_CHECK( !newsambit_id(db).can_owner_update_mssr() ); + if( hf2467 ) + BOOST_CHECK( !newsambit_id(db).can_owner_update_bdsm() ); + else + BOOST_CHECK( newsambit_id(db).can_owner_update_bdsm() ); // Able to propose too propose( acop2 ); @@ -1193,6 +1202,12 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) } } +BOOST_AUTO_TEST_CASE( invalid_flags_in_asset_after_hf2467 ) +{ + hf2467 = true; + INVOKE( invalid_flags_in_asset ); +} + BOOST_AUTO_TEST_CASE( update_asset_precision ) { try { From 29925a89d4131d53a050258c2487306f5c79a4a2 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 8 Aug 2021 09:57:58 +0000 Subject: [PATCH 114/258] Refactor asset_object::update_median_feeds(...) --- libraries/chain/asset_evaluator.cpp | 8 +-- libraries/chain/asset_object.cpp | 42 +++++++-------- libraries/chain/db_maint.cpp | 26 +++------- libraries/chain/db_update.cpp | 51 ++++++++++--------- .../include/graphene/chain/asset_object.hpp | 25 +++++---- .../chain/include/graphene/chain/database.hpp | 2 + 6 files changed, 76 insertions(+), 78 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 7e6c3675f5..3966d3dacd 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -815,7 +815,7 @@ static bool update_bitasset_object_options( if( op.new_options.minimum_feeds != bdo.options.minimum_feeds ) should_update_feeds = true; - // after hardfork core-868-890, we also should call update_median_feeds if the feed_lifetime_sec changed + // after hardfork core-868-890, we also should call update_asset_current_feed if the feed_lifetime_sec changed if( after_hf_core_868_890 && op.new_options.feed_lifetime_sec != bdo.options.feed_lifetime_sec ) { @@ -895,7 +895,7 @@ static bool update_bitasset_object_options( if( should_update_feeds ) { const auto old_feed = bdo.current_feed; - bdo.update_median_feeds( db.head_block_time(), next_maint_time ); + db.update_asset_current_feed( asset_to_update, bdo ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); @@ -984,8 +984,8 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f { a.feeds[acc]; } - a.update_median_feeds( head_time, next_maint_time ); }); + d.update_asset_current_feed( *asset_to_update, bitasset_to_update ); // Process margin calls, allow black swan, not for a new limit order d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); @@ -1212,8 +1212,8 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope d.modify( bad , [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed, o.extensions.value.initial_collateral_ratio ) ); - a.update_median_feeds( head_time, next_maint_time ); }); + d.update_asset_current_feed( base, bad ); if( !old_feed.margin_call_params_equal(bad.current_feed) ) { diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 8fe311cecc..669381f633 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -49,25 +49,25 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin { bool after_core_hardfork_1270 = ( next_maintenance_time > HARDFORK_CORE_1270_TIME ); // call price caching issue current_feed_publication_time = current_time; - vector> current_feeds; + vector> effective_feeds; // find feeds that were alive at current_time for( const pair>& f : feeds ) { if( (current_time - f.second.first).to_seconds() < options.feed_lifetime_sec && f.second.first != time_point_sec() ) { - current_feeds.emplace_back(f.second.second); + effective_feeds.emplace_back(f.second.second); current_feed_publication_time = std::min(current_feed_publication_time, f.second.first); } } // If there are no valid feeds, or the number available is less than the minimum to calculate a median... - if( current_feeds.size() < options.minimum_feeds ) + if( effective_feeds.size() < options.minimum_feeds ) { //... don't calculate a median, and set a null feed feed_cer_updated = false; // new median cer is null, won't update asset_object anyway, set to false for better performance current_feed_publication_time = current_time; - current_feed = price_feed_with_icr(); + median_feed = price_feed_with_icr(); if( after_core_hardfork_1270 ) { // update data derived from MCR, ICR and etc @@ -75,21 +75,22 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin } return; } - if( current_feeds.size() == 1 ) + + if( effective_feeds.size() == 1 ) { - if( current_feed.core_exchange_rate != current_feeds.front().get().core_exchange_rate ) + if( median_feed.core_exchange_rate != effective_feeds.front().get().core_exchange_rate ) feed_cer_updated = true; - current_feed = current_feeds.front(); + median_feed = effective_feeds.front(); // Note: perhaps can defer updating current_maintenance_collateralization for better performance if( after_core_hardfork_1270 ) { const auto& exts = options.extensions.value; if( exts.maintenance_collateral_ratio.valid() ) - current_feed.maintenance_collateral_ratio = *exts.maintenance_collateral_ratio; + median_feed.maintenance_collateral_ratio = *exts.maintenance_collateral_ratio; if( exts.maximum_short_squeeze_ratio.valid() ) - current_feed.maximum_short_squeeze_ratio = *exts.maximum_short_squeeze_ratio; + median_feed.maximum_short_squeeze_ratio = *exts.maximum_short_squeeze_ratio; if( exts.initial_collateral_ratio.valid() ) - current_feed.initial_collateral_ratio = *exts.initial_collateral_ratio; + median_feed.initial_collateral_ratio = *exts.initial_collateral_ratio; // update data derived from MCR, ICR and etc refresh_cache(); } @@ -97,18 +98,18 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin } // *** Begin Median Calculations *** - price_feed_with_icr median_feed; - const auto median_itr = current_feeds.begin() + current_feeds.size() / 2; + price_feed_with_icr tmp_median_feed; + const auto median_itr = effective_feeds.begin() + effective_feeds.size() / 2; #define CALCULATE_MEDIAN_VALUE(r, data, field_name) \ - std::nth_element( current_feeds.begin(), median_itr, current_feeds.end(), \ + std::nth_element( effective_feeds.begin(), median_itr, effective_feeds.end(), \ [](const price_feed_with_icr& a, const price_feed_with_icr& b) { \ return a.field_name < b.field_name; \ }); \ - median_feed.field_name = median_itr->get().field_name; + tmp_median_feed.field_name = median_itr->get().field_name; #define CHECK_AND_CALCULATE_MEDIAN_VALUE(r, data, field_name) \ if( options.extensions.value.field_name.valid() ) { \ - median_feed.field_name = *options.extensions.value.field_name; \ + tmp_median_feed.field_name = *options.extensions.value.field_name; \ } else { \ CALCULATE_MEDIAN_VALUE(r, data, field_name); \ } @@ -120,9 +121,9 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin #undef CALCULATE_MEDIAN_VALUE // *** End Median Calculations *** - if( current_feed.core_exchange_rate != median_feed.core_exchange_rate ) + if( median_feed.core_exchange_rate != tmp_median_feed.core_exchange_rate ) feed_cer_updated = true; - current_feed = median_feed; + median_feed = tmp_median_feed; // Note: perhaps can defer updating current_maintenance_collateralization for better performance if( after_core_hardfork_1270 ) { @@ -133,9 +134,9 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin void asset_bitasset_data_object::refresh_cache() { - current_maintenance_collateralization = current_feed.maintenance_collateralization(); - if( current_feed.initial_collateral_ratio > current_feed.maintenance_collateral_ratio ) // if ICR is above MCR - current_initial_collateralization = current_feed.calculate_initial_collateralization(); + current_maintenance_collateralization = median_feed.maintenance_collateralization(); + if( median_feed.initial_collateral_ratio > median_feed.maintenance_collateral_ratio ) // if ICR is above MCR + current_initial_collateralization = median_feed.calculate_initial_collateralization(); else // if ICR is not above MCR current_initial_collateralization = current_maintenance_collateralization; } @@ -223,6 +224,7 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_dynamic_data_object, (gra FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (graphene::db::object), (asset_id) (feeds) + (median_feed) (current_feed) (current_feed_publication_time) (current_maintenance_collateralization) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 96464c4476..7ad359c50d 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -991,19 +991,11 @@ void process_hf_2103( database& db ) } } -void update_median_feeds(database& db) +static void update_asset_current_feeds(database& db) { - time_point_sec head_time = db.head_block_time(); - time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; - - const auto update_bitasset = [head_time, next_maint_time]( asset_bitasset_data_object &o ) + for( const auto& bitasset : db.get_index_type().indices() ) { - o.update_median_feeds( head_time, next_maint_time ); - }; - - for( const auto& d : db.get_index_type().indices() ) - { - db.modify( d, update_bitasset ); + db.update_asset_current_feed( bitasset.asset_id(db), bitasset ); } } @@ -1027,8 +1019,6 @@ void update_median_feeds(database& db) // feeds were found. void process_hf_868_890( database& db, bool skip_check_call_orders ) { - const auto next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; - const auto head_time = db.head_block_time(); // for each market issued asset const auto& asset_idx = db.get_index_type().indices().get(); for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_idx.end(); ++asset_itr ) @@ -1074,11 +1064,9 @@ void process_hf_868_890( database& db, bool skip_check_call_orders ) } // end loop of each feed // always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890 - db.modify( bitasset_data, [head_time,next_maint_time]( asset_bitasset_data_object &obj ) { - obj.update_median_feeds( head_time, next_maint_time ); - // NOTE: Normally we should call check_call_orders() after called update_median_feeds(), but for - // mainnet actually check_call_orders() would do nothing, so we skipped it for better performance. - }); + db.update_asset_current_feed( current_asset, bitasset_data ); + // NOTE: Normally we should call check_call_orders() after called update_asset_current_feed(), but for + // mainnet actually check_call_orders() would do nothing, so we skipped it for better performance. } // for each market issued asset } @@ -1497,7 +1485,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if( to_update_and_match_call_orders_for_hf_1270 ) { update_call_orders_hf_1270(*this); - update_median_feeds(*this); + update_asset_current_feeds(*this); match_call_orders(*this); } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 9bd8aeb4fd..97137a4891 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -323,6 +323,18 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s return false; } +void database::update_asset_current_feed( const asset_object& mia, const asset_bitasset_data_object& bitasset ) +{ + modify( bitasset, [this,&mia]( asset_bitasset_data_object& abdo ) { + const auto& head_time = head_block_time(); + const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; + //bool after_core_hardfork_2467 = HARDFORK_CORE_2467_PASSED( maint_time ); // bad debt settlement methods + abdo.update_median_feeds( head_time, maint_time ); + // TODO add logic related to bdsm + abdo.current_feed = abdo.median_feed; + } ); +} + void database::clear_expired_orders() { try { //Cancel expired limit orders @@ -549,7 +561,6 @@ void database::clear_expired_force_settlements() void database::update_expired_feeds() { const auto head_time = head_block_time(); - const auto next_maint_time = get_dynamic_global_properties().next_maintenance_time; bool after_hardfork_615 = ( head_time >= HARDFORK_615_TIME ); const auto& idx = get_index_type().indices().get(); @@ -558,40 +569,32 @@ void database::update_expired_feeds() { const asset_bitasset_data_object& b = *itr; ++itr; // not always process begin() because old code skipped updating some assets before hf 615 - bool update_cer = false; // for better performance, to only update bitasset once, also check CER in this function - const asset_object* asset_ptr = nullptr; // update feeds, check margin calls if( after_hardfork_615 || b.feed_is_expired_before_hardfork_615( head_time ) ) { auto old_median_feed = b.current_feed; - modify( b, [head_time,next_maint_time,&update_cer]( asset_bitasset_data_object& abdo ) - { - abdo.update_median_feeds( head_time, next_maint_time ); - if( abdo.need_to_update_cer() ) - { - update_cer = true; - abdo.asset_cer_updated = false; - abdo.feed_cer_updated = false; - } - }); + const asset_object& asset_obj = b.asset_id( *this ); + update_asset_current_feed( asset_obj, b ); if( !b.current_feed.settlement_price.is_null() && !b.current_feed.margin_call_params_equal( old_median_feed ) ) { - asset_ptr = &b.asset_id( *this ); - check_call_orders( *asset_ptr, true, false, &b, true ); + check_call_orders( asset_obj, true, false, &b, true ); } - } - // update CER - if( update_cer ) - { - if( !asset_ptr ) - asset_ptr = &b.asset_id( *this ); - if( asset_ptr->options.core_exchange_rate != b.current_feed.core_exchange_rate ) + // update CER + if( b.need_to_update_cer() ) { - modify( *asset_ptr, [&b]( asset_object& ao ) + modify( b, []( asset_bitasset_data_object& abdo ) { - ao.options.core_exchange_rate = b.current_feed.core_exchange_rate; + abdo.asset_cer_updated = false; + abdo.feed_cer_updated = false; }); + if( asset_obj.options.core_exchange_rate != b.current_feed.core_exchange_rate ) + { + modify( asset_obj, [&b]( asset_object& ao ) + { + ao.options.core_exchange_rate = b.current_feed.core_exchange_rate; + }); + } } } } // for each asset whose feed is expired diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 9bd265fb1f..faeb21ef75 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -265,12 +265,13 @@ namespace graphene { namespace chain { /// The tunable options for BitAssets are stored in this field. bitasset_options options; - /// Feeds published for this asset. If issuer is not committee, the keys in this map are the feed publishing - /// accounts; otherwise, the feed publishers are the currently active committee_members and witnesses and this map - /// should be treated as an implementation detail. The timestamp on each feed is the time it was published. + /// Feeds published for this asset. + /// The keys in this map are the feed publishing accounts. + /// The timestamp on each feed is the time it was published. flat_map> feeds; - /// This is the currently active price feed, calculated as the median of values from the currently active - /// feeds. + /// This is the median of values from the currently active feeds. + price_feed_with_icr median_feed; + /// This is the currently active price feed, calculated from @ref median_feed and other parameters. price_feed_with_icr current_feed; /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; @@ -285,10 +286,6 @@ namespace graphene { namespace chain { /// should be kept consistent. price current_initial_collateralization; - /// Derive @ref current_maintenance_collateralization and @ref current_initial_collateralization from - /// other member variables. - void refresh_cache(); - /// True if this asset implements a @ref prediction_market bool is_prediction_market = false; @@ -352,13 +349,19 @@ namespace graphene { namespace chain { * * This calculates the median feed from @ref feeds, feed_lifetime_sec * in @ref options, and the given parameters. - * It may update the current_feed_publication_time, current_feed and - * current_maintenance_collateralization member variables. + * It may update the @ref median_feed, @ref current_feed_publication_time and + * @ref current_maintenance_collateralization member variables. * * @param current_time the current time to use in the calculations * @param next_maintenance_time the next chain maintenance time + * + * @note Called by @ref database::update_asset_current_feed() which updates @ref current_feed afterwards. */ void update_median_feeds(time_point_sec current_time, time_point_sec next_maintenance_time); + private: + /// Derive @ref current_maintenance_collateralization and @ref current_initial_collateralization from + /// other member variables. + void refresh_cache(); }; // key extractor for short backing asset diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index a082ffab7b..794be6a452 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -672,6 +672,8 @@ namespace graphene { namespace chain { //////////////////// db_update.cpp //////////////////// public: generic_operation_result process_tickets(); + /// Derive @ref asset_bitasset_data_object::current_feed from other data in the database + void update_asset_current_feed( const asset_object& mia, const asset_bitasset_data_object& bitasset ); private: void update_global_dynamic_data( const signed_block& b, const uint32_t missed_blocks ); void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block); From 013cea1d6650bb5c41aca81dff8aa93ed72b21f8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 8 Aug 2021 22:53:07 +0000 Subject: [PATCH 115/258] Partially implement BDSM::no_settlement --- libraries/chain/asset_evaluator.cpp | 8 +- libraries/chain/db_maint.cpp | 10 +-- libraries/chain/db_update.cpp | 76 ++++++++++++++++--- .../include/graphene/chain/asset_object.hpp | 20 +++-- .../chain/include/graphene/chain/database.hpp | 5 +- libraries/chain/market_evaluator.cpp | 7 +- libraries/protocol/asset_ops.cpp | 6 ++ .../include/graphene/protocol/asset_ops.hpp | 4 +- 8 files changed, 109 insertions(+), 27 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 3966d3dacd..4339ce92e0 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -815,7 +815,7 @@ static bool update_bitasset_object_options( if( op.new_options.minimum_feeds != bdo.options.minimum_feeds ) should_update_feeds = true; - // after hardfork core-868-890, we also should call update_asset_current_feed if the feed_lifetime_sec changed + // after hardfork core-868-890, we also should call update_bitasset_current_feed if the feed_lifetime_sec changed if( after_hf_core_868_890 && op.new_options.feed_lifetime_sec != bdo.options.feed_lifetime_sec ) { @@ -895,7 +895,7 @@ static bool update_bitasset_object_options( if( should_update_feeds ) { const auto old_feed = bdo.current_feed; - db.update_asset_current_feed( asset_to_update, bdo ); + db.update_bitasset_current_feed( bdo ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); @@ -985,7 +985,7 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f a.feeds[acc]; } }); - d.update_asset_current_feed( *asset_to_update, bitasset_to_update ); + d.update_bitasset_current_feed( bitasset_to_update ); // Process margin calls, allow black swan, not for a new limit order d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); @@ -1213,7 +1213,7 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed, o.extensions.value.initial_collateral_ratio ) ); }); - d.update_asset_current_feed( base, bad ); + d.update_bitasset_current_feed( bad ); if( !old_feed.margin_call_params_equal(bad.current_feed) ) { diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 7ad359c50d..89f1bb2082 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -991,11 +991,11 @@ void process_hf_2103( database& db ) } } -static void update_asset_current_feeds(database& db) +static void update_bitasset_current_feeds(database& db) { for( const auto& bitasset : db.get_index_type().indices() ) { - db.update_asset_current_feed( bitasset.asset_id(db), bitasset ); + db.update_bitasset_current_feed( bitasset ); } } @@ -1064,8 +1064,8 @@ void process_hf_868_890( database& db, bool skip_check_call_orders ) } // end loop of each feed // always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890 - db.update_asset_current_feed( current_asset, bitasset_data ); - // NOTE: Normally we should call check_call_orders() after called update_asset_current_feed(), but for + db.update_bitasset_current_feed( bitasset_data ); + // NOTE: Normally we should call check_call_orders() after called update_bitasset_current_feed(), but for // mainnet actually check_call_orders() would do nothing, so we skipped it for better performance. } // for each market issued asset @@ -1485,7 +1485,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if( to_update_and_match_call_orders_for_hf_1270 ) { update_call_orders_hf_1270(*this); - update_asset_current_feeds(*this); + update_bitasset_current_feeds(*this); match_call_orders(*this); } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 97137a4891..a814a4ab00 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -279,7 +279,8 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // * if there is no force settlement, we check here with margin call fee in consideration. auto least_collateral = call_ptr->collateralization(); - if( ~least_collateral >= highest ) + bool gs = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); + if( gs ) { wdump( (*call_ptr) ); elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" @@ -323,15 +324,70 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s return false; } -void database::update_asset_current_feed( const asset_object& mia, const asset_bitasset_data_object& bitasset ) +// Helper function to check whether we need to udpate current_feed.settlement_price. +static optional get_derived_current_feed_price( const database& db, + const asset_bitasset_data_object& bitasset ) { - modify( bitasset, [this,&mia]( asset_bitasset_data_object& abdo ) { - const auto& head_time = head_block_time(); - const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; - //bool after_core_hardfork_2467 = HARDFORK_CORE_2467_PASSED( maint_time ); // bad debt settlement methods - abdo.update_median_feeds( head_time, maint_time ); - // TODO add logic related to bdsm - abdo.current_feed = abdo.median_feed; + optional result; + // check for null first + if( bitasset.median_feed.settlement_price.is_null() ) + return result; + + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + if( bdsm_type::no_settlement == bdsm ) + { + const auto& call_collateral_index = db.get_index_type().indices().get(); + auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); + auto call_itr = call_collateral_index.lower_bound( call_min ); + if( call_itr != call_collateral_index.end() && call_itr->debt_type() == bitasset.asset_id ) + { + // GS if : call_itr->collateralization() < ~( _bitasset_data->median_feed.max_short_squeeze_price() ) + auto least_collateral = call_itr->collateralization(); + auto lowest_callable_price = (~least_collateral) * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, + bitasset.median_feed.maximum_short_squeeze_ratio ); + if( bitasset.median_feed.settlement_price < lowest_callable_price + && bitasset.current_feed.settlement_price != lowest_callable_price ) + result = lowest_callable_price ; + } + } + else if( bdsm_type::individual_settlement_to_fund == bdsm && bitasset.individual_settlement_debt > 0 ) + { + price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) + / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + // FIXME : deal with MSSR and/or MCFR + if( bitasset.median_feed.settlement_price < fund_price + && bitasset.current_feed.settlement_price != fund_price ) + result = fund_price; + } + return result; +} + +void database::update_bitasset_current_feed( const asset_bitasset_data_object& bitasset, bool skip_median_update ) +{ + // For better performance, if nothing to update, we return + optional new_current_feed_price; + if( skip_median_update ) + { + new_current_feed_price = get_derived_current_feed_price( *this, bitasset ); + if( !new_current_feed_price.valid() ) + return; + } + + // We need to update the database + modify( bitasset, [this, skip_median_update, &new_current_feed_price] + ( asset_bitasset_data_object& abdo ) + { + if( !skip_median_update ) + { + const auto& head_time = head_block_time(); + const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; + abdo.update_median_feeds( head_time, maint_time ); + abdo.current_feed = abdo.median_feed; + new_current_feed_price = get_derived_current_feed_price( *this, abdo ); + } + if( new_current_feed_price.valid() ) + abdo.current_feed.settlement_price = *new_current_feed_price; } ); } @@ -574,7 +630,7 @@ void database::update_expired_feeds() { auto old_median_feed = b.current_feed; const asset_object& asset_obj = b.asset_id( *this ); - update_asset_current_feed( asset_obj, b ); + update_bitasset_current_feed( b ); if( !b.current_feed.settlement_price.is_null() && !b.current_feed.margin_call_params_equal( old_median_feed ) ) { diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index faeb21ef75..1aff7937cd 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -294,7 +294,7 @@ namespace graphene { namespace chain { /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply share_type max_force_settlement_volume(share_type current_supply)const; - /** return true if there has been a black swan, false otherwise */ + /** return true if the bitasset has been globally settled, false otherwise */ bool has_settlement()const { return !settlement_price.is_null(); } /** @@ -309,8 +309,8 @@ namespace graphene { namespace chain { share_type settlement_fund; ///@} - /// In the event of individual settlements, debt and collateral of the margin positions which got settled - /// are moved here. + /// In the event of individual settlements to fund, debt and collateral of the margin positions which got + /// settled are moved here. ///@{ /// Amount of debt due to individual settlements share_type individual_settlement_debt; @@ -318,6 +318,15 @@ namespace graphene { namespace chain { share_type individual_settlement_fund; ///@} + /// Get the effective bad debt settlement method of this bitasset + bitasset_options::bad_debt_settlement_type get_bad_debt_settlement_method() const + { + using bdsm_type = bitasset_options::bad_debt_settlement_type; + if( !options.extensions.value.bad_debt_settlement_method.valid() ) + return bdsm_type::global_settlement; + return static_cast( *options.extensions.value.bad_debt_settlement_method ); + } + /// Track whether core_exchange_rate in corresponding asset_object has updated bool asset_cer_updated = false; @@ -349,13 +358,14 @@ namespace graphene { namespace chain { * * This calculates the median feed from @ref feeds, feed_lifetime_sec * in @ref options, and the given parameters. - * It may update the @ref median_feed, @ref current_feed_publication_time and + * It may update the @ref median_feed, @ref current_feed_publication_time, + * @ref current_initial_collateralization and * @ref current_maintenance_collateralization member variables. * * @param current_time the current time to use in the calculations * @param next_maintenance_time the next chain maintenance time * - * @note Called by @ref database::update_asset_current_feed() which updates @ref current_feed afterwards. + * @note Called by @ref database::update_bitasset_current_feed() which updates @ref current_feed afterwards. */ void update_median_feeds(time_point_sec current_time, time_point_sec next_maintenance_time); private: diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 794be6a452..f04d42a4f5 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -673,7 +673,10 @@ namespace graphene { namespace chain { public: generic_operation_result process_tickets(); /// Derive @ref asset_bitasset_data_object::current_feed from other data in the database - void update_asset_current_feed( const asset_object& mia, const asset_bitasset_data_object& bitasset ); + /// @param bitasset The bitasset object + /// @param skip_median_update Whether to skip updating @ref asset_bitasset_data_object::median_feed + void update_bitasset_current_feed( const asset_bitasset_data_object& bitasset, + bool skip_median_update = false ); private: void update_global_dynamic_data( const signed_block& b, const uint32_t missed_blocks ); void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block); diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index bdce36ff2b..99dbedd63a 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -333,7 +333,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // force settlement order. if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) { - FC_ASSERT( call_obj->collateralization() > ~( _bitasset_data->current_feed.max_short_squeeze_price() ), + FC_ASSERT( call_obj->collateralization() >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), "Could not create a debt position which would trigger a blackswan event instantly" ); } // check to see if the order needs to be margin called now, but don't allow black swans and require there to be @@ -406,6 +406,11 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope ); } } + // Update current_feed if needed + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); + if( bdsm_type::no_settlement == bdsm ) + d.update_bitasset_current_feed( *_bitasset_data, true ); } return call_order_id; diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 5dc5f04323..648c71e715 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -258,6 +258,12 @@ void bitasset_options::validate() const if( extensions.value.force_settle_fee_percent.valid() ) FC_ASSERT( *extensions.value.force_settle_fee_percent <= GRAPHENE_100_PERCENT ); + if( extensions.value.bad_debt_settlement_method.valid() ) + { + auto bdsm_count = static_cast( bad_debt_settlement_type::BDSM_TYPE_COUNT ); + FC_ASSERT( *extensions.value.bad_debt_settlement_method < bdsm_count, + "bad_debt_settlement_method should be less than ${c}", ("c",bdsm_count) ); + } } void asset_options::validate()const diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index a7e535ad1f..00611c0d88 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -127,7 +127,9 @@ namespace graphene { namespace protocol { /// Only the undercollateralized debt positions are closed and their collateral is moved to a limit order /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining /// debt positions could be margin called at a worse price. - individual_settlement_to_order = 3 + individual_settlement_to_order = 3, + /// Total number of available bad debt settlement methods + BDSM_TYPE_COUNT = 4 }; struct ext From c0b3724761ded12655e003ff383ec424ff7a233a Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 10 Aug 2021 21:28:53 +0000 Subject: [PATCH 116/258] Implement BDSM::no_settlement --- libraries/chain/db_market.cpp | 49 ++++++++++------- libraries/chain/db_update.cpp | 54 ++++++++++++------- .../include/graphene/chain/asset_object.hpp | 18 +++++++ .../chain/include/graphene/chain/database.hpp | 13 ++++- libraries/chain/market_evaluator.cpp | 9 +++- libraries/protocol/asset.cpp | 28 ++++++---- .../include/graphene/protocol/asset.hpp | 11 +++- 7 files changed, 127 insertions(+), 55 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 32538f7d0e..229f1fd4df 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -533,8 +533,7 @@ bool database::apply_order(const limit_order_object& new_order_object) call_match_price = ~sell_abd->current_feed.max_short_squeeze_price_before_hf_1270(); call_pays_price = call_match_price; } else { - call_match_price = ~sell_abd->current_feed. - margin_call_order_price(sell_abd->options.extensions.value.margin_call_fee_ratio); + call_match_price = ~sell_abd->get_margin_call_order_price(); call_pays_price = ~sell_abd->current_feed.max_short_squeeze_price(); } if( ~new_order_object.sell_price <= call_match_price ) // If new limit order price is good enough to @@ -576,7 +575,7 @@ bool database::apply_order(const limit_order_object& new_order_object) sell_abd->current_feed.settlement_price, sell_abd->current_feed.maintenance_collateral_ratio, sell_abd->current_maintenance_collateralization, - call_pays_price); + *sell_abd, call_pays_price ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hf core-834), we no longer only check if the result is 2. @@ -605,7 +604,7 @@ bool database::apply_order(const limit_order_object& new_order_object) const auto match_result = match( new_order_object, *call_itr, call_match_price, sell_abd->current_feed.settlement_price, sell_abd->current_feed.maintenance_collateral_ratio, - optional() ); + optional(), *sell_abd ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hard fork core-834), @@ -654,8 +653,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Price at which margin calls sit on the books. // It is the MCOP, which may deviate from MSSP due to MCFR. - price call_match_price = bitasset.current_feed. - margin_call_order_price(bitasset.options.extensions.value.margin_call_fee_ratio); + price call_match_price = bitasset.get_margin_call_order_price(); // Price margin call actually relinquishes collateral at. Equals the MSSP and it may // differ from call_match_price if there is a Margin Call Fee. price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); @@ -685,7 +683,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Note: the call order should be able to pay at call_pays_price, // thus no need to pass in margin_call_pays_ratio - match( new_settlement, *call_itr, call_pays_price, max_debt_to_cover, call_match_price, true ); + match( new_settlement, *call_itr, call_pays_price, bitasset, max_debt_to_cover, call_match_price, true ); // Check whether the new order is gone finished = ( nullptr == find_object( new_obj_id ) ); @@ -789,6 +787,7 @@ database::match_result_type database::match( const limit_order_object& bid, cons const price& match_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization, + const asset_bitasset_data_object& bitasset, const price& call_pays_price ) { FC_ASSERT( bid.sell_asset_id() == ask.debt_type() ); @@ -842,6 +841,10 @@ database::match_result_type database::match( const limit_order_object& bid, cons bool taker_filled = fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false ); bool maker_filled = fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee ); + // Update current_feed after filled call order if needed + if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + update_bitasset_current_feed( bitasset, true ); + // Note: result can be none_filled when call order has target_collateral_ratio option set. match_result_type result = get_match_result( taker_filled, maker_filled ); return result; @@ -851,29 +854,32 @@ database::match_result_type database::match( const limit_order_object& bid, cons asset database::match( const force_settlement_object& settle, const call_order_object& call, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, bool is_margin_call, const ratio_type* margin_call_pays_ratio ) { - return match_impl( settle, call, match_price, max_settlement, fill_price, is_margin_call, true, + return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, is_margin_call, true, margin_call_pays_ratio ); } asset database::match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, const ratio_type* margin_call_pays_ratio ) { - return match_impl( settle, call, match_price, max_settlement, fill_price, true, false, + return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, true, false, margin_call_pays_ratio ); } asset database::match_impl( const force_settlement_object& settle, const call_order_object& call, const price& p_match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& p_fill_price, bool is_margin_call, @@ -1070,6 +1076,10 @@ asset database::match_impl( const force_settlement_object& settle, // do not pay force-settlement fee if the call is being margin called fill_settle_order( settle, settle_pays, settle_receives, fill_price, !settle_is_taker, !is_margin_call ); + // Update current_feed after filled call order if needed + if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + update_bitasset_current_feed( bitasset, true ); + if( cull_settle_order ) cancel_settle_order( settle ); @@ -1389,11 +1399,9 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // Stop when limit orders are selling too little USD for too much CORE. // Note that since BSIP74, margin calls offer somewhat less CORE per USD // if the issuer claims a Margin Call Fee. - auto min_price = ( before_core_hardfork_1270 ? + auto min_price = before_core_hardfork_1270 ? bitasset.current_feed.max_short_squeeze_price_before_hf_1270() - : bitasset.current_feed.margin_call_order_price( - bitasset.options.extensions.value.margin_call_fee_ratio ) - ); + : bitasset.get_margin_call_order_price(); // NOTE limit_price_index is sorted from greatest to least auto limit_itr = limit_price_index.lower_bound( max_price ); @@ -1462,8 +1470,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa price match_price = limit_order.sell_price; // There was a check `match_price.validate();` here, which is removed now because it always passes - price call_pays_price = match_price * bitasset.current_feed.margin_call_pays_ratio( - bitasset.options.extensions.value.margin_call_fee_ratio); + price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the // excess being kept by the asset issuer as a margin call fee. In what follows, we use // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, @@ -1600,6 +1607,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); + // Update current_feed after filled call order if needed + if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + update_bitasset_current_feed( bitasset, true ); + if( !before_core_hardfork_1270 ) call_collateral_itr = call_collateral_index.lower_bound( call_min ); else if( !before_core_hardfork_343 ) @@ -1650,15 +1661,13 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // Price at which margin calls sit on the books. // It is the MCOP, which may deviate from MSSP due to MCFR. // It is in debt/collateral . - price call_match_price = bitasset.current_feed. - margin_call_order_price(bitasset.options.extensions.value.margin_call_fee_ratio); + price call_match_price = bitasset.get_margin_call_order_price(); // Price margin call actually relinquishes collateral at. Equals the MSSP and it may // differ from call_match_price if there is a Margin Call Fee. // It is in debt/collateral . price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - auto margin_call_pays_ratio = bitasset.current_feed.margin_call_pays_ratio( - bitasset.options.extensions.value.margin_call_fee_ratio); + auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio(); bool margin_called = false; while( settle_itr != settle_end && call_itr != call_end ) @@ -1680,7 +1689,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // Note: if the call order's CR is too low, it is probably unable to fill at call_pays_price. // In this case, the call order pays at its CR, the settle order may receive less due to margin call fee. // It is processed inside the function. - auto result = match( call_order, settle_order, call_pays_price, max_debt_to_cover, call_match_price, + auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price, &margin_call_pays_ratio ); if( !margin_called && result.amount > 0 ) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index a814a4ab00..68be70bd6a 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -259,8 +259,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( after_core_hardfork_2481 ) { // due to margin call fee, we check with MCPP (margin call pays price) here - call_pays_price = call_pays_price * bitasset.current_feed.margin_call_pays_ratio( - bitasset.options.extensions.value.margin_call_fee_ratio ); + call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); } highest = std::max( call_pays_price, highest ); } @@ -331,7 +330,12 @@ static optional get_derived_current_feed_price( const database& db, optional result; // check for null first if( bitasset.median_feed.settlement_price.is_null() ) - return result; + { + if( bitasset.current_feed.settlement_price.is_null() ) + return result; + else + return bitasset.median_feed.settlement_price; + } using bdsm_type = bitasset_options::bad_debt_settlement_type; const auto bdsm = bitasset.get_bad_debt_settlement_method(); @@ -342,24 +346,31 @@ static optional get_derived_current_feed_price( const database& db, auto call_itr = call_collateral_index.lower_bound( call_min ); if( call_itr != call_collateral_index.end() && call_itr->debt_type() == bitasset.asset_id ) { - // GS if : call_itr->collateralization() < ~( _bitasset_data->median_feed.max_short_squeeze_price() ) + // GS if : call_itr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() ) auto least_collateral = call_itr->collateralization(); - auto lowest_callable_price = (~least_collateral) * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, - bitasset.median_feed.maximum_short_squeeze_ratio ); - if( bitasset.median_feed.settlement_price < lowest_callable_price - && bitasset.current_feed.settlement_price != lowest_callable_price ) - result = lowest_callable_price ; + auto lowest_callable_feed_price = (~least_collateral) / ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, + bitasset.current_feed.maximum_short_squeeze_ratio ); + result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); } + else // there is no call order of this bitasset + result = bitasset.median_feed.settlement_price; } - else if( bdsm_type::individual_settlement_to_fund == bdsm && bitasset.individual_settlement_debt > 0 ) + else if( bdsm_type::individual_settlement_to_fund == bdsm ) { - price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) - / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); - // FIXME : deal with MSSR and/or MCFR - if( bitasset.median_feed.settlement_price < fund_price - && bitasset.current_feed.settlement_price != fund_price ) - result = fund_price; + if( bitasset.individual_settlement_debt <= 0 ) + result = bitasset.median_feed.settlement_price; + else + { + // Now bitasset.individual_settlement_debt > 0 + price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) + / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + auto lowest_callable_feed_price = fund_price * bitasset.get_margin_call_order_ratio(); + result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); + } } + // Check whether it's necessary to update + if( result.valid() && (*result) == bitasset.current_feed.settlement_price ) + result.reset(); return result; } @@ -367,15 +378,19 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b { // For better performance, if nothing to update, we return optional new_current_feed_price; + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( skip_median_update ) { + if( bdsm_type::no_settlement != bdsm && bdsm_type::individual_settlement_to_fund != bdsm ) + return; new_current_feed_price = get_derived_current_feed_price( *this, bitasset ); if( !new_current_feed_price.valid() ) return; } // We need to update the database - modify( bitasset, [this, skip_median_update, &new_current_feed_price] + modify( bitasset, [this, skip_median_update, &new_current_feed_price, &bdsm] ( asset_bitasset_data_object& abdo ) { if( !skip_median_update ) @@ -384,7 +399,8 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; abdo.update_median_feeds( head_time, maint_time ); abdo.current_feed = abdo.median_feed; - new_current_feed_price = get_derived_current_feed_price( *this, abdo ); + if( bdsm_type::no_settlement == bdsm || bdsm_type::individual_settlement_to_fund == bdsm ) + new_current_feed_price = get_derived_current_feed_price( *this, abdo ); } if( new_current_feed_price.valid() ) abdo.current_feed.settlement_price = *new_current_feed_price; @@ -578,7 +594,7 @@ void database::clear_expired_force_settlements() break; } try { - asset new_settled = match(order, *itr, settlement_price, max_settlement, settlement_fill_price); + asset new_settled = match(order, *itr, settlement_price, mia, max_settlement, settlement_fill_price); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { if( find_object( order_id ) ) // the settle order hasn't been cancelled diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 1aff7937cd..99b8252d8b 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -327,6 +327,24 @@ namespace graphene { namespace chain { return static_cast( *options.extensions.value.bad_debt_settlement_method ); } + /// Get margin call order price (MCOP) of this bitasset + price get_margin_call_order_price() const + { + return current_feed.margin_call_order_price( options.extensions.value.margin_call_fee_ratio ); + } + + /// Get margin call order ratio (MCOR) of this bitasset + ratio_type get_margin_call_order_ratio() const + { + return current_feed.margin_call_order_ratio( options.extensions.value.margin_call_fee_ratio ); + } + + /// Get margin call pays ratio (MCPR) of this bitasset + ratio_type get_margin_call_pays_ratio() const + { + return current_feed.margin_call_pays_ratio( options.extensions.value.margin_call_fee_ratio ); + } + /// Track whether core_exchange_rate in corresponding asset_object has updated bool asset_cer_updated = false; diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index f04d42a4f5..6bfa9afc9c 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -403,6 +403,7 @@ namespace graphene { namespace chain { /// @param settle the force-settlement order /// @param call the call order /// @param match_price the price to calculate how much the call order pays + /// @param bitasset the bitasset object corresponding to debt asset of the call order /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives when the call order is being margin called @@ -413,6 +414,7 @@ namespace graphene { namespace chain { asset match_impl( const force_settlement_object& settle, const call_order_object& call, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, bool is_margin_call = false, @@ -473,6 +475,7 @@ namespace graphene { namespace chain { * @param feed_price the price of the current feed * @param maintenance_collateral_ratio the maintenance collateral ratio * @param maintenance_collateralization the maintenance collateralization + * @param bitasset the bitasset object corresponding to debt asset of the call order * @param call_pays_price price call order pays. Call order may pay more collateral * than limit order takes if call order subject to a margin call fee. * @returns 0 if no orders were matched, 1 if taker was filled, 2 if maker was filled, 3 if both were filled @@ -481,14 +484,16 @@ namespace graphene { namespace chain { const price& trade_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, const optional& maintenance_collateralization, + const asset_bitasset_data_object& bitasset, const price& call_pays_price); /// If separate call_pays_price not provided, assume call pays at trade_price: match_result_type match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization) { + const optional& maintenance_collateralization, + const asset_bitasset_data_object& bitasset) { return match(taker, maker, trade_price, feed_price, maintenance_collateral_ratio, - maintenance_collateralization, trade_price); + maintenance_collateralization, bitasset, trade_price); } ///@} @@ -497,6 +502,7 @@ namespace graphene { namespace chain { /// @param settle the force-settlement order /// @param call the call order /// @param match_price the price to calculate how much the call order pays + /// @param bitasset the bitasset object corresponding to debt asset of the call order /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives when the call order is being margin called @@ -506,6 +512,7 @@ namespace graphene { namespace chain { asset match( const force_settlement_object& settle, const call_order_object& call, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, bool is_margin_call = false, @@ -515,6 +522,7 @@ namespace graphene { namespace chain { /// @param call the call order being margin called /// @param settle the force-settlement order /// @param match_price the price to calculate how much the call order pays + /// @param bitasset the bitasset object corresponding to debt asset of the call order /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives. @@ -523,6 +531,7 @@ namespace graphene { namespace chain { asset match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, + const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, const ratio_type* margin_call_pays_ratio = nullptr ); diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 99dbedd63a..bbcd11cf87 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -303,6 +303,12 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope { FC_ASSERT( new_collateral == 0, "Should claim all collateral when closing debt position" ); d.remove( *call_obj ); + + // Update current_feed if needed + const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); + if( bitasset_options::bad_debt_settlement_type::no_settlement == bdsm ) + d.update_bitasset_current_feed( *_bitasset_data, true ); + return call_order_id; } @@ -407,9 +413,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } } // Update current_feed if needed - using bdsm_type = bitasset_options::bad_debt_settlement_type; const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); - if( bdsm_type::no_settlement == bdsm ) + if( bitasset_options::bad_debt_settlement_type::no_settlement == bdsm ) d.update_bitasset_current_feed( *_bitasset_data, true ); } diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index 0e7418a99b..af811d8bb1 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -301,27 +301,35 @@ namespace graphene { namespace protocol { // Documentation in header. // Calculation: MCOP = settlement_price / (MSSR - MCFR); result is in debt/collateral - price price_feed::margin_call_order_price(const fc::optional maybe_mcfr)const + price price_feed::margin_call_order_price(const fc::optional& maybe_mcfr)const + { + return settlement_price / margin_call_order_ratio( maybe_mcfr ); + } + + // Calculation: MCOR = MSSR - MCFR, floor at 1.00 + uint16_t price_feed::get_margin_call_price_numerator(const fc::optional& maybe_mcfr)const { const uint16_t mcfr = maybe_mcfr.valid() ? *maybe_mcfr : 0; uint16_t numerator = (mcfr < maximum_short_squeeze_ratio) ? (maximum_short_squeeze_ratio - mcfr) : GRAPHENE_COLLATERAL_RATIO_DENOM; // won't underflow if (numerator < GRAPHENE_COLLATERAL_RATIO_DENOM) numerator = GRAPHENE_COLLATERAL_RATIO_DENOM; // floor at 1.00 - return settlement_price * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, numerator ); + return numerator; + } + + // Documentation in header. + // Calculation: MCOR = MSSR - MCFR + ratio_type price_feed::margin_call_order_ratio(const fc::optional& maybe_mcfr)const + { + auto numerator = get_margin_call_price_numerator( maybe_mcfr ); + return ratio_type( numerator, GRAPHENE_COLLATERAL_RATIO_DENOM ); } // Reason for this function is explained in header. // Calculation: (MSSR - MCFR) / MSSR - ratio_type price_feed::margin_call_pays_ratio(const fc::optional maybe_mcfr)const + ratio_type price_feed::margin_call_pays_ratio(const fc::optional& maybe_mcfr)const { - if (!maybe_mcfr.valid()) - return ratio_type(1,1); - const uint16_t mcfr = *maybe_mcfr; - uint16_t numerator = (mcfr < maximum_short_squeeze_ratio) ? - (maximum_short_squeeze_ratio - mcfr) : GRAPHENE_COLLATERAL_RATIO_DENOM; // won't underflow - if (numerator < GRAPHENE_COLLATERAL_RATIO_DENOM) - numerator = GRAPHENE_COLLATERAL_RATIO_DENOM; // floor at 1.00 + auto numerator = get_margin_call_price_numerator( maybe_mcfr ); return ratio_type( numerator, maximum_short_squeeze_ratio ); // Note: This ratio, if it multiplied margin_call_order_price, would yield the // max_short_squeeze_price, apart perhaps for truncation (rounding) error. diff --git a/libraries/protocol/include/graphene/protocol/asset.hpp b/libraries/protocol/include/graphene/protocol/asset.hpp index ffd26a8a1d..29a8e7e803 100644 --- a/libraries/protocol/include/graphene/protocol/asset.hpp +++ b/libraries/protocol/include/graphene/protocol/asset.hpp @@ -262,7 +262,11 @@ namespace graphene { namespace protocol { * * @return The MCOP in units of DEBT per COLLATERAL. */ - price margin_call_order_price(const fc::optional margin_call_fee_ratio)const; + price margin_call_order_price(const fc::optional& margin_call_fee_ratio)const; + + /// Compute the MCOR, the ratio between margin_call_order_price and feed price + /// @return MSSR - MCFR + ratio_type margin_call_order_ratio( const fc::optional& margin_call_fee_ratio )const; /** * Ratio between max_short_squeeze_price and margin_call_order_price. @@ -281,7 +285,7 @@ namespace graphene { namespace protocol { * * @return (MSSR - MCFR) / MSSR */ - ratio_type margin_call_pays_ratio(const fc::optional margin_call_fee_ratio)const; + ratio_type margin_call_pays_ratio(const fc::optional& margin_call_fee_ratio)const; /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call /// territory. @@ -301,6 +305,9 @@ namespace graphene { namespace protocol { void validate() const; bool is_for( asset_id_type asset_id ) const; + private: + /// Helper function for other functions e.g. @ref margin_call_order_price + uint16_t get_margin_call_price_numerator(const fc::optional& margin_call_fee_ratio)const; }; } } From 055f7c5591b285ebefe4f3171ad46421e95c6a96 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 14 Aug 2021 14:16:51 +0000 Subject: [PATCH 117/258] Implement force-settlement against individual pool --- libraries/chain/asset_evaluator.cpp | 149 ++++++++++++++---- .../graphene/chain/asset_evaluator.hpp | 27 ++-- .../include/graphene/chain/asset_object.hpp | 13 +- 3 files changed, 147 insertions(+), 42 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 4339ce92e0..d6fbcb800d 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1029,16 +1029,35 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op asset_to_settle = &op.amount.asset_id(d); FC_ASSERT( asset_to_settle->is_market_issued(), "Can only force settle a predition market or a market issued asset" ); + const auto& bitasset = asset_to_settle->bitasset_data(d); - FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.has_settlement(), - "Either the asset need to have the force_settle flag enabled, or it need to be globally settled" ); + FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.has_settlement() + || bitasset.has_individual_settlement(), + "Either the asset need to have the force_settle flag enabled, or it need to be globally settled, " + "or the individual bad debt settlement pool is not empty" ); + if( bitasset.is_prediction_market ) + { FC_ASSERT( bitasset.has_settlement(), "Global settlement must occur before force settling a prediction market" ); - else if( bitasset.current_feed.settlement_price.is_null() - && ( d.head_block_time() <= HARDFORK_CORE_216_TIME // TODO check whether the HF check can be removed - || !bitasset.has_settlement() ) ) - FC_THROW_EXCEPTION(insufficient_feeds, "Cannot force settle with no price feed."); + } + else if( bitasset.current_feed.settlement_price.is_null() ) + { + // TODO check whether the HF check can be removed + if( d.head_block_time() <= HARDFORK_CORE_216_TIME ) + { + FC_THROW_EXCEPTION( insufficient_feeds, + "Before the core-216 hard fork, unable to force settle when there is no sufficient " + " price feeds, no matter if the asset has been globally settled" ); + } + if( !bitasset.has_settlement() && !bitasset.has_individual_settlement() ) + { + FC_THROW_EXCEPTION( insufficient_feeds, + "Cannot force settle with no price feed if the asset is not globally settled and the " + "individual bad debt settlement pool is not empty" ); + } + } + FC_ASSERT( d.get_balance( op.account, op.amount.asset_id ) >= op.amount, "Insufficient balance" ); // Since hard fork core-973, check asset authorization limitations @@ -1061,37 +1080,95 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: const auto head_time = d.head_block_time(); const auto& bitasset = *bitasset_ptr; + + extendable_operation_result result; + + // Process individual bad debt settlement pool first + asset to_settle = op.amount; + asset individual_settled( 0, bitasset.options.short_backing_asset ); + const asset_object* backing_asset_ptr = nullptr; + const asset_dynamic_data_object* mia_dyn = nullptr; + if( bitasset.has_individual_settlement() ) + { + asset pays; + if( to_settle.amount < bitasset.individual_settlement_debt ) + { + auto settlement_price = bitasset.get_individual_settlement_price(); + individual_settled = to_settle * settlement_price; // round down, in favor of settlement fund + FC_ASSERT( individual_settled.amount > 0, "Settle amount is too small to receive anything due to rounding" ); + pays = individual_settled.multiply_and_round_up( settlement_price ); + } + else + { + pays = bitasset.individual_settlement_debt; + individual_settled = bitasset.individual_settlement_fund; + } + + to_settle -= pays; + d.adjust_balance( op.account, -pays ); + d.modify( bitasset, [&pays,&individual_settled]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt -= pays.amount; + obj.individual_settlement_fund -= individual_settled.amount; + }); + mia_dyn = &asset_to_settle->dynamic_asset_data_id(d); + d.modify( *mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ + obj.current_supply -= pays.amount; + }); + backing_asset_ptr = &bitasset.options.short_backing_asset(d); + auto issuer_fees = d.pay_market_fees( fee_paying_account, *backing_asset_ptr, individual_settled, false ); + individual_settled -= issuer_fees; + + if( individual_settled.amount > 0 ) + d.adjust_balance( op.account, individual_settled ); + + result.value.paid = vector({ pays }); + result.value.received = vector({ individual_settled }); + result.value.fees = vector({ issuer_fees }); + + if( 0 == to_settle.amount ) + return result; + } + + // Then process global settlement fund or others + auto maint_time = d.get_dynamic_global_properties().next_maintenance_time; if( bitasset.has_settlement() ) { - const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); + if( !mia_dyn ) + mia_dyn = &asset_to_settle->dynamic_asset_data_id(d); - auto settled_amount = op.amount * bitasset.settlement_price; // round down, in favor of global settlement fund - if( op.amount.amount == mia_dyn.current_supply ) - settled_amount.amount = bitasset.settlement_fund; // avoid rounding problems - else - // should be strictly < except for PM with zero outcome - FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund ); + asset settled_amount = ( to_settle.amount == mia_dyn->current_supply ) + ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset ) + : to_settle * bitasset.settlement_price; // round down, favors global settlement fund + if( to_settle.amount != mia_dyn->current_supply ) + { + // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero + FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund, + "Internal error: amount in the global settlement fund is not sufficient to pay the settlement" ); + } - if( settled_amount.amount == 0 && !bitasset.is_prediction_market ) + if( 0 == settled_amount.amount && !bitasset.is_prediction_market ) { - if( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + if( maint_time > HARDFORK_CORE_184_TIME && 0 == individual_settled.amount ) FC_THROW( "Settle amount is too small to receive anything due to rounding" ); + else if( maint_time > HARDFORK_CORE_184_TIME ) // and individual_settled.amount > 0 + return result; // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur } - asset pays = op.amount; - if( op.amount.amount != mia_dyn.current_supply + asset pays = to_settle; + if( to_settle.amount != mia_dyn->current_supply && settled_amount.amount != 0 - && d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_342_TIME ) + && maint_time > HARDFORK_CORE_342_TIME ) { pays = settled_amount.multiply_and_round_up( bitasset.settlement_price ); } d.adjust_balance( op.account, -pays ); + asset issuer_fees( 0, bitasset.options.short_backing_asset ); if( settled_amount.amount > 0 ) { - d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){ + d.modify( bitasset, [&settled_amount]( asset_bitasset_data_object& obj ){ obj.settlement_fund -= settled_amount.amount; }); @@ -1102,8 +1179,9 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: // performance loss. Needs testing. if( head_time >= HARDFORK_CORE_1780_TIME ) { - auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), - settled_amount, false ); + if( !backing_asset_ptr ) + backing_asset_ptr = &bitasset.options.short_backing_asset(d); + issuer_fees = d.pay_market_fees( fee_paying_account, *backing_asset_ptr, settled_amount, false ); settled_amount -= issuer_fees; } @@ -1111,27 +1189,42 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: d.adjust_balance( op.account, settled_amount ); } - d.modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ + d.modify( *mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ obj.current_supply -= pays.amount; }); - return settled_amount; + if( to_settle.amount == op.amount.amount ) // it means there was no individual settlement + { + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); + } + else + { + result.value.paid->push_back( pays ); + result.value.received->push_back( settled_amount ); + result.value.fees->push_back( issuer_fees ); + } + return result; } else { - d.adjust_balance( op.account, -op.amount ); - const auto& settle = d.create([&op,&head_time,&bitasset](force_settlement_object& s) { + d.adjust_balance( op.account, -to_settle ); + const auto& settle = d.create( + [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { s.owner = op.account; - s.balance = op.amount; + s.balance = to_settle; s.settlement_date = head_time + bitasset.options.force_settlement_delay_sec; }); auto id = settle.id; - auto maint_time = d.get_dynamic_global_properties().next_maintenance_time; if( HARDFORK_CORE_2481_PASSED( maint_time ) ) { d.apply_force_settlement( settle, bitasset ); } - return id; + + result.value.new_objects = flat_set({ id }); + + return result; } } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index ede754db74..d765185a42 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -34,7 +34,7 @@ namespace graphene { namespace chain { class asset_create_evaluator : public evaluator { public: - typedef asset_create_operation operation_type; + using operation_type = asset_create_operation; void_result do_evaluate( const asset_create_operation& o ); object_id_type do_apply( const asset_create_operation& o ); @@ -50,7 +50,7 @@ namespace graphene { namespace chain { class asset_issue_evaluator : public evaluator { public: - typedef asset_issue_operation operation_type; + using operation_type = asset_issue_operation; void_result do_evaluate( const asset_issue_operation& o ); void_result do_apply( const asset_issue_operation& o ); @@ -61,7 +61,7 @@ namespace graphene { namespace chain { class asset_reserve_evaluator : public evaluator { public: - typedef asset_reserve_operation operation_type; + using operation_type = asset_reserve_operation; void_result do_evaluate( const asset_reserve_operation& o ); void_result do_apply( const asset_reserve_operation& o ); @@ -73,7 +73,7 @@ namespace graphene { namespace chain { class asset_update_evaluator : public evaluator { public: - typedef asset_update_operation operation_type; + using operation_type = asset_update_operation; void_result do_evaluate( const asset_update_operation& o ); void_result do_apply( const asset_update_operation& o ); @@ -85,7 +85,7 @@ namespace graphene { namespace chain { class asset_update_issuer_evaluator : public evaluator { public: - typedef asset_update_issuer_operation operation_type; + using operation_type = asset_update_issuer_operation; void_result do_evaluate( const asset_update_issuer_operation& o ); void_result do_apply( const asset_update_issuer_operation& o ); @@ -96,7 +96,7 @@ namespace graphene { namespace chain { class asset_update_bitasset_evaluator : public evaluator { public: - typedef asset_update_bitasset_operation operation_type; + using operation_type = asset_update_bitasset_operation; void_result do_evaluate( const asset_update_bitasset_operation& o ); void_result do_apply( const asset_update_bitasset_operation& o ); @@ -108,7 +108,7 @@ namespace graphene { namespace chain { class asset_update_feed_producers_evaluator : public evaluator { public: - typedef asset_update_feed_producers_operation operation_type; + using operation_type = asset_update_feed_producers_operation; void_result do_evaluate( const operation_type& o ); void_result do_apply( const operation_type& o ); @@ -119,7 +119,7 @@ namespace graphene { namespace chain { class asset_fund_fee_pool_evaluator : public evaluator { public: - typedef asset_fund_fee_pool_operation operation_type; + using operation_type = asset_fund_fee_pool_operation; void_result do_evaluate(const asset_fund_fee_pool_operation& op); void_result do_apply(const asset_fund_fee_pool_operation& op); @@ -130,7 +130,7 @@ namespace graphene { namespace chain { class asset_global_settle_evaluator : public evaluator { public: - typedef asset_global_settle_operation operation_type; + using operation_type = asset_global_settle_operation; void_result do_evaluate(const operation_type& op); void_result do_apply(const operation_type& op); @@ -140,11 +140,12 @@ namespace graphene { namespace chain { class asset_settle_evaluator : public evaluator { public: - typedef asset_settle_operation operation_type; + using operation_type = asset_settle_operation; void_result do_evaluate(const operation_type& op); operation_result do_apply(const operation_type& op); + private: const asset_object* asset_to_settle = nullptr; const asset_bitasset_data_object* bitasset_ptr = nullptr; }; @@ -152,7 +153,7 @@ namespace graphene { namespace chain { class asset_publish_feeds_evaluator : public evaluator { public: - typedef asset_publish_feed_operation operation_type; + using operation_type = asset_publish_feed_operation; void_result do_evaluate( const asset_publish_feed_operation& o ); void_result do_apply( const asset_publish_feed_operation& o ); @@ -164,7 +165,7 @@ namespace graphene { namespace chain { class asset_claim_fees_evaluator : public evaluator { public: - typedef asset_claim_fees_operation operation_type; + using operation_type = asset_claim_fees_operation; void_result do_evaluate( const asset_claim_fees_operation& o ); void_result do_apply( const asset_claim_fees_operation& o ); @@ -176,7 +177,7 @@ namespace graphene { namespace chain { class asset_claim_pool_evaluator : public evaluator { public: - typedef asset_claim_pool_operation operation_type; + using operation_type = asset_claim_pool_operation; void_result do_evaluate( const asset_claim_pool_operation& o ); void_result do_apply( const asset_claim_pool_operation& o ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 99b8252d8b..b757018a85 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -294,7 +294,7 @@ namespace graphene { namespace chain { /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply share_type max_force_settlement_volume(share_type current_supply)const; - /** return true if the bitasset has been globally settled, false otherwise */ + /// return true if the bitasset has been globally settled, false otherwise bool has_settlement()const { return !settlement_price.is_null(); } /** @@ -309,6 +309,7 @@ namespace graphene { namespace chain { share_type settlement_fund; ///@} + /// The individual bad debt settlement pool. /// In the event of individual settlements to fund, debt and collateral of the margin positions which got /// settled are moved here. ///@{ @@ -318,6 +319,16 @@ namespace graphene { namespace chain { share_type individual_settlement_fund; ///@} + /// return true if the individual bad debt settlement pool is not empty, false otherwise + bool has_individual_settlement()const { return ( individual_settlement_debt != 0 ); } + + /// Get the price of the individual bad debt settlement pool + price get_individual_settlement_price() const + { + return asset( individual_settlement_debt, asset_id ) + / asset( individual_settlement_fund, options.short_backing_asset ); + } + /// Get the effective bad debt settlement method of this bitasset bitasset_options::bad_debt_settlement_type get_bad_debt_settlement_method() const { From e83f8420f69cf0ee84a83384506f8a2e8137a39e Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 14 Aug 2021 15:42:29 +0000 Subject: [PATCH 118/258] Update tests to adapt settle_op return type change --- tests/tests/database_api_tests.cpp | 5 +- tests/tests/force_settle_fee_tests.cpp | 18 ++++--- tests/tests/force_settle_match_tests.cpp | 66 ++++++++++++++++-------- tests/tests/operation_tests2.cpp | 9 ++-- tests/tests/settle_tests.cpp | 50 ++++++++++-------- 5 files changed, 93 insertions(+), 55 deletions(-) diff --git a/tests/tests/database_api_tests.cpp b/tests/tests/database_api_tests.cpp index 7dbf2f73b2..72979d1626 100644 --- a/tests/tests/database_api_tests.cpp +++ b/tests/tests/database_api_tests.cpp @@ -1695,9 +1695,10 @@ BOOST_AUTO_TEST_CASE( get_settle_orders_by_account ) { auto settlements = db_api.get_settle_orders_by_account("settler", force_settlement_id_type(), 100); BOOST_CHECK(settlements.size() == 1); - BOOST_CHECK(settlements[0].id == result.get()); + BOOST_CHECK(settlements[0].id == *result.get().value.new_objects->begin()); - GRAPHENE_CHECK_THROW(db_api.get_settle_orders_by_account("nosuchaccount", force_settlement_id_type(), 100), fc::exception); + GRAPHENE_CHECK_THROW( db_api.get_settle_orders_by_account("nosuchaccount", force_settlement_id_type(), 100), + fc::exception ); } catch (fc::exception& e) { edump((e.to_detail_string())); diff --git a/tests/tests/force_settle_fee_tests.cpp b/tests/tests/force_settle_fee_tests.cpp index d03fd8595c..ea2f732ab5 100644 --- a/tests/tests/force_settle_fee_tests.cpp +++ b/tests/tests/force_settle_fee_tests.cpp @@ -252,7 +252,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t rachel_settle_amount = 20 * bitusd_unit; operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); - force_settlement_id_type rachel_settle_id = result.get(); + force_settlement_id_type rachel_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount); // Check Rachel's balance @@ -497,7 +498,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t rachel_settle_amount = 2 * bitusd_unit; operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); - force_settlement_id_type rachel_settle_id = result.get(); + force_settlement_id_type rachel_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount); // Advance time to complete the force settlement and to update the price feed @@ -596,7 +598,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t michael_settle_amount = 5 * bitusd_unit; result = force_settle(michael, bitusd.amount(michael_settle_amount)); - force_settlement_id_type michael_settle_id = result.get(); + force_settlement_id_type michael_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(michael_settle_id(db).balance.amount.value, michael_settle_amount); // Advance time to complete the force settlement and to update the price feed @@ -683,7 +686,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t yanna_settle_amount = 10 * bitusd_unit; result = force_settle(yanna, bitusd.amount(yanna_settle_amount)); - force_settlement_id_type yanna_settle_id = result.get(); + force_settlement_id_type yanna_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(yanna_settle_id(db).balance.amount.value, yanna_settle_amount); // Advance time to complete the force settlement and to update the price feed @@ -772,7 +776,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t vikram_settle_amount = 10 * bitusd_unit; result = force_settle(vikram, bitusd.amount(vikram_settle_amount)); - force_settlement_id_type vikram_settle_id = result.get(); + force_settlement_id_type vikram_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(vikram_settle_id(db).balance.amount.value, vikram_settle_amount); // Advance time to complete the force settlement and to update the price feed @@ -1119,7 +1124,8 @@ BOOST_FIXTURE_TEST_SUITE(force_settle_tests, force_settle_database_fixture) const int64_t rachel_settle_amount = 2 * bitusd_unit; // 200 satoshi bitusd operation_result result = force_settle(rachel, bitusd.amount(rachel_settle_amount)); - force_settlement_id_type rachel_settle_id = result.get(); + force_settlement_id_type rachel_settle_id = *result.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL(rachel_settle_id(db).balance.amount.value, rachel_settle_amount); // Advance time to complete the force settlement and to update the price feed diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 4df32e3686..97299f4c0a 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -152,8 +152,10 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_settle_call) // Create a force settlement, will be matched with several call orders auto result = force_settle( seller, bitusd.amount(700*4) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) != nullptr ); // buy orders won't change @@ -295,8 +297,10 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // Create a force settlement, will be matched with the call order share_type amount_to_settle = 117; auto result = force_settle( seller, bitusd.amount(amount_to_settle) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) == nullptr ); // the settle order will match with call2, at mssp: 100/11, @@ -338,8 +342,10 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // Settle again share_type amount_to_settle2 = 100; result = force_settle( seller, bitusd.amount(amount_to_settle2) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle2_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle2_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle2_id ) == nullptr ); // the settle order will match with call, at mssp: 100/11 @@ -382,8 +388,10 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // Settle again with a much smaller amount share_type amount_to_settle3 = 9; result = force_settle( seller, bitusd.amount(amount_to_settle3) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle3_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle3_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle3_id ) == nullptr ); // the settle order will match with call, at mssp @@ -415,8 +423,10 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) // Settle again with a tiny amount that would receive nothing share_type amount_to_settle4 = 5; result = force_settle( seller, bitusd.amount(amount_to_settle4) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle4_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle4_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle4_id ) == nullptr ); // the settle order will match with call, at mssp @@ -542,8 +552,10 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // Create a force settlement, will be matched with several call orders later auto result = force_settle( seller, bitusd.amount(2400) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) != nullptr ); // prepare price feed to get call and call2 (but not call3) into margin call territory @@ -759,8 +771,10 @@ BOOST_AUTO_TEST_CASE(hf2481_cross_test) // Create a force settlement, will be matched with several call orders later auto result = force_settle( seller, bitusd.amount(2400) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) != nullptr ); BOOST_CHECK_EQUAL( 2400, settle_id(db).balance.amount.value ); @@ -1023,22 +1037,28 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // Create a force settlement, will be matched with several call orders later auto result = force_settle( seller, bitusd.amount(40000) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle_id ) != nullptr ); expected_seller_usd_balance -= 40000; // Create another force settlement result = force_settle( seller, bitusd.amount(10000) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle2_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle2_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle2_id ) != nullptr ); expected_seller_usd_balance -= 10000; // Create the third force settlement which is small result = force_settle( seller, bitusd.amount(3) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle3_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle3_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle3_id ) != nullptr ); expected_seller_usd_balance -= 3; @@ -1049,8 +1069,10 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // Create the fourth force settlement which is a little bigger but still small // Note: different execution path than settle3 result = force_settle( seller, bitusd.amount(5) ); - BOOST_REQUIRE( result.is_type() ); - force_settlement_id_type settle4_id = result.get(); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle4_id = *result.get().value.new_objects->begin(); BOOST_CHECK( db.find( settle4_id ) != nullptr ); expected_seller_usd_balance -= 5; diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index e972dafac8..e86498b383 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -1512,7 +1512,8 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) BOOST_TEST_MESSAGE( "Verify partial settlement of call" ); // Partially settle a call - force_settlement_id_type settle_id = force_settle( nathan_id, asset( 50, bitusd_id ) ).get< object_id_type >(); + force_settlement_id_type settle_id = *force_settle( nathan_id, asset( 50, bitusd_id ) ) + .get< extendable_operation_result >().value.new_objects->begin(); // Call does not take effect immediately BOOST_CHECK_EQUAL( get_balance(nathan_id, bitusd_id), 14950); @@ -1536,7 +1537,8 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) BOOST_TEST_MESSAGE( "Verify pending settlement is cancelled when asset's force_settle is disabled" ); // Ensure pending settlement is cancelled when force settle is disabled - settle_id = force_settle( nathan_id, asset( 50, bitusd_id ) ).get< object_id_type >(); + settle_id = *force_settle( nathan_id, asset( 50, bitusd_id ) ) + .get< extendable_operation_result >().value.new_objects->begin(); BOOST_CHECK( !db.get_index_type().indices().empty() ); update_asset_options( bitusd_id, [&]( asset_options& new_options ) @@ -1546,7 +1548,8 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) { new_options.flags &= ~disable_force_settle; } ); BOOST_TEST_MESSAGE( "Perform iterative settlement" ); - settle_id = force_settle( nathan_id, asset( 12500, bitusd_id ) ).get< object_id_type >(); + settle_id = *force_settle( nathan_id, asset( 12500, bitusd_id ) ) + .get< extendable_operation_result >().value.new_objects->begin(); // c3 2950 : 5731 1.9427 fully settled // c5 5000 : 9800 1.9600 fully settled diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 5d3312c089..4bad2a299b 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -89,7 +89,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) // add settle order and check rounding issue operation_result result = force_settle(rachel, bitusd.amount(4)); - force_settlement_id_type settle_id = result.get(); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 4 ); BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); @@ -139,7 +139,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) set_expiration( db, trx ); operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34)); - force_settlement_id_type settle_id2 = result2.get(); + force_settlement_id_type settle_id2 = *result2.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id2(db).balance.amount.value, 34 ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); @@ -194,13 +194,13 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) const operation_result result4 = force_settle(rachel_id(db), bitusd_id(db).amount(434)); const operation_result result5 = force_settle(rachel_id(db), bitusd_id(db).amount(5)); - force_settlement_id_type settle_id3 = result3.get(); + force_settlement_id_type settle_id3 = *result3.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id3(db).balance.amount.value, 3 ); - force_settlement_id_type settle_id4 = result4.get(); + force_settlement_id_type settle_id4 = *result4.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 434 ); - force_settlement_id_type settle_id5 = result5.get(); + force_settlement_id_type settle_id5 = *result5.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); @@ -386,13 +386,13 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) const operation_result result7 = force_settle(ted_id(db), bitusd_id(db).amount(21)); const operation_result result8 = force_settle(ted_id(db), bitusd_id(db).amount(22)); - force_settlement_id_type settle_id6 = result6.get(); + force_settlement_id_type settle_id6 = *result6.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); - force_settlement_id_type settle_id7 = result7.get(); + force_settlement_id_type settle_id7 = *result7.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); - force_settlement_id_type settle_id8 = result8.get(); + force_settlement_id_type settle_id8 = *result8.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); @@ -403,13 +403,16 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) const operation_result result102 = force_settle(joe_id(db), bitcny_id(db).amount(1000)); const operation_result result103 = force_settle(joe_id(db), bitcny_id(db).amount(300)); - force_settlement_id_type settle_id101 = result101.get(); + force_settlement_id_type settle_id101 = *result101.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id101(db).balance.amount.value, 100 ); - force_settlement_id_type settle_id102 = result102.get(); + force_settlement_id_type settle_id102 = *result102.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 1000 ); - force_settlement_id_type settle_id103 = result103.get(); + force_settlement_id_type settle_id103 = *result103.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0); @@ -700,7 +703,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) // add settle order and check rounding issue const operation_result result = force_settle(rachel, bitusd.amount(4)); - force_settlement_id_type settle_id = result.get(); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 4 ); BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); @@ -750,7 +753,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) set_expiration( db, trx ); const operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34)); - force_settlement_id_type settle_id2 = result2.get(); + force_settlement_id_type settle_id2 = *result2.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id2(db).balance.amount.value, 34 ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); @@ -805,13 +808,13 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) const operation_result result4 = force_settle(rachel_id(db), bitusd_id(db).amount(434)); const operation_result result5 = force_settle(rachel_id(db), bitusd_id(db).amount(5)); - force_settlement_id_type settle_id3 = result3.get(); + force_settlement_id_type settle_id3 = *result3.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id3(db).balance.amount.value, 3 ); - force_settlement_id_type settle_id4 = result4.get(); + force_settlement_id_type settle_id4 = *result4.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 434 ); - force_settlement_id_type settle_id5 = result5.get(); + force_settlement_id_type settle_id5 = *result5.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); @@ -999,13 +1002,13 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) const operation_result result7 = force_settle(ted_id(db), bitusd_id(db).amount(21)); const operation_result result8 = force_settle(ted_id(db), bitusd_id(db).amount(22)); - force_settlement_id_type settle_id6 = result6.get(); + force_settlement_id_type settle_id6 = *result6.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); - force_settlement_id_type settle_id7 = result7.get(); + force_settlement_id_type settle_id7 = *result7.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); - force_settlement_id_type settle_id8 = result8.get(); + force_settlement_id_type settle_id8 = *result8.get().value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); @@ -1016,13 +1019,16 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) const operation_result result102 = force_settle(joe_id(db), bitcny_id(db).amount(1000)); const operation_result result103 = force_settle(joe_id(db), bitcny_id(db).amount(300)); - force_settlement_id_type settle_id101 = result101.get(); + force_settlement_id_type settle_id101 = *result101.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id101(db).balance.amount.value, 100 ); - force_settlement_id_type settle_id102 = result102.get(); + force_settlement_id_type settle_id102 = *result102.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id102(db).balance.amount.value, 1000 ); - force_settlement_id_type settle_id103 = result103.get(); + force_settlement_id_type settle_id103 = *result103.get() + .value.new_objects->begin(); BOOST_CHECK_EQUAL( settle_id103(db).balance.amount.value, 300 ); BOOST_CHECK_EQUAL(get_balance(joe_id(db), core_id(db)), 0); From 32276523e0782d26108e2b3534fdb6c0575cdb8c Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 15 Aug 2021 22:48:37 +0000 Subject: [PATCH 119/258] Implement BDSM::individual_settlement_to_fund --- libraries/chain/asset_evaluator.cpp | 14 +- libraries/chain/db_market.cpp | 120 +++++++++++++----- libraries/chain/db_update.cpp | 19 ++- .../chain/include/graphene/chain/database.hpp | 15 ++- 4 files changed, 131 insertions(+), 37 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index d6fbcb800d..2656959678 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -999,19 +999,22 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle FC_ASSERT( asset_to_settle->is_market_issued(), "Can only globally settle market-issued assets" ); FC_ASSERT( asset_to_settle->can_global_settle(), "The global_settle permission of this asset is disabled" ); FC_ASSERT( asset_to_settle->issuer == op.issuer, "Only asset issuer can globally settle an asset" ); - FC_ASSERT( asset_to_settle->dynamic_data(d).current_supply > 0, "Can not globally settle an asset with zero supply" ); + FC_ASSERT( asset_to_settle->dynamic_data(d).current_supply > 0, + "Can not globally settle an asset with zero supply" ); const asset_bitasset_data_object& _bitasset_data = asset_to_settle->bitasset_data(d); // if there is a settlement for this asset, then no further global settle may be taken FC_ASSERT( !_bitasset_data.has_settlement(), "This asset has settlement, cannot global settle again" ); + // FIXME due to individual_settlement_to_order, there can be no debt position const auto& idx = d.get_index_type().indices().get(); FC_ASSERT( !idx.empty(), "Internal error: no debt position found" ); auto itr = idx.lower_bound( price::min( _bitasset_data.options.short_backing_asset, op.asset_to_settle ) ); FC_ASSERT( itr != idx.end() && itr->debt_type() == op.asset_to_settle, "Internal error: no debt position found" ); const call_order_object& least_collateralized_short = *itr; - FC_ASSERT(least_collateralized_short.get_debt() * op.settle_price <= least_collateralized_short.get_collateral(), - "Cannot force settle at supplied price: least collateralized short lacks sufficient collateral to settle."); + FC_ASSERT( least_collateralized_short.get_debt() * op.settle_price <= least_collateralized_short.get_collateral(), + "Cannot force settle at supplied price: least collateralized short lacks " + "sufficient collateral to settle." ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -1121,6 +1124,11 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( individual_settled.amount > 0 ) d.adjust_balance( op.account, individual_settled ); + // Update current_feed if needed + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + if( bitasset_options::bad_debt_settlement_type::individual_settlement_to_fund == bdsm ) + d.update_bitasset_current_feed( bitasset, true ); + result.value.paid = vector({ pays }); result.value.received = vector({ individual_settled }); result.value.fees = vector({ issuer_fees }); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 229f1fd4df..01024ed9d4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -146,25 +146,51 @@ void database::globally_settle_asset_impl( const asset_object& mia, } // call order is maker - FC_ASSERT( fill_call_order( order, pays, order_debt, fund_receives_price, true, margin_call_fee ), - "Internal error: unable to close margin call" ); + FC_ASSERT( fill_call_order( order, pays, order_debt, fund_receives_price, true, margin_call_fee, false ), + "Internal error: unable to close margin call ${o}", ("o", order) ); } - modify( bitasset, [&mia,original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ - obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; - obj.settlement_fund = collateral_gathered.amount; - }); + // TODO move individual settlement fund and order to the GS fund + // TODO update BDSM to GS - /// After all margin positions are closed, the current supply will be reported as 0, but - /// that is a lie, the supply didn't change. We need to capture the current supply before - /// filling all call orders and then restore it afterward. Then in the force settlement - /// evaluator reduce the supply - modify( mia_dyn, [original_mia_supply]( asset_dynamic_data_object& obj ){ - obj.current_supply = original_mia_supply; - }); + modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ + obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; + obj.settlement_fund = collateral_gathered.amount; + }); } FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } +void database::individually_settle_to_fund( const asset_bitasset_data_object& bitasset, + const call_order_object& order ) +{ + auto order_debt = order.get_debt(); + auto order_collateral = order.get_collateral(); + auto fund_receives_price = (~order.collateralization()) / bitasset.get_margin_call_pays_ratio(); + auto fund_receives = order_debt.multiply_and_round_up( fund_receives_price ); + if( fund_receives.amount > order.collateral ) // should not happen, just be defensive + fund_receives.amount = order.collateral; + + auto margin_call_fee = order_collateral - fund_receives; + + modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt += order.debt; + obj.individual_settlement_fund += fund_receives.amount; + }); + + // call order is maker + FC_ASSERT( fill_call_order( order, order.get_collateral(), order_debt, + fund_receives_price, true, margin_call_fee, false ), + "Internal error: unable to close margin call ${o}", ("o", order) ); + + update_bitasset_current_feed( bitasset, true ); + +} + +void database::individually_settle_to_order( const asset_object& mia, const asset_bitasset_data_object& bitasset, + const call_order_object& call_order ) +{ +} + void database::revive_bitasset( const asset_object& bitasset ) { try { FC_ASSERT( bitasset.is_market_issued() ); @@ -558,6 +584,7 @@ bool database::apply_order(const limit_order_object& new_order_object) if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls + // FIXME there can be no debt position due to individual_settlement_to_order const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) @@ -661,6 +688,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle bool finished = false; // whether the new order is gone // check if there are margin calls + // FIXME there can be no debt position due to individual_settlement_to_order const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, new_settlement.balance.asset_id ); while( !finished ) @@ -1203,7 +1231,7 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p * @returns TRUE if the call order was completely filled */ bool database::fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker, const asset& margin_call_fee ) + const price& fill_price, const bool is_maker, const asset& margin_call_fee, bool reduce_current_supply ) { try { FC_ASSERT( order.debt_type() == receives.asset_id ); FC_ASSERT( order.collateral_type() == pays.asset_id ); @@ -1238,10 +1266,13 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay }); // update current supply - const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); - modify( mia_ddo, [&receives]( asset_dynamic_data_object& ao ){ + if( reduce_current_supply ) + { + const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); + modify( mia_ddo, [&receives]( asset_dynamic_data_object& ao ){ ao.current_supply -= receives.amount; }); + } // If the whole debt is paid, adjust borrower's collateral balance if( collateral_freed.valid() ) @@ -1382,7 +1413,13 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if ( maint_time >= HARDFORK_CORE_460_TIME && bitasset.is_prediction_market ) return false; - if( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + + // Only check for black swan here if BDSM is not individual settlement + if( bdsm_type::individual_settlement_to_fund != bdsm + && bdsm_type::individual_settlement_to_order != bdsm + && check_for_blackswan( mia, enable_black_swan, &bitasset ) ) return false; if( bitasset.is_prediction_market ) return false; @@ -1449,33 +1486,36 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option + // FIXME there can be no call order due to individual_settlement_to_order + auto has_call_order = [ before_core_hardfork_1270, + &call_collateral_itr,&call_collateral_end, + &call_price_itr,&call_price_end ]() + { + return before_core_hardfork_1270 ? ( call_price_itr != call_price_end ) + : ( call_collateral_itr != call_collateral_end ); + }; + while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators && limit_itr != limit_end - && ( ( !before_core_hardfork_1270 && call_collateral_itr != call_collateral_end ) - || ( before_core_hardfork_1270 && call_price_itr != call_price_end ) ) ) + && has_call_order() ) { bool filled_call = false; const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - if( ( !before_core_hardfork_1270 - && bitasset.current_maintenance_collateralization < call_order.collateralization() ) - || ( before_core_hardfork_1270 - && after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) ) + bool feed_protected = before_core_hardfork_1270 ? + ( after_hardfork_436 + && bitasset.current_feed.settlement_price > ~call_order.call_price ) + : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); + if( feed_protected ) return margin_called; const limit_order_object& limit_order = *limit_itr; price match_price = limit_order.sell_price; // There was a check `match_price.validate();` here, which is removed now because it always passes - price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); - // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the - // excess being kept by the asset issuer as a margin call fee. In what follows, we use - // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, - // of course, to determine what the limit order receives. Note margin_call_pays_ratio() returns - // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit. // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606 if( before_core_hardfork_606 && match_price > ~call_order.call_price ) @@ -1483,6 +1523,13 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa margin_called = true; + price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); + // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the + // excess being kept by the asset issuer as a margin call fee. In what follows, we use + // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, + // of course, to determine what the limit order receives. Note margin_call_pays_ratio() returns + // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit. + // Although we checked for black swan above, we do one more check to ensure the call order can // pay the amount of collateral which we intend to take from it (including margin call fee). // TODO refactor code for better performance and readability, perhaps extract the new logic to a new @@ -1625,6 +1672,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } // while call_itr != call_end + // Check margin calls against force settlements if( after_core_hardfork_2481 && !bitasset.has_settlement() ) { @@ -1633,7 +1681,18 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( called_some ) margin_called = true; // At last, check for blackswan // TODO perhaps improve performance by passing in iterators - check_for_blackswan( mia, enable_black_swan, &bitasset ); + if( bdsm_type::individual_settlement_to_fund == bdsm + || bdsm_type::individual_settlement_to_order == bdsm ) + { + // Run multiple times, each time one call order gets settled + // TODO perhaps improve performance by settling multiple call orders inside in one call + while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) + { + // do nothing + } + } + else + check_for_blackswan( mia, enable_black_swan, &bitasset ); } return margin_called; @@ -1652,6 +1711,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); auto settle_end = settlement_index.upper_bound( bitasset.asset_id ); + // FIXME there can be no debt position due to individual_settlement_to_order const auto& call_collateral_index = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id ); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 68be70bd6a..47261962fe 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -212,6 +212,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); + // FIXME there can be no debt position due to individual_settlement_to_order if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price { const auto& call_price_index = get_index_type().indices().get(); @@ -295,8 +296,22 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s edump((enable_black_swan)); FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - if( after_core_hardfork_2481 ) + + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + if( bdsm_type::individual_settlement_to_fund == bdsm ) + { + individually_settle_to_fund( bitasset, *call_ptr ); + } + else if( bdsm_type::individual_settlement_to_order == bdsm ) + { + individually_settle_to_order( mia, bitasset, *call_ptr ); + } + // Global settlement or no settlement, but we should not be here if BDSM is no_settlement + else if( after_core_hardfork_2481 ) { + if( bdsm_type::no_settlement == bdsm ) // this should not happen, be defensive here + wlog( "Internal error: BDSM is no_settlement but undercollateralization occurred" ); // After hf_2481, when a global settlement occurs, // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and // they are closed at the same price, @@ -341,6 +356,7 @@ static optional get_derived_current_feed_price( const database& db, const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( bdsm_type::no_settlement == bdsm ) { + // FIXME there can be no debt position due to individual_settlement_to_order const auto& call_collateral_index = db.get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_itr = call_collateral_index.lower_bound( call_min ); @@ -576,6 +592,7 @@ void database::clear_expired_force_settlements() else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset settlement_price = settlement_fill_price; + // FIXME there can be no debt position due to individual_settlement_to_order auto& call_index = get_index_type().indices().get(); asset settled = mia_object.amount(mia.force_settled_volume); // Match against the least collateralized short until the settlement is finished or we reach max settlements diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 6bfa9afc9c..6e72a87a19 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -395,6 +395,10 @@ namespace graphene { namespace chain { const price& settle_price, const IndexType& call_index, bool check_margin_calls = false ); + void individually_settle_to_fund( const asset_bitasset_data_object& bitasset, + const call_order_object& call_order ); + void individually_settle_to_order( const asset_object& mia, const asset_bitasset_data_object& bitasset, + const call_order_object& call_order ); /// Match force settlements with margin calls /// @param bitasset the asset that to be checked /// @return true if matched at least one margin call order @@ -556,16 +560,21 @@ namespace graphene { namespace chain { * @param fill_price the price the transaction executed at * @param is_maker TRUE if the buyer is the maker, FALSE if the buyer is the taker * @param margin_fee Margin call fees paid in collateral asset + * @param reduce_current_supply Whether to reduce current supply of the asset. Usually it is true. + * When globally settleing or individually settling it is false. * @returns TRUE if the order was completely filled */ bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker, const asset& margin_fee ); + const price& fill_price, const bool is_maker, const asset& margin_fee, + bool reduce_current_supply = true ); /// Overload provides compatible default value for margin_fee: (margin_fee.asset_id == pays.asset_id) bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker ) + const price& fill_price, const bool is_maker, + bool reduce_current_supply = true ) { - return fill_call_order( order, pays, receives, fill_price, is_maker, asset(0, pays.asset_id) ); + return fill_call_order( order, pays, receives, fill_price, is_maker, asset(0, pays.asset_id), + reduce_current_supply ); } bool fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives, From 57b837a4404d89f01a7416fdf19f88aa18608b43 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 16 Aug 2021 20:12:21 +0000 Subject: [PATCH 120/258] Implement BDSM::individual_settlement_to_order --- libraries/chain/db_getter.cpp | 10 + libraries/chain/db_market.cpp | 201 ++++++++++++++---- libraries/chain/db_update.cpp | 8 +- .../chain/include/graphene/chain/database.hpp | 20 +- .../include/graphene/chain/market_object.hpp | 8 + 5 files changed, 196 insertions(+), 51 deletions(-) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 98414cb8a8..584c0ecfc9 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace graphene { namespace chain { @@ -146,4 +147,13 @@ const witness_schedule_object& database::get_witness_schedule_object()const return *_p_witness_schedule_obj; } +const limit_order_object* database::find_bad_debt_settlement_order( const asset_id_type& a )const +{ + const auto& limit_index = get_index_type().indices().get(); + auto itr = limit_index.lower_bound( std::make_tuple( true, a ) ); + if( itr != limit_index.end() && itr->receive_asset_id() == a ) + return &(*itr); + return nullptr; +} + } } diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 01024ed9d4..a5b924695b 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -150,19 +150,36 @@ void database::globally_settle_asset_impl( const asset_object& mia, "Internal error: unable to close margin call ${o}", ("o", order) ); } - // TODO move individual settlement fund and order to the GS fund - // TODO update BDSM to GS + // Move the (individual) bad-debt settlement order to the GS fund + const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); + if( limit_ptr != nullptr ) + { + collateral_gathered.amount += limit_ptr->for_sale; + remove( *limit_ptr ); + } + + // Move individual settlement fund to the GS fund + collateral_gathered.amount += bitasset.individual_settlement_fund; modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ + obj.options.extensions.value.bad_debt_settlement_method.reset(); // Update BDSM to GS + obj.individual_settlement_debt = 0; + obj.individual_settlement_fund = 0; obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; obj.settlement_fund = collateral_gathered.amount; }); } FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } -void database::individually_settle_to_fund( const asset_bitasset_data_object& bitasset, - const call_order_object& order ) +void database::individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& order ) { + FC_ASSERT( bitasset.asset_id == order.debt_type(), "Internal error: asset type mismatch" ); + + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + FC_ASSERT( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm, + "Internal error: Invalid BDSM" ); + auto order_debt = order.get_debt(); auto order_collateral = order.get_collateral(); auto fund_receives_price = (~order.collateralization()) / bitasset.get_margin_call_pays_ratio(); @@ -172,23 +189,45 @@ void database::individually_settle_to_fund( const asset_bitasset_data_object& bi auto margin_call_fee = order_collateral - fund_receives; - modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){ - obj.individual_settlement_debt += order.debt; - obj.individual_settlement_fund += fund_receives.amount; - }); + if( bdsm_type::individual_settlement_to_fund == bdsm ) // settle to fund + { + modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt += order.debt; + obj.individual_settlement_fund += fund_receives.amount; + }); + } + else // settle to order + { + const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); + if( limit_ptr != nullptr ) + { + modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { + obj.for_sale += fund_receives.amount; + obj.sell_price.base.amount = obj.for_sale; + obj.sell_price.quote.amount += order.debt; + } ); + } + else + { + create< limit_order_object >( [&order,&fund_receives]( limit_order_object& obj ) { + obj.expiration = time_point_sec::maximum(); + obj.seller = GRAPHENE_NULL_ACCOUNT; + obj.for_sale = fund_receives.amount; + obj.sell_price = fund_receives / order.get_debt(); + obj.is_settled_debt = true; + } ); + } + } // call order is maker FC_ASSERT( fill_call_order( order, order.get_collateral(), order_debt, fund_receives_price, true, margin_call_fee, false ), "Internal error: unable to close margin call ${o}", ("o", order) ); - update_bitasset_current_feed( bitasset, true ); - -} + // Update current feed if needed + if( bdsm_type::individual_settlement_to_fund == bdsm ) + update_bitasset_current_feed( bitasset, true ); -void database::individually_settle_to_order( const asset_object& mia, const asset_bitasset_data_object& bitasset, - const call_order_object& call_order ) -{ } void database::revive_bitasset( const asset_object& bitasset ) @@ -740,77 +779,157 @@ static database::match_result_type get_match_result( bool taker_filled, bool mak * 2 - maker was filled * 3 - both were filled */ -database::match_result_type database::match( const limit_order_object& usd, const limit_order_object& core, +database::match_result_type database::match( const limit_order_object& taker, const limit_order_object& maker, const price& match_price ) { - FC_ASSERT( usd.sell_price.quote.asset_id == core.sell_price.base.asset_id ); - FC_ASSERT( usd.sell_price.base.asset_id == core.sell_price.quote.asset_id ); - FC_ASSERT( usd.for_sale > 0 && core.for_sale > 0 ); + FC_ASSERT( taker.sell_price.quote.asset_id == maker.sell_price.base.asset_id ); + FC_ASSERT( taker.sell_price.base.asset_id == maker.sell_price.quote.asset_id ); + FC_ASSERT( taker.for_sale > 0 && maker.for_sale > 0 ); - auto usd_for_sale = usd.amount_for_sale(); - auto core_for_sale = core.amount_for_sale(); + return maker.is_settled_debt ? match_limit_settled_debt( taker, maker, match_price ) + : match_limit_normal_limit( taker, maker, match_price ); +} - asset usd_pays, usd_receives, core_pays, core_receives; +database::match_result_type database::match_limit_normal_limit( const limit_order_object& taker, + const limit_order_object& maker, const price& match_price ) +{ + FC_ASSERT( !maker.is_settled_debt, "Internal error: maker is settled debt" ); + + auto taker_for_sale = taker.amount_for_sale(); + auto maker_for_sale = maker.amount_for_sale(); + + asset taker_pays; + asset taker_receives; + asset maker_pays; + asset maker_receives; auto maint_time = get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding bool cull_taker = false; - if( usd_for_sale <= core_for_sale * match_price ) // rounding down here should be fine + if( taker_for_sale <= maker_for_sale * match_price ) // rounding down here should be fine { - usd_receives = usd_for_sale * match_price; // round down, in favor of bigger order + taker_receives = taker_for_sale * match_price; // round down, in favor of bigger order // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later - if( usd_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) + if( taker_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) return match_result_type::only_taker_filled; if( before_core_hardfork_342 ) - core_receives = usd_for_sale; + maker_receives = taker_for_sale; else { - // The remaining amount in order `usd` would be too small, + // The remaining amount in order `taker` would be too small, // so we should cull the order in fill_limit_order() below. // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, // so calling maybe_cull_small() will always cull it. - core_receives = usd_receives.multiply_and_round_up( match_price ); + maker_receives = taker_receives.multiply_and_round_up( match_price ); cull_taker = true; } } else { - //This line once read: assert( core_for_sale < usd_for_sale * match_price ); + //This line once read: assert( maker_for_sale < taker_for_sale * match_price ); //This assert is not always true -- see trade_amount_equals_zero in operation_tests.cpp - //Although usd_for_sale is greater than core_for_sale * match_price, core_for_sale == usd_for_sale * match_price + //Although taker_for_sale is greater than maker_for_sale * match_price, + // maker_for_sale == taker_for_sale * match_price //Removing the assert seems to be safe -- apparently no asset is created or destroyed. // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. - core_receives = core_for_sale * match_price; // round down, in favor of bigger order + maker_receives = maker_for_sale * match_price; // round down, in favor of bigger order if( before_core_hardfork_342 ) - usd_receives = core_for_sale; + taker_receives = maker_for_sale; else - // The remaining amount in order `core` would be too small, + // The remaining amount in order `maker` would be too small, // so the order will be culled in fill_limit_order() below - usd_receives = core_receives.multiply_and_round_up( match_price ); + taker_receives = maker_receives.multiply_and_round_up( match_price ); } - core_pays = usd_receives; - usd_pays = core_receives; + maker_pays = taker_receives; + taker_pays = maker_receives; if( before_core_hardfork_342 ) - FC_ASSERT( usd_pays == usd.amount_for_sale() || - core_pays == core.amount_for_sale() ); + FC_ASSERT( taker_pays == taker.amount_for_sale() || + maker_pays == maker.amount_for_sale() ); // the first param of match() is taker - bool taker_filled = fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ); + bool taker_filled = fill_limit_order( taker, taker_pays, taker_receives, cull_taker, match_price, false ); // the second param of match() is maker - bool maker_filled = fill_limit_order( core, core_pays, core_receives, true, match_price, true ); + bool maker_filled = fill_limit_order( maker, maker_pays, maker_receives, true, match_price, true ); match_result_type result = get_match_result( taker_filled, maker_filled ); FC_ASSERT( result != match_result_type::none_filled ); return result; } +// When matching a limit order against settled debt, the maker actually behaviors like a call order +database::match_result_type database::match_limit_settled_debt( const limit_order_object& taker, + const limit_order_object& maker, const price& match_price ) +{ + FC_ASSERT( maker.is_settled_debt, "Internal error: maker is not settled debt" ); + + bool cull_taker = false; + bool maker_filled = false; + + auto usd_for_sale = taker.amount_for_sale(); + auto usd_to_buy = maker.sell_price.quote; + + asset call_receives; + asset order_receives; + if( usd_to_buy > usd_for_sale ) + { // fill taker limit order + order_receives = usd_for_sale * match_price; // round down here, in favor of call order + + // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. + // In this case, we see it as filled and cancel it later + if( order_receives.amount == 0 ) + return match_result_type::only_taker_filled; + + // The remaining amount in the limit order could be too small, + // so we should cull the order in fill_limit_order() below. + // If the order would receive 0 even at `match_price`, it would receive 0 at its own price, + // so calling maybe_cull_small() will always cull it. + call_receives = order_receives.multiply_and_round_up( match_price ); + cull_taker = true; + } + else + { // fill maker "call order" + call_receives = usd_to_buy; + order_receives = maker.amount_for_sale(); + maker_filled = true; + } + + // seller, pays, receives, ... + bool taker_filled = fill_limit_order( taker, call_receives, order_receives, cull_taker, match_price, false ); + + // Reduce current supply + const asset_dynamic_data_object& mia_ddo = call_receives.asset_id(*this).dynamic_asset_data_id(*this); + modify( mia_ddo, [&call_receives]( asset_dynamic_data_object& ao ){ + ao.current_supply -= call_receives.amount; + }); + + // Push fill_order vitual operation + // id, seller, pays, receives, ... + push_applied_operation( fill_order_operation( maker.id, maker.seller, order_receives, call_receives, + asset(0, call_receives.asset_id), match_price, true ) ); + + // Update the maker order + if( maker_filled ) + remove( maker ); + else + { + modify( maker, [&order_receives,&call_receives]( limit_order_object& obj ) { + obj.for_sale -= order_receives.amount; + obj.sell_price.base.amount = obj.for_sale; + obj.sell_price.quote.amount -= call_receives.amount; + }); + } + + match_result_type result = get_match_result( taker_filled, maker_filled ); + return result; +} + database::match_result_type database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price, const price& feed_price, const uint16_t maintenance_collateral_ratio, @@ -839,9 +958,9 @@ database::match_result_type database::match( const limit_order_object& bid, cons if( order_receives.amount == 0 ) return match_result_type::only_taker_filled; - // The remaining amount in the limit order would be too small, + // The remaining amount in the limit order could be too small, // so we should cull the order in fill_limit_order() below. - // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, + // If the order would receive 0 even at `match_price`, it would receive 0 at its own price, // so calling maybe_cull_small() will always cull it. call_receives = order_receives.multiply_and_round_up( match_price ); cull_taker = true; diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 47261962fe..cb0de76f32 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -299,13 +299,9 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s using bdsm_type = bitasset_options::bad_debt_settlement_type; const auto bdsm = bitasset.get_bad_debt_settlement_method(); - if( bdsm_type::individual_settlement_to_fund == bdsm ) + if( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm ) { - individually_settle_to_fund( bitasset, *call_ptr ); - } - else if( bdsm_type::individual_settlement_to_order == bdsm ) - { - individually_settle_to_order( mia, bitasset, *call_ptr ); + individually_settle( bitasset, *call_ptr ); } // Global settlement or no settlement, but we should not be here if BDSM is no_settlement else if( after_core_hardfork_2481 ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 6e72a87a19..4cbde09366 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -296,7 +296,13 @@ namespace graphene { namespace chain { rejected_predicate_map* rejected_authorities = nullptr )const; uint32_t last_non_undoable_block_num() const; + + /// Find the limit order which is the bad-debt settlement fund of the specified asset + /// @param a ID of the asset + /// @return nullptr if not found, pointer to the limit order if found + const limit_order_object* find_bad_debt_settlement_order( const asset_id_type& a )const; //////////////////// db_init.cpp //////////////////// + ///@{ void initialize_evaluators(); /// Reset the object graph in-memory @@ -310,6 +316,7 @@ namespace graphene { namespace chain { = std::make_unique>(); } + ///@} //////////////////// db_balance.cpp //////////////////// /** @@ -395,10 +402,11 @@ namespace graphene { namespace chain { const price& settle_price, const IndexType& call_index, bool check_margin_calls = false ); - void individually_settle_to_fund( const asset_bitasset_data_object& bitasset, - const call_order_object& call_order ); - void individually_settle_to_order( const asset_object& mia, const asset_bitasset_data_object& bitasset, - const call_order_object& call_order ); + /// Individually settle the @p call_order. Called when the call order is undercollateralized. + /// See @ref protocol::bitasset_options::bad_debt_settlement_type for more info. + /// @param bitasset the bitasset object + /// @param call_order the call order + void individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& call_order ); /// Match force settlements with margin calls /// @param bitasset the asset that to be checked /// @return true if matched at least one margin call order @@ -471,6 +479,10 @@ namespace graphene { namespace chain { }; match_result_type match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price ); + match_result_type match_limit_normal_limit( const limit_order_object& taker, const limit_order_object& maker, + const price& trade_price ); + match_result_type match_limit_settled_debt( const limit_order_object& taker, const limit_order_object& maker, + const price& trade_price ); /*** * @brief Match limit order as taker to a call order as maker * @param taker the order that is removing liquidity from the book diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 62876889f6..779fca5210 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -72,6 +72,7 @@ struct by_price; struct by_expiration; struct by_account; struct by_account_price; +struct by_is_settled_debt; typedef multi_index_container< limit_order_object, indexed_by< @@ -89,6 +90,13 @@ typedef multi_index_container< >, composite_key_compare< std::greater, std::less > >, + ordered_unique< tag, + composite_key< limit_order_object, + member< limit_order_object, bool, &limit_order_object::is_settled_debt >, + const_mem_fun< limit_order_object, asset_id_type, &limit_order_object::receive_asset_id >, + member< object, object_id_type, &object::id> + > + >, // index used by APIs ordered_unique< tag, composite_key< limit_order_object, From 6d73d008c37174a1fc8be806b7ac93d8ef174399 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 16 Aug 2021 22:08:44 +0000 Subject: [PATCH 121/258] Fix scenarios that no debt position exists --- libraries/chain/asset_evaluator.cpp | 18 ++++++++++-------- libraries/chain/db_market.cpp | 9 +++++---- libraries/chain/db_update.cpp | 22 ++++++++++++++-------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 2656959678..0f2a7c0283 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1004,18 +1004,20 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle const asset_bitasset_data_object& _bitasset_data = asset_to_settle->bitasset_data(d); // if there is a settlement for this asset, then no further global settle may be taken - FC_ASSERT( !_bitasset_data.has_settlement(), "This asset has settlement, cannot global settle again" ); + FC_ASSERT( !_bitasset_data.has_settlement(), + "This asset has been globally settled, cannot globally settle again" ); - // FIXME due to individual_settlement_to_order, there can be no debt position + // Note: there can be no debt position due to individual settlements, processed below const auto& idx = d.get_index_type().indices().get(); - FC_ASSERT( !idx.empty(), "Internal error: no debt position found" ); auto itr = idx.lower_bound( price::min( _bitasset_data.options.short_backing_asset, op.asset_to_settle ) ); - FC_ASSERT( itr != idx.end() && itr->debt_type() == op.asset_to_settle, "Internal error: no debt position found" ); - const call_order_object& least_collateralized_short = *itr; - FC_ASSERT( least_collateralized_short.get_debt() * op.settle_price <= least_collateralized_short.get_collateral(), - "Cannot force settle at supplied price: least collateralized short lacks " + if( itr != idx.end() && itr->debt_type() == op.asset_to_settle ) + { + const call_order_object& least_collateralized_short = *itr; + FC_ASSERT( ( least_collateralized_short.get_debt() * op.settle_price ) + <= least_collateralized_short.get_collateral(), + "Cannot globally settle at supplied price: least collateralized short lacks " "sufficient collateral to settle." ); - + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index a5b924695b..e87e300195 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -68,6 +68,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett } else { + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements globally_settle_asset_impl( mia, settlement_price, get_index_type().indices().get(), check_margin_calls ); @@ -623,7 +624,7 @@ bool database::apply_order(const limit_order_object& new_order_object) if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) @@ -727,7 +728,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle bool finished = false; // whether the new order is gone // check if there are margin calls - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, new_settlement.balance.asset_id ); while( !finished ) @@ -1569,6 +1570,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements const auto& call_collateral_index = call_index.indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); @@ -1605,7 +1607,6 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option - // FIXME there can be no call order due to individual_settlement_to_order auto has_call_order = [ before_core_hardfork_1270, &call_collateral_itr,&call_collateral_end, &call_price_itr,&call_price_end ]() @@ -1830,7 +1831,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); auto settle_end = settlement_index.upper_bound( bitasset.asset_id ); - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id ); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index cb0de76f32..7014b6325d 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -212,7 +212,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); - // FIXME there can be no debt position due to individual_settlement_to_order if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price { const auto& call_price_index = get_index_type().indices().get(); @@ -223,6 +222,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s } else // after core-1270 hard fork, check with collateralization { + // Note: it is safe to check here even if there is no call order due to individual bad debt settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_itr = call_collateral_index.lower_bound( call_min ); if( call_itr == call_collateral_index.end() ) // no call order @@ -352,7 +352,7 @@ static optional get_derived_current_feed_price( const database& db, const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( bdsm_type::no_settlement == bdsm ) { - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: it is safe to check here even if there is no call order due to individual bad debt settlements const auto& call_collateral_index = db.get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_itr = call_collateral_index.lower_bound( call_min ); @@ -549,7 +549,8 @@ void database::clear_expired_force_settlements() continue; } if( max_settlement_volume.asset_id != current_asset ) - max_settlement_volume = mia_object.amount(mia.max_force_settlement_volume(mia_object.dynamic_data(*this).current_supply)); + max_settlement_volume = mia_object.amount( mia.max_force_settlement_volume( + mia_object.dynamic_data(*this).current_supply ) ); // When current_asset_finished is true, this would be the 2nd time processing the same order. // In this case, we move to the next asset. if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) @@ -588,16 +589,21 @@ void database::clear_expired_force_settlements() else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset settlement_price = settlement_fill_price; - // FIXME there can be no debt position due to individual_settlement_to_order + // Note: there can be no debt position due to individual settlements, processed below auto& call_index = get_index_type().indices().get(); asset settled = mia_object.amount(mia.force_settled_volume); // Match against the least collateralized short until the settlement is finished or we reach max settlements while( settled < max_settlement_volume && find_object(order_id) ) { - auto itr = call_index.lower_bound(boost::make_tuple(price::min(mia_object.bitasset_data(*this).options.short_backing_asset, - mia_object.get_id()))); - // There should always be a call order, since asset exists! - assert(itr != call_index.end() && itr->debt_type() == mia_object.get_id()); + auto itr = call_index.lower_bound( price::min( mia.options.short_backing_asset, mia.asset_id ) ); + // Note: there can be no debt position due to individual settlements + if( itr == call_index.end() || itr->debt_type() != mia.asset_id ) // no debt position + { + wlog( "No debt position found when processing force settlement ${o}", ("o",order) ); + cancel_settle_order( order ); + break; + } + asset max_settlement = max_settlement_volume - settled; if( order.balance.amount == 0 ) From 3834127a744c062f682c5018341798ddc6f747cb Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 16 Aug 2021 23:21:01 +0000 Subject: [PATCH 122/258] Implement BDSM update --- libraries/chain/asset_evaluator.cpp | 54 ++++++++++++++----- .../graphene/chain/asset_evaluator.hpp | 3 ++ .../include/graphene/chain/asset_object.hpp | 5 +- .../include/graphene/protocol/asset_ops.hpp | 8 +++ 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 0f2a7c0283..646dff27fc 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -640,7 +640,8 @@ void check_children_of_bitasset(const database& d, const asset_update_bitasset_o { const auto& child = bitasset_data.asset_id(d); FC_ASSERT( child.get_id() != op.new_options.short_backing_asset, - "A BitAsset would be invalidated by changing this backing asset ('A' backed by 'B' backed by 'A')." ); + "A BitAsset would be invalidated by changing this backing asset " + "('A' backed by 'B' backed by 'A')." ); FC_ASSERT( child.issuer != GRAPHENE_COMMITTEE_ACCOUNT, "A blockchain-controlled market asset would be invalidated by changing this backing asset." ); @@ -702,10 +703,32 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita || ( old_mssr.valid() && *old_mssr != *new_mssr ) ); FC_ASSERT( !mssr_changed, "No permission to update MSSR" ); } + // check if BDSM will change + const auto old_bdsm = current_bitasset_data.get_bad_debt_settlement_method(); + const auto new_bdsm = op.new_options.get_bad_debt_settlement_method(); + if( old_bdsm != new_bdsm ) + { + FC_ASSERT( asset_obj.can_owner_update_bdsm(), "No permission to update BDSM" ); + FC_ASSERT( !current_bitasset_data.has_settlement(), + "Unable to update BDSM when the asset has been globally settled" ); + + // Note: it is probably OK to allow BDSM update, be conservative here so far + using bdsm_type = bitasset_options::bad_debt_settlement_type; + if( bdsm_type::individual_settlement_to_fund == old_bdsm ) + FC_ASSERT( !current_bitasset_data.has_individual_settlement(), + "Unable to update BDSM when the individual bad debt settlement pool is not empty" ); + else if( bdsm_type::individual_settlement_to_order == old_bdsm ) + FC_ASSERT( nullptr == d.find_bad_debt_settlement_order( op.asset_to_update ), + "Unable to update BDSM when there exists a bad debt settlement order" ); + + // Since we do not allow updating in some cases (above), only check no_settlement here + if( bdsm_type::no_settlement == old_bdsm || bdsm_type::no_settlement == new_bdsm ) + update_feeds_due_to_bdsm_change = true; + } + // hf 922_931 is a consensus/logic change. This hf cannot be removed. - bool after_hf_core_922_931 = ( d.get_dynamic_global_properties().next_maintenance_time - > HARDFORK_CORE_922_931_TIME ); + bool after_hf_core_922_931 = ( next_maint_time > HARDFORK_CORE_922_931_TIME ); // Are we changing the backing asset? if( op.new_options.short_backing_asset != current_bitasset_data.options.short_backing_asset ) @@ -766,7 +789,8 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita if ( new_backing_asset.is_market_issued() ) { asset_id_type backing_backing_asset_id = new_backing_asset.bitasset_data(d).options.short_backing_asset; - FC_ASSERT( (backing_backing_asset_id == asset_id_type() || !backing_backing_asset_id(d).is_market_issued()), + FC_ASSERT( (backing_backing_asset_id == asset_id_type() + || !backing_backing_asset_id(d).is_market_issued()), "A BitAsset cannot be backed by a BitAsset that itself is backed by a BitAsset."); } } @@ -805,7 +829,8 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita */ static bool update_bitasset_object_options( const asset_update_bitasset_operation& op, database& db, - asset_bitasset_data_object& bdo, const asset_object& asset_to_update ) + asset_bitasset_data_object& bdo, const asset_object& asset_to_update, + bool update_feeds_due_to_bdsm_change ) { const fc::time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; bool after_hf_core_868_890 = ( next_maint_time > HARDFORK_CORE_868_890_TIME ); @@ -892,10 +917,13 @@ static bool update_bitasset_object_options( } bool feed_actually_changed = false; - if( should_update_feeds ) + if( should_update_feeds || update_feeds_due_to_bdsm_change ) { const auto old_feed = bdo.current_feed; - db.update_bitasset_current_feed( bdo ); + if( should_update_feeds ) + db.update_bitasset_current_feed( bdo ); + else // to update feeds due to bdsm change + db.update_bitasset_current_feed( bdo, true ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); @@ -912,25 +940,25 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse try { auto& db_conn = db(); - const auto& asset_being_updated = (*asset_to_update); bool to_check_call_orders = false; db_conn.modify( *bitasset_to_update, - [&op, &asset_being_updated, &to_check_call_orders, &db_conn]( asset_bitasset_data_object& bdo ) + [&op, &to_check_call_orders, &db_conn, this]( asset_bitasset_data_object& bdo ) { - to_check_call_orders = update_bitasset_object_options( op, db_conn, bdo, asset_being_updated ); + to_check_call_orders = update_bitasset_object_options( op, db_conn, bdo, *asset_to_update, + update_feeds_due_to_bdsm_change ); }); if( to_check_call_orders ) // Process margin calls, allow black swan, not for a new limit order - db_conn.check_call_orders( asset_being_updated, true, false, bitasset_to_update ); + db_conn.check_call_orders( *asset_to_update, true, false, bitasset_to_update ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_evaluator::operation_type& o) +void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_operation& o) { try { database& d = db(); @@ -954,7 +982,7 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_evaluator::operation_type& o) +void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_operation& o) { try { database& d = db(); const auto head_time = d.head_block_time(); diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index d765185a42..5726dff1cd 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -101,8 +101,11 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_update_bitasset_operation& o ); void_result do_apply( const asset_update_bitasset_operation& o ); + private: const asset_bitasset_data_object* bitasset_to_update = nullptr; const asset_object* asset_to_update = nullptr; + + bool update_feeds_due_to_bdsm_change = false; }; class asset_update_feed_producers_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index b757018a85..4c7b13a161 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -332,10 +332,7 @@ namespace graphene { namespace chain { /// Get the effective bad debt settlement method of this bitasset bitasset_options::bad_debt_settlement_type get_bad_debt_settlement_method() const { - using bdsm_type = bitasset_options::bad_debt_settlement_type; - if( !options.extensions.value.bad_debt_settlement_method.valid() ) - return bdsm_type::global_settlement; - return static_cast( *options.extensions.value.bad_debt_settlement_method ); + return options.get_bad_debt_settlement_method(); } /// Get margin call order price (MCOP) of this bitasset diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 00611c0d88..ccde864aca 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -171,6 +171,14 @@ namespace graphene { namespace protocol { /// Perform internal consistency checks. /// @throws fc::exception if any check fails void validate()const; + + /// Get the effective bad debt settlement method + bad_debt_settlement_type get_bad_debt_settlement_method() const + { + if( !extensions.value.bad_debt_settlement_method.valid() ) + return bad_debt_settlement_type::global_settlement; + return static_cast( *extensions.value.bad_debt_settlement_method ); + } }; From 1a7721215626ee2714c7cc9f009ee73e5ca1813d Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 10:56:32 +0000 Subject: [PATCH 123/258] Fix code smells --- libraries/chain/asset_object.cpp | 4 +- libraries/chain/db_update.cpp | 2 +- .../include/graphene/chain/asset_object.hpp | 75 ++++++++++--------- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 669381f633..b896c871a3 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -136,12 +136,12 @@ void asset_bitasset_data_object::refresh_cache() { current_maintenance_collateralization = median_feed.maintenance_collateralization(); if( median_feed.initial_collateral_ratio > median_feed.maintenance_collateral_ratio ) // if ICR is above MCR - current_initial_collateralization = median_feed.calculate_initial_collateralization(); + current_initial_collateralization = median_feed.get_initial_collateralization(); else // if ICR is not above MCR current_initial_collateralization = current_maintenance_collateralization; } -price price_feed_with_icr::calculate_initial_collateralization()const +price price_feed_with_icr::get_initial_collateralization()const { if( settlement_price.is_null() ) return price(); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 7014b6325d..66e64879c0 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -661,7 +661,7 @@ void database::update_expired_feeds() const asset_bitasset_data_object& b = *itr; ++itr; // not always process begin() because old code skipped updating some assets before hf 615 // update feeds, check margin calls - if( after_hardfork_615 || b.feed_is_expired_before_hardfork_615( head_time ) ) + if( after_hardfork_615 || b.feed_is_expired_before_hf_615( head_time ) ) { auto old_median_feed = b.current_feed; const asset_object& asset_obj = b.asset_id( *this ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 4c7b13a161..3002633b1f 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -40,8 +40,6 @@ namespace graphene { namespace chain { class asset_bitasset_data_object; - class database; - using namespace graphene::db; /** * @brief tracks the asset information that changes frequently @@ -92,27 +90,28 @@ namespace graphene { namespace chain { /// @return true if this is a share asset of a liquidity pool; false otherwise. bool is_liquidity_pool_share_asset()const { return for_liquidity_pool.valid(); } /// @return true if users may request force-settlement of this market-issued asset; false otherwise - bool can_force_settle()const { return !(options.flags & disable_force_settle); } + bool can_force_settle()const { return (0 == (options.flags & disable_force_settle)); } /// @return true if the issuer of this market-issued asset may globally settle the asset; false otherwise - bool can_global_settle()const { return options.issuer_permissions & global_settle; } + bool can_global_settle()const { return (0 != (options.issuer_permissions & global_settle)); } /// @return true if this asset charges a fee for the issuer on market operations; false otherwise - bool charges_market_fees()const { return options.flags & charge_market_fee; } + bool charges_market_fees()const { return (0 != (options.flags & charge_market_fee)); } /// @return true if this asset may only be transferred to/from the issuer or market orders - bool is_transfer_restricted()const { return options.flags & transfer_restricted; } - bool can_override()const { return options.flags & override_authority; } - bool allow_confidential()const { return !(options.flags & asset_issuer_permission_flags::disable_confidential); } + bool is_transfer_restricted()const { return (0 != (options.flags & transfer_restricted)); } + bool can_override()const { return (0 != (options.flags & override_authority)); } + bool allow_confidential()const + { return (0 == (options.flags & asset_issuer_permission_flags::disable_confidential)); } /// @return true if max supply of the asset can be updated - bool can_update_max_supply()const { return !(options.flags & lock_max_supply); } + bool can_update_max_supply()const { return (0 == (options.flags & lock_max_supply)); } /// @return true if can create new supply for the asset - bool can_create_new_supply()const { return !(options.flags & disable_new_supply); } + bool can_create_new_supply()const { return (0 == (options.flags & disable_new_supply)); } /// @return true if the asset owner can update MCR directly - bool can_owner_update_mcr()const { return !(options.issuer_permissions & disable_mcr_update); } + bool can_owner_update_mcr()const { return (0 == (options.issuer_permissions & disable_mcr_update)); } /// @return true if the asset owner can update ICR directly - bool can_owner_update_icr()const { return !(options.issuer_permissions & disable_icr_update); } + bool can_owner_update_icr()const { return (0 == (options.issuer_permissions & disable_icr_update)); } /// @return true if the asset owner can update MSSR directly - bool can_owner_update_mssr()const { return !(options.issuer_permissions & disable_mssr_update); } + bool can_owner_update_mssr()const { return (0 == (options.issuer_permissions & disable_mssr_update)); } /// @return true if the asset owner can change bad debt settlement method - bool can_owner_update_bdsm()const { return !(options.issuer_permissions & disable_bdsm_update); } + bool can_owner_update_bdsm()const { return (0 == (options.issuer_permissions & disable_bdsm_update)); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } @@ -158,8 +157,9 @@ namespace graphene { namespace chain { // UIAs may not be prediction markets, have force settlement, or global settlements if( !is_market_issued() ) { - FC_ASSERT(!(options.flags & disable_force_settle || options.flags & global_settle)); - FC_ASSERT(!(options.issuer_permissions & disable_force_settle || options.issuer_permissions & global_settle)); + FC_ASSERT(0 == (options.flags & disable_force_settle) && 0 == (options.flags & global_settle)); + FC_ASSERT(0 == (options.issuer_permissions & disable_force_settle) + && 0 == (options.issuer_permissions & global_settle)); } } @@ -244,7 +244,7 @@ namespace graphene { namespace chain { /// The result will be used to check new debt positions and position updates. /// Calculation: ~settlement_price * initial_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM - price calculate_initial_collateralization()const; + price get_initial_collateralization()const; }; /** @@ -294,7 +294,7 @@ namespace graphene { namespace chain { /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply share_type max_force_settlement_volume(share_type current_supply)const; - /// return true if the bitasset has been globally settled, false otherwise + /// @return true if the bitasset has been globally settled, false otherwise bool has_settlement()const { return !settlement_price.is_null(); } /** @@ -319,7 +319,7 @@ namespace graphene { namespace chain { share_type individual_settlement_fund; ///@} - /// return true if the individual bad debt settlement pool is not empty, false otherwise + /// @return true if the individual bad debt settlement pool is not empty, false otherwise bool has_individual_settlement()const { return ( individual_settlement_debt != 0 ); } /// Get the price of the individual bad debt settlement pool @@ -353,13 +353,13 @@ namespace graphene { namespace chain { return current_feed.margin_call_pays_ratio( options.extensions.value.margin_call_fee_ratio ); } - /// Track whether core_exchange_rate in corresponding asset_object has updated + /// Track whether core_exchange_rate in corresponding @ref asset_object has updated bool asset_cer_updated = false; /// Track whether core exchange rate in current feed has updated bool feed_cer_updated = false; - /// Whether need to update core_exchange_rate in asset_object + /// Whether need to update core_exchange_rate in @ref asset_object bool need_to_update_cer() const { return ( ( feed_cer_updated || asset_cer_updated ) && !current_feed.core_exchange_rate.is_null() ); @@ -369,13 +369,16 @@ namespace graphene { namespace chain { time_point_sec feed_expiration_time()const { uint32_t current_feed_seconds = current_feed_publication_time.sec_since_epoch(); - if( std::numeric_limits::max() - current_feed_seconds <= options.feed_lifetime_sec ) + if( (std::numeric_limits::max() - current_feed_seconds) <= options.feed_lifetime_sec ) return time_point_sec::maximum(); else return current_feed_publication_time + options.feed_lifetime_sec; } - bool feed_is_expired_before_hardfork_615(time_point_sec current_time)const + /// The old and buggy implementation of @ref feed_is_expired before the No. 615 hardfork. + /// See https://github.com/cryptonomex/graphene/issues/615 + bool feed_is_expired_before_hf_615(time_point_sec current_time)const { return feed_expiration_time() >= current_time; } + /// @return whether @ref current_feed has expired bool feed_is_expired(time_point_sec current_time)const { return feed_expiration_time() <= current_time; } @@ -400,10 +403,10 @@ namespace graphene { namespace chain { void refresh_cache(); }; - // key extractor for short backing asset - struct bitasset_short_backing_asset_extractor + /// Key extractor for short backing asset + struct bitasset_backing_asst_extractor { - typedef asset_id_type result_type; + using result_type = asset_id_type; result_type operator() (const asset_bitasset_data_object& obj) const { return obj.options.short_backing_asset; @@ -414,28 +417,30 @@ namespace graphene { namespace chain { struct by_feed_expiration; struct by_cer_update; - typedef multi_index_container< + using bitasset_data_multi_index_type = multi_index_container< asset_bitasset_data_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, - ordered_non_unique< tag, bitasset_short_backing_asset_extractor >, + ordered_non_unique< tag, bitasset_backing_asst_extractor >, ordered_unique< tag, composite_key< asset_bitasset_data_object, - const_mem_fun< asset_bitasset_data_object, time_point_sec, &asset_bitasset_data_object::feed_expiration_time >, + const_mem_fun< asset_bitasset_data_object, time_point_sec, + &asset_bitasset_data_object::feed_expiration_time >, member< asset_bitasset_data_object, asset_id_type, &asset_bitasset_data_object::asset_id > > >, ordered_non_unique< tag, - const_mem_fun< asset_bitasset_data_object, bool, &asset_bitasset_data_object::need_to_update_cer > + const_mem_fun< asset_bitasset_data_object, bool, + &asset_bitasset_data_object::need_to_update_cer > > > - > asset_bitasset_data_object_multi_index_type; - typedef generic_index asset_bitasset_data_index; + >; + using asset_bitasset_data_index = generic_index< asset_bitasset_data_object, bitasset_data_multi_index_type >; struct by_symbol; struct by_type; struct by_issuer; - typedef multi_index_container< + using asset_object_multi_index_type = multi_index_container< asset_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, @@ -453,8 +458,8 @@ namespace graphene { namespace chain { > > > - > asset_object_multi_index_type; - typedef generic_index asset_index; + >; + using asset_index = generic_index< asset_object, asset_object_multi_index_type >; } } // graphene::chain From ccf9e3f8594a9661b37353a2f305106b31f0e8c3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 19:02:39 +0000 Subject: [PATCH 124/258] Fix more code smells --- libraries/chain/asset_evaluator.cpp | 8 ++- libraries/chain/asset_object.cpp | 8 +-- libraries/chain/db_market.cpp | 4 +- libraries/chain/db_update.cpp | 52 ++++++++++--------- .../graphene/chain/asset_evaluator.hpp | 3 +- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 646dff27fc..226eefb078 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -982,13 +982,11 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_operation& o) +void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_operation& o) const { try { database& d = db(); - const auto head_time = d.head_block_time(); - const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; const asset_bitasset_data_object& bitasset_to_update = asset_to_update->bitasset_data(d); - d.modify( bitasset_to_update, [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { + d.modify( bitasset_to_update, [&o](asset_bitasset_data_object& a) { //This is tricky because I have a set of publishers coming in, but a map of publisher to feed is stored. //I need to update the map such that the keys match the new publishers, but not munge the old price feeds from //publishers who are being kept. @@ -1340,7 +1338,7 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope auto old_feed = bad.current_feed; // Store medians for this asset - d.modify( bad , [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { + d.modify( bad , [&o,&head_time](asset_bitasset_data_object& a) { a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed, o.extensions.value.initial_collateral_ratio ) ); }); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index b896c871a3..0d843d98f6 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -32,9 +32,9 @@ using namespace graphene::chain; share_type asset_bitasset_data_object::max_force_settlement_volume(share_type current_supply) const { - if( options.maximum_force_settlement_volume == 0 ) + if( 0 == options.maximum_force_settlement_volume ) return 0; - if( options.maximum_force_settlement_volume == GRAPHENE_100_PERCENT ) + if( GRAPHENE_100_PERCENT == options.maximum_force_settlement_volume ) return current_supply + force_settled_volume; fc::uint128_t volume = current_supply.value; @@ -76,7 +76,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin return; } - if( effective_feeds.size() == 1 ) + if( 1u == effective_feeds.size() ) { if( median_feed.core_exchange_rate != effective_feeds.front().get().core_exchange_rate ) feed_cer_updated = true; @@ -99,7 +99,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin // *** Begin Median Calculations *** price_feed_with_icr tmp_median_feed; - const auto median_itr = effective_feeds.begin() + effective_feeds.size() / 2; + const auto median_itr = effective_feeds.begin() + ( effective_feeds.size() / 2 ); #define CALCULATE_MEDIAN_VALUE(r, data, field_name) \ std::nth_element( effective_feeds.begin(), median_itr, effective_feeds.end(), \ [](const price_feed_with_icr& a, const price_feed_with_icr& b) { \ diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index e87e300195..296ae4ea3f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -808,7 +808,7 @@ database::match_result_type database::match_limit_normal_limit( const limit_orde bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding bool cull_taker = false; - if( taker_for_sale <= maker_for_sale * match_price ) // rounding down here should be fine + if( taker_for_sale <= ( maker_for_sale * match_price ) ) // rounding down here should be fine { taker_receives = taker_for_sale * match_price; // round down, in favor of bigger order @@ -831,7 +831,7 @@ database::match_result_type database::match_limit_normal_limit( const limit_orde } else { - //This line once read: assert( maker_for_sale < taker_for_sale * match_price ); + //This line once read: assert( maker_for_sale < taker_for_sale * match_price ); // check //This assert is not always true -- see trade_amount_equals_zero in operation_tests.cpp //Although taker_for_sale is greater than maker_for_sale * match_price, // maker_for_sale == taker_for_sale * match_price diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 66e64879c0..235dd2377a 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -595,9 +595,9 @@ void database::clear_expired_force_settlements() // Match against the least collateralized short until the settlement is finished or we reach max settlements while( settled < max_settlement_volume && find_object(order_id) ) { - auto itr = call_index.lower_bound( price::min( mia.options.short_backing_asset, mia.asset_id ) ); + auto call_itr = call_index.lower_bound( price::min( mia.options.short_backing_asset, mia.asset_id ) ); // Note: there can be no debt position due to individual settlements - if( itr == call_index.end() || itr->debt_type() != mia.asset_id ) // no debt position + if( call_itr == call_index.end() || call_itr->debt_type() != mia.asset_id ) // no debt position { wlog( "No debt position found when processing force settlement ${o}", ("o",order) ); cancel_settle_order( order ); @@ -613,7 +613,8 @@ void database::clear_expired_force_settlements() break; } try { - asset new_settled = match(order, *itr, settlement_price, mia, max_settlement, settlement_fill_price); + asset new_settled = match( order, *call_itr, settlement_price, mia, + max_settlement, settlement_fill_price ); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { if( find_object( order_id ) ) // the settle order hasn't been cancelled @@ -622,12 +623,12 @@ void database::clear_expired_force_settlements() } settled += new_settled; // before hard fork core-342, `new_settled > 0` is always true, we'll have: - // * call order is completely filled (thus itr will change in next loop), or + // * call order is completely filled (thus call_itr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). // // after hard fork core-342, if new_settled > 0, we'll have: - // * call order is completely filled (thus itr will change in next loop), or + // * call order is completely filled (thus call_itr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. @@ -660,32 +661,33 @@ void database::update_expired_feeds() { const asset_bitasset_data_object& b = *itr; ++itr; // not always process begin() because old code skipped updating some assets before hf 615 + // update feeds, check margin calls - if( after_hardfork_615 || b.feed_is_expired_before_hf_615( head_time ) ) + if( !( after_hardfork_615 || b.feed_is_expired_before_hf_615( head_time ) ) ) + continue; + + auto old_median_feed = b.current_feed; + const asset_object& asset_obj = b.asset_id( *this ); + update_bitasset_current_feed( b ); + if( !b.current_feed.settlement_price.is_null() + && !b.current_feed.margin_call_params_equal( old_median_feed ) ) + { + check_call_orders( asset_obj, true, false, &b, true ); + } + // update CER + if( b.need_to_update_cer() ) { - auto old_median_feed = b.current_feed; - const asset_object& asset_obj = b.asset_id( *this ); - update_bitasset_current_feed( b ); - if( !b.current_feed.settlement_price.is_null() - && !b.current_feed.margin_call_params_equal( old_median_feed ) ) + modify( b, []( asset_bitasset_data_object& abdo ) { - check_call_orders( asset_obj, true, false, &b, true ); - } - // update CER - if( b.need_to_update_cer() ) + abdo.asset_cer_updated = false; + abdo.feed_cer_updated = false; + }); + if( asset_obj.options.core_exchange_rate != b.current_feed.core_exchange_rate ) { - modify( b, []( asset_bitasset_data_object& abdo ) + modify( asset_obj, [&b]( asset_object& ao ) { - abdo.asset_cer_updated = false; - abdo.feed_cer_updated = false; + ao.options.core_exchange_rate = b.current_feed.core_exchange_rate; }); - if( asset_obj.options.core_exchange_rate != b.current_feed.core_exchange_rate ) - { - modify( asset_obj, [&b]( asset_object& ao ) - { - ao.options.core_exchange_rate = b.current_feed.core_exchange_rate; - }); - } } } } // for each asset whose feed is expired diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 5726dff1cd..84645f831f 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -114,7 +114,7 @@ namespace graphene { namespace chain { using operation_type = asset_update_feed_producers_operation; void_result do_evaluate( const operation_type& o ); - void_result do_apply( const operation_type& o ); + void_result do_apply( const operation_type& o ) const; const asset_object* asset_to_update = nullptr; }; @@ -161,6 +161,7 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_publish_feed_operation& o ); void_result do_apply( const asset_publish_feed_operation& o ); + private: const asset_object* asset_ptr = nullptr; const asset_bitasset_data_object* bitasset_ptr = nullptr; }; From 61005500bb9ca7f54d31fcd26808b4fe7b552878 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 19:58:07 +0000 Subject: [PATCH 125/258] Refactor asset_settle_evaluator::do_apply --- libraries/chain/asset_evaluator.cpp | 164 ++++++++++++---------------- 1 file changed, 71 insertions(+), 93 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 226eefb078..c00ae2500f 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1108,74 +1108,22 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) { try { database& d = db(); - const auto head_time = d.head_block_time(); + const auto& head_time = d.head_block_time(); + const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time; const auto& bitasset = *bitasset_ptr; extendable_operation_result result; - // Process individual bad debt settlement pool first - asset to_settle = op.amount; - asset individual_settled( 0, bitasset.options.short_backing_asset ); - const asset_object* backing_asset_ptr = nullptr; - const asset_dynamic_data_object* mia_dyn = nullptr; - if( bitasset.has_individual_settlement() ) - { - asset pays; - if( to_settle.amount < bitasset.individual_settlement_debt ) - { - auto settlement_price = bitasset.get_individual_settlement_price(); - individual_settled = to_settle * settlement_price; // round down, in favor of settlement fund - FC_ASSERT( individual_settled.amount > 0, "Settle amount is too small to receive anything due to rounding" ); - pays = individual_settled.multiply_and_round_up( settlement_price ); - } - else - { - pays = bitasset.individual_settlement_debt; - individual_settled = bitasset.individual_settlement_fund; - } - - to_settle -= pays; - d.adjust_balance( op.account, -pays ); - d.modify( bitasset, [&pays,&individual_settled]( asset_bitasset_data_object& obj ){ - obj.individual_settlement_debt -= pays.amount; - obj.individual_settlement_fund -= individual_settled.amount; - }); - mia_dyn = &asset_to_settle->dynamic_asset_data_id(d); - d.modify( *mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ - obj.current_supply -= pays.amount; - }); - backing_asset_ptr = &bitasset.options.short_backing_asset(d); - auto issuer_fees = d.pay_market_fees( fee_paying_account, *backing_asset_ptr, individual_settled, false ); - individual_settled -= issuer_fees; - - if( individual_settled.amount > 0 ) - d.adjust_balance( op.account, individual_settled ); - - // Update current_feed if needed - const auto bdsm = bitasset.get_bad_debt_settlement_method(); - if( bitasset_options::bad_debt_settlement_type::individual_settlement_to_fund == bdsm ) - d.update_bitasset_current_feed( bitasset, true ); - - result.value.paid = vector({ pays }); - result.value.received = vector({ individual_settled }); - result.value.fees = vector({ issuer_fees }); - - if( 0 == to_settle.amount ) - return result; - } - - // Then process global settlement fund or others - auto maint_time = d.get_dynamic_global_properties().next_maintenance_time; + // Process global settlement fund if( bitasset.has_settlement() ) { - if( !mia_dyn ) - mia_dyn = &asset_to_settle->dynamic_asset_data_id(d); + const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); - asset settled_amount = ( to_settle.amount == mia_dyn->current_supply ) + asset settled_amount = ( op.amount.amount == mia_dyn.current_supply ) ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset ) - : to_settle * bitasset.settlement_price; // round down, favors global settlement fund - if( to_settle.amount != mia_dyn->current_supply ) + : op.amount * bitasset.settlement_price; // round down, favors global settlement fund + if( op.amount.amount != mia_dyn.current_supply ) { // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund, @@ -1184,15 +1132,13 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( 0 == settled_amount.amount && !bitasset.is_prediction_market ) { - if( maint_time > HARDFORK_CORE_184_TIME && 0 == individual_settled.amount ) + if( maint_time > HARDFORK_CORE_184_TIME ) FC_THROW( "Settle amount is too small to receive anything due to rounding" ); - else if( maint_time > HARDFORK_CORE_184_TIME ) // and individual_settled.amount > 0 - return result; // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur } - asset pays = to_settle; - if( to_settle.amount != mia_dyn->current_supply + asset pays = op.amount; + if( op.amount.amount != mia_dyn.current_supply && settled_amount.amount != 0 && maint_time > HARDFORK_CORE_342_TIME ) { @@ -1215,9 +1161,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: // performance loss. Needs testing. if( head_time >= HARDFORK_CORE_1780_TIME ) { - if( !backing_asset_ptr ) - backing_asset_ptr = &bitasset.options.short_backing_asset(d); - issuer_fees = d.pay_market_fees( fee_paying_account, *backing_asset_ptr, settled_amount, false ); + issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); settled_amount -= issuer_fees; } @@ -1225,43 +1169,77 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: d.adjust_balance( op.account, settled_amount ); } - d.modify( *mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ + d.modify( mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ obj.current_supply -= pays.amount; }); - if( to_settle.amount == op.amount.amount ) // it means there was no individual settlement - { - result.value.paid = vector({ pays }); - result.value.received = vector({ settled_amount }); - result.value.fees = vector({ issuer_fees }); - } - else - { - result.value.paid->push_back( pays ); - result.value.received->push_back( settled_amount ); - result.value.fees->push_back( issuer_fees ); - } + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); + return result; } - else + + // Process individual bad debt settlement pool + asset to_settle = op.amount; + if( bitasset.has_individual_settlement() ) { - d.adjust_balance( op.account, -to_settle ); - const auto& settle = d.create( - [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { - s.owner = op.account; - s.balance = to_settle; - s.settlement_date = head_time + bitasset.options.force_settlement_delay_sec; - }); - auto id = settle.id; - if( HARDFORK_CORE_2481_PASSED( maint_time ) ) + asset pays( bitasset.individual_settlement_debt, bitasset.asset_id ); + asset settled_amount( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + if( to_settle.amount < bitasset.individual_settlement_debt ) { - d.apply_force_settlement( settle, bitasset ); + auto settlement_price = bitasset.get_individual_settlement_price(); + settled_amount = to_settle * settlement_price; // round down, in favor of settlement fund + FC_ASSERT( settled_amount.amount > 0, "Settle amount is too small to receive anything due to rounding" ); + pays = settled_amount.multiply_and_round_up( settlement_price ); } - result.value.new_objects = flat_set({ id }); + d.adjust_balance( op.account, -pays ); + d.modify( bitasset, [&pays,&settled_amount]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt -= pays.amount; + obj.individual_settlement_fund -= settled_amount.amount; + }); + d.modify( asset_to_settle->dynamic_asset_data_id(d), [&pays]( asset_dynamic_data_object& obj ){ + obj.current_supply -= pays.amount; + }); + auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); + settled_amount -= issuer_fees; - return result; + if( settled_amount.amount > 0 ) + d.adjust_balance( op.account, settled_amount ); + + // Update current_feed since fund price changed + d.update_bitasset_current_feed( bitasset, true ); + + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); + + // If the amount to settle is too small, we return + if( bitasset.has_individual_settlement() ) + return result; + + to_settle -= pays; } + + // Process the rest + d.adjust_balance( op.account, -to_settle ); + const auto& settle = d.create( + [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { + s.owner = op.account; + s.balance = to_settle; + s.settlement_date = head_time + bitasset.options.force_settlement_delay_sec; + }); + + result.value.new_objects = flat_set({ settle.id }); + + if( HARDFORK_CORE_2481_PASSED( maint_time ) ) + { + d.apply_force_settlement( settle, bitasset ); + } + + return result; + } FC_CAPTURE_AND_RETHROW( (op) ) } void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_operation& o) From 7aa02cb78f6fa6ab5eff6d3440cc41e38ee54fcd Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 20:56:20 +0000 Subject: [PATCH 126/258] Disallow BDSM on PM --- libraries/chain/asset_evaluator.cpp | 28 ++++++++++++++++---------- libraries/protocol/asset_ops.cpp | 31 ++++++++++++++++------------- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index c00ae2500f..94e26d25a3 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -70,7 +70,7 @@ namespace detail { if ( !HARDFORK_BSIP_48_75_PASSED( block_time ) ) { // new issuer permissions should not be set until activation of BSIP_48_75 - FC_ASSERT( !(options.issuer_permissions & ~ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK), + FC_ASSERT( 0 == (options.issuer_permissions & (uint16_t)(~ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK)), "New asset issuer permission bits should not be set before HARDFORK_BSIP_48_75_TIME" ); // Note: no check for flags here because we didn't check in the past } @@ -463,8 +463,9 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( dyn_data.current_supply != 0 ) { // new issuer_permissions must be subset of old issuer permissions - FC_ASSERT(!(o.new_options.get_enabled_issuer_permissions_mask() & ~enabled_issuer_permissions_mask), - "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero."); + FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() + & (uint16_t)(~enabled_issuer_permissions_mask) ), + "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero."); // precision can not be changed FC_ASSERT( !o.extensions.value.new_precision.valid(), "Cannot update precision if current supply is non-zero" ); @@ -488,12 +489,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) // Note: if an invalid bit was set, it can be unset regardless of the permissions uint16_t check_bits = ( a.is_market_issued() ? VALID_FLAGS_MASK : UIA_VALID_FLAGS_MASK ); - FC_ASSERT( !((o.new_options.flags ^ a.options.flags) & check_bits & ~enabled_issuer_permissions_mask), + FC_ASSERT( 0 == ( (o.new_options.flags ^ a.options.flags) & check_bits + & (uint16_t)(~enabled_issuer_permissions_mask) ), "Flag change is forbidden by issuer permissions" ); } else { - FC_ASSERT( !((o.new_options.flags ^ a.options.flags) & ~a.options.issuer_permissions), + FC_ASSERT( 0 == ( (o.new_options.flags ^ a.options.flags) & (uint16_t)(~a.options.issuer_permissions) ), "Flag change is forbidden by issuer permissions" ); } @@ -544,7 +546,7 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) database& d = db(); // If we are now disabling force settlements, cancel all open force settlement orders - if( (o.new_options.flags & disable_force_settle) && asset_to_update->can_force_settle() ) + if( 0 != (o.new_options.flags & disable_force_settle) && asset_to_update->can_force_settle() ) { const auto& idx = d.get_index_type().indices().get(); // Funky iteration code because we're removing objects as we go. We have to re-initialize itr every loop instead @@ -675,6 +677,10 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( !current_bitasset_data.has_settlement(), "Cannot update a bitasset after a global settlement has executed" ); + if( current_bitasset_data.is_prediction_market ) + FC_ASSERT( !op.new_options.extensions.value.bad_debt_settlement_method.valid(), + "Can not set bad_debt_settlement_method for Prediction Markets" ); + // TODO simplify code below when made sure operator==(optional,optional) works if( !asset_obj.can_owner_update_mcr() ) { @@ -855,7 +861,7 @@ static bool update_bitasset_object_options( { backing_asset_changed = true; should_update_feeds = true; - if( asset_to_update.options.flags & ( witness_fed_asset | committee_fed_asset ) ) + if( 0 != ( asset_to_update.options.flags & ( witness_fed_asset | committee_fed_asset ) ) ) is_witness_or_committee_fed = true; } @@ -968,8 +974,8 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat const asset_object& a = o.asset_to_update(d); FC_ASSERT(a.is_market_issued(), "Cannot update feed producers on a non-BitAsset."); - FC_ASSERT(!(a.options.flags & committee_fed_asset), "Cannot set feed producers on a committee-fed asset."); - FC_ASSERT(!(a.options.flags & witness_fed_asset), "Cannot set feed producers on a witness-fed asset."); + FC_ASSERT(0 == (a.options.flags & committee_fed_asset), "Cannot set feed producers on a committee-fed asset."); + FC_ASSERT(0 == (a.options.flags & witness_fed_asset), "Cannot set feed producers on a witness-fed asset."); FC_ASSERT( a.issuer == o.issuer, "Only asset issuer can update feed producers of an asset" ); @@ -1282,12 +1288,12 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ } //Verify that the publisher is authoritative to publish a feed - if( base.options.flags & witness_fed_asset ) + if( 0 != ( base.options.flags & witness_fed_asset ) ) { FC_ASSERT( d.get(GRAPHENE_WITNESS_ACCOUNT).active.account_auths.count(o.publisher) > 0, "Only active witnesses are allowed to publish price feeds for this asset" ); } - else if( base.options.flags & committee_fed_asset ) + else if( 0 != ( base.options.flags & committee_fed_asset ) ) { FC_ASSERT( d.get(GRAPHENE_COMMITTEE_ACCOUNT).active.account_auths.count(o.publisher) > 0, "Only active committee members are allowed to publish price feeds for this asset" ); diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 648c71e715..93c07fe685 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -112,13 +112,16 @@ void asset_create_operation::validate()const FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( is_valid_symbol(symbol) ); common_options.validate(); - if( common_options.issuer_permissions - & (disable_force_settle|global_settle|disable_mcr_update|disable_icr_update|disable_mssr_update) ) + if( 0 != ( common_options.issuer_permissions + & (disable_force_settle|global_settle + |disable_mcr_update|disable_icr_update|disable_mssr_update|disable_bdsm_update) ) ) FC_ASSERT( bitasset_opts.valid() ); if( is_prediction_market ) { FC_ASSERT( bitasset_opts.valid(), "Cannot have a User-Issued Asset implement a prediction market." ); - FC_ASSERT( common_options.issuer_permissions & global_settle ); + FC_ASSERT( 0 != (common_options.issuer_permissions & global_settle) ); + FC_ASSERT( !bitasset_opts->extensions.value.bad_debt_settlement_method.valid(), + "Can not set bad_debt_settlement_method for Prediction Markets" ); } if( bitasset_opts ) bitasset_opts->validate(); @@ -279,9 +282,9 @@ void asset_options::validate()const FC_ASSERT( max_market_fee >= 0 && max_market_fee <= GRAPHENE_MAX_SHARE_SUPPLY ); // There must be no high bits in permissions whose meaning is not known. - FC_ASSERT( !(issuer_permissions & ~ASSET_ISSUER_PERMISSION_MASK) ); + FC_ASSERT( 0 == (issuer_permissions & (uint16_t)(~ASSET_ISSUER_PERMISSION_MASK)) ); // The permission-only bits can not be set in flag - FC_ASSERT( !(flags & global_settle), + FC_ASSERT( 0 == (flags & global_settle), "Can not set global_settle flag, it is for issuer permission only" ); // the witness_fed and committee_fed flags cannot be set simultaneously @@ -291,7 +294,7 @@ void asset_options::validate()const core_exchange_rate.quote.asset_id.instance.value == 0 ); if(!whitelist_authorities.empty() || !blacklist_authorities.empty()) - FC_ASSERT( flags & white_list ); + FC_ASSERT( 0 != (flags & white_list) ); for( auto item : whitelist_markets ) { FC_ASSERT( blacklist_markets.find(item) == blacklist_markets.end() ); @@ -306,20 +309,20 @@ void asset_options::validate()const void asset_options::validate_flags( bool is_market_issued )const { - FC_ASSERT( !(flags & ~ASSET_ISSUER_PERMISSION_MASK), + FC_ASSERT( 0 == (flags & (uint16_t)(~ASSET_ISSUER_PERMISSION_MASK)), "Can not set an unknown bit in flags" ); // Note: global_settle is checked in validate(), so do not check again here - FC_ASSERT( !(flags & disable_mcr_update), + FC_ASSERT( 0 == (flags & disable_mcr_update), "Can not set disable_mcr_update flag, it is for issuer permission only" ); - FC_ASSERT( !(flags & disable_icr_update), + FC_ASSERT( 0 == (flags & disable_icr_update), "Can not set disable_icr_update flag, it is for issuer permission only" ); - FC_ASSERT( !(flags & disable_mssr_update), - "Can not set disable_mssr_update flag, it is for issuer permission only" ); - FC_ASSERT( !(flags & disable_bdsm_update), + FC_ASSERT( 0 == (flags & disable_mssr_update), "Can not set disable_mssr_update flag, it is for issuer permission only" ); + FC_ASSERT( 0 == (flags & disable_bdsm_update), + "Can not set disable_bdsm_update flag, it is for issuer permission only" ); if( !is_market_issued ) { - FC_ASSERT( !(flags & ~UIA_ASSET_ISSUER_PERMISSION_MASK), + FC_ASSERT( 0 == (flags & (uint16_t)(~UIA_ASSET_ISSUER_PERMISSION_MASK)), "Can not set a flag for bitassets only to UIA" ); } } @@ -327,7 +330,7 @@ void asset_options::validate_flags( bool is_market_issued )const uint16_t asset_options::get_enabled_issuer_permissions_mask() const { return ( (issuer_permissions & ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK) - | (~issuer_permissions & ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK) ); + | ((uint16_t)(~issuer_permissions) & ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK) ); } void asset_claim_fees_operation::validate()const { From e998ddd5b2fe143aa2663377eeb2b5f5bec6340a Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 21:59:37 +0000 Subject: [PATCH 127/258] Refactor asset_settle_evaluator::do_apply --- libraries/chain/asset_evaluator.cpp | 193 +++++++++++++++------------- 1 file changed, 107 insertions(+), 86 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 94e26d25a3..31b90747ed 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1111,124 +1111,145 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) -{ try { - database& d = db(); +static extendable_operation_result pay_settle_from_gs_fund( database& d, + const asset_settle_evaluator::operation_type& op, + const account_object* fee_paying_account, + const asset_object& asset_to_settle, + const asset_bitasset_data_object& bitasset ) +{ const auto& head_time = d.head_block_time(); const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time; - const auto& bitasset = *bitasset_ptr; + const auto& mia_dyn = asset_to_settle.dynamic_asset_data_id(d); - extendable_operation_result result; + asset settled_amount = ( op.amount.amount == mia_dyn.current_supply ) + ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset ) + : op.amount * bitasset.settlement_price; // round down, favors global settlement fund + if( op.amount.amount != mia_dyn.current_supply ) + { + // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero + FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund, + "Internal error: amount in the global settlement fund is not sufficient to pay the settlement" ); + } - // Process global settlement fund - if( bitasset.has_settlement() ) + if( 0 == settled_amount.amount && !bitasset.is_prediction_market && maint_time > HARDFORK_CORE_184_TIME ) + FC_THROW( "Settle amount is too small to receive anything due to rounding" ); + // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur + + asset pays = op.amount; + if( op.amount.amount != mia_dyn.current_supply + && settled_amount.amount != 0 + && maint_time > HARDFORK_CORE_342_TIME ) { - const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); + pays = settled_amount.multiply_and_round_up( bitasset.settlement_price ); + } - asset settled_amount = ( op.amount.amount == mia_dyn.current_supply ) - ? asset( bitasset.settlement_fund, bitasset.options.short_backing_asset ) - : op.amount * bitasset.settlement_price; // round down, favors global settlement fund - if( op.amount.amount != mia_dyn.current_supply ) - { - // should be strictly < except for PM with zero outcome since in that case bitasset.settlement_fund is zero - FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund, - "Internal error: amount in the global settlement fund is not sufficient to pay the settlement" ); - } + d.adjust_balance( op.account, -pays ); - if( 0 == settled_amount.amount && !bitasset.is_prediction_market ) - { - if( maint_time > HARDFORK_CORE_184_TIME ) - FC_THROW( "Settle amount is too small to receive anything due to rounding" ); - // else do nothing. Before the hf, something for nothing issue (#184, variant F) could occur - } + asset issuer_fees( 0, bitasset.options.short_backing_asset ); + if( settled_amount.amount > 0 ) + { + d.modify( bitasset, [&settled_amount]( asset_bitasset_data_object& obj ){ + obj.settlement_fund -= settled_amount.amount; + }); - asset pays = op.amount; - if( op.amount.amount != mia_dyn.current_supply - && settled_amount.amount != 0 - && maint_time > HARDFORK_CORE_342_TIME ) + // The account who settles pays market fees to the issuer of the collateral asset after HF core-1780 + // + // TODO Check whether the HF check can be removed after the HF. + // Note: even if logically it can be removed, perhaps the removal will lead to a small + // performance loss. Needs testing. + if( head_time >= HARDFORK_CORE_1780_TIME ) { - pays = settled_amount.multiply_and_round_up( bitasset.settlement_price ); + issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); + settled_amount -= issuer_fees; } - d.adjust_balance( op.account, -pays ); - - asset issuer_fees( 0, bitasset.options.short_backing_asset ); if( settled_amount.amount > 0 ) - { - d.modify( bitasset, [&settled_amount]( asset_bitasset_data_object& obj ){ - obj.settlement_fund -= settled_amount.amount; - }); + d.adjust_balance( op.account, settled_amount ); + } - // The account who settles pays market fees to the issuer of the collateral asset after HF core-1780 - // - // TODO Check whether the HF check can be removed after the HF. - // Note: even if logically it can be removed, perhaps the removal will lead to a small - // performance loss. Needs testing. - if( head_time >= HARDFORK_CORE_1780_TIME ) - { - issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); - settled_amount -= issuer_fees; - } + d.modify( mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ + obj.current_supply -= pays.amount; + }); - if( settled_amount.amount > 0 ) - d.adjust_balance( op.account, settled_amount ); - } + extendable_operation_result result; - d.modify( mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ - obj.current_supply -= pays.amount; - }); + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); - result.value.paid = vector({ pays }); - result.value.received = vector({ settled_amount }); - result.value.fees = vector({ issuer_fees }); + return result; +} - return result; +static extendable_operation_result pay_settle_from_individual_pool( database& d, + const asset_settle_evaluator::operation_type& op, + const account_object* fee_paying_account, + const asset_object& asset_to_settle, + const asset_bitasset_data_object& bitasset ) +{ + asset pays( bitasset.individual_settlement_debt, bitasset.asset_id ); + asset settled_amount( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + if( op.amount.amount < bitasset.individual_settlement_debt ) + { + auto settlement_price = bitasset.get_individual_settlement_price(); + settled_amount = op.amount * settlement_price; // round down, in favor of settlement fund + FC_ASSERT( settled_amount.amount > 0, "Settle amount is too small to receive anything due to rounding" ); + pays = settled_amount.multiply_and_round_up( settlement_price ); } - // Process individual bad debt settlement pool - asset to_settle = op.amount; - if( bitasset.has_individual_settlement() ) - { - asset pays( bitasset.individual_settlement_debt, bitasset.asset_id ); - asset settled_amount( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); - if( to_settle.amount < bitasset.individual_settlement_debt ) - { - auto settlement_price = bitasset.get_individual_settlement_price(); - settled_amount = to_settle * settlement_price; // round down, in favor of settlement fund - FC_ASSERT( settled_amount.amount > 0, "Settle amount is too small to receive anything due to rounding" ); - pays = settled_amount.multiply_and_round_up( settlement_price ); - } + d.adjust_balance( op.account, -pays ); + d.modify( bitasset, [&pays,&settled_amount]( asset_bitasset_data_object& obj ){ + obj.individual_settlement_debt -= pays.amount; + obj.individual_settlement_fund -= settled_amount.amount; + }); + d.modify( asset_to_settle.dynamic_asset_data_id(d), [&pays]( asset_dynamic_data_object& obj ){ + obj.current_supply -= pays.amount; + }); + auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); + settled_amount -= issuer_fees; - d.adjust_balance( op.account, -pays ); - d.modify( bitasset, [&pays,&settled_amount]( asset_bitasset_data_object& obj ){ - obj.individual_settlement_debt -= pays.amount; - obj.individual_settlement_fund -= settled_amount.amount; - }); - d.modify( asset_to_settle->dynamic_asset_data_id(d), [&pays]( asset_dynamic_data_object& obj ){ - obj.current_supply -= pays.amount; - }); - auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount, false ); - settled_amount -= issuer_fees; + if( settled_amount.amount > 0 ) + d.adjust_balance( op.account, settled_amount ); - if( settled_amount.amount > 0 ) - d.adjust_balance( op.account, settled_amount ); + // Update current_feed since fund price changed + d.update_bitasset_current_feed( bitasset, true ); + + extendable_operation_result result; - // Update current_feed since fund price changed - d.update_bitasset_current_feed( bitasset, true ); + result.value.paid = vector({ pays }); + result.value.received = vector({ settled_amount }); + result.value.fees = vector({ issuer_fees }); - result.value.paid = vector({ pays }); - result.value.received = vector({ settled_amount }); - result.value.fees = vector({ issuer_fees }); + return result; +} + +operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) +{ try { + database& d = db(); + + const auto& bitasset = *bitasset_ptr; + + // Process global settlement fund + if( bitasset.has_settlement() ) + return pay_settle_from_gs_fund( d, op, fee_paying_account, *asset_to_settle, bitasset ); + + // Process individual bad debt settlement pool + extendable_operation_result result; + asset to_settle = op.amount; + if( bitasset.has_individual_settlement() ) + { + result = pay_settle_from_individual_pool( d, op, fee_paying_account, *asset_to_settle, bitasset ); // If the amount to settle is too small, we return if( bitasset.has_individual_settlement() ) return result; - to_settle -= pays; + to_settle -= result.value.paid->front(); } // Process the rest + const auto& head_time = d.head_block_time(); + const auto& maint_time = d.get_dynamic_global_properties().next_maintenance_time; d.adjust_balance( op.account, -to_settle ); const auto& settle = d.create( [&op,&to_settle,&head_time,&bitasset](force_settlement_object& s) { From 67dd8a11447b76a63b8661a73bfba9cfbdb3d492 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 22:50:50 +0000 Subject: [PATCH 128/258] Add database::find_least_collateralized_short() --- libraries/chain/db_getter.cpp | 36 ++++++++++++++ libraries/chain/db_update.cpp | 49 +++++-------------- .../chain/include/graphene/chain/database.hpp | 8 +++ 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 584c0ecfc9..987fafafb2 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -24,6 +24,8 @@ #include +#include + #include #include #include @@ -156,4 +158,38 @@ const limit_order_object* database::find_bad_debt_settlement_order( const asset_ return nullptr; } +const call_order_object* database::find_least_collateralized_short( const asset_bitasset_data_object& bitasset, + bool force_by_collateral_index )const +{ + bool find_by_collateral = true; + if( !force_by_collateral_index ) + // core-1270 hard fork : call price caching issue + find_by_collateral = ( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME ); + + const call_order_object* call_ptr = nullptr; // place holder + + auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); + + if( !find_by_collateral ) // before core-1270 hard fork, check with call_price + { + const auto& call_price_index = get_index_type().indices().get(); + auto call_itr = call_price_index.lower_bound( call_min ); + if( call_itr != call_price_index.end() ) // no call order + call_ptr = &(*call_itr); + } + else // after core-1270 hard fork, check with collateralization + { + // Note: it is safe to check here even if there is no call order due to individual bad debt settlements + const auto& call_collateral_index = get_index_type().indices().get(); + auto call_itr = call_collateral_index.lower_bound( call_min ); + if( call_itr != call_collateral_index.end() ) // no call order + call_ptr = &(*call_itr); + } + if( nullptr == call_ptr ) + return nullptr; + if( call_ptr->debt_type() != bitasset.asset_id ) // no call order + return nullptr; + return call_ptr; +} + } } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 235dd2377a..4291f0e685 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -208,28 +208,9 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s return false; } - const call_order_object* call_ptr = nullptr; // place holder for the call order with least collateral ratio - - auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); - - if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price - { - const auto& call_price_index = get_index_type().indices().get(); - auto call_itr = call_price_index.lower_bound( call_min ); - if( call_itr == call_price_index.end() ) // no call order - return false; - call_ptr = &(*call_itr); - } - else // after core-1270 hard fork, check with collateralization - { - // Note: it is safe to check here even if there is no call order due to individual bad debt settlements - const auto& call_collateral_index = get_index_type().indices().get(); - auto call_itr = call_collateral_index.lower_bound( call_min ); - if( call_itr == call_collateral_index.end() ) // no call order - return false; - call_ptr = &(*call_itr); - } - if( call_ptr->debt_type() != debt_asset_id ) // no call order + // Find the call order with the least collateral ratio + const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false ); + if( nullptr == call_ptr ) // no call order return false; price highest = settle_price; @@ -352,14 +333,12 @@ static optional get_derived_current_feed_price( const database& db, const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( bdsm_type::no_settlement == bdsm ) { - // Note: it is safe to check here even if there is no call order due to individual bad debt settlements - const auto& call_collateral_index = db.get_index_type().indices().get(); - auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); - auto call_itr = call_collateral_index.lower_bound( call_min ); - if( call_itr != call_collateral_index.end() && call_itr->debt_type() == bitasset.asset_id ) + // Find the call order with the least collateral ratio + const call_order_object* call_ptr = db.find_least_collateralized_short( bitasset, true ); + if( nullptr != call_ptr ) { - // GS if : call_itr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() ) - auto least_collateral = call_itr->collateralization(); + // GS if : call_ptr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() ) + auto least_collateral = call_ptr->collateralization(); auto lowest_callable_feed_price = (~least_collateral) / ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, bitasset.current_feed.maximum_short_squeeze_ratio ); result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); @@ -589,15 +568,13 @@ void database::clear_expired_force_settlements() else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset settlement_price = settlement_fill_price; - // Note: there can be no debt position due to individual settlements, processed below - auto& call_index = get_index_type().indices().get(); asset settled = mia_object.amount(mia.force_settled_volume); // Match against the least collateralized short until the settlement is finished or we reach max settlements while( settled < max_settlement_volume && find_object(order_id) ) { - auto call_itr = call_index.lower_bound( price::min( mia.options.short_backing_asset, mia.asset_id ) ); + auto call_ptr = find_least_collateralized_short( mia, true ); // Note: there can be no debt position due to individual settlements - if( call_itr == call_index.end() || call_itr->debt_type() != mia.asset_id ) // no debt position + if( nullptr == call_ptr ) // no debt position { wlog( "No debt position found when processing force settlement ${o}", ("o",order) ); cancel_settle_order( order ); @@ -613,7 +590,7 @@ void database::clear_expired_force_settlements() break; } try { - asset new_settled = match( order, *call_itr, settlement_price, mia, + asset new_settled = match( order, *call_ptr, settlement_price, mia, max_settlement, settlement_fill_price ); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { @@ -623,12 +600,12 @@ void database::clear_expired_force_settlements() } settled += new_settled; // before hard fork core-342, `new_settled > 0` is always true, we'll have: - // * call order is completely filled (thus call_itr will change in next loop), or + // * call order is completely filled (thus call_ptr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). // // after hard fork core-342, if new_settled > 0, we'll have: - // * call order is completely filled (thus call_itr will change in next loop), or + // * call order is completely filled (thus call_ptr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 4cbde09366..4466de2b96 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -301,6 +301,14 @@ namespace graphene { namespace chain { /// @param a ID of the asset /// @return nullptr if not found, pointer to the limit order if found const limit_order_object* find_bad_debt_settlement_order( const asset_id_type& a )const; + + /// Find the call order with the least collateral ratio + /// @param bitasset The bitasset object + /// @param force_by_collateral_index Whether to forcefully search via the by_collateral index + /// @return nullptr if not found, pointer to the call order if found + const call_order_object* find_least_collateralized_short( const asset_bitasset_data_object& bitasset, + bool force_by_collateral_index )const; + //////////////////// db_init.cpp //////////////////// ///@{ From d1b6bda1f1d67af9aad91444db69770e42ca2a77 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Aug 2021 23:29:55 +0000 Subject: [PATCH 129/258] Update macos-11.0 to macos-11 for Github Actions --- .github/workflows/build-and-test.mac.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.mac.yml b/.github/workflows/build-and-test.mac.yml index ed3874107a..6557de8603 100644 --- a/.github/workflows/build-and-test.mac.yml +++ b/.github/workflows/build-and-test.mac.yml @@ -8,7 +8,7 @@ jobs: name: Build and test in macOS strategy: matrix: - os: [macos-10.15, macos-11.0] + os: [macos-10.15, macos-11] runs-on: ${{ matrix.os }} steps: - name: Install dependencies From 0684f3f9b825d0c5b68344a126a6f7a541e5fb9e Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Aug 2021 19:23:49 +0000 Subject: [PATCH 130/258] Fix a code smell, rename variables for readability --- libraries/chain/db_update.cpp | 54 ++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 4291f0e685..1a9152df7a 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -210,7 +210,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // Find the call order with the least collateral ratio const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false ); - if( nullptr == call_ptr ) // no call order + if( !call_ptr ) // no call order return false; price highest = settle_price; @@ -335,7 +335,7 @@ static optional get_derived_current_feed_price( const database& db, { // Find the call order with the least collateral ratio const call_order_object* call_ptr = db.find_least_collateralized_short( bitasset, true ); - if( nullptr != call_ptr ) + if( call_ptr ) { // GS if : call_ptr->collateralization() < ~( bitasset.median_feed.max_short_squeeze_price() ) auto least_collateral = call_ptr->collateralization(); @@ -478,9 +478,9 @@ void database::clear_expired_force_settlements() itr = settlement_index.lower_bound(current_asset) ) { ++count; - const force_settlement_object& order = *itr; - auto order_id = order.id; - current_asset = order.settlement_asset_id(); + const force_settlement_object& settle_order = *itr; + auto settle_order_id = settle_order.id; + current_asset = settle_order.settlement_asset_id(); const asset_object& mia_object = get(current_asset); const asset_bitasset_data_object& mia = mia_object.bitasset_data(*this); @@ -495,18 +495,18 @@ void database::clear_expired_force_settlements() if( mia.has_settlement() ) { ilog( "Canceling a force settlement because of black swan" ); - cancel_settle_order( order ); + cancel_settle_order( settle_order ); continue; } // Has this order not reached its settlement date? - if( order.settlement_date > head_time ) + if( settle_order.settlement_date > head_time ) { if( next_asset() ) { if( extra_dump ) { - ilog( "next_asset() returned true when order.settlement_date > head_block_time()" ); + ilog( "next_asset() returned true when settle_order.settlement_date > head_block_time()" ); } continue; } @@ -517,14 +517,14 @@ void database::clear_expired_force_settlements() { ilog("Canceling a force settlement in ${asset} because settlement price is null", ("asset", mia_object.symbol)); - cancel_settle_order(order); + cancel_settle_order(settle_order); continue; } if( GRAPHENE_100_PERCENT == mia.options.force_settlement_offset_percent ) // settle something for nothing { ilog( "Canceling a force settlement in ${asset} because settlement offset is 100%", ("asset", mia_object.symbol)); - cancel_settle_order(order); + cancel_settle_order(settle_order); continue; } if( max_settlement_volume.asset_id != current_asset ) @@ -557,12 +557,12 @@ void database::clear_expired_force_settlements() if( before_core_hardfork_342 ) { - auto& pays = order.balance; - auto receives = (order.balance * mia.current_feed.settlement_price); + auto& pays = settle_order.balance; + auto receives = (settle_order.balance * mia.current_feed.settlement_price); receives.amount = static_cast( fc::uint128_t(receives.amount.value) * (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / GRAPHENE_100_PERCENT ); - assert(receives <= order.balance * mia.current_feed.settlement_price); + assert(receives <= settle_order.balance * mia.current_feed.settlement_price); settlement_price = pays / receives; } else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset @@ -570,50 +570,52 @@ void database::clear_expired_force_settlements() asset settled = mia_object.amount(mia.force_settled_volume); // Match against the least collateralized short until the settlement is finished or we reach max settlements - while( settled < max_settlement_volume && find_object(order_id) ) + while( settled < max_settlement_volume && find_object(settle_order_id) ) { - auto call_ptr = find_least_collateralized_short( mia, true ); + const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); // Note: there can be no debt position due to individual settlements - if( nullptr == call_ptr ) // no debt position + if( !call_ptr ) // no debt position { - wlog( "No debt position found when processing force settlement ${o}", ("o",order) ); - cancel_settle_order( order ); + wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); + cancel_settle_order( settle_order ); break; } asset max_settlement = max_settlement_volume - settled; - if( order.balance.amount == 0 ) + if( settle_order.balance.amount == 0 ) { wlog( "0 settlement detected" ); - cancel_settle_order( order ); + cancel_settle_order( settle_order ); break; } try { - asset new_settled = match( order, *call_ptr, settlement_price, mia, + asset new_settled = match( settle_order, *call_ptr, settlement_price, mia, max_settlement, settlement_fill_price ); if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { - if( find_object( order_id ) ) // the settle order hasn't been cancelled + if( find_object( settle_order_id ) ) // the settle order hasn't been cancelled current_asset_finished = true; break; } settled += new_settled; // before hard fork core-342, `new_settled > 0` is always true, we'll have: // * call order is completely filled (thus call_ptr will change in next loop), or - // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or + // * settle order is completely filled (thus find_object(settle_order_id) will be false so will + // break out), or // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). // // after hard fork core-342, if new_settled > 0, we'll have: // * call order is completely filled (thus call_ptr will change in next loop), or - // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or + // * settle order is completely filled (thus find_object(settle_order_id) will be false so will + // break out), or // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. } catch ( const black_swan_exception& e ) { wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}", - ("o", order)("e", e.to_detail_string()) ); - cancel_settle_order( order ); + ("o", settle_order)("e", e.to_detail_string()) ); + cancel_settle_order( settle_order ); break; } } From d9180f05ba6a7602491c09eee0522731d674cccf Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Aug 2021 21:43:31 +0000 Subject: [PATCH 131/258] Refactor database::clear_expired_force_settlements to reduce the number of levels of nested if, for, or while statements --- libraries/chain/db_update.cpp | 321 ++++++++++++++++------------------ 1 file changed, 152 insertions(+), 169 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 1a9152df7a..771118f127 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -176,7 +176,7 @@ void database::clear_expired_proposals() /** * let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral) - * let SP = current median feed's Settlement Price + * let SP = current median feed's Settlement Price * let LC = the least collateralized call order's swan price (debt/collateral) * * If there is no valid price feed or no bids then there is no black swan. @@ -311,7 +311,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s else globally_settle_asset(mia, ~least_collateral ); return true; - } + } return false; } @@ -429,11 +429,6 @@ void database::clear_expired_orders() void database::clear_expired_force_settlements() { try { // Process expired force settlement orders - auto head_time = head_block_time(); - auto maint_time = get_dynamic_global_properties().next_maintenance_time; - - bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing - bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding // TODO Possible performance optimization. Looping through all assets is not ideal. // - One idea is to check time first, if any expired settlement found, check asset. @@ -441,191 +436,179 @@ void database::clear_expired_force_settlements() // skip due to volume limit. // - Instead, maintain some data e.g. (whether_force_settle_volome_meets, first_settle_time) // in bitasset_data object and index by them, then we could process here faster. - auto& settlement_index = get_index_type().indices().get(); - if( !settlement_index.empty() ) - { - asset_id_type current_asset = settlement_index.begin()->settlement_asset_id(); - asset max_settlement_volume; - price settlement_fill_price; - price settlement_price; - bool current_asset_finished = false; - bool extra_dump = false; - - auto next_asset = [¤t_asset, ¤t_asset_finished, &settlement_index, &extra_dump] { - auto bound = settlement_index.upper_bound(current_asset); - if( bound == settlement_index.end() ) - { - if( extra_dump ) - { - ilog( "next_asset() returning false" ); - } - return false; - } - if( extra_dump ) - { - ilog( "next_asset returning true, bound is ${b}", ("b", *bound) ); - } - current_asset = bound->settlement_asset_id(); - current_asset_finished = false; - return true; - }; + const auto& settlement_index = get_index_type().indices().get(); + if( settlement_index.empty() ) + return; - uint32_t count = 0; + const auto& head_time = head_block_time(); + const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; - // At each iteration, we either consume the current order and remove it, or we move to the next asset - for( auto itr = settlement_index.lower_bound(current_asset); - itr != settlement_index.end(); - itr = settlement_index.lower_bound(current_asset) ) - { - ++count; - const force_settlement_object& settle_order = *itr; - auto settle_order_id = settle_order.id; - current_asset = settle_order.settlement_asset_id(); - const asset_object& mia_object = get(current_asset); - const asset_bitasset_data_object& mia = mia_object.bitasset_data(*this); + const bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing + const bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding - extra_dump = ((count >= 1000) && (count <= 1020)); + asset_id_type current_asset = settlement_index.begin()->settlement_asset_id(); + const asset_object* mia_object_ptr = &get(current_asset); + const asset_bitasset_data_object* mia_ptr = &mia_object_ptr->bitasset_data(*this); - if( extra_dump ) - { - wlog( "clear_expired_orders() dumping extra data for iteration ${c}", ("c", count) ); - ilog( "head_block_num is ${hb} current_asset is ${a}", ("hb", head_block_num())("a", current_asset) ); - } + asset max_settlement_volume; + price settlement_fill_price; + price settlement_price; + bool current_asset_finished = false; - if( mia.has_settlement() ) - { - ilog( "Canceling a force settlement because of black swan" ); - cancel_settle_order( settle_order ); - continue; - } + auto next_asset = [¤t_asset, &mia_object_ptr, &mia_ptr, ¤t_asset_finished, &settlement_index, this] { + const auto bound = settlement_index.upper_bound(current_asset); + if( bound == settlement_index.end() ) + return false; + current_asset = bound->settlement_asset_id(); + mia_object_ptr = &get(current_asset); + mia_ptr = &mia_object_ptr->bitasset_data(*this); + current_asset_finished = false; + return true; + }; + + // At each iteration, we either consume the current order and remove it, or we move to the next asset + for( auto itr = settlement_index.lower_bound(current_asset); + itr != settlement_index.end(); + itr = settlement_index.lower_bound(current_asset) ) + { + const force_settlement_object& settle_order = *itr; + auto settle_order_id = settle_order.id; - // Has this order not reached its settlement date? - if( settle_order.settlement_date > head_time ) - { - if( next_asset() ) - { - if( extra_dump ) - { - ilog( "next_asset() returned true when settle_order.settlement_date > head_block_time()" ); - } - continue; - } - break; - } - // Can we still settle in this asset? - if( mia.current_feed.settlement_price.is_null() ) - { - ilog("Canceling a force settlement in ${asset} because settlement price is null", - ("asset", mia_object.symbol)); - cancel_settle_order(settle_order); + if( current_asset != settle_order.settlement_asset_id() ) + { + current_asset = settle_order.settlement_asset_id(); + mia_object_ptr = &get(current_asset); + mia_ptr = &mia_object_ptr->bitasset_data(*this); + // Note: we did not reset current_asset_finished to false here, it is OK, + // because current_asset should not have changed if current_asset_finished is true + } + const asset_object& mia_object = *mia_object_ptr; + const asset_bitasset_data_object& mia = *mia_ptr; + + if( mia.has_settlement() ) + { + ilog( "Canceling a force settlement because of black swan" ); + cancel_settle_order( settle_order ); + continue; + } + + // Has this order not reached its settlement date? + if( settle_order.settlement_date > head_time ) + { + if( next_asset() ) continue; - } - if( GRAPHENE_100_PERCENT == mia.options.force_settlement_offset_percent ) // settle something for nothing - { - ilog( "Canceling a force settlement in ${asset} because settlement offset is 100%", - ("asset", mia_object.symbol)); - cancel_settle_order(settle_order); + break; + } + // Can we still settle in this asset? + if( mia.current_feed.settlement_price.is_null() ) + { + ilog("Canceling a force settlement in ${asset} because settlement price is null", + ("asset", mia_object.symbol)); + cancel_settle_order(settle_order); + continue; + } + if( GRAPHENE_100_PERCENT == mia.options.force_settlement_offset_percent ) // settle something for nothing + { + ilog( "Canceling a force settlement in ${asset} because settlement offset is 100%", + ("asset", mia_object.symbol)); + cancel_settle_order(settle_order); + continue; + } + // Note: although current supply would decrease during filling the settle orders, + // we always calculate with the initial value + if( max_settlement_volume.asset_id != current_asset ) + max_settlement_volume = mia_object.amount( mia.max_force_settlement_volume( + mia_object.dynamic_data(*this).current_supply ) ); + // When current_asset_finished is true, this would be the 2nd time processing the same order. + // In this case, we move to the next asset. + if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) + { + /* + ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}", + ("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null()) + ("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume)); // for debug + */ + if( next_asset() ) continue; - } - if( max_settlement_volume.asset_id != current_asset ) - max_settlement_volume = mia_object.amount( mia.max_force_settlement_volume( - mia_object.dynamic_data(*this).current_supply ) ); - // When current_asset_finished is true, this would be the 2nd time processing the same order. - // In this case, we move to the next asset. - if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) - { - /* - ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}", - ("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null()) - ("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume)); - */ - if( next_asset() ) - { - if( extra_dump ) - { - ilog( "next_asset() returned true when mia.force_settled_volume >= max_settlement_volume.amount" ); - } - continue; - } - break; - } + break; + } + + if( settlement_fill_price.base.asset_id != current_asset ) // only calculate once per asset + settlement_fill_price = mia.current_feed.settlement_price + / ratio_type( GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent, + GRAPHENE_100_PERCENT ); - if( settlement_fill_price.base.asset_id != current_asset ) // only calculate once per asset - settlement_fill_price = mia.current_feed.settlement_price - / ratio_type( GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent, - GRAPHENE_100_PERCENT ); + if( before_core_hardfork_342 ) + { + auto& pays = settle_order.balance; + auto receives = (settle_order.balance * mia.current_feed.settlement_price); + receives.amount = static_cast( ( fc::uint128_t(receives.amount.value) * + (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) ) / + GRAPHENE_100_PERCENT ); + assert(receives <= settle_order.balance * mia.current_feed.settlement_price); + settlement_price = pays / receives; + } + else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset + settlement_price = settlement_fill_price; - if( before_core_hardfork_342 ) + asset settled = mia_object.amount(mia.force_settled_volume); + // Match against the least collateralized short until the settlement is finished or we reach max settlements + while( settled < max_settlement_volume && find_object(settle_order_id) ) + { + const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); + // Note: there can be no debt position due to individual settlements + if( !call_ptr ) // no debt position { - auto& pays = settle_order.balance; - auto receives = (settle_order.balance * mia.current_feed.settlement_price); - receives.amount = static_cast( fc::uint128_t(receives.amount.value) * - (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / - GRAPHENE_100_PERCENT ); - assert(receives <= settle_order.balance * mia.current_feed.settlement_price); - settlement_price = pays / receives; + wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); + cancel_settle_order( settle_order ); + break; } - else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset - settlement_price = settlement_fill_price; - asset settled = mia_object.amount(mia.force_settled_volume); - // Match against the least collateralized short until the settlement is finished or we reach max settlements - while( settled < max_settlement_volume && find_object(settle_order_id) ) + if( 0 == settle_order.balance.amount ) { - const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); - // Note: there can be no debt position due to individual settlements - if( !call_ptr ) // no debt position - { - wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); - cancel_settle_order( settle_order ); - break; - } + wlog( "0 settlement detected" ); + cancel_settle_order( settle_order ); + break; + } + try { asset max_settlement = max_settlement_volume - settled; - if( settle_order.balance.amount == 0 ) + asset new_settled = match( settle_order, *call_ptr, settlement_price, mia, + max_settlement, settlement_fill_price ); + if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { - wlog( "0 settlement detected" ); - cancel_settle_order( settle_order ); - break; - } - try { - asset new_settled = match( settle_order, *call_ptr, settlement_price, mia, - max_settlement, settlement_fill_price ); - if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order - { - if( find_object( settle_order_id ) ) // the settle order hasn't been cancelled - current_asset_finished = true; - break; - } - settled += new_settled; - // before hard fork core-342, `new_settled > 0` is always true, we'll have: - // * call order is completely filled (thus call_ptr will change in next loop), or - // * settle order is completely filled (thus find_object(settle_order_id) will be false so will - // break out), or - // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). - // - // after hard fork core-342, if new_settled > 0, we'll have: - // * call order is completely filled (thus call_ptr will change in next loop), or - // * settle order is completely filled (thus find_object(settle_order_id) will be false so will - // break out), or - // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, - // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. - } - catch ( const black_swan_exception& e ) { - wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}", - ("o", settle_order)("e", e.to_detail_string()) ); - cancel_settle_order( settle_order ); + // current asset is finished when the settle order hasn't been cancelled + current_asset_finished = ( nullptr != find_object( settle_order_id ) ); break; } + settled += new_settled; + // before hard fork core-342, `new_settled > 0` is always true, we'll have: + // * call order is completely filled (thus call_ptr will change in next loop), or + // * settle order is completely filled (thus find_object(settle_order_id) will be false so will + // break out), or + // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). + // + // after hard fork core-342, if new_settled > 0, we'll have: + // * call order is completely filled (thus call_ptr will change in next loop), or + // * settle order is completely filled (thus find_object(settle_order_id) will be false so will + // break out), or + // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, + // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. } - if( mia.force_settled_volume != settled.amount ) - { - modify(mia, [&settled](asset_bitasset_data_object& b) { - b.force_settled_volume = settled.amount; - }); + catch ( const black_swan_exception& e ) { + wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}", + ("o", settle_order)("e", e.to_detail_string()) ); + cancel_settle_order( settle_order ); + break; } } + if( mia.force_settled_volume != settled.amount ) + { + modify(mia, [&settled](asset_bitasset_data_object& b) { + b.force_settled_volume = settled.amount; + }); + } } } FC_CAPTURE_AND_RETHROW() } From 183b80f9d33d81c9ddf695526e7e2030e1c8877d Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Aug 2021 22:32:33 +0000 Subject: [PATCH 132/258] Update coding style, fix code smells --- libraries/chain/asset_evaluator.cpp | 2 +- libraries/chain/db_getter.cpp | 2 +- libraries/chain/db_market.cpp | 29 ++++++++++++++--------------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 31b90747ed..024d78c88d 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -724,7 +724,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( !current_bitasset_data.has_individual_settlement(), "Unable to update BDSM when the individual bad debt settlement pool is not empty" ); else if( bdsm_type::individual_settlement_to_order == old_bdsm ) - FC_ASSERT( nullptr == d.find_bad_debt_settlement_order( op.asset_to_update ), + FC_ASSERT( !d.find_bad_debt_settlement_order( op.asset_to_update ), "Unable to update BDSM when there exists a bad debt settlement order" ); // Since we do not allow updating in some cases (above), only check no_settlement here diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 987fafafb2..63bd436453 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -185,7 +185,7 @@ const call_order_object* database::find_least_collateralized_short( const asset_ if( call_itr != call_collateral_index.end() ) // no call order call_ptr = &(*call_itr); } - if( nullptr == call_ptr ) + if( !call_ptr ) return nullptr; if( call_ptr->debt_type() != bitasset.asset_id ) // no call order return nullptr; diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 296ae4ea3f..d62dc871c2 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -153,7 +153,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, // Move the (individual) bad-debt settlement order to the GS fund const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); - if( limit_ptr != nullptr ) + if( limit_ptr ) { collateral_gathered.amount += limit_ptr->for_sale; remove( *limit_ptr ); @@ -200,7 +200,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, else // settle to order { const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); - if( limit_ptr != nullptr ) + if( limit_ptr ) { modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { obj.for_sale += fund_receives.amount; @@ -360,8 +360,8 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ // if there is any CORE fee to deduct, redirect it to referral program if( core_cancel_fee.amount > 0 ) { - seller_acc_stats = &order.seller( *this ).statistics( *this ); - modify( *seller_acc_stats, [&]( account_statistics_object& obj ) { + seller_acc_stats = &get_account_stats_by_owner( order.seller ); + modify( *seller_acc_stats, [&core_cancel_fee, this]( account_statistics_object& obj ) { obj.pay_fee( core_cancel_fee.amount, get_global_properties().parameters.cashback_vesting_threshold ); } ); deferred_fee -= core_cancel_fee.amount; @@ -382,7 +382,7 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ share_type cancel_fee_amount = static_cast(fee128); // cancel_fee should be positive, pay it to asset's accumulated_fees fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); - modify( *fee_asset_dyn_data, [&](asset_dynamic_data_object& addo) { + modify( *fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) { addo.accumulated_fees += cancel_fee_amount; }); // cancel_fee should be no more than deferred_paid_fee @@ -397,9 +397,9 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ auto refunded = order.amount_for_sale(); if( refunded.asset_id == asset_id_type() ) { - if( seller_acc_stats == nullptr ) - seller_acc_stats = &order.seller( *this ).statistics( *this ); - modify( *seller_acc_stats, [&]( account_statistics_object& obj ) { + if( !seller_acc_stats ) + seller_acc_stats = &get_account_stats_by_owner( order.seller ); + modify( *seller_acc_stats, [&refunded]( account_statistics_object& obj ) { obj.total_core_in_orders -= refunded.amount; }); } @@ -418,7 +418,7 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ { adjust_balance(order.seller, deferred_paid_fee); // be here, must have: fee_asset != CORE - if( fee_asset_dyn_data == nullptr ) + if( !fee_asset_dyn_data ) fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); modify( *fee_asset_dyn_data, [&](asset_dynamic_data_object& addo) { addo.fee_pool += deferred_fee; @@ -498,7 +498,7 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord check_call_orders(receive_asset); // the other side, same as above const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); - if( updated_order_object == nullptr ) + if( !updated_order_object ) return true; if( head_block_time() <= HARDFORK_555_TIME ) return false; @@ -694,7 +694,7 @@ bool database::apply_order(const limit_order_object& new_order_object) } const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); - if( updated_order_object == nullptr ) + if( !updated_order_object ) return true; // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() @@ -1106,8 +1106,7 @@ asset database::match_impl( const force_settlement_object& settle, { call_pays.amount = call.collateral; match_price = call_debt / call_collateral; - fill_price = ( margin_call_pays_ratio != nullptr ) ? ( match_price / (*margin_call_pays_ratio) ) - : match_price; + fill_price = margin_call_pays_ratio ? ( match_price / (*margin_call_pays_ratio) ) : match_price; } settle_receives = call_receives.multiply_and_round_up( fill_price ); } @@ -1175,7 +1174,7 @@ asset database::match_impl( const force_settlement_object& settle, // price changed, update call_receives // round up to mitigate rounding issues (hf core-342) call_receives = call_pays.multiply_and_round_up( match_price ); // round up // update fill price and settle_receives - if( margin_call_pays_ratio != nullptr ) // check to be defensive + if( margin_call_pays_ratio ) // check to be defensive { fill_price = match_price / (*margin_call_pays_ratio); settle_receives = call_receives * fill_price; // round down here, in favor of call order @@ -1961,7 +1960,7 @@ asset database::pay_market_fees(const account_object* seller, const asset_object asset reward = recv_asset.amount(0); auto is_rewards_allowed = [&recv_asset, seller]() { - if (seller == nullptr) + if ( !seller ) return false; const auto &white_list = recv_asset.options.extensions.value.whitelist_market_fee_sharing; return ( !white_list || (*white_list).empty() From f5c0fbfa4066c2e04b43ecf5cb570134c3107850 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Aug 2021 22:51:30 +0000 Subject: [PATCH 133/258] Remove code that was commented out --- libraries/chain/db_update.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 771118f127..023ccec9e9 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -523,11 +523,6 @@ void database::clear_expired_force_settlements() // In this case, we move to the next asset. if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) { - /* - ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}", - ("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null()) - ("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume)); // for debug - */ if( next_asset() ) continue; break; From f58a0cb0e50243091788bb037938df608a9d3b6c Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 19 Aug 2021 17:01:29 +0000 Subject: [PATCH 134/258] Add a comment --- libraries/chain/db_update.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 023ccec9e9..5a73827b57 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -436,6 +436,7 @@ void database::clear_expired_force_settlements() // skip due to volume limit. // - Instead, maintain some data e.g. (whether_force_settle_volome_meets, first_settle_time) // in bitasset_data object and index by them, then we could process here faster. + // Note: due to rounding, even when settled < max_volume, it is still possible that we have to skip const auto& settlement_index = get_index_type().indices().get(); if( settlement_index.empty() ) return; From 5dd32b25b63c53bd04ade5fbc3b39584dd570ec7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 19 Aug 2021 17:01:45 +0000 Subject: [PATCH 135/258] Add GS price tests --- tests/tests/market_tests.cpp | 101 +++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 5d227fdd13..92e73dbcff 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1448,6 +1448,107 @@ BOOST_AUTO_TEST_CASE(mcfr_blackswan_test_after_hf_core_2481) } FC_LOG_AND_RETHROW() } +/*** + * Tests GS price + */ +BOOST_AUTO_TEST_CASE(gs_price_test) +{ try { + // Proceeds to a desired hard fork time + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + if( hf2481 ) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 800% collateral, call price is 40/1.75 CORE/USD = 160/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(40000)); + call_order_id_type call2_id = call2.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 40000, call2.collateral.value ); + BOOST_CHECK_EQUAL( 2000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // No margin call at this moment + + // This order is right at of the first debt position + limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(2000), core.amount(30000))->id; + + BOOST_CHECK_EQUAL( 2000, sell_mid(db).for_sale.value ); + + BOOST_CHECK_EQUAL( 1000, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15000, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to a value so that mssp is equal to call's collateralization + current_feed.settlement_price = bitusd.amount( 11 ) / core.amount(150); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 11/150, mssp = (11/150)*(10/11) = 1/15 + + if( !hf2481 ) + { + // GS occurs + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + // sell order did not change + BOOST_CHECK_EQUAL( 2000, sell_mid(db).for_sale.value ); + } + else + { + // GS does not occur, call got filled + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + + // sell order got half-filled + BOOST_CHECK_EQUAL( 1000, sell_mid(db).for_sale.value ); + + // call2 did not change + BOOST_CHECK_EQUAL( 1000, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call2_id(db).collateral.value ); + } + + // generate a block to include operations above + BOOST_TEST_MESSAGE( "Generating a new block" ); + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(gs_price_test_after_hf2481) +{ + hf2481 = true; + INVOKE(gs_price_test); +} + /*** * Tests a scenario about rounding errors related to margin call fee */ From b1db7e6fa50f7f52eb6d70f3fa3ce3d8330dc305 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 01:57:32 +0000 Subject: [PATCH 136/258] Undo unneeded changes in bsip48_75_tests wrt bdsm --- tests/tests/bsip48_75_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 6721fd0837..14d5fe1d51 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK & ~disable_bdsm_update; + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set @@ -992,7 +992,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK & ~disable_bdsm_update; + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set From 5518907970d8fd67914bc9ca50ad147a642f5e64 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 04:01:27 +0000 Subject: [PATCH 137/258] Disallow non-UIA issuer permission bits on UIA --- libraries/chain/asset_evaluator.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 024d78c88d..9b4d53416c 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -433,6 +433,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) detail::check_asset_update_extensions_hf_bsip_48_75( now, o.extensions.value ); bool hf_bsip_48_75_passed = ( HARDFORK_BSIP_48_75_PASSED( now ) ); + bool hf_core_2467_passed = ( HARDFORK_CORE_2467_PASSED( next_maint_time ) ); const asset_object& a = o.asset_to_update(d); auto a_copy = a; @@ -446,6 +447,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } + // Unable to set non-UIA issuer permission bits on UIA + if( hf_core_2467_passed && !a.is_market_issued() ) + { + FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ), + "Unable to set non-UIA issuer permission bits on UIA" ); + } + uint16_t enabled_issuer_permissions_mask = a.options.get_enabled_issuer_permissions_mask(); if( hf_bsip_48_75_passed && a.is_market_issued() ) { @@ -463,7 +471,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( dyn_data.current_supply != 0 ) { // new issuer_permissions must be subset of old issuer permissions - FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() + if( hf_core_2467_passed && !a.is_market_issued() ) // for UIA, ignore non-UIA bits + FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask() + & (uint16_t)(~enabled_issuer_permissions_mask) ) & UIA_ASSET_ISSUER_PERMISSION_MASK ), + "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero, " + "unless to unset non-UIA issuer permission bits for UIA."); + else + FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() & (uint16_t)(~enabled_issuer_permissions_mask) ), "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero."); // precision can not be changed From 7b7eabec2780ab4d3b6397f4a1804463bcbbf38d Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 04:03:07 +0000 Subject: [PATCH 138/258] Update logic about bdsm hf time in bsip48_75_tests --- tests/tests/bsip48_75_tests.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 14d5fe1d51..ee97c25211 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -1063,7 +1063,11 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // advance to bsip48/75 hard fork generate_blocks( HARDFORK_BSIP_48_75_TIME ); if( hf2467 ) - generate_blocks( HARDFORK_CORE_2467_TIME ); + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } set_expiration( db, trx ); // take a look at flags of UIA From 23b78d35032541d3dc613a40053c539300160749 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 06:17:08 +0000 Subject: [PATCH 139/258] Disallow disable_bdsm_update permission bit on PM --- libraries/chain/asset_evaluator.cpp | 44 ++++++++++++++++------------- libraries/protocol/asset_ops.cpp | 1 + 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9b4d53416c..553f5f15c4 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -447,24 +447,29 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } - // Unable to set non-UIA issuer permission bits on UIA - if( hf_core_2467_passed && !a.is_market_issued() ) + if( a.is_market_issued() ) + bitasset_data = &a.bitasset_data(d); + + if( hf_core_2467_passed ) { - FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ), - "Unable to set non-UIA issuer permission bits on UIA" ); + // Unable to set non-UIA issuer permission bits on UIA + if( !a.is_market_issued() ) + FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ), + "Unable to set non-UIA issuer permission bits on UIA" ); + // Unable to set disable_bdsm_update issuer permission bit on PM + else if( bitasset_data->is_prediction_market ) + FC_ASSERT( 0 == ( o.new_options.issuer_permissions & disable_bdsm_update ), + "Unable to set disable_bdsm_update issuer permission bit on PM" ); + // else do nothing } uint16_t enabled_issuer_permissions_mask = a.options.get_enabled_issuer_permissions_mask(); - if( hf_bsip_48_75_passed && a.is_market_issued() ) + if( hf_bsip_48_75_passed && a.is_market_issued() && bitasset_data->is_prediction_market ) { - bitasset_data = &a.bitasset_data(d); - if( bitasset_data->is_prediction_market ) - { - // Note: if the global_settle permission was unset, it should be corrected - FC_ASSERT( a_copy.can_global_settle(), - "The global_settle permission should be enabled for prediction markets" ); - enabled_issuer_permissions_mask |= global_settle; - } + // Note: if the global_settle permission was unset, it should be corrected + FC_ASSERT( a_copy.can_global_settle(), + "The global_settle permission should be enabled for prediction markets" ); + enabled_issuer_permissions_mask |= global_settle; } const auto& dyn_data = a.dynamic_asset_data_id(d); @@ -474,8 +479,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( hf_core_2467_passed && !a.is_market_issued() ) // for UIA, ignore non-UIA bits FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask() & (uint16_t)(~enabled_issuer_permissions_mask) ) & UIA_ASSET_ISSUER_PERMISSION_MASK ), - "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero, " - "unless to unset non-UIA issuer permission bits for UIA."); + "Cannot reinstate previously revoked issuer permissions on a UIA if current supply is non-zero, " + "unless to unset non-UIA issuer permission bits."); + else if( hf_core_2467_passed && bitasset_data->is_prediction_market ) // for PM, ignore disable_bdsm_update + FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask() + & (uint16_t)(~enabled_issuer_permissions_mask) ) & (uint16_t)(~disable_bdsm_update) ), + "Cannot reinstate previously revoked issuer permissions on a PM if current supply is non-zero, " + "unless to unset the disable_bdsm_update issuer permission bit."); else FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() & (uint16_t)(~enabled_issuer_permissions_mask) ), @@ -527,11 +537,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) "Specified a new precision but it does not change" ); if( a.is_market_issued() ) - { - if( !bitasset_data ) - bitasset_data = &asset_to_update->bitasset_data(d); FC_ASSERT( !bitasset_data->is_prediction_market, "Can not update precision of a prediction market" ); - } // If any other asset is backed by this asset, this asset's precision can't be updated const auto& idx = d.get_index_type() diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 93c07fe685..a723338827 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -120,6 +120,7 @@ void asset_create_operation::validate()const { FC_ASSERT( bitasset_opts.valid(), "Cannot have a User-Issued Asset implement a prediction market." ); FC_ASSERT( 0 != (common_options.issuer_permissions & global_settle) ); + FC_ASSERT( 0 == (common_options.issuer_permissions & disable_bdsm_update) ); FC_ASSERT( !bitasset_opts->extensions.value.bad_debt_settlement_method.valid(), "Can not set bad_debt_settlement_method for Prediction Markets" ); } From 370080079e55b457fa88dad53ae98f4226a1e0a3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 06:17:43 +0000 Subject: [PATCH 140/258] Add some tests for BDSM --- tests/tests/bdsm_tests.cpp | 667 +++++++++++++++++++++++++++++++++++++ 1 file changed, 667 insertions(+) create mode 100644 tests/tests/bdsm_tests.cpp diff --git a/tests/tests/bdsm_tests.cpp b/tests/tests/bdsm_tests.cpp new file mode 100644 index 0000000000..dd7288d591 --- /dev/null +++ b/tests/tests/bdsm_tests.cpp @@ -0,0 +1,667 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( bdsm_tests, database_fixture ) + +/// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork +BOOST_AUTO_TEST_CASE( hardfork_protection_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update; + uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + + uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; + + vector ops; + + // Testing asset_create_operation + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = bitflag; + acop.common_options.issuer_permissions = old_bitmask; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 3; + + trx.operations.clear(); + trx.operations.push_back( acop ); + + { + auto& op = trx.operations.front().get(); + + // Unable to set new permission bit + op.common_options.issuer_permissions = new_bitmask; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.common_options.issuer_permissions = old_bitmask; + + // Unable to set new extensions in bitasset options + op.bitasset_opts->extensions.value.bad_debt_settlement_method = 0; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.bitasset_opts->extensions.value.bad_debt_settlement_method = {}; + + acop = op; + } + + // Able to create asset without new data + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& samcoin = db.get(ptx.operation_results[0].get()); + asset_id_type samcoin_id = samcoin.id; + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 ); + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 ); + + // Able to propose the good operation + propose( acop ); + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.market_fee_percent = 200; + + // Unable to set new permission bit + op.new_options.issuer_permissions = new_bitmask; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.issuer_permissions = old_bitmask; + + auop = op; + } + + // Able to update asset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 ); + + // Able to propose the good operation + propose( auop ); + + // Testing asset_update_bitasset_operation + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = samcoin_id; + aubop.new_options = samcoin_id(db).bitasset_data(db).options; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.minimum_feeds = 1; + + // Unable to set new extensions + op.new_options.extensions.value.bad_debt_settlement_method = 1; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.extensions.value.bad_debt_settlement_method = {}; + + aubop = op; + } + + // Able to update bitasset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 ); + + // Able to propose the good operation + propose( aubop ); + + // Unable to propose the invalid operations + for( const operation& op : ops ) + BOOST_CHECK_THROW( propose( op ), fc::exception ); + + // Check what we have now + idump( (samcoin) ); + idump( (samcoin.bitasset_data(db)) ); + + generate_block(); + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Now able to propose the operations that was invalid + for( const operation& op : ops ) + propose( op ); + + generate_block(); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests scenarios about setting non-UIA issuer permission bits on an UIA +BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update; + uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; + + uint16_t uiaflag = uiamask & ~disable_new_supply; // Allow creating new supply + + vector ops; + + asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), uiaflag ).id; + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + auop.new_options.issuer_permissions = old_bitmask & ~global_settle & ~disable_force_settle; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + // Able to update asset with non-UIA issuer permission bits + PUSH_TX(db, trx, ~0); + + // Able to propose too + propose( auop ); + + // Issue some coin + issue_uia( sam_id, asset( 1, samcoin_id ) ); + + // Unable to unset the non-UIA "disable" issuer permission bits + auto perms = samcoin_id(db).options.issuer_permissions; + + auop.new_options.issuer_permissions = perms & ~disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = perms & ~disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = perms & ~disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Still able to propose + auop.new_options.issuer_permissions = new_bitmask; + propose( auop ); + + // But no longer able to update directly + auop.new_options.issuer_permissions = uiamask | witness_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | committee_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unset the non-UIA bits in issuer permissions, should succeed + auop.new_options.issuer_permissions = uiamask; + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin_id(db).options.issuer_permissions, uiamask ); + + // Burn all supply + reserve_asset( sam_id, asset( 1, samcoin_id ) ); + + BOOST_CHECK_EQUAL( samcoin_id(db).dynamic_asset_data_id(db).current_supply.value, 0 ); + + // Still unable to set the non-UIA bits in issuer permissions + auop.new_options.issuer_permissions = uiamask | witness_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | committee_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + generate_block(); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests what kind of assets can have BDSM-related flags / issuer permissions / extensions +BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // Unable to create a PM with the disable_bdsm_update bit in flags + BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bdsm_update ), fc::exception ); + + // Unable to create a MPA with the disable_bdsm_update bit in flags + BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bdsm_update ), fc::exception ); + + // Unable to create a UIA with the disable_bdsm_update bit in flags + BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bdsm_update ), fc::exception ); + + // create a PM with a zero market_fee_percent + const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); + asset_id_type pm_id = pm.id; + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + // create a UIA with a zero market_fee_percent + const asset_object& uia = create_user_issued_asset( "TESTUIA", sam_id(db), charge_market_fee ); + asset_id_type uia_id = uia.id; + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + + // Unable to set disable_bdsm_update bit in flags for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.flags |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bdsm_update bit in flags for MPA + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + auop.new_options.flags |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bdsm_update bit in flags for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.flags |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bdsm_update bit in issuer_permissions for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.issuer_permissions |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to set disable_bdsm_update bit in issuer_permissions for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.issuer_permissions |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to create a UIA with disable_bdsm_update permission bit + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bdsm_update; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create UIA without disable_bdsm_update permission bit + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Unable to create a PM with disable_bdsm_update permission bit + acop.symbol = "SAMPM"; + acop.precision = asset_id_type()(db).precision; + acop.is_prediction_market = true; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bdsm_update; + acop.bitasset_opts = bitasset_options(); + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Unable to create a PM with BDSM in extensions + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; + acop.bitasset_opts->extensions.value.bad_debt_settlement_method = 0; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create PM with no disable_bdsm_update permission bit nor BDSM in extensions + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; + acop.bitasset_opts->extensions.value.bad_debt_settlement_method.reset(); + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Unable to update PM to set BDSM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = pm_id; + aubop.new_options = pm_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Able to propose + propose( aubop ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests whether asset owner has permission to update bdsm +BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa_id(db).can_owner_update_bdsm() ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + + // disable owner's permission to update bdsm + auop.new_options.issuer_permissions |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + + // check that owner can not update bdsm + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.bad_debt_settlement_method.reset(); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + + // enable owner's permission to update bdsm + auop.new_options.issuer_permissions &= ~disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( mpa_id(db).can_owner_update_bdsm() ); + + // check that owner can update bdsm + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method, 1u ); + + // check bdsm' valid range + aubop.new_options.extensions.value.bad_debt_settlement_method = 4; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + + // Sam borrow some + borrow( sam, asset(1000, mpa_id), asset(2000) ); + + // disable owner's permission to update bdsm + auop.new_options.issuer_permissions |= disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + + // check that owner can not update bdsm + aubop.new_options.extensions.value.bad_debt_settlement_method = 0; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.bad_debt_settlement_method.reset(); + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + + // able to update other params that still has permission E.G. force_settlement_delay_sec + aubop.new_options.force_settlement_delay_sec += 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, + aubop.new_options.force_settlement_delay_sec ); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method, 1u ); + + // unable to enable the permission to update bdsm + auop.new_options.issuer_permissions &= ~disable_bdsm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() From 7baa8f5d46843a70c006301c3894c4d2c3e202fd Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 07:18:30 +0000 Subject: [PATCH 141/258] Fix asset_settle when force settlement is disabled after paid from individual settlement fund, cancel the rest --- libraries/chain/asset_evaluator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 553f5f15c4..c10690670c 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1260,8 +1260,8 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: { result = pay_settle_from_individual_pool( d, op, fee_paying_account, *asset_to_settle, bitasset ); - // If the amount to settle is too small, we return - if( bitasset.has_individual_settlement() ) + // If the amount to settle is too small, or force settlement is disabled, we return + if( bitasset.has_individual_settlement() || !asset_to_settle->can_force_settle() ) return result; to_settle -= result.value.paid->front(); From 23902a6b8dc07d0c125aa81dc643e6b35b039108 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 17:54:03 +0000 Subject: [PATCH 142/258] Add a comment --- libraries/chain/db_update.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 5a73827b57..76115f98a9 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -260,6 +260,10 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // * if there is no force settlement, we check here with margin call fee in consideration. auto least_collateral = call_ptr->collateralization(); + // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, + // if the matching limit order is smaller, due to rounding, it is still possible that the + // call order's collateralization would increase and become higher than ~highest after matched. + // However, for simplicity, we only compare the prices here. bool gs = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); if( gs ) { From 8bbe40826f70bdbf509271de6c433df87b26a768 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 19:50:30 +0000 Subject: [PATCH 143/258] Fix uncapping of settlement price --- libraries/chain/db_market.cpp | 1 + libraries/chain/db_update.cpp | 36 ++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index d62dc871c2..f2d9eac6d4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -164,6 +164,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ obj.options.extensions.value.bad_debt_settlement_method.reset(); // Update BDSM to GS + obj.current_feed = obj.median_feed; // reset current feed price if was capped obj.individual_settlement_debt = 0; obj.individual_settlement_fund = 0; obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 76115f98a9..7d5b85c759 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -350,19 +350,17 @@ static optional get_derived_current_feed_price( const database& db, else // there is no call order of this bitasset result = bitasset.median_feed.settlement_price; } - else if( bdsm_type::individual_settlement_to_fund == bdsm ) + else if( bdsm_type::individual_settlement_to_fund == bdsm && bitasset.individual_settlement_debt > 0 ) { - if( bitasset.individual_settlement_debt <= 0 ) - result = bitasset.median_feed.settlement_price; - else - { - // Now bitasset.individual_settlement_debt > 0 - price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) - / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); - auto lowest_callable_feed_price = fund_price * bitasset.get_margin_call_order_ratio(); - result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); - } + // Check whether to cap + price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) + / asset( bitasset.individual_settlement_fund, bitasset.options.short_backing_asset ); + auto lowest_callable_feed_price = fund_price * bitasset.get_margin_call_order_ratio(); + result = std::max( bitasset.median_feed.settlement_price, lowest_callable_feed_price ); } + else // should not cap + result = bitasset.median_feed.settlement_price; + // Check whether it's necessary to update if( result.valid() && (*result) == bitasset.current_feed.settlement_price ) result.reset(); @@ -378,10 +376,18 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b if( skip_median_update ) { if( bdsm_type::no_settlement != bdsm && bdsm_type::individual_settlement_to_fund != bdsm ) - return; - new_current_feed_price = get_derived_current_feed_price( *this, bitasset ); - if( !new_current_feed_price.valid() ) - return; + { + // it's possible that current_feed was capped thus we still need to update it + if( bitasset.current_feed.settlement_price == bitasset.median_feed.settlement_price ) + return; + new_current_feed_price = bitasset.median_feed.settlement_price; + } + else + { + new_current_feed_price = get_derived_current_feed_price( *this, bitasset ); + if( !new_current_feed_price.valid() ) + return; + } } // We need to update the database From 151ba844de8b4ef110844f90a3c90ce69cb05886 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Aug 2021 20:35:04 +0000 Subject: [PATCH 144/258] Slightly refactor clear_expired_force_settlements --- libraries/chain/db_update.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 7d5b85c759..a259d6cea7 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -561,18 +561,18 @@ void database::clear_expired_force_settlements() // Match against the least collateralized short until the settlement is finished or we reach max settlements while( settled < max_settlement_volume && find_object(settle_order_id) ) { - const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); - // Note: there can be no debt position due to individual settlements - if( !call_ptr ) // no debt position + if( 0 == settle_order.balance.amount ) { - wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); + wlog( "0 settlement detected" ); cancel_settle_order( settle_order ); break; } - if( 0 == settle_order.balance.amount ) + const call_order_object* call_ptr = find_least_collateralized_short( mia, true ); + // Note: there can be no debt position due to individual settlements + if( !call_ptr ) // no debt position { - wlog( "0 settlement detected" ); + wlog( "No debt position found when processing force settlement ${o}", ("o",settle_order) ); cancel_settle_order( settle_order ); break; } From 8e9f3e4f1922fba43dbd8e6ec07fe76f242df5d1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 26 Aug 2021 07:02:40 +0000 Subject: [PATCH 145/258] Fix individual_settlement_to_fund black swan check --- libraries/chain/db_update.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index a259d6cea7..d71b18d486 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -213,13 +213,21 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( !call_ptr ) // no call order return false; + using bdsm_type = bitasset_options::bad_debt_settlement_type; + const auto bdsm = bitasset.get_bad_debt_settlement_method(); + price highest = settle_price; - if( !before_core_hardfork_1270 ) - // due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + // * If BDSM is individual_settlement_to_fund, check with median_feed to decide whether to settle. + // * If BDSM is no_settlement, check with current_feed to NOT trigger global settlement. + // * If BDSM is global_settlement or individual_settlement_to_order, median_feed == current_feed. + if( bdsm_type::individual_settlement_to_fund == bdsm ) + highest = bitasset.median_feed.max_short_squeeze_price(); + else if( !before_core_hardfork_1270 ) highest = bitasset.current_feed.max_short_squeeze_price(); else if( maint_time > HARDFORK_CORE_338_TIME ) - // due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); + // else do nothing const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); @@ -264,8 +272,8 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // if the matching limit order is smaller, due to rounding, it is still possible that the // call order's collateralization would increase and become higher than ~highest after matched. // However, for simplicity, we only compare the prices here. - bool gs = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); - if( gs ) + bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); + if( is_blackswan ) { wdump( (*call_ptr) ); elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" @@ -282,8 +290,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); if( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm ) { individually_settle( bitasset, *call_ptr ); From d0351225eadb1195e2246adc70fefc03aeac4f69 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 26 Aug 2021 07:47:49 +0000 Subject: [PATCH 146/258] Rename BDSM to BSRM Rename "bad-debt settlement method" to "black swan response method" --- libraries/chain/asset_evaluator.cpp | 66 ++++----- libraries/chain/db_getter.cpp | 4 +- libraries/chain/db_market.cpp | 50 +++---- libraries/chain/db_update.cpp | 38 ++--- libraries/chain/hardfork.d/CORE_2467.hf | 2 +- .../graphene/chain/asset_evaluator.hpp | 2 +- .../include/graphene/chain/asset_object.hpp | 16 +- .../chain/include/graphene/chain/database.hpp | 6 +- .../include/graphene/chain/market_object.hpp | 2 +- libraries/chain/market_evaluator.cpp | 8 +- libraries/protocol/asset_ops.cpp | 20 +-- .../include/graphene/protocol/asset_ops.hpp | 22 +-- .../include/graphene/protocol/types.hpp | 10 +- tests/tests/bsip48_75_tests.cpp | 6 +- .../tests/{bdsm_tests.cpp => bsrm_tests.cpp} | 140 +++++++++--------- 15 files changed, 196 insertions(+), 196 deletions(-) rename tests/tests/{bdsm_tests.cpp => bsrm_tests.cpp} (84%) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index c10690670c..fd7e9d5f04 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -150,7 +150,7 @@ namespace detail { if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) ) { // new issuer permissions should not be set until activation of the hardfork - FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_bdsm_update), + FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_bsrm_update), "New asset issuer permission bits should not be set before Hardfork core-2467" ); } } @@ -160,8 +160,8 @@ namespace detail { // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: if ( !HARDFORK_CORE_2467_PASSED(next_maint_time) ) { - FC_ASSERT( !options.extensions.value.bad_debt_settlement_method.valid(), - "A BitAsset's bad debt settlement method cannot be set before Hardfork core-2467" ); + FC_ASSERT( !options.extensions.value.black_swan_response_method.valid(), + "A BitAsset's black swan response method cannot be set before Hardfork core-2467" ); } } @@ -456,10 +456,10 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( !a.is_market_issued() ) FC_ASSERT( 0 == ( o.new_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK ), "Unable to set non-UIA issuer permission bits on UIA" ); - // Unable to set disable_bdsm_update issuer permission bit on PM + // Unable to set disable_bsrm_update issuer permission bit on PM else if( bitasset_data->is_prediction_market ) - FC_ASSERT( 0 == ( o.new_options.issuer_permissions & disable_bdsm_update ), - "Unable to set disable_bdsm_update issuer permission bit on PM" ); + FC_ASSERT( 0 == ( o.new_options.issuer_permissions & disable_bsrm_update ), + "Unable to set disable_bsrm_update issuer permission bit on PM" ); // else do nothing } @@ -481,11 +481,11 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) & (uint16_t)(~enabled_issuer_permissions_mask) ) & UIA_ASSET_ISSUER_PERMISSION_MASK ), "Cannot reinstate previously revoked issuer permissions on a UIA if current supply is non-zero, " "unless to unset non-UIA issuer permission bits."); - else if( hf_core_2467_passed && bitasset_data->is_prediction_market ) // for PM, ignore disable_bdsm_update + else if( hf_core_2467_passed && bitasset_data->is_prediction_market ) // for PM, ignore disable_bsrm_update FC_ASSERT( 0 == ( ( o.new_options.get_enabled_issuer_permissions_mask() - & (uint16_t)(~enabled_issuer_permissions_mask) ) & (uint16_t)(~disable_bdsm_update) ), + & (uint16_t)(~enabled_issuer_permissions_mask) ) & (uint16_t)(~disable_bsrm_update) ), "Cannot reinstate previously revoked issuer permissions on a PM if current supply is non-zero, " - "unless to unset the disable_bdsm_update issuer permission bit."); + "unless to unset the disable_bsrm_update issuer permission bit."); else FC_ASSERT( 0 == ( o.new_options.get_enabled_issuer_permissions_mask() & (uint16_t)(~enabled_issuer_permissions_mask) ), @@ -698,8 +698,8 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita "Cannot update a bitasset after a global settlement has executed" ); if( current_bitasset_data.is_prediction_market ) - FC_ASSERT( !op.new_options.extensions.value.bad_debt_settlement_method.valid(), - "Can not set bad_debt_settlement_method for Prediction Markets" ); + FC_ASSERT( !op.new_options.extensions.value.black_swan_response_method.valid(), + "Can not set black_swan_response_method for Prediction Markets" ); // TODO simplify code below when made sure operator==(optional,optional) works if( !asset_obj.can_owner_update_mcr() ) @@ -729,27 +729,27 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita || ( old_mssr.valid() && *old_mssr != *new_mssr ) ); FC_ASSERT( !mssr_changed, "No permission to update MSSR" ); } - // check if BDSM will change - const auto old_bdsm = current_bitasset_data.get_bad_debt_settlement_method(); - const auto new_bdsm = op.new_options.get_bad_debt_settlement_method(); - if( old_bdsm != new_bdsm ) + // check if BSRM will change + const auto old_bsrm = current_bitasset_data.get_black_swan_response_method(); + const auto new_bsrm = op.new_options.get_black_swan_response_method(); + if( old_bsrm != new_bsrm ) { - FC_ASSERT( asset_obj.can_owner_update_bdsm(), "No permission to update BDSM" ); + FC_ASSERT( asset_obj.can_owner_update_bsrm(), "No permission to update BSRM" ); FC_ASSERT( !current_bitasset_data.has_settlement(), - "Unable to update BDSM when the asset has been globally settled" ); + "Unable to update BSRM when the asset has been globally settled" ); - // Note: it is probably OK to allow BDSM update, be conservative here so far - using bdsm_type = bitasset_options::bad_debt_settlement_type; - if( bdsm_type::individual_settlement_to_fund == old_bdsm ) + // Note: it is probably OK to allow BSRM update, be conservative here so far + using bsrm_type = bitasset_options::black_swan_response_type; + if( bsrm_type::individual_settlement_to_fund == old_bsrm ) FC_ASSERT( !current_bitasset_data.has_individual_settlement(), - "Unable to update BDSM when the individual bad debt settlement pool is not empty" ); - else if( bdsm_type::individual_settlement_to_order == old_bdsm ) - FC_ASSERT( !d.find_bad_debt_settlement_order( op.asset_to_update ), - "Unable to update BDSM when there exists a bad debt settlement order" ); + "Unable to update BSRM when the individual settlement pool is not empty" ); + else if( bsrm_type::individual_settlement_to_order == old_bsrm ) + FC_ASSERT( !d.find_individual_settlemnt_order( op.asset_to_update ), + "Unable to update BSRM when there exists an individual settlement order" ); // Since we do not allow updating in some cases (above), only check no_settlement here - if( bdsm_type::no_settlement == old_bdsm || bdsm_type::no_settlement == new_bdsm ) - update_feeds_due_to_bdsm_change = true; + if( bsrm_type::no_settlement == old_bsrm || bsrm_type::no_settlement == new_bsrm ) + update_feeds_due_to_bsrm_change = true; } @@ -856,7 +856,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita static bool update_bitasset_object_options( const asset_update_bitasset_operation& op, database& db, asset_bitasset_data_object& bdo, const asset_object& asset_to_update, - bool update_feeds_due_to_bdsm_change ) + bool update_feeds_due_to_bsrm_change ) { const fc::time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; bool after_hf_core_868_890 = ( next_maint_time > HARDFORK_CORE_868_890_TIME ); @@ -943,12 +943,12 @@ static bool update_bitasset_object_options( } bool feed_actually_changed = false; - if( should_update_feeds || update_feeds_due_to_bdsm_change ) + if( should_update_feeds || update_feeds_due_to_bsrm_change ) { const auto old_feed = bdo.current_feed; if( should_update_feeds ) db.update_bitasset_current_feed( bdo ); - else // to update feeds due to bdsm change + else // to update feeds due to bsrm change db.update_bitasset_current_feed( bdo, true ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 @@ -972,7 +972,7 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse [&op, &to_check_call_orders, &db_conn, this]( asset_bitasset_data_object& bdo ) { to_check_call_orders = update_bitasset_object_options( op, db_conn, bdo, *asset_to_update, - update_feeds_due_to_bdsm_change ); + update_feeds_due_to_bsrm_change ); }); if( to_check_call_orders ) @@ -1091,7 +1091,7 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op FC_ASSERT( asset_to_settle->can_force_settle() || bitasset.has_settlement() || bitasset.has_individual_settlement(), "Either the asset need to have the force_settle flag enabled, or it need to be globally settled, " - "or the individual bad debt settlement pool is not empty" ); + "or the individual settlement pool is not empty" ); if( bitasset.is_prediction_market ) { @@ -1111,7 +1111,7 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op { FC_THROW_EXCEPTION( insufficient_feeds, "Cannot force settle with no price feed if the asset is not globally settled and the " - "individual bad debt settlement pool is not empty" ); + "individual settlement pool is not empty" ); } } @@ -1253,7 +1253,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( bitasset.has_settlement() ) return pay_settle_from_gs_fund( d, op, fee_paying_account, *asset_to_settle, bitasset ); - // Process individual bad debt settlement pool + // Process individual settlement pool extendable_operation_result result; asset to_settle = op.amount; if( bitasset.has_individual_settlement() ) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 63bd436453..ed4fc84ac6 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -149,7 +149,7 @@ const witness_schedule_object& database::get_witness_schedule_object()const return *_p_witness_schedule_obj; } -const limit_order_object* database::find_bad_debt_settlement_order( const asset_id_type& a )const +const limit_order_object* database::find_individual_settlemnt_order( const asset_id_type& a )const { const auto& limit_index = get_index_type().indices().get(); auto itr = limit_index.lower_bound( std::make_tuple( true, a ) ); @@ -179,7 +179,7 @@ const call_order_object* database::find_least_collateralized_short( const asset_ } else // after core-1270 hard fork, check with collateralization { - // Note: it is safe to check here even if there is no call order due to individual bad debt settlements + // Note: it is safe to check here even if there is no call order due to individual settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_itr = call_collateral_index.lower_bound( call_min ); if( call_itr != call_collateral_index.end() ) // no call order diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f2d9eac6d4..e783e048a6 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -68,7 +68,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett } else { - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements globally_settle_asset_impl( mia, settlement_price, get_index_type().indices().get(), check_margin_calls ); @@ -151,8 +151,8 @@ void database::globally_settle_asset_impl( const asset_object& mia, "Internal error: unable to close margin call ${o}", ("o", order) ); } - // Move the (individual) bad-debt settlement order to the GS fund - const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); + // Move the individual settlement order to the GS fund + const limit_order_object* limit_ptr = find_individual_settlemnt_order( bitasset.asset_id ); if( limit_ptr ) { collateral_gathered.amount += limit_ptr->for_sale; @@ -163,7 +163,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, collateral_gathered.amount += bitasset.individual_settlement_fund; modify( bitasset, [&mia,&original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ - obj.options.extensions.value.bad_debt_settlement_method.reset(); // Update BDSM to GS + obj.options.extensions.value.black_swan_response_method.reset(); // Update BSRM to GS obj.current_feed = obj.median_feed; // reset current feed price if was capped obj.individual_settlement_debt = 0; obj.individual_settlement_fund = 0; @@ -177,10 +177,10 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, { FC_ASSERT( bitasset.asset_id == order.debt_type(), "Internal error: asset type mismatch" ); - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); - FC_ASSERT( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm, - "Internal error: Invalid BDSM" ); + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + FC_ASSERT( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm, + "Internal error: Invalid BSRM" ); auto order_debt = order.get_debt(); auto order_collateral = order.get_collateral(); @@ -191,7 +191,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, auto margin_call_fee = order_collateral - fund_receives; - if( bdsm_type::individual_settlement_to_fund == bdsm ) // settle to fund + if( bsrm_type::individual_settlement_to_fund == bsrm ) // settle to fund { modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){ obj.individual_settlement_debt += order.debt; @@ -200,7 +200,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } else // settle to order { - const limit_order_object* limit_ptr = find_bad_debt_settlement_order( bitasset.asset_id ); + const limit_order_object* limit_ptr = find_individual_settlemnt_order( bitasset.asset_id ); if( limit_ptr ) { modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { @@ -227,7 +227,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, "Internal error: unable to close margin call ${o}", ("o", order) ); // Update current feed if needed - if( bdsm_type::individual_settlement_to_fund == bdsm ) + if( bsrm_type::individual_settlement_to_fund == bsrm ) update_bitasset_current_feed( bitasset, true ); } @@ -625,7 +625,7 @@ bool database::apply_order(const limit_order_object& new_order_object) if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) @@ -729,7 +729,7 @@ void database::apply_force_settlement( const force_settlement_object& new_settle bool finished = false; // whether the new order is gone // check if there are margin calls - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, new_settlement.balance.asset_id ); while( !finished ) @@ -991,7 +991,7 @@ database::match_result_type database::match( const limit_order_object& bid, cons bool maker_filled = fill_call_order( ask, call_pays, call_receives, match_price, true, margin_call_fee ); // Update current_feed after filled call order if needed - if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() ) update_bitasset_current_feed( bitasset, true ); // Note: result can be none_filled when call order has target_collateral_ratio option set. @@ -1225,7 +1225,7 @@ asset database::match_impl( const force_settlement_object& settle, fill_settle_order( settle, settle_pays, settle_receives, fill_price, !settle_is_taker, !is_margin_call ); // Update current_feed after filled call order if needed - if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() ) update_bitasset_current_feed( bitasset, true ); if( cull_settle_order ) @@ -1533,12 +1533,12 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if ( maint_time >= HARDFORK_CORE_460_TIME && bitasset.is_prediction_market ) return false; - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); - // Only check for black swan here if BDSM is not individual settlement - if( bdsm_type::individual_settlement_to_fund != bdsm - && bdsm_type::individual_settlement_to_order != bdsm + // Only check for black swan here if BSRM is not individual settlement + if( bsrm_type::individual_settlement_to_fund != bsrm + && bsrm_type::individual_settlement_to_order != bsrm && check_for_blackswan( mia, enable_black_swan, &bitasset ) ) return false; @@ -1570,7 +1570,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_index = call_index.indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); @@ -1775,7 +1775,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); // Update current_feed after filled call order if needed - if( bitasset_options::bad_debt_settlement_type::no_settlement == bitasset.get_bad_debt_settlement_method() ) + if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() ) update_bitasset_current_feed( bitasset, true ); if( !before_core_hardfork_1270 ) @@ -1801,8 +1801,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( called_some ) margin_called = true; // At last, check for blackswan // TODO perhaps improve performance by passing in iterators - if( bdsm_type::individual_settlement_to_fund == bdsm - || bdsm_type::individual_settlement_to_order == bdsm ) + if( bsrm_type::individual_settlement_to_fund == bsrm + || bsrm_type::individual_settlement_to_order == bsrm ) { // Run multiple times, each time one call order gets settled // TODO perhaps improve performance by settling multiple call orders inside in one call @@ -1831,7 +1831,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); auto settle_end = settlement_index.upper_bound( bitasset.asset_id ); - // Note: it is safe to iterate here even if there is no call order due to individual bad debt settlements + // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, bitasset.asset_id ); auto call_max = price::max( bitasset.options.short_backing_asset, bitasset.asset_id ); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index d71b18d486..d9924039f9 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -213,15 +213,15 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( !call_ptr ) // no call order return false; - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); price highest = settle_price; // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here - // * If BDSM is individual_settlement_to_fund, check with median_feed to decide whether to settle. - // * If BDSM is no_settlement, check with current_feed to NOT trigger global settlement. - // * If BDSM is global_settlement or individual_settlement_to_order, median_feed == current_feed. - if( bdsm_type::individual_settlement_to_fund == bdsm ) + // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. + // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. + // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. + if( bsrm_type::individual_settlement_to_fund == bsrm ) highest = bitasset.median_feed.max_short_squeeze_price(); else if( !before_core_hardfork_1270 ) highest = bitasset.current_feed.max_short_squeeze_price(); @@ -290,15 +290,15 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - if( bdsm_type::individual_settlement_to_fund == bdsm || bdsm_type::individual_settlement_to_order == bdsm ) + if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) { individually_settle( bitasset, *call_ptr ); } - // Global settlement or no settlement, but we should not be here if BDSM is no_settlement + // Global settlement or no settlement, but we should not be here if BSRM is no_settlement else if( after_core_hardfork_2481 ) { - if( bdsm_type::no_settlement == bdsm ) // this should not happen, be defensive here - wlog( "Internal error: BDSM is no_settlement but undercollateralization occurred" ); + if( bsrm_type::no_settlement == bsrm ) // this should not happen, be defensive here + wlog( "Internal error: BSRM is no_settlement but undercollateralization occurred" ); // After hf_2481, when a global settlement occurs, // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and // they are closed at the same price, @@ -339,9 +339,9 @@ static optional get_derived_current_feed_price( const database& db, return bitasset.median_feed.settlement_price; } - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); - if( bdsm_type::no_settlement == bdsm ) + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + if( bsrm_type::no_settlement == bsrm ) { // Find the call order with the least collateral ratio const call_order_object* call_ptr = db.find_least_collateralized_short( bitasset, true ); @@ -356,7 +356,7 @@ static optional get_derived_current_feed_price( const database& db, else // there is no call order of this bitasset result = bitasset.median_feed.settlement_price; } - else if( bdsm_type::individual_settlement_to_fund == bdsm && bitasset.individual_settlement_debt > 0 ) + else if( bsrm_type::individual_settlement_to_fund == bsrm && bitasset.individual_settlement_debt > 0 ) { // Check whether to cap price fund_price = asset( bitasset.individual_settlement_debt, bitasset.asset_id ) @@ -377,11 +377,11 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b { // For better performance, if nothing to update, we return optional new_current_feed_price; - using bdsm_type = bitasset_options::bad_debt_settlement_type; - const auto bdsm = bitasset.get_bad_debt_settlement_method(); + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); if( skip_median_update ) { - if( bdsm_type::no_settlement != bdsm && bdsm_type::individual_settlement_to_fund != bdsm ) + if( bsrm_type::no_settlement != bsrm && bsrm_type::individual_settlement_to_fund != bsrm ) { // it's possible that current_feed was capped thus we still need to update it if( bitasset.current_feed.settlement_price == bitasset.median_feed.settlement_price ) @@ -397,7 +397,7 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b } // We need to update the database - modify( bitasset, [this, skip_median_update, &new_current_feed_price, &bdsm] + modify( bitasset, [this, skip_median_update, &new_current_feed_price, &bsrm] ( asset_bitasset_data_object& abdo ) { if( !skip_median_update ) @@ -406,7 +406,7 @@ void database::update_bitasset_current_feed( const asset_bitasset_data_object& b const auto& maint_time = get_dynamic_global_properties().next_maintenance_time; abdo.update_median_feeds( head_time, maint_time ); abdo.current_feed = abdo.median_feed; - if( bdsm_type::no_settlement == bdsm || bdsm_type::individual_settlement_to_fund == bdsm ) + if( bsrm_type::no_settlement == bsrm || bsrm_type::individual_settlement_to_fund == bsrm ) new_current_feed_price = get_derived_current_feed_price( *this, abdo ); } if( new_current_feed_price.valid() ) diff --git a/libraries/chain/hardfork.d/CORE_2467.hf b/libraries/chain/hardfork.d/CORE_2467.hf index 8d575bb5d0..43fc67ab14 100644 --- a/libraries/chain/hardfork.d/CORE_2467.hf +++ b/libraries/chain/hardfork.d/CORE_2467.hf @@ -1,4 +1,4 @@ -// bitshares-core issue #2467 Alternative bad debt settlement methods +// bitshares-core issue #2467 Alternative black swan response methods #ifndef HARDFORK_CORE_2467_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2467_TIME (fc::time_point_sec( 1893456000 )) diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 84645f831f..8b35585f02 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -105,7 +105,7 @@ namespace graphene { namespace chain { const asset_bitasset_data_object* bitasset_to_update = nullptr; const asset_object* asset_to_update = nullptr; - bool update_feeds_due_to_bdsm_change = false; + bool update_feeds_due_to_bsrm_change = false; }; class asset_update_feed_producers_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 3002633b1f..a9481604ea 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -110,8 +110,8 @@ namespace graphene { namespace chain { bool can_owner_update_icr()const { return (0 == (options.issuer_permissions & disable_icr_update)); } /// @return true if the asset owner can update MSSR directly bool can_owner_update_mssr()const { return (0 == (options.issuer_permissions & disable_mssr_update)); } - /// @return true if the asset owner can change bad debt settlement method - bool can_owner_update_bdsm()const { return (0 == (options.issuer_permissions & disable_bdsm_update)); } + /// @return true if the asset owner can change black swan response method + bool can_owner_update_bsrm()const { return (0 == (options.issuer_permissions & disable_bsrm_update)); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } @@ -309,7 +309,7 @@ namespace graphene { namespace chain { share_type settlement_fund; ///@} - /// The individual bad debt settlement pool. + /// The individual settlement pool. /// In the event of individual settlements to fund, debt and collateral of the margin positions which got /// settled are moved here. ///@{ @@ -319,20 +319,20 @@ namespace graphene { namespace chain { share_type individual_settlement_fund; ///@} - /// @return true if the individual bad debt settlement pool is not empty, false otherwise + /// @return true if the individual settlement pool is not empty, false otherwise bool has_individual_settlement()const { return ( individual_settlement_debt != 0 ); } - /// Get the price of the individual bad debt settlement pool + /// Get the price of the individual settlement pool price get_individual_settlement_price() const { return asset( individual_settlement_debt, asset_id ) / asset( individual_settlement_fund, options.short_backing_asset ); } - /// Get the effective bad debt settlement method of this bitasset - bitasset_options::bad_debt_settlement_type get_bad_debt_settlement_method() const + /// Get the effective black swan response method of this bitasset + bitasset_options::black_swan_response_type get_black_swan_response_method() const { - return options.get_bad_debt_settlement_method(); + return options.get_black_swan_response_method(); } /// Get margin call order price (MCOP) of this bitasset diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 4466de2b96..63fa00fb2f 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -297,10 +297,10 @@ namespace graphene { namespace chain { uint32_t last_non_undoable_block_num() const; - /// Find the limit order which is the bad-debt settlement fund of the specified asset + /// Find the limit order which is the individual settlement fund of the specified asset /// @param a ID of the asset /// @return nullptr if not found, pointer to the limit order if found - const limit_order_object* find_bad_debt_settlement_order( const asset_id_type& a )const; + const limit_order_object* find_individual_settlemnt_order( const asset_id_type& a )const; /// Find the call order with the least collateral ratio /// @param bitasset The bitasset object @@ -411,7 +411,7 @@ namespace graphene { namespace chain { const IndexType& call_index, bool check_margin_calls = false ); /// Individually settle the @p call_order. Called when the call order is undercollateralized. - /// See @ref protocol::bitasset_options::bad_debt_settlement_type for more info. + /// See @ref protocol::bitasset_options::black_swan_response_type for more info. /// @param bitasset the bitasset object /// @param call_order the call order void individually_settle( const asset_bitasset_data_object& bitasset, const call_order_object& call_order ); diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 779fca5210..3f62130a50 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -53,7 +53,7 @@ class limit_order_object : public abstract_object price sell_price; share_type deferred_fee; ///< fee converted to CORE asset deferred_paid_fee; ///< originally paid fee - bool is_settled_debt = false; ///< Whether this order is a bad-debt settlement fund + bool is_settled_debt = false; ///< Whether this order is an individual settlement fund pair get_market()const { diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index bbcd11cf87..e534af7f7a 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -305,8 +305,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope d.remove( *call_obj ); // Update current_feed if needed - const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); - if( bitasset_options::bad_debt_settlement_type::no_settlement == bdsm ) + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) d.update_bitasset_current_feed( *_bitasset_data, true ); return call_order_id; @@ -413,8 +413,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } } // Update current_feed if needed - const auto bdsm = _bitasset_data->get_bad_debt_settlement_method(); - if( bitasset_options::bad_debt_settlement_type::no_settlement == bdsm ) + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) d.update_bitasset_current_feed( *_bitasset_data, true ); } diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index a723338827..264f8f0649 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -114,15 +114,15 @@ void asset_create_operation::validate()const common_options.validate(); if( 0 != ( common_options.issuer_permissions & (disable_force_settle|global_settle - |disable_mcr_update|disable_icr_update|disable_mssr_update|disable_bdsm_update) ) ) + |disable_mcr_update|disable_icr_update|disable_mssr_update|disable_bsrm_update) ) ) FC_ASSERT( bitasset_opts.valid() ); if( is_prediction_market ) { FC_ASSERT( bitasset_opts.valid(), "Cannot have a User-Issued Asset implement a prediction market." ); FC_ASSERT( 0 != (common_options.issuer_permissions & global_settle) ); - FC_ASSERT( 0 == (common_options.issuer_permissions & disable_bdsm_update) ); - FC_ASSERT( !bitasset_opts->extensions.value.bad_debt_settlement_method.valid(), - "Can not set bad_debt_settlement_method for Prediction Markets" ); + FC_ASSERT( 0 == (common_options.issuer_permissions & disable_bsrm_update) ); + FC_ASSERT( !bitasset_opts->extensions.value.black_swan_response_method.valid(), + "Can not set black_swan_response_method for Prediction Markets" ); } if( bitasset_opts ) bitasset_opts->validate(); @@ -262,11 +262,11 @@ void bitasset_options::validate() const if( extensions.value.force_settle_fee_percent.valid() ) FC_ASSERT( *extensions.value.force_settle_fee_percent <= GRAPHENE_100_PERCENT ); - if( extensions.value.bad_debt_settlement_method.valid() ) + if( extensions.value.black_swan_response_method.valid() ) { - auto bdsm_count = static_cast( bad_debt_settlement_type::BDSM_TYPE_COUNT ); - FC_ASSERT( *extensions.value.bad_debt_settlement_method < bdsm_count, - "bad_debt_settlement_method should be less than ${c}", ("c",bdsm_count) ); + auto bsrm_count = static_cast( black_swan_response_type::BSRM_TYPE_COUNT ); + FC_ASSERT( *extensions.value.black_swan_response_method < bsrm_count, + "black_swan_response_method should be less than ${c}", ("c",bsrm_count) ); } } @@ -319,8 +319,8 @@ void asset_options::validate_flags( bool is_market_issued )const "Can not set disable_icr_update flag, it is for issuer permission only" ); FC_ASSERT( 0 == (flags & disable_mssr_update), "Can not set disable_mssr_update flag, it is for issuer permission only" ); - FC_ASSERT( 0 == (flags & disable_bdsm_update), - "Can not set disable_bdsm_update flag, it is for issuer permission only" ); + FC_ASSERT( 0 == (flags & disable_bsrm_update), + "Can not set disable_bsrm_update flag, it is for issuer permission only" ); if( !is_market_issued ) { FC_ASSERT( 0 == (flags & (uint16_t)(~UIA_ASSET_ISSUER_PERMISSION_MASK)), diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index ccde864aca..cb14713a68 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -108,8 +108,8 @@ namespace graphene { namespace protocol { */ struct bitasset_options { - /// Defines what will happen when bad debt appears - enum class bad_debt_settlement_type + /// Defines how to response to black swan events + enum class black_swan_response_type { /// All debt positions are closed, all or some collateral is moved to a global-settlement fund. /// Debt asset holders can claim collateral via force-settlement. @@ -128,8 +128,8 @@ namespace graphene { namespace protocol { /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining /// debt positions could be margin called at a worse price. individual_settlement_to_order = 3, - /// Total number of available bad debt settlement methods - BDSM_TYPE_COUNT = 4 + /// Total number of available black swan response methods + BSRM_TYPE_COUNT = 4 }; struct ext @@ -145,7 +145,7 @@ namespace graphene { namespace protocol { fc::optional margin_call_fee_ratio; // BSIP 74 fc::optional force_settle_fee_percent; // BSIP-87 // https://github.com/bitshares/bitshares-core/issues/2467 - fc::optional bad_debt_settlement_method; + fc::optional black_swan_response_method; }; /// Time before a price feed expires @@ -172,12 +172,12 @@ namespace graphene { namespace protocol { /// @throws fc::exception if any check fails void validate()const; - /// Get the effective bad debt settlement method - bad_debt_settlement_type get_bad_debt_settlement_method() const + /// Get the effective black swan response method + black_swan_response_type get_black_swan_response_method() const { - if( !extensions.value.bad_debt_settlement_method.valid() ) - return bad_debt_settlement_type::global_settlement; - return static_cast( *extensions.value.bad_debt_settlement_method ); + if( !extensions.value.black_swan_response_method.valid() ) + return black_swan_response_type::global_settlement; + return static_cast( *extensions.value.black_swan_response_method ); } }; @@ -635,7 +635,7 @@ FC_REFLECT( graphene::protocol::bitasset_options::ext, (maximum_short_squeeze_ratio) (margin_call_fee_ratio) (force_settle_fee_percent) - (bad_debt_settlement_method) + (black_swan_response_method) ) FC_REFLECT( graphene::protocol::bitasset_options, diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index b98c0eccdf..675de8e350 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -180,7 +180,7 @@ enum asset_issuer_permission_flags { disable_mcr_update = 0x800, ///< the bitasset owner can not update MCR, permisison only disable_icr_update = 0x1000, ///< the bitasset owner can not update ICR, permisison only disable_mssr_update = 0x2000, ///< the bitasset owner can not update MSSR, permisison only - disable_bdsm_update = 0x4000 ///< the bitasset owner can not update BDSM, permission only + disable_bsrm_update = 0x4000 ///< the bitasset owner can not update BSRM, permission only ///@} ///@} }; @@ -201,7 +201,7 @@ const static uint16_t ASSET_ISSUER_PERMISSION_MASK = | disable_mcr_update | disable_icr_update | disable_mssr_update - | disable_bdsm_update; + | disable_bsrm_update; // The "enable" bits for non-UIA assets const static uint16_t ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK = charge_market_fee @@ -220,7 +220,7 @@ const static uint16_t ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK = | disable_mcr_update | disable_icr_update | disable_mssr_update - | disable_bdsm_update; + | disable_bsrm_update; // The bits that can be used in asset issuer permissions for UIA assets const static uint16_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee @@ -246,7 +246,7 @@ const static uint16_t PERMISSION_ONLY_MASK = | disable_mcr_update | disable_icr_update | disable_mssr_update - | disable_bdsm_update; + | disable_bsrm_update; // The bits that can be used in flags for non-UIA assets const static uint16_t VALID_FLAGS_MASK = ASSET_ISSUER_PERMISSION_MASK & ~PERMISSION_ONLY_MASK; // the bits that can be used in flags for UIA assets @@ -364,7 +364,7 @@ FC_REFLECT_ENUM(graphene::protocol::asset_issuer_permission_flags, (disable_mcr_update) (disable_icr_update) (disable_mssr_update) - (disable_bdsm_update) + (disable_bsrm_update) ) namespace fc { namespace raw { diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index ee97c25211..6861b9e233 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -1164,7 +1164,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) acop2.symbol = "NEWSAMBIT"; // With all possible bits in permissions set to 1 acop2.common_options.issuer_permissions = hf2467 ? ASSET_ISSUER_PERMISSION_MASK - : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update ); + : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update ); for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { acop2.common_options.flags = valid_bitflag | bit; @@ -1191,9 +1191,9 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_CHECK( !newsambit_id(db).can_owner_update_mcr() ); BOOST_CHECK( !newsambit_id(db).can_owner_update_mssr() ); if( hf2467 ) - BOOST_CHECK( !newsambit_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( !newsambit_id(db).can_owner_update_bsrm() ); else - BOOST_CHECK( newsambit_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( newsambit_id(db).can_owner_update_bsrm() ); // Able to propose too propose( acop2 ); diff --git a/tests/tests/bdsm_tests.cpp b/tests/tests/bsrm_tests.cpp similarity index 84% rename from tests/tests/bdsm_tests.cpp rename to tests/tests/bsrm_tests.cpp index dd7288d591..4f3353afee 100644 --- a/tests/tests/bdsm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -34,7 +34,7 @@ using namespace graphene::chain; using namespace graphene::chain::test; -BOOST_FIXTURE_TEST_SUITE( bdsm_tests, database_fixture ) +BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) /// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork BOOST_AUTO_TEST_CASE( hardfork_protection_test ) @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update; + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; @@ -84,10 +84,10 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) op.common_options.issuer_permissions = old_bitmask; // Unable to set new extensions in bitasset options - op.bitasset_opts->extensions.value.bad_debt_settlement_method = 0; + op.bitasset_opts->extensions.value.black_swan_response_method = 0; BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); ops.push_back( op ); - op.bitasset_opts->extensions.value.bad_debt_settlement_method = {}; + op.bitasset_opts->extensions.value.black_swan_response_method = {}; acop = op; } @@ -147,10 +147,10 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) op.new_options.minimum_feeds = 1; // Unable to set new extensions - op.new_options.extensions.value.bad_debt_settlement_method = 1; + op.new_options.extensions.value.black_swan_response_method = 1; BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); ops.push_back( op ); - op.new_options.extensions.value.bad_debt_settlement_method = {}; + op.new_options.extensions.value.black_swan_response_method = {}; aubop = op; } @@ -205,7 +205,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bdsm_update; + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; @@ -293,7 +293,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - auop.new_options.issuer_permissions = uiamask | disable_bdsm_update; + auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); @@ -338,7 +338,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - auop.new_options.issuer_permissions = uiamask | disable_bdsm_update; + auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); @@ -350,7 +350,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) } } -/// Tests what kind of assets can have BDSM-related flags / issuer permissions / extensions +/// Tests what kind of assets can have BSRM-related flags / issuer permissions / extensions BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) { try { @@ -367,14 +367,14 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) fund( sam, asset(init_amount) ); fund( feeder, asset(init_amount) ); - // Unable to create a PM with the disable_bdsm_update bit in flags - BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bdsm_update ), fc::exception ); + // Unable to create a PM with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bsrm_update ), fc::exception ); - // Unable to create a MPA with the disable_bdsm_update bit in flags - BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bdsm_update ), fc::exception ); + // Unable to create a MPA with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bsrm_update ), fc::exception ); - // Unable to create a UIA with the disable_bdsm_update bit in flags - BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bdsm_update ), fc::exception ); + // Unable to create a UIA with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bsrm_update ), fc::exception ); // create a PM with a zero market_fee_percent const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); @@ -392,57 +392,57 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) asset_update_operation auop; auop.issuer = sam_id; - // Unable to set disable_bdsm_update bit in flags for PM + // Unable to set disable_bsrm_update bit in flags for PM auop.asset_to_update = pm_id; auop.new_options = pm_id(db).options; - auop.new_options.flags |= disable_bdsm_update; + auop.new_options.flags |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // Unable to propose either BOOST_CHECK_THROW( propose( auop ), fc::exception ); - // Unable to set disable_bdsm_update bit in flags for MPA + // Unable to set disable_bsrm_update bit in flags for MPA auop.asset_to_update = mpa_id; auop.new_options = mpa_id(db).options; - auop.new_options.flags |= disable_bdsm_update; + auop.new_options.flags |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // Unable to propose either BOOST_CHECK_THROW( propose( auop ), fc::exception ); - // Unable to set disable_bdsm_update bit in flags for UIA + // Unable to set disable_bsrm_update bit in flags for UIA auop.asset_to_update = uia_id; auop.new_options = uia_id(db).options; - auop.new_options.flags |= disable_bdsm_update; + auop.new_options.flags |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // Unable to propose either BOOST_CHECK_THROW( propose( auop ), fc::exception ); - // Unable to set disable_bdsm_update bit in issuer_permissions for PM + // Unable to set disable_bsrm_update bit in issuer_permissions for PM auop.asset_to_update = pm_id; auop.new_options = pm_id(db).options; - auop.new_options.issuer_permissions |= disable_bdsm_update; + auop.new_options.issuer_permissions |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // But able to propose propose( auop ); - // Unable to set disable_bdsm_update bit in issuer_permissions for UIA + // Unable to set disable_bsrm_update bit in issuer_permissions for UIA auop.asset_to_update = uia_id; auop.new_options = uia_id(db).options; - auop.new_options.issuer_permissions |= disable_bdsm_update; + auop.new_options.issuer_permissions |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // But able to propose propose( auop ); - // Unable to create a UIA with disable_bdsm_update permission bit + // Unable to create a UIA with disable_bsrm_update permission bit asset_create_operation acop; acop.issuer = sam_id; acop.symbol = "SAMCOIN"; @@ -451,7 +451,7 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; acop.common_options.market_fee_percent = 100; acop.common_options.flags = charge_market_fee; - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bdsm_update; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( acop ); @@ -460,17 +460,17 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) // Unable to propose either BOOST_CHECK_THROW( propose( acop ), fc::exception ); - // Able to create UIA without disable_bdsm_update permission bit + // Able to create UIA without disable_bsrm_update permission bit acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; trx.operations.clear(); trx.operations.push_back( acop ); PUSH_TX(db, trx, ~0); - // Unable to create a PM with disable_bdsm_update permission bit + // Unable to create a PM with disable_bsrm_update permission bit acop.symbol = "SAMPM"; acop.precision = asset_id_type()(db).precision; acop.is_prediction_market = true; - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bdsm_update; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bsrm_update; acop.bitasset_opts = bitasset_options(); trx.operations.clear(); @@ -480,9 +480,9 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) // Unable to propose either BOOST_CHECK_THROW( propose( acop ), fc::exception ); - // Unable to create a PM with BDSM in extensions + // Unable to create a PM with BSRM in extensions acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; - acop.bitasset_opts->extensions.value.bad_debt_settlement_method = 0; + acop.bitasset_opts->extensions.value.black_swan_response_method = 0; trx.operations.clear(); trx.operations.push_back( acop ); @@ -491,19 +491,19 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) // Unable to propose either BOOST_CHECK_THROW( propose( acop ), fc::exception ); - // Able to create PM with no disable_bdsm_update permission bit nor BDSM in extensions + // Able to create PM with no disable_bsrm_update permission bit nor BSRM in extensions acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; - acop.bitasset_opts->extensions.value.bad_debt_settlement_method.reset(); + acop.bitasset_opts->extensions.value.black_swan_response_method.reset(); trx.operations.clear(); trx.operations.push_back( acop ); PUSH_TX(db, trx, ~0); - // Unable to update PM to set BDSM + // Unable to update PM to set BSRM asset_update_bitasset_operation aubop; aubop.issuer = sam_id; aubop.asset_to_update = pm_id; aubop.new_options = pm_id(db).bitasset_data(db).options; - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + aubop.new_options.extensions.value.black_swan_response_method = 1; trx.operations.clear(); trx.operations.push_back( aubop ); @@ -520,8 +520,8 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) } } -/// Tests whether asset owner has permission to update bdsm -BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) +/// Tests whether asset owner has permission to update bsrm +BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) { try { @@ -541,9 +541,9 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); asset_id_type mpa_id = mpa.id; - BOOST_CHECK( mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -564,76 +564,76 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) auop.asset_to_update = mpa_id; auop.new_options = mpa_id(db).options; - // disable owner's permission to update bdsm - auop.new_options.issuer_permissions |= disable_bdsm_update; + // disable owner's permission to update bsrm + auop.new_options.issuer_permissions |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - // check that owner can not update bdsm + // check that owner can not update bsrm asset_update_bitasset_operation aubop; aubop.issuer = sam_id; aubop.asset_to_update = mpa_id; aubop.new_options = mpa_id(db).bitasset_data(db).options; - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + aubop.new_options.extensions.value.black_swan_response_method = 1; trx.operations.clear(); trx.operations.push_back( aubop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.bad_debt_settlement_method.reset(); + aubop.new_options.extensions.value.black_swan_response_method.reset(); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - // enable owner's permission to update bdsm - auop.new_options.issuer_permissions &= ~disable_bdsm_update; + // enable owner's permission to update bsrm + auop.new_options.issuer_permissions &= ~disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK( mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); - // check that owner can update bdsm - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + // check that owner can update bsrm + aubop.new_options.extensions.value.black_swan_response_method = 1; trx.operations.clear(); trx.operations.push_back( aubop ); PUSH_TX(db, trx, ~0); - BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method, 1u ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); - // check bdsm' valid range - aubop.new_options.extensions.value.bad_debt_settlement_method = 4; + // check bsrm' valid range + aubop.new_options.extensions.value.black_swan_response_method = 4; trx.operations.clear(); trx.operations.push_back( aubop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + aubop.new_options.extensions.value.black_swan_response_method = 1; // Sam borrow some borrow( sam, asset(1000, mpa_id), asset(2000) ); - // disable owner's permission to update bdsm - auop.new_options.issuer_permissions |= disable_bdsm_update; + // disable owner's permission to update bsrm + auop.new_options.issuer_permissions |= disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - // check that owner can not update bdsm - aubop.new_options.extensions.value.bad_debt_settlement_method = 0; + // check that owner can not update bsrm + aubop.new_options.extensions.value.black_swan_response_method = 0; trx.operations.clear(); trx.operations.push_back( aubop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.bad_debt_settlement_method.reset(); + aubop.new_options.extensions.value.black_swan_response_method.reset(); trx.operations.clear(); trx.operations.push_back( aubop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.bad_debt_settlement_method = 1; + aubop.new_options.extensions.value.black_swan_response_method = 1; // able to update other params that still has permission E.G. force_settlement_delay_sec aubop.new_options.force_settlement_delay_sec += 1; @@ -644,17 +644,17 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bdsm ) BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, aubop.new_options.force_settlement_delay_sec ); - BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.bad_debt_settlement_method, 1u ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); - // unable to enable the permission to update bdsm - auop.new_options.issuer_permissions &= ~disable_bdsm_update; + // unable to enable the permission to update bsrm + auop.new_options.issuer_permissions &= ~disable_bsrm_update; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - BOOST_CHECK( !mpa_id(db).can_owner_update_bdsm() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); generate_block(); From fe6575801cd8b7856e8aaf040bdd61761383f8d3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 27 Aug 2021 00:53:15 +0000 Subject: [PATCH 147/258] Fix apply_order and apply_force_settlement by calculating with new current_feed after filled a call order --- libraries/chain/db_market.cpp | 33 ++++++++++++++++++- .../include/graphene/chain/asset_object.hpp | 4 +++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index e783e048a6..0cebe2d8ca 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -628,6 +628,11 @@ bool database::apply_order(const limit_order_object& new_order_object) // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_idx = get_index_type().indices().get(); auto call_min = price::min( recv_asset_id, sell_asset_id ); + // Note: when BSRM is no_settlement, current_feed can change after filled a call order, + // so we recalculate inside the loop + using bsrm_type = bitasset_options::black_swan_response_type; + bool update_call_price = ( sell_abd->get_black_swan_response_method() == bsrm_type::no_settlement + && sell_abd->is_current_feed_price_capped() ); while( !finished ) { // hard fork core-343 and core-625 took place at same time, @@ -650,6 +655,12 @@ bool database::apply_order(const limit_order_object& new_order_object) if( match_result_type::only_taker_filled == match_result || match_result_type::both_filled == match_result ) finished = true; + else if( update_call_price ) + { + call_match_price = ~sell_abd->get_margin_call_order_price(); + call_pays_price = ~sell_abd->current_feed.max_short_squeeze_price(); + update_call_price = sell_abd->is_current_feed_price_capped(); + } } } else if( !finished ) // and before core-1270 hard fork @@ -726,6 +737,12 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // differ from call_match_price if there is a Margin Call Fee. price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + // Note: when BSRM is no_settlement, current_feed can change after filled a call order, + // so we recalculate inside the loop + using bsrm_type = bitasset_options::black_swan_response_type; + bool update_call_price = ( bitasset.get_black_swan_response_method() == bsrm_type::no_settlement + && bitasset.is_current_feed_price_capped() ); + bool finished = false; // whether the new order is gone // check if there are margin calls @@ -756,6 +773,13 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Check whether the new order is gone finished = ( nullptr == find_object( new_obj_id ) ); + + if( !finished && update_call_price ) + { + call_match_price = bitasset.get_margin_call_order_price(); + call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + update_call_price = bitasset.is_current_feed_price_capped(); + } } } @@ -1615,6 +1639,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa : ( call_collateral_itr != call_collateral_end ); }; + using bsrm_type = bitasset_options::black_swan_response_type; + bool update_current_feed = ( bsrm_type::no_settlement == bitasset.get_black_swan_response_method() + && bitasset.is_current_feed_price_capped() ); + while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators && limit_itr != limit_end @@ -1775,8 +1803,11 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); // Update current_feed after filled call order if needed - if( bitasset_options::black_swan_response_type::no_settlement == bitasset.get_black_swan_response_method() ) + if( update_current_feed ) + { update_bitasset_current_feed( bitasset, true ); + update_current_feed = bitasset.is_current_feed_price_capped(); + } if( !before_core_hardfork_1270 ) call_collateral_itr = call_collateral_index.lower_bound( call_min ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index a9481604ea..5f6ef1cbbb 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -276,6 +276,10 @@ namespace graphene { namespace chain { /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; + /// @return whether @ref median_feed and @ref current_feed is different + bool is_current_feed_price_capped()const + { return ( median_feed.settlement_price != current_feed.settlement_price ); } + /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin /// call territory. /// This value is derived from @ref current_feed for better performance and should be kept consistent. From 3e42d21ff31ccf02205285aa9ca2750b2c90a4df Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 27 Aug 2021 01:22:35 +0000 Subject: [PATCH 148/258] Add tests about BSRM no_settlement --- tests/tests/bsrm_tests.cpp | 418 +++++++++++++++++++++++++++++++++++++ 1 file changed, 418 insertions(+) diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_tests.cpp index 4f3353afee..9bb6b12aee 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -545,6 +545,9 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + using bsrm_type = bitasset_options::black_swan_response_type; + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -603,6 +606,7 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); // check bsrm' valid range aubop.new_options.extensions.value.black_swan_response_method = 4; @@ -664,4 +668,418 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) } } +/// Tests margin calls when BSRM is no_settlement +BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(10,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 1000 * (2000/1250) * 1.9 = 3040 + // 1000 * (22/10) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(1000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower is unable to adjust debt position if it's still undercollateralized + // 1000 * (2000/1250) * 1.25 = 2000 + // 1000 * (22/10) * 1.25 = 2750 + BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); + // borrower adjust debt position to right at MSSR + borrow( borrower, asset(0, mpa_id), asset(750) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Sam update MSSR and MCFR + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; + aubop.new_options.extensions.value.margin_call_fee_ratio = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Transfer funds to seller + transfer( borrower, seller, asset(1000,mpa_id) ); + transfer( borrower2, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(1000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells some, due to MCFR, this order won't be filled + const limit_order_object* sell_high = create_sell_order( seller, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_high ); + BOOST_CHECK_EQUAL( sell_high->for_sale.value, 100 ); + + // seller sells some again, this order will be filled + const limit_order_object* sell_low = create_sell_order( seller, asset(111,mpa_id), asset(210) ); + BOOST_REQUIRE( !sell_low ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2789 ); // 3000 - 100 - 111 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(18670) ) ); // 13:10 * (1000-111):(2100-111*210/100) + // 13:10 * 889:1867 + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells more + sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) ); + BOOST_REQUIRE( !sell_low ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1789 ); // 3000 - 100 - 111 - 1000 + // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300) + // 232 + 1866 + 305 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(24450) ) ); // 13:10 * (1000-111):(2750-111*275/100) + // 13:10 * 889:2445 + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2445 ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells more + sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) ); + BOOST_REQUIRE( sell_low ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 789 ); // 3000 - 100 - 111 - 1000 - 1000 + // 2403 + round_up(889*(24450/11557)*(1299/1000)) + // 2403 + 2444 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4847); + + BOOST_CHECK_EQUAL( sell_low->for_sale.value, 111 ); + + // check + BOOST_CHECK( !mpa.bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK( !db.find(call_id) ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests force settlements when BSRM is no_settlement +BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(10,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 1000 * (2000/1250) * 1.9 = 3040 + // 1000 * (22/10) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(1000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower is unable to adjust debt position if it's still undercollateralized + // 1000 * (2000/1250) * 1.25 = 2000 + // 1000 * (22/10) * 1.25 = 2750 + BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); + // borrower adjust debt position to right at MSSR + borrow( borrower, asset(0, mpa_id), asset(750) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1250,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Sam update MSSR and MCFR + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; + aubop.new_options.extensions.value.margin_call_fee_ratio = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Transfer funds to seller + transfer( borrower, seller, asset(1000,mpa_id) ); + transfer( borrower2, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(1000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells some, due to MCFR, this order won't be filled + const limit_order_object* sell_high = create_sell_order( seller, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_high ); + BOOST_CHECK_EQUAL( sell_high->for_sale.value, 100 ); + + // seller settles some + auto result = force_settle( seller, asset(111,mpa_id) ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2789 ); // 3000 - 100 - 111 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) + + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_CHECK( !db.find(settle_id) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(18670) ) ); // 13:10 * (1000-111):(2100-111*210/100) + // 13:10 * 889:1867 + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller settles some more + result = force_settle( seller, asset(1000,mpa_id) ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1789 ); // 3000 - 100 - 111 - 1000 + // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300) + // 232 + 1866 + 305 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); + + settle_id = *result.get().value.new_objects->begin(); + BOOST_CHECK( !db.find(settle_id) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(24450) ) ); // 13:10 * (1000-111):(2750-111*275/100) + // 13:10 * 889:2445 + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2445 ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller settles more + result = force_settle( seller, asset(1000,mpa_id) ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 789 ); // 3000 - 100 - 111 - 1000 - 1000 + // 2403 + round_up(889*(24450/11557)*(1299/1000)) + // 2403 + 2444 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4847); + + settle_id = *result.get().value.new_objects->begin(); + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 111 ); + + // check + BOOST_CHECK( !mpa.bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK( !db.find(call_id) ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 2d706d0d683e66eb6dc6663c0909cb5399c8563d Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 27 Aug 2021 01:52:01 +0000 Subject: [PATCH 149/258] Fix match_force_settlements by calculating with new current_feed after filled a call order --- libraries/chain/db_market.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 0cebe2d8ca..43be9aeca4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1639,9 +1639,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa : ( call_collateral_itr != call_collateral_end ); }; - using bsrm_type = bitasset_options::black_swan_response_type; - bool update_current_feed = ( bsrm_type::no_settlement == bitasset.get_black_swan_response_method() - && bitasset.is_current_feed_price_capped() ); + bool update_current_feed = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() ); while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators @@ -1880,6 +1878,12 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio(); + // Note: when BSRM is no_settlement, current_feed can change after filled a call order, + // so we recalculate inside the loop + using bsrm_type = bitasset_options::black_swan_response_type; + bool update_call_price = ( bitasset.get_black_swan_response_method() == bsrm_type::no_settlement + && bitasset.is_current_feed_price_capped() ); + bool margin_called = false; while( settle_itr != settle_end && call_itr != call_end ) { @@ -1906,6 +1910,13 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass if( !margin_called && result.amount > 0 ) margin_called = true; + if( update_call_price ) + { + call_match_price = bitasset.get_margin_call_order_price(); + call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + update_call_price = bitasset.is_current_feed_price_capped(); + } + settle_itr = settlement_index.lower_bound( bitasset.asset_id ); call_itr = call_collateral_index.lower_bound( call_min ); } From 4c5ac83f4eda1983e3d5f238393beb6c9fb1198c Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 02:16:58 +0000 Subject: [PATCH 150/258] Call check_call_orders() in apply_order() when feed price is updated and the new limit order is fully filled --- libraries/chain/db_market.cpp | 50 +++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 43be9aeca4..0f6777253a 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -609,8 +609,10 @@ bool database::apply_order(const limit_order_object& new_order_object) } bool finished = false; // whether the new order is gone + bool feed_price_updated = false; // whether current_feed.settlement_price has been updated if( to_check_call_orders ) { + auto call_min = price::min( recv_asset_id, sell_asset_id ); // check limit orders first, match the ones with better price in comparison to call orders while( !finished && limit_itr != limit_end && limit_itr->sell_price > call_match_price ) { @@ -627,12 +629,12 @@ bool database::apply_order(const limit_order_object& new_order_object) // check if there are margin calls // Note: it is safe to iterate here even if there is no call order due to individual settlements const auto& call_collateral_idx = get_index_type().indices().get(); - auto call_min = price::min( recv_asset_id, sell_asset_id ); // Note: when BSRM is no_settlement, current_feed can change after filled a call order, // so we recalculate inside the loop using bsrm_type = bitasset_options::black_swan_response_type; - bool update_call_price = ( sell_abd->get_black_swan_response_method() == bsrm_type::no_settlement - && sell_abd->is_current_feed_price_capped() ); + auto bsrm = sell_abd->get_black_swan_response_method(); + bool update_call_price = ( bsrm_type::no_settlement == bsrm && sell_abd->is_current_feed_price_capped() ); + auto old_current_feed_price = sell_abd->current_feed.settlement_price; while( !finished ) { // hard fork core-343 and core-625 took place at same time, @@ -660,14 +662,19 @@ bool database::apply_order(const limit_order_object& new_order_object) call_match_price = ~sell_abd->get_margin_call_order_price(); call_pays_price = ~sell_abd->current_feed.max_short_squeeze_price(); update_call_price = sell_abd->is_current_feed_price_capped(); + // Since current feed price (in debt/collateral) can only decrease after updated, if there still + // exists a call order in margin call territory, it would be on the top of the order book, + // so no need to check if the current limit (buy) order would match another limit (sell) order atm. + // On the other hand, the current limit order is on the top of the other side of the order book. } - } - } + } // while !finished + if( bsrm_type::no_settlement == bsrm && sell_abd->current_feed.settlement_price != old_current_feed_price ) + feed_price_updated = true; + } // if after core-1270 hf else if( !finished ) // and before core-1270 hard fork { // check if there are margin calls const auto& call_price_idx = get_index_type().indices().get(); - auto call_min = price::min( recv_asset_id, sell_asset_id ); while( !finished ) { // assume hard fork core-343 and core-625 will take place at same time, @@ -691,9 +698,9 @@ bool database::apply_order(const limit_order_object& new_order_object) if( match_result_type::only_taker_filled == match_result || match_result_type::both_filled == match_result ) finished = true; - } - } - } + } // while !finished + } // if before core-1270 hf + } // if to check call // still need to check limit orders while( !finished && limit_itr != limit_end ) @@ -705,16 +712,25 @@ bool database::apply_order(const limit_order_object& new_order_object) != match_result_type::only_maker_filled ); } + bool limit_order_is_gone = true; const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); - if( !updated_order_object ) - return true; + if( updated_order_object ) + // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() + // being called by match() above + // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that + // was done too eagerly before, and + // this is the point it's deferred to. + limit_order_is_gone = maybe_cull_small_order( *this, *updated_order_object ); + + if( limit_order_is_gone && feed_price_updated ) + { + // If current_feed got updated, and the new limit order is gone, + // it is possible that other limit orders are able to get filled, + // so we need to call check_call_orders() + check_call_orders( sell_asset, true, false, sell_abd ); + } - // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() - // being called by match() above - // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that - // was done too eagerly before, and - // this is the point it's deferred to. - return maybe_cull_small_order( *this, *updated_order_object ); + return limit_order_is_gone; } void database::apply_force_settlement( const force_settlement_object& new_settlement, From 2244a21fd65b5d1b618345f13d3705f57d17c414 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 03:58:16 +0000 Subject: [PATCH 151/258] Call check_call_orders in apply_force_settlements check limit orders first when feed price is updated --- libraries/chain/asset_evaluator.cpp | 2 +- libraries/chain/db_market.cpp | 29 ++++++++++++------- .../chain/include/graphene/chain/database.hpp | 7 +++-- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index fd7e9d5f04..b35ac6441f 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1282,7 +1282,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: if( HARDFORK_CORE_2481_PASSED( maint_time ) ) { - d.apply_force_settlement( settle, bitasset ); + d.apply_force_settlement( settle, bitasset, *asset_to_settle ); } return result; diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 0f6777253a..d377eab96f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -734,7 +734,8 @@ bool database::apply_order(const limit_order_object& new_order_object) } void database::apply_force_settlement( const force_settlement_object& new_settlement, - const asset_bitasset_data_object& bitasset ) + const asset_bitasset_data_object& bitasset, + const asset_object& asset_obj ) { // Defensive checks auto maint_time = get_dynamic_global_properties().next_maintenance_time; @@ -756,8 +757,8 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Note: when BSRM is no_settlement, current_feed can change after filled a call order, // so we recalculate inside the loop using bsrm_type = bitasset_options::black_swan_response_type; - bool update_call_price = ( bitasset.get_black_swan_response_method() == bsrm_type::no_settlement - && bitasset.is_current_feed_price_capped() ); + auto bsrm = bitasset.get_black_swan_response_method(); + bool update_call_price = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() ); bool finished = false; // whether the new order is gone @@ -790,11 +791,17 @@ void database::apply_force_settlement( const force_settlement_object& new_settle // Check whether the new order is gone finished = ( nullptr == find_object( new_obj_id ) ); - if( !finished && update_call_price ) + if( update_call_price ) { - call_match_price = bitasset.get_margin_call_order_price(); - call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - update_call_price = bitasset.is_current_feed_price_capped(); + // when current_feed is updated, it is possible that there are limit orders able to get filled, + // so we need to call check_call_orders(), but skip matching call orders with force settlements + check_call_orders( asset_obj, true, false, &bitasset, false, true ); + if( !finished ) + { + call_match_price = bitasset.get_margin_call_order_price(); + call_pays_price = bitasset.current_feed.max_short_squeeze_price(); + update_call_price = bitasset.is_current_feed_price_capped(); + } } } @@ -1550,11 +1557,13 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a * function that calls this with for_new_limit_order true.) * @param bitasset_ptr - an optional pointer to the bitasset_data object of the asset * @param mute_exceptions - whether to mute exceptions in a special case + * @param skip_matching_settle_orders - whether to skip matching call orders with force settlements * * @return true if a margin call was executed. */ bool database::check_call_orders( const asset_object& mia, bool enable_black_swan, bool for_new_limit_order, - const asset_bitasset_data_object* bitasset_ptr, bool mute_exceptions ) + const asset_bitasset_data_object* bitasset_ptr, + bool mute_exceptions, bool skip_matching_settle_orders ) { try { const auto& dyn_prop = get_dynamic_global_properties(); auto maint_time = dyn_prop.next_maintenance_time; @@ -1820,6 +1829,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( update_current_feed ) { update_bitasset_current_feed( bitasset, true ); + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); update_current_feed = bitasset.is_current_feed_price_capped(); } @@ -1837,9 +1847,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } // while call_itr != call_end - // Check margin calls against force settlements - if( after_core_hardfork_2481 && !bitasset.has_settlement() ) + if( !skip_matching_settle_orders && after_core_hardfork_2481 && !bitasset.has_settlement() ) { // Be here, there exists at least one margin call not processed bool called_some = match_force_settlements( bitasset ); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 63fa00fb2f..7fb1681fd2 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -460,12 +460,14 @@ namespace graphene { namespace chain { * @brief Process a new force-settlement request * @param new_settlement The new force-settlement request * @param bitasset The bitasset data object + * @param asset_obj The asset object * * Since the core-2481 hard fork, this function is called after a new force-settlement object is created * to check if there are margin calls to be matched instantly. */ void apply_force_settlement( const force_settlement_object& new_settlement, - const asset_bitasset_data_object& bitasset ); + const asset_bitasset_data_object& bitasset, + const asset_object& asset_obj ); /** * Matches the two orders, the first parameter is taker, the second is maker. @@ -603,7 +605,8 @@ namespace graphene { namespace chain { bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, bool for_new_limit_order = false, const asset_bitasset_data_object* bitasset_ptr = nullptr, - bool mute_exceptions = false ); + bool mute_exceptions = false, + bool skip_matching_settle_orders = false ); /// helpers to fill_order /// @{ From 565c73fe48bb06f73e3cb40bdbe378de4f930da3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 07:11:10 +0000 Subject: [PATCH 152/258] Fix tests about BSRM no_settlement when current_feed got updated, some existing limit orders may be filled --- tests/tests/bsrm_tests.cpp | 238 +++++++++++++++++++++++++++---------- 1 file changed, 172 insertions(+), 66 deletions(-) diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_tests.cpp index 9bb6b12aee..f525bc7ca5 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -668,8 +668,8 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) } } -/// Tests margin calls when BSRM is no_settlement -BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) +/// Tests margin calls when BSRM is no_settlement and call order is maker +BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) { try { @@ -679,7 +679,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); - ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)); + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); @@ -769,6 +769,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); // Sam update MSSR and MCFR + // note: borrower's position is undercollateralized again due to the mssr change asset_update_bitasset_operation aubop; aubop.issuer = sam_id; aubop.asset_to_update = mpa_id; @@ -787,10 +788,11 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - // Transfer funds to seller + // Transfer funds to sellers transfer( borrower, seller, asset(1000,mpa_id) ); transfer( borrower2, seller, asset(1000,mpa_id) ); - transfer( borrower3, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(500,mpa_id) ); + transfer( borrower3, seller2, asset(500,mpa_id) ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); @@ -799,16 +801,31 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - // seller sells some, due to MCFR, this order won't be filled - const limit_order_object* sell_high = create_sell_order( seller, asset(100,mpa_id), asset(210) ); + // seller2 sells some, due to MCFR, this order won't be filled + const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); BOOST_REQUIRE( sell_high ); - BOOST_CHECK_EQUAL( sell_high->for_sale.value, 100 ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - // seller sells some again, this order will be filled + // seller2 sells more, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // 500 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller sells some, this order will be filled const limit_order_object* sell_low = create_sell_order( seller, asset(111,mpa_id), asset(210) ); - BOOST_REQUIRE( !sell_low ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2789 ); // 3000 - 100 - 111 + BOOST_CHECK( !sell_low ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); @@ -826,22 +843,36 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) // seller sells more sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) ); - BOOST_REQUIRE( !sell_low ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1789 ); // 3000 - 100 - 111 - 1000 + BOOST_CHECK( !sell_low ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1389 ); // 2500 - 111 - 1000 // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300) // 232 + 1866 + 305 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); + // now feed price is 13:10 * (1000-111):(2750-111*275/100) + // = 13:10 * 889:2445 = 11557:24450 + + // seller2's sell_mid got filled too + BOOST_CHECK( !db.find( sell_mid_id ) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + // call pays round_down(210*1300/1299) = 210, fee = 0 + // now feed price is 13:10 * (889-100):(2445-210) + // = 13:10 * 789:2235 = 10257:22350 + + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); // check BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price - == price( asset(11557,mpa_id), asset(24450) ) ); // 13:10 * (1000-111):(2750-111*275/100) - // 13:10 * 889:2445 + == price( asset(10257,mpa_id), asset(22350) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK_EQUAL( call_id(db).debt.value, 889 ); - BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2445 ); + BOOST_CHECK_EQUAL( call_id(db).debt.value, 789 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2235 ); BOOST_CHECK( !db.find(call2_id) ); BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); @@ -849,23 +880,39 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) // seller sells more sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(100) ); BOOST_REQUIRE( sell_low ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 789 ); // 3000 - 100 - 111 - 1000 - 1000 - // 2403 + round_up(889*(24450/11557)*(1299/1000)) - // 2403 + 2444 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4847); + limit_order_id_type sell_low_id = sell_low->id; - BOOST_CHECK_EQUAL( sell_low->for_sale.value, 111 ); + auto final_check = [&] + { + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 211 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - // check - BOOST_CHECK( !mpa.bitasset_data(db).is_current_feed_price_capped() ); - BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000 + // 2403 + round_up(789*(22350/10257)*(1299/1000)) + // 2403 + 2234 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4637 ); - BOOST_CHECK( !db.find(call_id) ); - BOOST_CHECK( !db.find(call2_id) ); - BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); - BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // no change + + // check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK( !db.find(call_id) ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); } catch (fc::exception& e) { edump((e.to_detail_string())); @@ -873,8 +920,8 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_margin_call_test ) } } -/// Tests force settlements when BSRM is no_settlement -BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) +/// Tests force settlements when BSRM is no_settlement and call order is maker +BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) { try { @@ -884,7 +931,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); - ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)); + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); @@ -974,6 +1021,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); // Sam update MSSR and MCFR + // note: borrower's position is undercollateralized again due to the mssr change asset_update_bitasset_operation aubop; aubop.issuer = sam_id; aubop.asset_to_update = mpa_id; @@ -992,10 +1040,11 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(1300,mpa_id), asset(2100) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - // Transfer funds to seller + // Transfer funds to sellers transfer( borrower, seller, asset(1000,mpa_id) ); transfer( borrower2, seller, asset(1000,mpa_id) ); - transfer( borrower3, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(500,mpa_id) ); + transfer( borrower3, seller2, asset(500,mpa_id) ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); @@ -1004,19 +1053,33 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - // seller sells some, due to MCFR, this order won't be filled - const limit_order_object* sell_high = create_sell_order( seller, asset(100,mpa_id), asset(210) ); + // seller2 sells some + const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); BOOST_REQUIRE( sell_high ); - BOOST_CHECK_EQUAL( sell_high->for_sale.value, 100 ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + + // seller2 sells more, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // 500 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // seller settles some auto result = force_settle( seller, asset(111,mpa_id) ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2789 ); // 3000 - 100 - 111 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) - force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( !db.find(settle_id) ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + // check BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price @@ -1033,48 +1096,91 @@ BOOST_AUTO_TEST_CASE( no_settlement_and_force_settle_test ) // seller settles some more result = force_settle( seller, asset(1000,mpa_id) ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1789 ); // 3000 - 100 - 111 - 1000 - // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(275/100)*(1299/1300) - // 232 + 1866 + 305 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); - settle_id = *result.get().value.new_objects->begin(); BOOST_CHECK( !db.find(settle_id) ); + // call2 is filled by settle order + BOOST_CHECK( !db.find(call2_id) ); + // now feed price is 13:10 * 1000:2750 = 26:55 (>10/22) + // call order match price is 1300:1299 * 1000:2750 = 0.363916299 + // sell_mid's price is 100/210 = 0.047619048 + + // then seller2's sell_mid got filled by call + BOOST_CHECK( !db.find( sell_mid_id ) ); + + // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price + // call pays round_down(210*1300/1299) = 210, fee = 0 + // now feed price is 13:10 * (1000-100):(2750-210) + // = 13:10 * 900:2540 = 11700:25400 (>10/22) + // call order match price is 1300:1299 * 900:2540 = 0.32732629 + // sell_high's price is 100/275 = 0.363636364 + + // then sell_high got filled by call + BOOST_CHECK( !db.find( sell_high_id ) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price + // sell_high was selling 100 MPA for 275 CORE as maker, matched at its price + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 + // call pays round_down(275*1300/1299) = 275, fee = 0 + // now feed price is 13:10 * (1000-100-100):(2750-210-275) + // = 13:10 * 800:2265 = 10400:22650 = 208:453 (>10/22) + + // then the settle order got filled by call + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1389 ); // 2500 - 111 - 1000 + // 232 + round_up(889*(18670/11557)*(1299/1000)) + 111*(22650/10400)*(1299/1000) + // 232 + 1866 + 314 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2412 ); + // now feed price is 13:10 * (800-111):(2265-111*(22650/10400)*(13/10)) + // = 13:10 * 689:1951 = 8957:19510 (>10/22) + // check BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price - == price( asset(11557,mpa_id), asset(24450) ) ); // 13:10 * (1000-111):(2750-111*275/100) - // 13:10 * 889:2445 + == price( asset(8957,mpa_id), asset(19510) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK_EQUAL( call_id(db).debt.value, 889 ); - BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2445 ); + BOOST_CHECK_EQUAL( call_id(db).debt.value, 689 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1951 ); BOOST_CHECK( !db.find(call2_id) ); BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); // seller settles more result = force_settle( seller, asset(1000,mpa_id) ); - BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 789 ); // 3000 - 100 - 111 - 1000 - 1000 - // 2403 + round_up(889*(24450/11557)*(1299/1000)) - // 2403 + 2444 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4847); - settle_id = *result.get().value.new_objects->begin(); - BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 111 ); - // check - BOOST_CHECK( !mpa.bitasset_data(db).is_current_feed_price_capped() ); - BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); - BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + auto final_check = [&] + { + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 311 ); - BOOST_CHECK( !db.find(call_id) ); - BOOST_CHECK( !db.find(call2_id) ); - BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); - BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000 + // 2412 + round_up(689*(19510/8957)*(1299/1000)) + // 2412 + 1950 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4362 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // no change + + // check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK( !db.find(call_id) ); + BOOST_CHECK( !db.find(call2_id) ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); } catch (fc::exception& e) { edump((e.to_detail_string())); From b8f788165b0bbaa33660be647bc9990ccc00fdc6 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 21:26:42 +0000 Subject: [PATCH 153/258] Fix check_call_orders() wrt no_settlement check limit orders again after matched a call order with a settle order --- libraries/chain/db_market.cpp | 421 ++++++++++++++++++---------------- 1 file changed, 221 insertions(+), 200 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index d377eab96f..f46ab033e0 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1666,210 +1666,231 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool update_current_feed = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() ); - while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance - // by passing in iterators - && limit_itr != limit_end - && has_call_order() ) + // If update_current_feed is true and we filled a call order with a settle order, + // we need to check limit orders again, in this case we loop multiple times. + // In other cases we only need to loop once. + while( true ) { - bool filled_call = false; - - const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); - - // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - bool feed_protected = before_core_hardfork_1270 ? - ( after_hardfork_436 - && bitasset.current_feed.settlement_price > ~call_order.call_price ) - : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); - if( feed_protected ) - return margin_called; - - const limit_order_object& limit_order = *limit_itr; - - price match_price = limit_order.sell_price; - // There was a check `match_price.validate();` here, which is removed now because it always passes - - // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606 - if( before_core_hardfork_606 && match_price > ~call_order.call_price ) - return margin_called; - - margin_called = true; - - price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); - // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the - // excess being kept by the asset issuer as a margin call fee. In what follows, we use - // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, - // of course, to determine what the limit order receives. Note margin_call_pays_ratio() returns - // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit. - - // Although we checked for black swan above, we do one more check to ensure the call order can - // pay the amount of collateral which we intend to take from it (including margin call fee). - // TODO refactor code for better performance and readability, perhaps extract the new logic to a new - // function and call it after hf_1270, hf_bsip74 or hf_2481. - auto usd_to_buy = call_order.get_debt(); - if( !after_core_hardfork_2481 && ( usd_to_buy * call_pays_price ) > call_order.get_collateral() ) - { - // Trigger black swan - elog( "black swan detected on asset ${symbol} (${id}) at block ${b}", - ("id",bitasset.asset_id)("symbol",mia.symbol)("b",head_num) ); - edump((enable_black_swan)); - FC_ASSERT( enable_black_swan ); - globally_settle_asset(mia, bitasset.current_feed.settlement_price ); - return true; - } - - if( !before_core_hardfork_1270 ) - { - usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price, + + while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance + // by passing in iterators + && limit_itr != limit_end + && has_call_order() ) + { + bool filled_call = false; + + const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); + + // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 + bool feed_protected = before_core_hardfork_1270 ? + ( after_hardfork_436 + && bitasset.current_feed.settlement_price > ~call_order.call_price ) + : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); + if( feed_protected ) + return margin_called; + + const limit_order_object& limit_order = *limit_itr; + + price match_price = limit_order.sell_price; + // There was a check `match_price.validate();` here, which is removed now because it always passes + + // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606 + if( before_core_hardfork_606 && match_price > ~call_order.call_price ) + return margin_called; + + margin_called = true; + + price call_pays_price = match_price * bitasset.get_margin_call_pays_ratio(); + // Since BSIP74, the call "pays" a bit more collateral per debt than the match price, with the + // excess being kept by the asset issuer as a margin call fee. In what follows, we use + // call_pays_price for the black swan check, and for the TCR, but we still use the match_price, + // of course, to determine what the limit order receives. Note margin_call_pays_ratio() returns + // 1/1 if margin_call_fee_ratio is unset (i.e. before BSIP74), so hardfork check is implicit. + + // Although we checked for black swan above, we do one more check to ensure the call order can + // pay the amount of collateral which we intend to take from it (including margin call fee). + // TODO refactor code for better performance and readability, perhaps extract the new logic to a new + // function and call it after hf_1270, hf_bsip74 or hf_2481. + auto usd_to_buy = call_order.get_debt(); + if( !after_core_hardfork_2481 && ( usd_to_buy * call_pays_price ) > call_order.get_collateral() ) + { + // Trigger black swan + elog( "black swan detected on asset ${symbol} (${id}) at block ${b}", + ("id",bitasset.asset_id)("symbol",mia.symbol)("b",head_num) ); + edump((enable_black_swan)); + FC_ASSERT( enable_black_swan ); + globally_settle_asset(mia, bitasset.current_feed.settlement_price ); + return true; + } + + if( !before_core_hardfork_1270 ) + { + usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price, bitasset.current_feed.settlement_price, bitasset.current_feed.maintenance_collateral_ratio, bitasset.current_maintenance_collateralization ); - } - else if( !before_core_hardfork_834 ) - { - usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price, + } + else if( !before_core_hardfork_834 ) + { + usd_to_buy.amount = call_order.get_max_debt_to_cover( call_pays_price, bitasset.current_feed.settlement_price, bitasset.current_feed.maintenance_collateral_ratio ); - } - - asset usd_for_sale = limit_order.amount_for_sale(); - asset call_pays, call_receives, limit_pays, limit_receives; - if( usd_to_buy > usd_for_sale ) - { // fill order - limit_receives = usd_for_sale * match_price; // round down, in favor of call order - if( !after_core_hardfork_2481 ) - // TODO add tests about CR change - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) - - // Be here, the limit order won't be paying something for nothing, since if it would, it would have - // been cancelled elsewhere already (a maker limit order won't be paying something for nothing): - // * after hard fork core-625, the limit order will be always a maker if entered this function; - // * before hard fork core-625, - // * when the limit order is a taker, it could be paying something for nothing only when - // the call order is smaller and is too small - // * when the limit order is a maker, it won't be paying something for nothing - - if( before_core_hardfork_342 ) - call_receives = usd_for_sale; - else - // The remaining amount in the limit order would be too small, - // so we should cull the order in fill_limit_order() below. - // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, - // so calling maybe_cull_small() will always cull it. - call_receives = limit_receives.multiply_and_round_up( match_price ); - - if( after_core_hardfork_2481 ) - { - call_pays = call_receives * call_pays_price; // calculate with updated call_receives - if( call_pays.amount >= call_order.collateral ) - break; - auto new_collateral = call_order.get_collateral() - call_pays; - auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math - if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease - break; - } - - filled_limit = true; - - } else { // fill call, could be partial fill due to TCR - call_receives = usd_to_buy; - - if( before_core_hardfork_342 ) - { - limit_receives = usd_to_buy * match_price; // round down, in favor of call order - call_pays = limit_receives; - } else { - call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. - // Note: Due to different rounding, this could potentialy be - // one satoshi more than the blackswan check above - if( call_pays.amount > call_order.collateral ) - { - if( after_core_hardfork_2481 ) - break; - if( mute_exceptions ) - call_pays.amount = call_order.collateral; - } - // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher - // than the old CR, so no additional check for potential blackswan here - - limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order - if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 - limit_receives.amount = call_order.collateral; - // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more - // debt asset, it means the call order could be receiving a bit too much less than its value. - // It is a sad thing for the call order, but it is the rule -- when a call order is margin called, - // it does not get more than it borrowed. - // On the other hand, if the call order is not being closed (due to TCR), - // it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it. - } - - filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) - - if( usd_to_buy == usd_for_sale ) - filled_limit = true; - else if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) - { - //NOTE: Multiple limit match problem (see issue 453, yes this happened) - if( before_hardfork_615 ) - _issue_453_affected_assets.insert( bitasset.asset_id ); - } - } - limit_pays = call_receives; - - // BSIP74: Margin call fee - FC_ASSERT(call_pays >= limit_receives); - const asset margin_call_fee = call_pays - limit_receives; - - if( filled_call && before_core_hardfork_343 ) - ++call_price_itr; - - // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker - fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); - - // Update current_feed after filled call order if needed - if( update_current_feed ) - { - update_bitasset_current_feed( bitasset, true ); - limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); - update_current_feed = bitasset.is_current_feed_price_capped(); - } - - if( !before_core_hardfork_1270 ) - call_collateral_itr = call_collateral_index.lower_bound( call_min ); - else if( !before_core_hardfork_343 ) - call_price_itr = call_price_index.lower_bound( call_min ); - - auto next_limit_itr = std::next( limit_itr ); - // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker - bool really_filled = fill_limit_order( limit_order, limit_pays, limit_receives, true, - match_price, !for_new_limit_order ); - if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) - limit_itr = next_limit_itr; - - } // while call_itr != call_end - - // Check margin calls against force settlements - if( !skip_matching_settle_orders && after_core_hardfork_2481 && !bitasset.has_settlement() ) - { - // Be here, there exists at least one margin call not processed - bool called_some = match_force_settlements( bitasset ); - if( called_some ) - margin_called = true; - // At last, check for blackswan // TODO perhaps improve performance by passing in iterators - if( bsrm_type::individual_settlement_to_fund == bsrm - || bsrm_type::individual_settlement_to_order == bsrm ) + } + + asset usd_for_sale = limit_order.amount_for_sale(); + asset call_pays, call_receives, limit_pays, limit_receives; + if( usd_to_buy > usd_for_sale ) + { // fill order + limit_receives = usd_for_sale * match_price; // round down, in favor of call order + if( !after_core_hardfork_2481 ) + // TODO add tests about CR change + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + + // Be here, the limit order won't be paying something for nothing, since if it would, it would have + // been cancelled elsewhere already (a maker limit order won't be paying something for nothing): + // * after hard fork core-625, the limit order will be always a maker if entered this function; + // * before hard fork core-625, + // * when the limit order is a taker, it could be paying something for nothing only when + // the call order is smaller and is too small + // * when the limit order is a maker, it won't be paying something for nothing + + if( before_core_hardfork_342 ) + call_receives = usd_for_sale; + else + // The remaining amount in the limit order would be too small, + // so we should cull the order in fill_limit_order() below. + // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, + // so calling maybe_cull_small() will always cull it. + call_receives = limit_receives.multiply_and_round_up( match_price ); + + if( after_core_hardfork_2481 ) + { + call_pays = call_receives * call_pays_price; // calculate with updated call_receives + if( call_pays.amount >= call_order.collateral ) + break; + auto new_collateral = call_order.get_collateral() - call_pays; + auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math + if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease + break; + } + + filled_limit = true; + + } else { // fill call, could be partial fill due to TCR + call_receives = usd_to_buy; + + if( before_core_hardfork_342 ) + { + limit_receives = usd_to_buy * match_price; // round down, in favor of call order + call_pays = limit_receives; + } else { + call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. + // Note: Due to different rounding, this could potentialy be + // one satoshi more than the blackswan check above + if( call_pays.amount > call_order.collateral ) + { + if( after_core_hardfork_2481 ) + break; + if( mute_exceptions ) + call_pays.amount = call_order.collateral; + } + // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher + // than the old CR, so no additional check for potential blackswan here + + limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order + if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 + limit_receives.amount = call_order.collateral; + // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more + // debt asset, it means the call order could be receiving a bit too much less than its value. + // It is a sad thing for the call order, but it is the rule + // -- when a call order is margin called, it does not get more than it borrowed. + // On the other hand, if the call order is not being closed (due to TCR), + // it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it. + } + + filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) + + if( usd_to_buy == usd_for_sale ) + filled_limit = true; + else if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) + { + //NOTE: Multiple limit match problem (see issue 453, yes this happened) + if( before_hardfork_615 ) + _issue_453_affected_assets.insert( bitasset.asset_id ); + } + } + limit_pays = call_receives; + + // BSIP74: Margin call fee + FC_ASSERT(call_pays >= limit_receives); + const asset margin_call_fee = call_pays - limit_receives; + + if( filled_call && before_core_hardfork_343 ) + ++call_price_itr; + + // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker + fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); + + // Update current_feed after filled call order if needed + if( update_current_feed ) + { + update_bitasset_current_feed( bitasset, true ); + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + update_current_feed = bitasset.is_current_feed_price_capped(); + } + + if( !before_core_hardfork_1270 ) + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + else if( !before_core_hardfork_343 ) + call_price_itr = call_price_index.lower_bound( call_min ); + + auto next_limit_itr = std::next( limit_itr ); + // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker + bool really_filled = fill_limit_order( limit_order, limit_pays, limit_receives, true, + match_price, !for_new_limit_order ); + if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) + limit_itr = next_limit_itr; + + } // while call_itr != call_end + + // Check margin calls against force settlements + if( !skip_matching_settle_orders && after_core_hardfork_2481 && !bitasset.has_settlement() ) { - // Run multiple times, each time one call order gets settled - // TODO perhaps improve performance by settling multiple call orders inside in one call - while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) + // Be here, there exists at least one margin call not processed + bool called_some = match_force_settlements( bitasset ); + if( called_some ) + { + margin_called = true; + // if called some, it means the call order is updated or removed, + // in this case, if update_current_feed is true, + // it is possible that there are limit orders able to get filled, + // so we need to check again + if( update_current_feed ) + { + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + update_current_feed = bitasset.is_current_feed_price_capped(); + continue; + } + } + // At last, check for blackswan // TODO perhaps improve performance by passing in iterators + if( bsrm_type::individual_settlement_to_fund == bsrm + || bsrm_type::individual_settlement_to_order == bsrm ) { - // do nothing + // Run multiple times, each time one call order gets settled + // TODO perhaps improve performance by settling multiple call orders inside in one call + while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) + { + // do nothing + } } + else + check_for_blackswan( mia, enable_black_swan, &bitasset ); } - else - check_for_blackswan( mia, enable_black_swan, &bitasset ); - } + break; + } // while true - return margin_called; + return margin_called; } FC_CAPTURE_AND_RETHROW() } bool database::match_force_settlements( const asset_bitasset_data_object& bitasset ) @@ -1932,15 +1953,15 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price, &margin_call_pays_ratio ); - if( !margin_called && result.amount > 0 ) - margin_called = true; - - if( update_call_price ) + // if result.amount > 0, it means the call order got updated or removed + if( result.amount > 0 ) { - call_match_price = bitasset.get_margin_call_order_price(); - call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - update_call_price = bitasset.is_current_feed_price_capped(); + // if update_call_price is true, we need to check limit orders first, so we return + if( update_call_price ) + return true; + margin_called = true; } + // else : result.amount == 0, it means the settle order got canceled directly and the call order did not change settle_itr = settlement_index.lower_bound( bitasset.asset_id ); call_itr = call_collateral_index.lower_bound( call_min ); From b50b2f82c6290ae6a273a72b597d65bcdf500dd4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 28 Aug 2021 22:18:24 +0000 Subject: [PATCH 154/258] Fix no_settlement tests about feed update --- tests/tests/bsrm_tests.cpp | 95 +++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_tests.cpp index f525bc7ca5..d156fcd12b 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -801,21 +801,27 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - // seller2 sells some, due to MCFR, this order won't be filled + // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + + // seller2 sells more, this order won't be filled in the beginning either const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); BOOST_REQUIRE( sell_high ); limit_order_id_type sell_high_id = sell_high->id; BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - // seller2 sells more, due to MCFR, this order won't be filled in the beginning, but will be filled later - const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); - BOOST_REQUIRE( sell_mid ); - limit_order_id_type sell_mid_id = sell_mid->id; - BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // 500 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); // 500 - 100 - 100 - 100 BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // seller sells some, this order will be filled @@ -824,7 +830,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // check @@ -851,28 +857,41 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2403 ); // now feed price is 13:10 * (1000-111):(2750-111*275/100) // = 13:10 * 889:2445 = 11557:24450 + // call order match price is 1300:1299 * 889:2445 = 0.363879089 + // sell_mid's price is 100/210 = 0.047619048 - // seller2's sell_mid got filled too + // sell_mid got filled too BOOST_CHECK( !db.find( sell_mid_id ) ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price - BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // call pays round_down(210*1300/1299) = 210, fee = 0 // now feed price is 13:10 * (889-100):(2445-210) // = 13:10 * 789:2235 = 10257:22350 + // call order match price is 1300:1299 * 789:2235 = 0.353291897 + // sell_high's price is 100/275 = 0.363636364 - BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + // sell_high got filled too + BOOST_CHECK( !db.find( sell_high_id ) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); + // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price + // sell_high was selling 100 MPA for 275 CORE as maker, matched at its price + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 + // call pays round_down(275*1300/1299) = 275, fee = 0 + // now feed price is 13:10 * (789-100):(2235-275) + // = 13:10 * 689:1960 = 8957:19600 (>10/22) + // call order match price is 1300:1299 * 689:1960 = 0.351801229 + // sell_highest's price is 100/285 = 0.350877193 // check BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price - == price( asset(10257,mpa_id), asset(22350) ) ); + == price( asset(8957,mpa_id), asset(19600) ) ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); - BOOST_CHECK_EQUAL( call_id(db).debt.value, 789 ); - BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2235 ); + BOOST_CHECK_EQUAL( call_id(db).debt.value, 689 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1960 ); BOOST_CHECK( !db.find(call2_id) ); BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); @@ -884,16 +903,16 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) auto final_check = [&] { - BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 211 ); - BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 311 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000 - // 2403 + round_up(789*(22350/10257)*(1299/1000)) - // 2403 + 2234 - BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4637 ); + // 2403 + round_up(689*(19600/8957)*(1299/1000)) + // 2403 + 1959 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4362 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // no change // check BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); @@ -1053,21 +1072,27 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - // seller2 sells some + // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + + // seller2 sells more, this order won't be filled in the beginning either const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); BOOST_REQUIRE( sell_high ); limit_order_id_type sell_high_id = sell_high->id; BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - // seller2 sells more, due to MCFR, this order won't be filled in the beginning, but will be filled later - const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); - BOOST_REQUIRE( sell_mid ); - limit_order_id_type sell_mid_id = sell_mid->id; - BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2500 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); // 500 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); // 500 - 100 - 100 - 100 BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // seller settles some @@ -1077,7 +1102,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 2389 ); // 2500 - 111 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); // 111 * (210/100) * (1299/1300) - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); // check @@ -1118,13 +1143,15 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) // then sell_high got filled by call BOOST_CHECK( !db.find( sell_high_id ) ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); // sell_mid was selling 100 MPA for 210 CORE as maker, matched at its price // sell_high was selling 100 MPA for 275 CORE as maker, matched at its price BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 // call pays round_down(275*1300/1299) = 275, fee = 0 // now feed price is 13:10 * (1000-100-100):(2750-210-275) // = 13:10 * 800:2265 = 10400:22650 = 208:453 (>10/22) + // call order match price is 1300:1299 * 800:2265 = 0.353472785 + // sell_highest's price is 100/285 = 0.350877193 // then the settle order got filled by call BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 1389 ); // 2500 - 111 - 1000 @@ -1133,6 +1160,8 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2412 ); // now feed price is 13:10 * (800-111):(2265-111*(22650/10400)*(13/10)) // = 13:10 * 689:1951 = 8957:19510 (>10/22) + // call order match price is 1300:1299 * 689:1951 = 0.353424094 + // sell_highest's price is 100/285 = 0.350877193 // check BOOST_CHECK( mpa.bitasset_data(db).is_current_feed_price_capped() ); @@ -1155,12 +1184,14 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) { BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 311 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389 ); // 2500 - 111 - 1000 - 1000 // 2412 + round_up(689*(19510/8957)*(1299/1000)) // 2412 + 1950 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4362 ); - BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 300 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200 ); BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // no change // check From 3a7a6c0956c6e06fb02e62e1e3de2137f4e96928 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 01:40:31 +0000 Subject: [PATCH 155/258] Reduce number of params for db::match(limit,call) --- libraries/chain/db_market.cpp | 16 +++++++--------- .../chain/include/graphene/chain/database.hpp | 10 +--------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f46ab033e0..fe863d6448 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -647,9 +647,6 @@ bool database::apply_order(const limit_order_object& new_order_object) break; // hard fork core-338 and core-625 took place at same time, not checking HARDFORK_CORE_338_TIME here. const auto match_result = match( new_order_object, *call_itr, call_match_price, - sell_abd->current_feed.settlement_price, - sell_abd->current_feed.maintenance_collateral_ratio, - sell_abd->current_maintenance_collateralization, *sell_abd, call_pays_price ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. @@ -687,10 +684,7 @@ bool database::apply_order(const limit_order_object& new_order_object) break; // assume hard fork core-338 and core-625 will take place at same time, // not checking HARDFORK_CORE_338_TIME here. - const auto match_result = match( new_order_object, *call_itr, call_match_price, - sell_abd->current_feed.settlement_price, - sell_abd->current_feed.maintenance_collateral_ratio, - optional(), *sell_abd ); + const auto match_result = match( new_order_object, *call_itr, call_match_price, *sell_abd ); // match returns 1 or 3 when the new order was fully filled. // In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hard fork core-834), @@ -981,8 +975,6 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde database::match_result_type database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization, const asset_bitasset_data_object& bitasset, const price& call_pays_price ) { @@ -992,6 +984,12 @@ database::match_result_type database::match( const limit_order_object& bid, cons bool cull_taker = false; + const auto& feed_price = bitasset.current_feed.settlement_price; + const auto& maintenance_collateral_ratio = bitasset.current_feed.maintenance_collateral_ratio; + optional maintenance_collateralization; + if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME ) // call price caching issue + maintenance_collateralization = bitasset.current_maintenance_collateralization; + asset usd_for_sale = bid.amount_for_sale(); asset usd_to_buy = asset( ask.get_max_debt_to_cover( call_pays_price, feed_price, maintenance_collateral_ratio, maintenance_collateralization ), ask.debt_type() ); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 7fb1681fd2..2f3452ae90 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -498,9 +498,6 @@ namespace graphene { namespace chain { * @param taker the order that is removing liquidity from the book * @param maker the order that put liquidity on the book * @param trade_price the price the trade should execute at - * @param feed_price the price of the current feed - * @param maintenance_collateral_ratio the maintenance collateral ratio - * @param maintenance_collateralization the maintenance collateralization * @param bitasset the bitasset object corresponding to debt asset of the call order * @param call_pays_price price call order pays. Call order may pay more collateral * than limit order takes if call order subject to a margin call fee. @@ -508,18 +505,13 @@ namespace graphene { namespace chain { */ match_result_type match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization, const asset_bitasset_data_object& bitasset, const price& call_pays_price); /// If separate call_pays_price not provided, assume call pays at trade_price: match_result_type match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio, - const optional& maintenance_collateralization, const asset_bitasset_data_object& bitasset) { - return match(taker, maker, trade_price, feed_price, maintenance_collateral_ratio, - maintenance_collateralization, bitasset, trade_price); + return match(taker, maker, trade_price, bitasset, trade_price); } ///@} From d6b3d43b8886bffebb752301c65fb4bcdfa5b74f Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 02:00:29 +0000 Subject: [PATCH 156/258] Reduce number of params for db::match(settle,call) --- libraries/chain/db_market.cpp | 38 +++++-------------- .../chain/include/graphene/chain/database.hpp | 12 ++---- 2 files changed, 13 insertions(+), 37 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index fe863d6448..a4ea0e34f7 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -778,8 +778,6 @@ void database::apply_force_settlement( const force_settlement_object& new_settle bitasset.current_maintenance_collateralization ), new_settlement.balance.asset_id ); - // Note: the call order should be able to pay at call_pays_price, - // thus no need to pass in margin_call_pays_ratio match( new_settlement, *call_itr, call_pays_price, bitasset, max_debt_to_cover, call_match_price, true ); // Check whether the new order is gone @@ -1051,11 +1049,9 @@ asset database::match( const force_settlement_object& settle, const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, - bool is_margin_call, - const ratio_type* margin_call_pays_ratio ) + bool is_margin_call ) { - return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, is_margin_call, true, - margin_call_pays_ratio ); + return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, is_margin_call, true ); } asset database::match( const call_order_object& call, @@ -1063,11 +1059,9 @@ asset database::match( const call_order_object& call, const price& match_price, const asset_bitasset_data_object& bitasset, const asset& max_settlement, - const price& fill_price, - const ratio_type* margin_call_pays_ratio ) + const price& fill_price ) { - return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, true, false, - margin_call_pays_ratio ); + return match_impl( settle, call, match_price, bitasset, max_settlement, fill_price, true, false ); } asset database::match_impl( const force_settlement_object& settle, @@ -1077,8 +1071,7 @@ asset database::match_impl( const force_settlement_object& settle, const asset& max_settlement, const price& p_fill_price, bool is_margin_call, - bool settle_is_taker, - const ratio_type* margin_call_pays_ratio ) + bool settle_is_taker ) { try { FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id ); FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0); @@ -1141,6 +1134,7 @@ asset database::match_impl( const force_settlement_object& settle, } // end : if after the core-184 hf and call_pays.amount == 0 else if( !before_core_hardfork_342 && call_pays.amount != 0 ) { + auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio(); // be here, the call order is not paying nothing, // but it is still possible that the settle order is paying more than minimum required due to rounding if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order @@ -1152,7 +1146,7 @@ asset database::match_impl( const force_settlement_object& settle, { call_pays.amount = call.collateral; match_price = call_debt / call_collateral; - fill_price = margin_call_pays_ratio ? ( match_price / (*margin_call_pays_ratio) ) : match_price; + fill_price = match_price / margin_call_pays_ratio; } settle_receives = call_receives.multiply_and_round_up( fill_price ); } @@ -1220,17 +1214,8 @@ asset database::match_impl( const force_settlement_object& settle, // price changed, update call_receives // round up to mitigate rounding issues (hf core-342) call_receives = call_pays.multiply_and_round_up( match_price ); // round up // update fill price and settle_receives - if( margin_call_pays_ratio ) // check to be defensive - { - fill_price = match_price / (*margin_call_pays_ratio); - settle_receives = call_receives * fill_price; // round down here, in favor of call order - } - else // normally this code won't be reached. be defensive here - { - wlog( "Unexpected: margin_call_pays_ratio == nullptr" ); - fill_price = match_price; - settle_receives = call_pays; - } + fill_price = match_price / margin_call_pays_ratio; + settle_receives = call_receives * fill_price; // round down here, in favor of call order } if( settle_receives.amount == 0 ) settle_receives.amount = 1; // reduce margin-call fee in this case. Note: here call_pays >= 1 @@ -1920,8 +1905,6 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // It is in debt/collateral . price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - auto margin_call_pays_ratio = bitasset.get_margin_call_pays_ratio(); - // Note: when BSRM is no_settlement, current_feed can change after filled a call order, // so we recalculate inside the loop using bsrm_type = bitasset_options::black_swan_response_type; @@ -1948,8 +1931,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // Note: if the call order's CR is too low, it is probably unable to fill at call_pays_price. // In this case, the call order pays at its CR, the settle order may receive less due to margin call fee. // It is processed inside the function. - auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price, - &margin_call_pays_ratio ); + auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price ); // if result.amount > 0, it means the call order got updated or removed if( result.amount > 0 ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 2f3452ae90..a6736096ca 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -429,7 +429,6 @@ namespace graphene { namespace chain { /// how much the settle order receives when the call order is being margin called /// @param is_margin_call whether the call order is being margin called /// @param settle_order_is_taker whether the settle_order is the taker - /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match_impl( const force_settlement_object& settle, const call_order_object& call, @@ -438,8 +437,7 @@ namespace graphene { namespace chain { const asset& max_settlement, const price& fill_price, bool is_margin_call = false, - bool settle_order_is_taker = true, - const ratio_type* margin_call_pays_ratio = nullptr ); + bool settle_order_is_taker = true ); public: @@ -525,7 +523,6 @@ namespace graphene { namespace chain { /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives when the call order is being margin called /// @param is_margin_call whether the call order is being margin called - /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match( const force_settlement_object& settle, const call_order_object& call, @@ -533,8 +530,7 @@ namespace graphene { namespace chain { const asset_bitasset_data_object& bitasset, const asset& max_settlement, const price& fill_price, - bool is_margin_call = false, - const ratio_type* margin_call_pays_ratio = nullptr ); + bool is_margin_call = false ); /// Matches the two orders, the first parameter is taker, the second is maker. /// @param call the call order being margin called @@ -544,15 +540,13 @@ namespace graphene { namespace chain { /// @param max_settlement the maximum debt amount to be filled during this match /// @param fill_price the price to be recorded in market history plugin. It is the price to calculate /// how much the settle order receives. - /// @param margin_call_pays_ratio see @ref price_feed::margin_call_pays_ratio /// @return the amount of asset settled asset match( const call_order_object& call, const force_settlement_object& settle, const price& match_price, const asset_bitasset_data_object& bitasset, const asset& max_settlement, - const price& fill_price, - const ratio_type* margin_call_pays_ratio = nullptr ); + const price& fill_price ); /** * @brief fills limit order From e7b12450af62e8d12a7a07c147fd3bf925cbc00b Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 19:48:34 +0000 Subject: [PATCH 157/258] Simplify code --- libraries/chain/asset_evaluator.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index b35ac6441f..9b184814f0 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -946,10 +946,8 @@ static bool update_bitasset_object_options( if( should_update_feeds || update_feeds_due_to_bsrm_change ) { const auto old_feed = bdo.current_feed; - if( should_update_feeds ) - db.update_bitasset_current_feed( bdo ); - else // to update feeds due to bsrm change - db.update_bitasset_current_feed( bdo, true ); + // skip recalculating median feed if it is not needed + db.update_bitasset_current_feed( bdo, !should_update_feeds ); // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); From 5415c03d412d35cc5984a328f667e01b45c47904 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 19:55:43 +0000 Subject: [PATCH 158/258] Move check_for_blackswan from db_update to market --- libraries/chain/db_market.cpp | 151 ++++++++++++++++++++++++++++++++++ libraries/chain/db_update.cpp | 151 ---------------------------------- 2 files changed, 151 insertions(+), 151 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index a4ea0e34f7..b9ec5029c1 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -47,6 +47,157 @@ namespace detail { } //detail +/** + * let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral) + * let SP = current median feed's Settlement Price + * let LC = the least collateralized call order's swan price (debt/collateral) + * + * If there is no valid price feed or no bids then there is no black swan. + * + * A black swan occurs if MAX(HB,SP) <= LC + */ +bool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan, + const asset_bitasset_data_object* bitasset_ptr ) +{ + if( !mia.is_market_issued() ) return false; + + const asset_bitasset_data_object& bitasset = bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this); + if( bitasset.has_settlement() ) return true; // already force settled + auto settle_price = bitasset.current_feed.settlement_price; + if( settle_price.is_null() ) return false; // no feed + + asset_id_type debt_asset_id = bitasset.asset_id; + + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls + + // After core-2481 hard fork, if there are force-settlements, match call orders with them first + if( after_core_hardfork_2481 ) + { + const auto& settlement_index = get_index_type().indices().get(); + auto lower_itr = settlement_index.lower_bound( debt_asset_id ); + if( lower_itr != settlement_index.end() && lower_itr->balance.asset_id == debt_asset_id ) + return false; + } + + // Find the call order with the least collateral ratio + const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false ); + if( !call_ptr ) // no call order + return false; + + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + + price highest = settle_price; + // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. + // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. + // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. + if( bsrm_type::individual_settlement_to_fund == bsrm ) + highest = bitasset.median_feed.max_short_squeeze_price(); + else if( !before_core_hardfork_1270 ) + highest = bitasset.current_feed.max_short_squeeze_price(); + else if( maint_time > HARDFORK_CORE_338_TIME ) + highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); + // else do nothing + + const limit_order_index& limit_index = get_index_type(); + const auto& limit_price_index = limit_index.indices().get(); + + // looking for limit orders selling the most USD for the least CORE + auto highest_possible_bid = price::max( debt_asset_id, bitasset.options.short_backing_asset ); + // stop when limit orders are selling too little USD for too much CORE + auto lowest_possible_bid = price::min( debt_asset_id, bitasset.options.short_backing_asset ); + + FC_ASSERT( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id ); + // NOTE limit_price_index is sorted from greatest to least + auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); + auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); + + if( limit_itr != limit_end ) + { + FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); + auto call_pays_price = limit_itr->sell_price; + if( after_core_hardfork_2481 ) + { + // due to margin call fee, we check with MCPP (margin call pays price) here + call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); + } + highest = std::max( call_pays_price, highest ); + } + + // The variable `highest` after hf_338: + // * if no limit order, it is expected to be the black swan price; if the call order with the least CR + // has CR below or equal to the black swan price, we trigger GS, + // * if there exists at least one limit order and the price is higher, we use the limit order's price, + // which means we will match the margin call orders with the limit order first. + // + // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered + // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got + // caught by an additional check in check_call_orders(). + // This bug is fixed in hf_2481. Actually, after hf_2481, + // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), + // * if there is no force settlement, we check here with margin call fee in consideration. + + auto least_collateral = call_ptr->collateralization(); + // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, + // if the matching limit order is smaller, due to rounding, it is still possible that the + // call order's collateralization would increase and become higher than ~highest after matched. + // However, for simplicity, we only compare the prices here. + bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); + if( is_blackswan ) + { + wdump( (*call_ptr) ); + elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" + " Least collateralized call: ${lc} ${~lc}\n" + // " Highest Bid: ${hb} ${~hb}\n" + " Settle Price: ${~sp} ${sp}\n" + " Max: ${~h} ${h}\n", + ("id",mia.id)("symbol",mia.symbol)("b",head_block_num()) + ("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real()) + // ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real()) + ("sp",settle_price.to_real())("~sp",(~settle_price).to_real()) + ("h",highest.to_real())("~h",(~highest).to_real()) ); + edump((enable_black_swan)); + FC_ASSERT( enable_black_swan, + "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); + + if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) + { + individually_settle( bitasset, *call_ptr ); + } + // Global settlement or no settlement, but we should not be here if BSRM is no_settlement + else if( after_core_hardfork_2481 ) + { + if( bsrm_type::no_settlement == bsrm ) // this should not happen, be defensive here + wlog( "Internal error: BSRM is no_settlement but undercollateralization occurred" ); + // After hf_2481, when a global settlement occurs, + // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and + // they are closed at the same price, + // * the debt positions with CR > MCR do not pay premium or margin call fee, and they are closed at a same + // price too. + // * The GS price would close the position with the least CR with no collateral left for the owner, + // but would close other positions with some collateral left (if any) for their owners. + // * Both the premium and the margin call fee paid by the margin calls go to the asset owner, none will go + // to the global settlement fund, because + // - if a part of the premium or fees goes to the global settlement fund, it means there would be a + // difference in settlement prices, so traders are incentivized to create new debt in the last minute + // then settle after GS to earn free money, + // - if no premium or fees goes to the global settlement fund, it means debt asset holders would only + // settle for less after GS, so they are incentivized to settle before GS which helps avoid GS. + globally_settle_asset(mia, ~least_collateral, true ); + } + else if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price ) + // global settle at feed price if possible + globally_settle_asset(mia, settle_price ); + else + globally_settle_asset(mia, ~least_collateral ); + return true; + } + return false; +} + /** * All margin positions are force closed at the swan price * Collateral received goes into a force-settlement fund diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index d9924039f9..c3ce21888f 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -174,157 +174,6 @@ void database::clear_expired_proposals() } } -/** - * let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral) - * let SP = current median feed's Settlement Price - * let LC = the least collateralized call order's swan price (debt/collateral) - * - * If there is no valid price feed or no bids then there is no black swan. - * - * A black swan occurs if MAX(HB,SP) <= LC - */ -bool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan, - const asset_bitasset_data_object* bitasset_ptr ) -{ - if( !mia.is_market_issued() ) return false; - - const asset_bitasset_data_object& bitasset = bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this); - if( bitasset.has_settlement() ) return true; // already force settled - auto settle_price = bitasset.current_feed.settlement_price; - if( settle_price.is_null() ) return false; // no feed - - asset_id_type debt_asset_id = bitasset.asset_id; - - auto maint_time = get_dynamic_global_properties().next_maintenance_time; - bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue - bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls - - // After core-2481 hard fork, if there are force-settlements, match call orders with them first - if( after_core_hardfork_2481 ) - { - const auto& settlement_index = get_index_type().indices().get(); - auto lower_itr = settlement_index.lower_bound( debt_asset_id ); - if( lower_itr != settlement_index.end() && lower_itr->balance.asset_id == debt_asset_id ) - return false; - } - - // Find the call order with the least collateral ratio - const call_order_object* call_ptr = find_least_collateralized_short( bitasset, false ); - if( !call_ptr ) // no call order - return false; - - using bsrm_type = bitasset_options::black_swan_response_type; - const auto bsrm = bitasset.get_black_swan_response_method(); - - price highest = settle_price; - // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here - // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. - // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. - // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. - if( bsrm_type::individual_settlement_to_fund == bsrm ) - highest = bitasset.median_feed.max_short_squeeze_price(); - else if( !before_core_hardfork_1270 ) - highest = bitasset.current_feed.max_short_squeeze_price(); - else if( maint_time > HARDFORK_CORE_338_TIME ) - highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); - // else do nothing - - const limit_order_index& limit_index = get_index_type(); - const auto& limit_price_index = limit_index.indices().get(); - - // looking for limit orders selling the most USD for the least CORE - auto highest_possible_bid = price::max( debt_asset_id, bitasset.options.short_backing_asset ); - // stop when limit orders are selling too little USD for too much CORE - auto lowest_possible_bid = price::min( debt_asset_id, bitasset.options.short_backing_asset ); - - FC_ASSERT( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id ); - // NOTE limit_price_index is sorted from greatest to least - auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); - auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); - - if( limit_itr != limit_end ) - { - FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); - auto call_pays_price = limit_itr->sell_price; - if( after_core_hardfork_2481 ) - { - // due to margin call fee, we check with MCPP (margin call pays price) here - call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); - } - highest = std::max( call_pays_price, highest ); - } - - // The variable `highest` after hf_338: - // * if no limit order, it is expected to be the black swan price; if the call order with the least CR - // has CR below or equal to the black swan price, we trigger GS, - // * if there exists at least one limit order and the price is higher, we use the limit order's price, - // which means we will match the margin call orders with the limit order first. - // - // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered - // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got - // caught by an additional check in check_call_orders(). - // This bug is fixed in hf_2481. Actually, after hf_2481, - // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), - // * if there is no force settlement, we check here with margin call fee in consideration. - - auto least_collateral = call_ptr->collateralization(); - // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, - // if the matching limit order is smaller, due to rounding, it is still possible that the - // call order's collateralization would increase and become higher than ~highest after matched. - // However, for simplicity, we only compare the prices here. - bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); - if( is_blackswan ) - { - wdump( (*call_ptr) ); - elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" - " Least collateralized call: ${lc} ${~lc}\n" - // " Highest Bid: ${hb} ${~hb}\n" - " Settle Price: ${~sp} ${sp}\n" - " Max: ${~h} ${h}\n", - ("id",mia.id)("symbol",mia.symbol)("b",head_block_num()) - ("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real()) - // ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real()) - ("sp",settle_price.to_real())("~sp",(~settle_price).to_real()) - ("h",highest.to_real())("~h",(~highest).to_real()) ); - edump((enable_black_swan)); - FC_ASSERT( enable_black_swan, - "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - - if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) - { - individually_settle( bitasset, *call_ptr ); - } - // Global settlement or no settlement, but we should not be here if BSRM is no_settlement - else if( after_core_hardfork_2481 ) - { - if( bsrm_type::no_settlement == bsrm ) // this should not happen, be defensive here - wlog( "Internal error: BSRM is no_settlement but undercollateralization occurred" ); - // After hf_2481, when a global settlement occurs, - // * the margin calls (whose CR <= MCR) pay a premium (by MSSR-MCFR) and a margin call fee (by MCFR), and - // they are closed at the same price, - // * the debt positions with CR > MCR do not pay premium or margin call fee, and they are closed at a same - // price too. - // * The GS price would close the position with the least CR with no collateral left for the owner, - // but would close other positions with some collateral left (if any) for their owners. - // * Both the premium and the margin call fee paid by the margin calls go to the asset owner, none will go - // to the global settlement fund, because - // - if a part of the premium or fees goes to the global settlement fund, it means there would be a - // difference in settlement prices, so traders are incentivized to create new debt in the last minute - // then settle after GS to earn free money, - // - if no premium or fees goes to the global settlement fund, it means debt asset holders would only - // settle for less after GS, so they are incentivized to settle before GS which helps avoid GS. - globally_settle_asset(mia, ~least_collateral, true ); - } - else if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price ) - // global settle at feed price if possible - globally_settle_asset(mia, settle_price ); - else - globally_settle_asset(mia, ~least_collateral ); - return true; - } - return false; -} - // Helper function to check whether we need to udpate current_feed.settlement_price. static optional get_derived_current_feed_price( const database& db, const asset_bitasset_data_object& bitasset ) From 221cd45956f2fd983eac5cdd83f396df810ffbfb Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 29 Aug 2021 21:00:46 +0000 Subject: [PATCH 159/258] Update order of members in database.hpp --- libraries/chain/db_notify.cpp | 4 +- .../chain/include/graphene/chain/database.hpp | 270 +++++++++--------- 2 files changed, 142 insertions(+), 132 deletions(-) diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 627aff52d2..a2c2c87210 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -387,6 +387,7 @@ struct get_impacted_account_visitor } // namespace detail +// Declared in impacted.hpp void operation_get_impacted_accounts( const operation& op, flat_set& result, bool ignore_custom_op_required_auths ) { @@ -395,6 +396,7 @@ void operation_get_impacted_accounts( const operation& op, flat_set& result, bool ignore_custom_op_required_auths ) { @@ -402,7 +404,7 @@ void transaction_get_impacted_accs( const transaction& tx, flat_set& accounts, +static void get_relevant_accounts( const object* obj, flat_set& accounts, bool ignore_custom_op_required_auths ) { FC_ASSERT( obj != nullptr, "Internal error: get_relevant_accounts called with nullptr" ); // This should not happen if( obj->id.space() == protocol_ids ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index a6736096ca..fecb6e6acc 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -69,11 +69,10 @@ namespace graphene { namespace chain { */ class database : public db::object_database { - public: //////////////////// db_management.cpp //////////////////// - + public: database(); - ~database(); + virtual ~database(); enum validation_steps { @@ -82,8 +81,8 @@ namespace graphene { namespace chain { skip_transaction_signatures = 1 << 1, ///< used by non-witness nodes skip_transaction_dupe_check = 1 << 2, ///< used while reindexing skip_block_size_check = 1 << 4, ///< used when applying locally generated transactions - skip_tapos_check = 1 << 5, ///< used while reindexing -- note this skips expiration check as well - // skip_authority_check = 1 << 6, ///< removed because effectively identical to skip_transaction_signatures + skip_tapos_check = 1 << 5, ///< used while reindexing -- note this skips expiration check too + // skip_authority_check = 1 << 6, // removed, effectively identical to skip_transaction_signatures skip_merkle_check = 1 << 7, ///< used while reindexing skip_assert_evaluation = 1 << 8, ///< used while reindexing skip_undo_history_check = 1 << 9, ///< used while reindexing @@ -126,101 +125,6 @@ namespace graphene { namespace chain { void wipe(const fc::path& data_dir, bool include_blocks); void close(bool rewind = true); - //////////////////// db_block.cpp //////////////////// - - /** - * @return true if the block is in our fork DB or saved to disk as - * part of the official chain, otherwise return false - */ - bool is_known_block( const block_id_type& id )const; - bool is_known_transaction( const transaction_id_type& id )const; - block_id_type get_block_id_for_num( uint32_t block_num )const; - optional fetch_block_by_id( const block_id_type& id )const; - optional fetch_block_by_number( uint32_t num )const; - const signed_transaction& get_recent_transaction( const transaction_id_type& trx_id )const; - std::vector get_block_ids_on_fork(block_id_type head_of_fork) const; - - /** - * Calculate the percent of block production slots that were missed in the - * past 128 blocks, not including the current block. - */ - uint32_t witness_participation_rate()const; - - void add_checkpoints( const flat_map& checkpts ); - const flat_map get_checkpoints()const { return _checkpoints; } - bool before_last_checkpoint()const; - - bool push_block( const signed_block& b, uint32_t skip = skip_nothing ); - processed_transaction push_transaction( const precomputable_transaction& trx, uint32_t skip = skip_nothing ); - bool _push_block( const signed_block& b ); - processed_transaction _push_transaction( const precomputable_transaction& trx ); - - ///@throws fc::exception if the proposed transaction fails to apply. - processed_transaction push_proposal( const proposal_object& proposal ); - - signed_block generate_block( - const fc::time_point_sec when, - witness_id_type witness_id, - const fc::ecc::private_key& block_signing_private_key, - uint32_t skip - ); - signed_block _generate_block( - const fc::time_point_sec when, - witness_id_type witness_id, - const fc::ecc::private_key& block_signing_private_key - ); - - void pop_block(); - void clear_pending(); - - /** - * This method is used to track appied operations during the evaluation of a block, these - * operations should include any operation actually included in a transaction as well - * as any implied/virtual operations that resulted, such as filling an order. The - * applied operations is cleared after applying each block and calling the block - * observers which may want to index these operations. - * - * @return the op_id which can be used to set the result after it has finished being applied. - */ - uint32_t push_applied_operation( const operation& op ); - void set_applied_operation_result( uint32_t op_id, const operation_result& r ); - const vector >& get_applied_operations()const; - - string to_pretty_string( const asset& a )const; - - /** - * This signal is emitted after all operations and virtual operation for a - * block have been applied but before the get_applied_operations() are cleared. - * - * You may not yield from this callback because the blockchain is holding - * the write lock and may be in an "inconstant state" until after it is - * released. - */ - fc::signal applied_block; - - /** - * This signal is emitted any time a new transaction is added to the pending - * block state. - */ - fc::signal on_pending_transaction; - - /** - * Emitted After a block has been applied and committed. The callback - * should not yield and should execute quickly. - */ - fc::signal&, const flat_set&)> new_objects; - - /** - * Emitted After a block has been applied and committed. The callback - * should not yield and should execute quickly. - */ - fc::signal&, const flat_set&)> changed_objects; - - /** this signal is emitted any time an object is removed and contains a - * pointer to the last value of every object that was removed. - */ - fc::signal&, const vector&, const flat_set&)> removed_objects; - //////////////////// db_witness_schedule.cpp //////////////////// /** @@ -259,10 +163,19 @@ namespace graphene { namespace chain { */ uint32_t get_slot_at_time(fc::time_point_sec when)const; + /** + * Calculate the percent of block production slots that were missed in the + * past 128 blocks, not including the current block. + */ + uint32_t witness_participation_rate()const; + private: + uint32_t update_witness_missed_blocks( const signed_block& b ); + void update_witness_schedule(); //////////////////// db_getter.cpp //////////////////// + public: const chain_id_type& get_chain_id()const; const asset_object& get_core_asset()const; const asset_dynamic_data_object& get_core_dynamic_data()const; @@ -312,9 +225,10 @@ namespace graphene { namespace chain { //////////////////// db_init.cpp //////////////////// ///@{ - void initialize_evaluators(); /// Reset the object graph in-memory - void initialize_indexes(); + void initialize_indexes(); // Mark as public since it is used in tests + private: + void initialize_evaluators(); void init_genesis(const genesis_state_type& genesis_state = genesis_state_type()); template @@ -323,10 +237,11 @@ namespace graphene { namespace chain { _operation_evaluators[operation::tag::value] = std::make_unique>(); } - ///@} + //////////////////// db_balance.cpp //////////////////// + public: /** * @brief Retrieve a particular account's balance in a given asset * @param owner Account whose balance should be retrieved @@ -345,7 +260,7 @@ namespace graphene { namespace chain { void adjust_balance(account_id_type account, asset delta); void deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta); - /** + /** * @brief Retrieve a particular account's market fee vesting balance in a given asset * @param account_id Account whose balance should be retrieved * @param asset_id ID of the asset to get balance in @@ -374,11 +289,13 @@ namespace graphene { namespace chain { account_id_type req_owner, bool require_vesting ); - // helper to handle cashback rewards + /// helper to handle cashback rewards void deposit_cashback(const account_object& acct, share_type amount, bool require_vesting = true); - // helper to handle witness pay + /// helper to handle witness pay void deposit_witness_pay(const witness_object& wit, share_type amount); + string to_pretty_string( const asset& a )const; + //////////////////// db_debug.cpp //////////////////// void debug_dump(); @@ -405,6 +322,9 @@ namespace graphene { namespace chain { share_type collateral_from_fund, const price_feed& current_feed ); private: + void _cancel_bids_and_revive_mpa( const asset_object& bitasset, const asset_bitasset_data_object& bad ); + bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, + const asset_bitasset_data_object* bitasset_ptr = nullptr ); template void globally_settle_asset_impl( const asset_object& bitasset, const price& settle_price, @@ -439,7 +359,6 @@ namespace graphene { namespace chain { bool is_margin_call = false, bool settle_order_is_taker = true ); - public: /** * @brief Process a new limit order through the markets @@ -467,6 +386,12 @@ namespace graphene { namespace chain { const asset_bitasset_data_object& bitasset, const asset_object& asset_obj ); + bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, + bool for_new_limit_order = false, + const asset_bitasset_data_object* bitasset_ptr = nullptr, + bool mute_exceptions = false, + bool skip_matching_settle_orders = false ); + /** * Matches the two orders, the first parameter is taker, the second is maker. * @@ -485,6 +410,8 @@ namespace graphene { namespace chain { only_maker_filled = 2, both_filled = 3 }; + + private: match_result_type match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price ); match_result_type match_limit_normal_limit( const limit_order_object& taker, const limit_order_object& maker, @@ -588,16 +515,11 @@ namespace graphene { namespace chain { bool fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives, const price& fill_price, bool is_maker, bool pay_force_settle_fee = true ); - bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, - bool for_new_limit_order = false, - const asset_bitasset_data_object* bitasset_ptr = nullptr, - bool mute_exceptions = false, - bool skip_matching_settle_orders = false ); - /// helpers to fill_order /// @{ void pay_order( const account_object& receiver, const asset& receives, const asset& pays ); + public: /** * @brief Calculate the market fee that is to be taken * @param trade_asset the asset (passed in to avoid a lookup) @@ -612,6 +534,97 @@ namespace graphene { namespace chain { /// @} ///@} + //////////////////// db_block.cpp //////////////////// + + /** + * @return true if the block is in our fork DB or saved to disk as + * part of the official chain, otherwise return false + */ + bool is_known_block( const block_id_type& id )const; + bool is_known_transaction( const transaction_id_type& id )const; + block_id_type get_block_id_for_num( uint32_t block_num )const; + optional fetch_block_by_id( const block_id_type& id )const; + optional fetch_block_by_number( uint32_t num )const; + const signed_transaction& get_recent_transaction( const transaction_id_type& trx_id )const; + std::vector get_block_ids_on_fork(block_id_type head_of_fork) const; + + void add_checkpoints( const flat_map& checkpts ); + const flat_map get_checkpoints()const { return _checkpoints; } + bool before_last_checkpoint()const; + + bool push_block( const signed_block& b, uint32_t skip = skip_nothing ); + processed_transaction push_transaction( const precomputable_transaction& trx, uint32_t skip = skip_nothing ); + private: + bool _push_block( const signed_block& b ); + public: + // It is public because it is used in pending_transactions_restorer in db_with.hpp + processed_transaction _push_transaction( const precomputable_transaction& trx ); + ///@throws fc::exception if the proposed transaction fails to apply. + processed_transaction push_proposal( const proposal_object& proposal ); + + signed_block generate_block( + const fc::time_point_sec when, + witness_id_type witness_id, + const fc::ecc::private_key& block_signing_private_key, + uint32_t skip + ); + private: + signed_block _generate_block( + const fc::time_point_sec when, + witness_id_type witness_id, + const fc::ecc::private_key& block_signing_private_key + ); + + public: + void pop_block(); + void clear_pending(); + + /** + * This method is used to track appied operations during the evaluation of a block, these + * operations should include any operation actually included in a transaction as well + * as any implied/virtual operations that resulted, such as filling an order. The + * applied operations is cleared after applying each block and calling the block + * observers which may want to index these operations. + * + * @return the op_id which can be used to set the result after it has finished being applied. + */ + uint32_t push_applied_operation( const operation& op ); + void set_applied_operation_result( uint32_t op_id, const operation_result& r ); + const vector >& get_applied_operations()const; + + /** + * This signal is emitted after all operations and virtual operation for a + * block have been applied but before the get_applied_operations() are cleared. + * + * You may not yield from this callback because the blockchain is holding + * the write lock and may be in an "inconstant state" until after it is + * released. + */ + fc::signal applied_block; + + /** + * This signal is emitted any time a new transaction is added to the pending + * block state. + */ + fc::signal on_pending_transaction; + + /** + * Emitted After a block has been applied and committed. The callback + * should not yield and should execute quickly. + */ + fc::signal&, const flat_set&)> new_objects; + + /** + * Emitted After a block has been applied and committed. The callback + * should not yield and should execute quickly. + */ + fc::signal&, const flat_set&)> changed_objects; + + /** this signal is emitted any time an object is removed and contains a + * pointer to the last value of every object that was removed. + */ + fc::signal&, + const vector&, const flat_set&)> removed_objects; ///@{ /** @@ -629,9 +642,6 @@ namespace graphene { namespace chain { * @} */ - /// Enable or disable tracking of votes of standby witnesses and committee members - inline void enable_standby_votes_tracking(bool enable) { _track_standby_votes = enable; } - /** Precomputes digests, signatures and operation validations depending * on skip flags. "Expensive" computations may be done in a parallel * thread. @@ -651,16 +661,14 @@ namespace graphene { namespace chain { * precomputations applied */ fc::future precompute_parallel( const precomputable_transaction& trx )const; - private: + private: template void _precompute_parallel( const Trx* trx, const size_t count, const uint32_t skip )const; - protected: - //Mark pop_undo() as protected -- we do not want outside calling pop_undo(); it should call pop_block() instead + protected: + // Mark pop_undo() as protected -- we do not want outside calling pop_undo(), + // it should call pop_block() instead void pop_undo() { object_database::pop_undo(); } - void notify_applied_block( const signed_block& block ); - void notify_on_pending_transaction( const signed_transaction& tx ); - void notify_changed_objects(); private: optional _pending_tx_session; @@ -669,8 +677,6 @@ namespace graphene { namespace chain { template vector> sort_votable_objects(size_t count)const; - //////////////////// db_block.cpp //////////////////// - public: // these were formerly private, but they have a fairly well-defined API, so let's make them public void apply_block( const signed_block& next_block, uint32_t skip = skip_nothing ); @@ -680,8 +686,6 @@ namespace graphene { namespace chain { private: void _apply_block( const signed_block& next_block ); processed_transaction _apply_transaction( const signed_transaction& trx ); - void _cancel_bids_and_revive_mpa( const asset_object& bitasset, - const asset_bitasset_data_object& bad ); ///Steps involved in applying a new block ///@{ @@ -692,9 +696,12 @@ namespace graphene { namespace chain { void update_witnesses( fork_item& fork_entry )const; void create_block_summary(const signed_block& next_block); - //////////////////// db_witness_schedule.cpp //////////////////// + //////////////////// db_notify.cpp //////////////////// - uint32_t update_witness_missed_blocks( const signed_block& b ); + protected: + void notify_applied_block( const signed_block& block ); + void notify_on_pending_transaction( const signed_transaction& tx ); + void notify_changed_objects(); //////////////////// db_update.cpp //////////////////// public: @@ -717,8 +724,6 @@ namespace graphene { namespace chain { void update_maintenance_flag( bool new_maintenance_flag ); void update_withdraw_permissions(); void update_credit_offers_and_deals(); - bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, - const asset_bitasset_data_object* bitasset_ptr = nullptr ); void clear_expired_htlcs(); ///Steps performed only at maintenance intervals @@ -792,7 +797,7 @@ namespace graphene { namespace chain { bool _opened = false; // Counts nested proposal updates - uint32_t _push_proposal_nesting_depth = 0; + uint32_t _push_proposal_nesting_depth = 0; /// Tracks assets affected by bitshares-core issue #453 before hard fork #615 in one block flat_set _issue_453_affected_assets; @@ -806,6 +811,9 @@ namespace graphene { namespace chain { const chain_property_object* _p_chain_property_obj = nullptr; const witness_schedule_object* _p_witness_schedule_obj = nullptr; ///@} + public: + /// Enable or disable tracking of votes of standby witnesses and committee members + inline void enable_standby_votes_tracking(bool enable) { _track_standby_votes = enable; } }; } } From caa70b073127535e3fec154c5a2b8c03dc6ff965 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 31 Aug 2021 06:10:18 +0000 Subject: [PATCH 160/258] Add no-settlement taker tests --- tests/tests/bsrm_tests.cpp | 1122 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1122 insertions(+) diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_tests.cpp index d156fcd12b..d63bb059c5 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_tests.cpp @@ -1219,4 +1219,1126 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) } } +/// Tests margin calls and force settlements when BSRM is no_settlement and call order is taker +BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // Several passes, with different limit orders and/or settle orders + for( int i = 0; i <= 20; ++ i ) + { + idump( (i) ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; // 1.1% + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(1000, mpa_id), asset(2750) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(1000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // 1000 * (22/10) * 1.9 = 4180 + const call_order_object* call3_ptr = borrow( borrower3, asset(1000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(1000,mpa_id) ); + transfer( borrower2, seller, asset(1000,mpa_id) ); + transfer( borrower3, seller, asset(500,mpa_id) ); + transfer( borrower3, seller2, asset(500,mpa_id) ); + + int64_t expected_seller_balance_mpa = 2500; + int64_t expected_seller2_balance_mpa = 500; + + // seller2 sells some + const limit_order_object* sell_highest = create_sell_order( seller2, asset(100,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + expected_seller2_balance_mpa -= 100; + + // seller2 sells more + const limit_order_object* sell_high = create_sell_order( seller2, asset(100,mpa_id), asset(275) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + expected_seller2_balance_mpa -= 100; + + // seller2 sells more, due to MCFR, this order won't be filled if no order is selling lower + const limit_order_object* sell_mid = create_sell_order( seller2, asset(100,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + expected_seller2_balance_mpa -= 100; + + // seller sells + const limit_order_object* sell_low = nullptr; + limit_order_id_type sell_low_id; + force_settlement_id_type settle_id; + force_settlement_id_type settle2_id; + if( 0 == i ) + { + // Nothing to do here + } + else if( 1 == i ) + { + sell_low = create_sell_order( seller, asset(111,mpa_id), asset(230) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 111 ); + expected_seller_balance_mpa -= 111; + } + else if( 2 == i ) + { + sell_low = create_sell_order( seller, asset(111,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 111 ); + expected_seller_balance_mpa -= 111; + } + else if( 3 == i ) + { + sell_low = create_sell_order( seller, asset(900,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 900 ); + expected_seller_balance_mpa -= 900; + } + else if( 4 == i ) + { + sell_low = create_sell_order( seller, asset(920,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 920 ); + expected_seller_balance_mpa -= 920; + } + else if( 5 == i ) + { + sell_low = create_sell_order( seller, asset(1000,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1000 ); + expected_seller_balance_mpa -= 1000; + } + else if( 6 == i ) + { + sell_low = create_sell_order( seller, asset(1050,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1050 ); + expected_seller_balance_mpa -= 1050; + } + else if( 7 == i ) + { + sell_low = create_sell_order( seller, asset(1800,mpa_id), asset(1870*2) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 1800 ); + expected_seller_balance_mpa -= 1800; + } + else if( 8 == i ) + { + sell_low = create_sell_order( seller, asset(2000,mpa_id), asset(1870) ); + BOOST_REQUIRE( sell_low ); + sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 2000 ); + expected_seller_balance_mpa -= 2000; + } + else if( 9 == i ) + { + auto result = force_settle( seller, asset(111,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 111; + } + else if( 10 == i ) + { + auto result = force_settle( seller, asset(990,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 990; + } + else if( 11 == i ) + { + auto result = force_settle( seller, asset(995,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 995; + } + else if( 12 == i ) + { + auto result = force_settle( seller, asset(1000,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 1000; + } + else if( 13 == i ) + { + auto result = force_settle( seller, asset(1050,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 1050; + } + else if( 14 == i ) + { + auto result = force_settle( seller, asset(1750,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 1750; + } + else if( 15 == i ) + { + auto result = force_settle( seller, asset(1800,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 1800; + } + else if( 16 == i ) + { + auto result = force_settle( seller, asset(2000,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + expected_seller_balance_mpa -= 2000; + } + else if( 17 == i ) + { + auto result = force_settle( seller, asset(492,mpa_id) ); + settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + result = force_settle( seller, asset(503,mpa_id) ); + settle2_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle2_id) ); + expected_seller_balance_mpa -= 995; + } + else + { + BOOST_TEST_MESSAGE( "No more test cases so far" ); + break; + } + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), expected_seller_balance_mpa ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // publish a new feed so that borrower's and borrower2's debt positions become undercollateralized + f.settlement_price = price( asset(10,mpa_id), asset(22) ); + f.maximum_short_squeeze_ratio = 1300; + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check result + auto check_result = [&] + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), expected_seller_balance_mpa ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), expected_seller2_balance_mpa ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + //BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + + if( 0 == i ) // no order is filled + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_mid price = 100:210 = 0.476190476 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(13,mpa_id), asset(21) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 1 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 111:230 = 0.482608696 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 230 + // call2 pays round_down(230*1300/1289) = 231, margin call fee = 1 + // now feed price is 13:10 * (1000-111):(2100-231) + // = 13:10 * 889:1869 = 11557:18690 = 0.61835206 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 889:1869 = 0.479714554 + // sell_mid price = 100:210 = 0.476190476 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(18690) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1869 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 230 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 2 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 111:210 = 0.504545455 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 210 + // call2 pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-111):(2100-211) + // = 13:10 * 889:1889 = 11557:18890 = 0.611805188 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 889:1889 = 0.474635522 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid receives 210 + // call2 pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-111-100):(2100-211-211) + // = 13:10 * 789:1678 = 10257:16780 = 0.611263409 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 789:1678 = 0.474215212 + // sell_high price = 100:275 = 0.363636364 + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10257,mpa_id), asset(16780) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 789 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1678 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 210 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 3 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 900:1870 = 0.481283422 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 1870 + // call2 pays round_down(1870*1300/1289) = 1885, margin call fee = 15 + // now feed price is 13:10 * (1000-900):(2100-1885) + // = 13:10 * 100:215 = 130:215 = 0.604651163 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 100:215 = 0.469085464 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid receives 210 + // call2 pays round_up(210*1300/1289) = 212, margin call fee = 2 + // call2 is fully filled, freed collateral = 215 - 212 = 3 + BOOST_CHECK( !db.find( call2_id ) ); + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_high price = 100:275 = 0.363636364 + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130,mpa_id), asset(275) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 3 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 4 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 920:1870 = 0.49197861 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 1870 + // call2 pays round_down(1870*1300/1289) = 1885, margin call fee = 15 + // now feed price is 13:10 * (1000-920):(2100-1885) + // = 13:10 * 80:215 = 104:215 = 0.48372093 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 80:215 = 0.375268371 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is partially filled + // sell_mid pays 80, receives 80 * 210/100 = 168 + // call2 pays round_up(80*(210/100)*(1300/1289)) = 170, margin call fee = 2 + // call2 is fully filled, freed collateral = 215-170 = 45 + BOOST_CHECK( !db.find( call2_id ) ); + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 20, receives 20 * 210/100 = 42 + // call pays round_down(20*(210/100)*(1300/1289)) = 42, margin call fee = 0 + // now feed price is 13:10 * (1000-20):(2750-42) + // = 13:10 * 980:2708 = 1274:2708 = 0.470457903 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 980:2708 = 0.364978978 + // sell_high price = 100:275 = 0.363636364 + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1274,mpa_id), asset(2708) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 980 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2708 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 45 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 5 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 1000:1870 = 0.534759358 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low receives 1870 + // call2 pays round_up(1870*1300/1289) = 1886, margin call fee = 16 + // call2 is fully filled, freed collateral = 2100-1886 = 214 + BOOST_CHECK( !db.find( call2_id ) ); + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1040,mpa_id), asset(2262) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 800 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 214 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 6 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 1050:1870 = 0.561497326 + // sell_low is partially filled + // sell_low pays 1000, receives round_up(1000 * 1870/1050) = 1781 + // call2 pays round_up(1000*(1870/1050)*(1300/1289)) = 1797, margin call fee = 16 + // call2 is fully filled, freed collateral = 2100-1797 = 303 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_low price = 1050:1870 = 0.561497326 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low pays 50, receives round_down(50*1870/1050) = 89 + // call pays round_down(50*(1870/1050)*(1300/1289)) = 89, margin call fee = 0 + // now feed price is 13:10 * (1000-50):(2750-89) + // = 13:10 * 950:2661 = 1235:2661 = 0.464111236 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 950:2661 = 0.360055265 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price from call is 13:10 * (1000-50-100):(2750-89-211) + // = 13:10 * 850:2450 = 1105:2450 = 0.451020408 (< 10:22 = 0.454545455) + // so feed price is 10:22 + // call match price is 1000:1289 * 10:22 = 0.352634177 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price from call is 13:10 * (1000-50-100-100):(2750-89-211-277) + // = 13:10 * 750:2173 = 975:2173 = 0.448688449 (< 10:22 = 0.454545455) + // so feed price is 10:22 + // call match price is 1000:1289 * 10:22 = 0.352634177 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 750 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2173 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 303 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 7 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 0.480254165 + // sell_low price = 900:1870 = 0.481283422 + // sell_low is partially filled + // sell_low pays 1000, receives round_up(1000 * 1870/900) = 2078 + // call2 pays round_up(1000*(1870/900)*(1300/1289)) = 2096, margin call fee = 18 + // call2 is fully filled, freed collateral = 2100-2096 = 4 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_low price = 900:1870 = 0.481283422 + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low pays 800, receives round_down(800*1870/900) = 1662 + // call pays round_down(800*(1870/900)*(1300/1289)) = 1676, margin call fee = 14 + // now call's debt is 1000-800=200, collateral is 2750-1676=1074 + // CR = 1074/200 / (22/10) = 2.44 > 1.85, out of margin call territory + // so feed price is 10:22 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 200 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1074 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 4 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870*2 ); // 2078+1662 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 8 == i ) + { + // sell_low would match call2 and call + + // sell_low pays 1000, receives round_up(1000 * 1870/2000) = 935 + // call2 pays round_up(1000*(1870/2000)*(1300/1289)) = 943, margin call fee = 8 + // call2 is fully filled, freed collateral = 2100-943 = 1157 + BOOST_CHECK( !db.find( call2_id ) ); + + // sell_low is fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + + // sell_low pays 1000, receives round_up(1000 * 1870/2000) = 935 + // call pays round_up(1000*(1870/2000)*(1300/1289)) = 943, margin call fee = 8 + // call is fully filled, freed collateral = 2750-943 = 1807 + BOOST_CHECK( !db.find( call_id ) ); + + // feed price is 10:22 + + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 + 1157 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 + 1807 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1870 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 9 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(111*27069/13000) = 231 + // call2 pays round_down(111*2100/1000) = 233, margin call fee = 2 + // now feed price is 13:10 * (1000-111):(2100-233) + // = 13:10 * 889:1867 = 11557:18670 = 0.619014462 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 889:1867 = 0.480228442 + // sell_mid price = 100:210 = 0.476190476 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(11557,mpa_id), asset(18670) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 889 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1867 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 231 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 10 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(990*27069/13000) = 2061 + // call2 pays round_down(990*2100/1000) = 2079, margin call fee = 18 + // now feed price is 13:10 * (1000-990):(2100-2079) + // = 13:10 * 10:21 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 10:21 = 0.480254165 + // sell_mid price = 100:210 = 0.476190476 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(13,mpa_id), asset(21) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 10 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 21 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2061 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 11 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(995*27069/13000) = 2071 + // call2 pays round_down(995*2100/1000) = 2089, margin call fee = 18 + // now feed price is 13:10 * (1000-995):(2100-2089) + // = 13:10 * 5:11 = 13:22 = 0.590909091 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 5:11 = 0.45842443 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is partially filled + // sell_mid pays 5, receives round_up(5 * 21/10) = 11 + // call2 pays round_up(5*(21/10)*(1300/1289)) = 11, margin call fee = 0 + // call2 is fully filled, freed collateral = 11-11 = 0 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 95, receives round_down(95*210/100) = 199 + // call pays round_down(95*(210/100)*(1300/1289)) = 201, margin call fee = 2 + // now feed price from call is 13:10 * (1000-95):(2750-201) + // = 13:10 * 905:2549 = 11765:25490 = 0.46155355 (> 10:22 = 0.454545455) + // so feed price is 11765:25490 + // call match price is 1300:1289 * 905:2549 = 0.358071024 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price from call is 13:10 * (1000-95-100):(2750-201-277) + // = 13:10 * 805:2272 = 10465:22720 = 0.460607394 (> 10:22 = 0.454545455) + // so feed price is 10465:22720 + // call match price is 1300:1289 * 805:2272 = 0.357337001 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10465,mpa_id), asset(22720) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 805 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2272 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2071 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 11+199+275 + } + else if( 12 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1040,mpa_id), asset(2262) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 800 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2083 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 13 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call pays price = 1000:2750 + // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193, does not match + + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(50*2915718/1040000) = 140 + // call pays round_down(50*2262/800) = 141, margin call fee = 1 + // now feed price is 13:10 * (800-50):(2262-141) + // = 13:10 * 750:2121 = 975:2121 = 0.459688826 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 750:2121 = 0.35662438 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(975,mpa_id), asset(2121) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 750 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2121 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2223 ); // 2083 + 140 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 14 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call pays price = 1000:2750 + // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193, does not match + + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(750*2915718/1040000) = 2102 + // call pays round_down(750*2262/800) = 2120, margin call fee = 18 + // now feed price is 13:10 * (800-750):(2262-2120) + // = 13:10 * 50:142 = 65:142 = 0.457746479 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 50:142 = 0.355117517 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(65,mpa_id), asset(142) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 50 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 142 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4185 ); // 2083 + 2102 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 15 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call pays price = 1000:2750 + // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193, does not match + + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_up(800*2915718/1040000) = 2243 + // call pays 2262, margin call fee = 19 + // call is fully filled + BOOST_CHECK( !db.find( call_id ) ); + // now feed price is 10:22, no margin call + + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4326 ); // 2083 + 2243 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 16 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // settle order receives round_up(1000*27069/13000) = 2083 + // call2 pays 2100, margin call fee = 17 + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call pays price = 1000:2750 + // call match price is 1300:1289 * 100:275 = 130000:354475 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 100, receives 210 + // call pays round_down(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (1000-100):(2750-211) + // = 13:10 * 900:2539 = 1170:2539 = 0.460811343 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 900:2539 = 0.357495223 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price is 13:10 * (1000-100-100):(2750-211-277) + // = 13:10 * 800:2262 = 1040:2262 = 0.459770115 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 800:2262 = 1040000:2915718 = 0.356687444 + // sell_highest price = 100:285 = 0.350877193, does not match + + // call is fully filled + BOOST_CHECK( !db.find( call_id ) ); + // settle order receives round_up(800*2915718/1040000) = 2243 + // call pays 2262, margin call fee = 19 + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 200 ); + + // now feed price is 10:22, no margin call + + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10,mpa_id), asset(22) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 4326 ); // 2083 + 2243 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210+275 + } + else if( 17 == i ) + { + // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) + // call2 pays price = 1000:2100 + // match price = 1000:2100 * 1300:1289 = 13000:27069 = 0.480254165 + // settle order is fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // settle order receives round_down(492*27069/13000) = 1024 + // call2 pays round_down(492*2100/1000) = 1033, margin call fee = 9 + // now feed price is 13:10 * (1000-492):(2100-1033) + // = 13:10 * 508:1067 = 6604:10670 = 0.618931584 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 508:1067 = 660400:1375363 = 0.480164146 + // sell_mid price = 100:210 = 0.476190476 does not match + // settle2 is fully filled + BOOST_CHECK( !db.find( settle2_id ) ); + // settle2 receives round_down(503*(1375363/660400)) = 1047 + // call2 pays round_down(503*1067/508) = 1056, margin call fee = 9 + // now feed price is 13:10 * (508-503):(1067-1056) + // = 13:10 * 5:11 = 13:22 = 0.590909091 (> 10:22 = 0.454545455) + // call2 match price is 1300:1289 * 5:11 = 0.45842443 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is partially filled + // sell_mid pays 5, receives round_up(5 * 21/10) = 11 + // call2 pays round_up(5*(21/10)*(1300/1289)) = 11, margin call fee = 0 + // call2 is fully filled, freed collateral = 11-11 = 0 + BOOST_CHECK( !db.find( call2_id ) ); + + // now feed price is 13:10 * 1000:2750 = 130:275 = 0.472727273 (> 10:22 = 0.454545455) + // call match price is 1300:1289 * 100:275 = 0.366739544 + // sell_mid price = 100:210 = 0.476190476 + // sell_mid is fully filled + BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid pays 95, receives round_down(95*210/100) = 199 + // call pays round_down(95*(210/100)*(1300/1289)) = 201, margin call fee = 2 + // now feed price from call is 13:10 * (1000-95):(2750-201) + // = 13:10 * 905:2549 = 11765:25490 = 0.46155355 (> 10:22 = 0.454545455) + // so feed price is 11765:25490 + // call match price is 1300:1289 * 905:2549 = 0.358071024 + // sell_high price = 100:275 = 0.363636364 + // sell_high is fully filled + BOOST_CHECK( !db.find( sell_high_id ) ); + // sell_high pays 100, receives 275 + // call pays round_down(275*1300/1289) = 277, margin call fee = 2 + // now feed price from call is 13:10 * (1000-95-100):(2750-201-277) + // = 13:10 * 805:2272 = 10465:22720 = 0.460607394 (> 10:22 = 0.454545455) + // so feed price is 10465:22720 + // call match price is 1300:1289 * 805:2272 = 0.357337001 + // sell_highest price = 100:285 = 0.350877193 + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 100 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(10465,mpa_id), asset(22720) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 805 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2272 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2071 ); // 1024+1047 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 11+199+275 + } + else + { + BOOST_FAIL( "to be fixed" ); + } + }; + + check_result(); + + // generate a block + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + // check again + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From b614a1a004ac53e81eb03650f340bfa2dd2445ba Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 00:24:32 +0000 Subject: [PATCH 161/258] Split bsrm_tests.cpp into multiple files --- tests/tests/bsrm_basic_tests.cpp | 671 ++++++++++++++++++ ...tests.cpp => bsrm_no_settlement_tests.cpp} | 632 ----------------- 2 files changed, 671 insertions(+), 632 deletions(-) create mode 100644 tests/tests/bsrm_basic_tests.cpp rename tests/tests/{bsrm_tests.cpp => bsrm_no_settlement_tests.cpp} (78%) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp new file mode 100644 index 0000000000..4257285d9a --- /dev/null +++ b/tests/tests/bsrm_basic_tests.cpp @@ -0,0 +1,671 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) + +/// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork +BOOST_AUTO_TEST_CASE( hardfork_protection_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; + uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + + uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; + + vector ops; + + // Testing asset_create_operation + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = bitflag; + acop.common_options.issuer_permissions = old_bitmask; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 3; + + trx.operations.clear(); + trx.operations.push_back( acop ); + + { + auto& op = trx.operations.front().get(); + + // Unable to set new permission bit + op.common_options.issuer_permissions = new_bitmask; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.common_options.issuer_permissions = old_bitmask; + + // Unable to set new extensions in bitasset options + op.bitasset_opts->extensions.value.black_swan_response_method = 0; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.bitasset_opts->extensions.value.black_swan_response_method = {}; + + acop = op; + } + + // Able to create asset without new data + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& samcoin = db.get(ptx.operation_results[0].get()); + asset_id_type samcoin_id = samcoin.id; + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 ); + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 ); + + // Able to propose the good operation + propose( acop ); + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.market_fee_percent = 200; + + // Unable to set new permission bit + op.new_options.issuer_permissions = new_bitmask; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.issuer_permissions = old_bitmask; + + auop = op; + } + + // Able to update asset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 ); + + // Able to propose the good operation + propose( auop ); + + // Testing asset_update_bitasset_operation + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = samcoin_id; + aubop.new_options = samcoin_id(db).bitasset_data(db).options; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.minimum_feeds = 1; + + // Unable to set new extensions + op.new_options.extensions.value.black_swan_response_method = 1; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.extensions.value.black_swan_response_method = {}; + + aubop = op; + } + + // Able to update bitasset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 ); + + // Able to propose the good operation + propose( aubop ); + + // Unable to propose the invalid operations + for( const operation& op : ops ) + BOOST_CHECK_THROW( propose( op ), fc::exception ); + + // Check what we have now + idump( (samcoin) ); + idump( (samcoin.bitasset_data(db)) ); + + generate_block(); + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Now able to propose the operations that was invalid + for( const operation& op : ops ) + propose( op ); + + generate_block(); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests scenarios about setting non-UIA issuer permission bits on an UIA +BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; + uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; + + uint16_t uiaflag = uiamask & ~disable_new_supply; // Allow creating new supply + + vector ops; + + asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), uiaflag ).id; + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + auop.new_options.issuer_permissions = old_bitmask & ~global_settle & ~disable_force_settle; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + // Able to update asset with non-UIA issuer permission bits + PUSH_TX(db, trx, ~0); + + // Able to propose too + propose( auop ); + + // Issue some coin + issue_uia( sam_id, asset( 1, samcoin_id ) ); + + // Unable to unset the non-UIA "disable" issuer permission bits + auto perms = samcoin_id(db).options.issuer_permissions; + + auop.new_options.issuer_permissions = perms & ~disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = perms & ~disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = perms & ~disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Still able to propose + auop.new_options.issuer_permissions = new_bitmask; + propose( auop ); + + // But no longer able to update directly + auop.new_options.issuer_permissions = uiamask | witness_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | committee_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unset the non-UIA bits in issuer permissions, should succeed + auop.new_options.issuer_permissions = uiamask; + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin_id(db).options.issuer_permissions, uiamask ); + + // Burn all supply + reserve_asset( sam_id, asset( 1, samcoin_id ) ); + + BOOST_CHECK_EQUAL( samcoin_id(db).dynamic_asset_data_id(db).current_supply.value, 0 ); + + // Still unable to set the non-UIA bits in issuer permissions + auop.new_options.issuer_permissions = uiamask | witness_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | committee_fed_asset; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + generate_block(); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests what kind of assets can have BSRM-related flags / issuer permissions / extensions +BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // Unable to create a PM with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bsrm_update ), fc::exception ); + + // Unable to create a MPA with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bsrm_update ), fc::exception ); + + // Unable to create a UIA with the disable_bsrm_update bit in flags + BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bsrm_update ), fc::exception ); + + // create a PM with a zero market_fee_percent + const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); + asset_id_type pm_id = pm.id; + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + // create a UIA with a zero market_fee_percent + const asset_object& uia = create_user_issued_asset( "TESTUIA", sam_id(db), charge_market_fee ); + asset_id_type uia_id = uia.id; + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + + // Unable to set disable_bsrm_update bit in flags for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.flags |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bsrm_update bit in flags for MPA + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + auop.new_options.flags |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bsrm_update bit in flags for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.flags |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // Unable to set disable_bsrm_update bit in issuer_permissions for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.issuer_permissions |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to set disable_bsrm_update bit in issuer_permissions for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.issuer_permissions |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to create a UIA with disable_bsrm_update permission bit + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bsrm_update; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create UIA without disable_bsrm_update permission bit + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Unable to create a PM with disable_bsrm_update permission bit + acop.symbol = "SAMPM"; + acop.precision = asset_id_type()(db).precision; + acop.is_prediction_market = true; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bsrm_update; + acop.bitasset_opts = bitasset_options(); + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Unable to create a PM with BSRM in extensions + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; + acop.bitasset_opts->extensions.value.black_swan_response_method = 0; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create PM with no disable_bsrm_update permission bit nor BSRM in extensions + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; + acop.bitasset_opts->extensions.value.black_swan_response_method.reset(); + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Unable to update PM to set BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = pm_id; + aubop.new_options = pm_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Able to propose + propose( aubop ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests whether asset owner has permission to update bsrm +BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + + using bsrm_type = bitasset_options::black_swan_response_type; + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + + // disable owner's permission to update bsrm + auop.new_options.issuer_permissions |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); + + // check that owner can not update bsrm + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + aubop.new_options.extensions.value.black_swan_response_method = 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.black_swan_response_method.reset(); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + + // enable owner's permission to update bsrm + auop.new_options.issuer_permissions &= ~disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); + + // check that owner can update bsrm + aubop.new_options.extensions.value.black_swan_response_method = 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // check bsrm' valid range + aubop.new_options.extensions.value.black_swan_response_method = 4; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.black_swan_response_method = 1; + + // Sam borrow some + borrow( sam, asset(1000, mpa_id), asset(2000) ); + + // disable owner's permission to update bsrm + auop.new_options.issuer_permissions |= disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); + + // check that owner can not update bsrm + aubop.new_options.extensions.value.black_swan_response_method = 0; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.black_swan_response_method.reset(); + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.black_swan_response_method = 1; + + // able to update other params that still has permission E.G. force_settlement_delay_sec + aubop.new_options.force_settlement_delay_sec += 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, + aubop.new_options.force_settlement_delay_sec ); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); + + // unable to enable the permission to update bsrm + auop.new_options.issuer_permissions &= ~disable_bsrm_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/bsrm_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp similarity index 78% rename from tests/tests/bsrm_tests.cpp rename to tests/tests/bsrm_no_settlement_tests.cpp index d63bb059c5..f9db05f030 100644 --- a/tests/tests/bsrm_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -36,638 +36,6 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) -/// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork -BOOST_AUTO_TEST_CASE( hardfork_protection_test ) -{ - try { - - // Proceeds to a recent hard fork - generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); - generate_block(); - set_expiration( db, trx ); - - ACTORS((sam)); - - auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; - fund( sam, asset(init_amount) ); - - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; - uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; - - uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; - - vector ops; - - // Testing asset_create_operation - asset_create_operation acop; - acop.issuer = sam_id; - acop.symbol = "SAMCOIN"; - acop.precision = 2; - acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); - acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; - acop.common_options.market_fee_percent = 100; - acop.common_options.flags = bitflag; - acop.common_options.issuer_permissions = old_bitmask; - acop.bitasset_opts = bitasset_options(); - acop.bitasset_opts->minimum_feeds = 3; - - trx.operations.clear(); - trx.operations.push_back( acop ); - - { - auto& op = trx.operations.front().get(); - - // Unable to set new permission bit - op.common_options.issuer_permissions = new_bitmask; - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - ops.push_back( op ); - op.common_options.issuer_permissions = old_bitmask; - - // Unable to set new extensions in bitasset options - op.bitasset_opts->extensions.value.black_swan_response_method = 0; - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - ops.push_back( op ); - op.bitasset_opts->extensions.value.black_swan_response_method = {}; - - acop = op; - } - - // Able to create asset without new data - processed_transaction ptx = PUSH_TX(db, trx, ~0); - const asset_object& samcoin = db.get(ptx.operation_results[0].get()); - asset_id_type samcoin_id = samcoin.id; - - BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 ); - BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 ); - - // Able to propose the good operation - propose( acop ); - - // Testing asset_update_operation - asset_update_operation auop; - auop.issuer = sam_id; - auop.asset_to_update = samcoin_id; - auop.new_options = samcoin_id(db).options; - - trx.operations.clear(); - trx.operations.push_back( auop ); - - { - auto& op = trx.operations.front().get(); - op.new_options.market_fee_percent = 200; - - // Unable to set new permission bit - op.new_options.issuer_permissions = new_bitmask; - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - ops.push_back( op ); - op.new_options.issuer_permissions = old_bitmask; - - auop = op; - } - - // Able to update asset without new data - PUSH_TX(db, trx, ~0); - - BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 ); - - // Able to propose the good operation - propose( auop ); - - // Testing asset_update_bitasset_operation - asset_update_bitasset_operation aubop; - aubop.issuer = sam_id; - aubop.asset_to_update = samcoin_id; - aubop.new_options = samcoin_id(db).bitasset_data(db).options; - - trx.operations.clear(); - trx.operations.push_back( aubop ); - - { - auto& op = trx.operations.front().get(); - op.new_options.minimum_feeds = 1; - - // Unable to set new extensions - op.new_options.extensions.value.black_swan_response_method = 1; - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - ops.push_back( op ); - op.new_options.extensions.value.black_swan_response_method = {}; - - aubop = op; - } - - // Able to update bitasset without new data - PUSH_TX(db, trx, ~0); - - BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 ); - - // Able to propose the good operation - propose( aubop ); - - // Unable to propose the invalid operations - for( const operation& op : ops ) - BOOST_CHECK_THROW( propose( op ), fc::exception ); - - // Check what we have now - idump( (samcoin) ); - idump( (samcoin.bitasset_data(db)) ); - - generate_block(); - - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - // Now able to propose the operations that was invalid - for( const operation& op : ops ) - propose( op ); - - generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -/// Tests scenarios about setting non-UIA issuer permission bits on an UIA -BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) -{ - try { - - // Proceeds to a recent hard fork - generate_blocks( HARDFORK_LIQUIDITY_POOL_TIME ); - generate_block(); - set_expiration( db, trx ); - - ACTORS((sam)); - - auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; - fund( sam, asset(init_amount) ); - - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; - uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; - uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; - - uint16_t uiaflag = uiamask & ~disable_new_supply; // Allow creating new supply - - vector ops; - - asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), uiaflag ).id; - - // Testing asset_update_operation - asset_update_operation auop; - auop.issuer = sam_id; - auop.asset_to_update = samcoin_id; - auop.new_options = samcoin_id(db).options; - auop.new_options.issuer_permissions = old_bitmask & ~global_settle & ~disable_force_settle; - - trx.operations.clear(); - trx.operations.push_back( auop ); - - // Able to update asset with non-UIA issuer permission bits - PUSH_TX(db, trx, ~0); - - // Able to propose too - propose( auop ); - - // Issue some coin - issue_uia( sam_id, asset( 1, samcoin_id ) ); - - // Unable to unset the non-UIA "disable" issuer permission bits - auto perms = samcoin_id(db).options.issuer_permissions; - - auop.new_options.issuer_permissions = perms & ~disable_icr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = perms & ~disable_mcr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = perms & ~disable_mssr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - // Still able to propose - auop.new_options.issuer_permissions = new_bitmask; - propose( auop ); - - // But no longer able to update directly - auop.new_options.issuer_permissions = uiamask | witness_fed_asset; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | committee_fed_asset; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_icr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_mcr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_mssr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Unset the non-UIA bits in issuer permissions, should succeed - auop.new_options.issuer_permissions = uiamask; - trx.operations.clear(); - trx.operations.push_back( auop ); - - PUSH_TX(db, trx, ~0); - - BOOST_CHECK_EQUAL( samcoin_id(db).options.issuer_permissions, uiamask ); - - // Burn all supply - reserve_asset( sam_id, asset( 1, samcoin_id ) ); - - BOOST_CHECK_EQUAL( samcoin_id(db).dynamic_asset_data_id(db).current_supply.value, 0 ); - - // Still unable to set the non-UIA bits in issuer permissions - auop.new_options.issuer_permissions = uiamask | witness_fed_asset; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | committee_fed_asset; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_icr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_mcr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_mssr_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - auop.new_options.issuer_permissions = uiamask | disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -/// Tests what kind of assets can have BSRM-related flags / issuer permissions / extensions -BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) -{ - try { - - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - ACTORS((sam)(feeder)); - - auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; - fund( sam, asset(init_amount) ); - fund( feeder, asset(init_amount) ); - - // Unable to create a PM with the disable_bsrm_update bit in flags - BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bsrm_update ), fc::exception ); - - // Unable to create a MPA with the disable_bsrm_update bit in flags - BOOST_CHECK_THROW( create_bitasset( "TESTBIT", sam_id, 0, disable_bsrm_update ), fc::exception ); - - // Unable to create a UIA with the disable_bsrm_update bit in flags - BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_bsrm_update ), fc::exception ); - - // create a PM with a zero market_fee_percent - const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); - asset_id_type pm_id = pm.id; - - // create a MPA with a zero market_fee_percent - const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); - asset_id_type mpa_id = mpa.id; - - // create a UIA with a zero market_fee_percent - const asset_object& uia = create_user_issued_asset( "TESTUIA", sam_id(db), charge_market_fee ); - asset_id_type uia_id = uia.id; - - // Prepare for asset update - asset_update_operation auop; - auop.issuer = sam_id; - - // Unable to set disable_bsrm_update bit in flags for PM - auop.asset_to_update = pm_id; - auop.new_options = pm_id(db).options; - auop.new_options.flags |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // Unable to propose either - BOOST_CHECK_THROW( propose( auop ), fc::exception ); - - // Unable to set disable_bsrm_update bit in flags for MPA - auop.asset_to_update = mpa_id; - auop.new_options = mpa_id(db).options; - auop.new_options.flags |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // Unable to propose either - BOOST_CHECK_THROW( propose( auop ), fc::exception ); - - // Unable to set disable_bsrm_update bit in flags for UIA - auop.asset_to_update = uia_id; - auop.new_options = uia_id(db).options; - auop.new_options.flags |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // Unable to propose either - BOOST_CHECK_THROW( propose( auop ), fc::exception ); - - // Unable to set disable_bsrm_update bit in issuer_permissions for PM - auop.asset_to_update = pm_id; - auop.new_options = pm_id(db).options; - auop.new_options.issuer_permissions |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // But able to propose - propose( auop ); - - // Unable to set disable_bsrm_update bit in issuer_permissions for UIA - auop.asset_to_update = uia_id; - auop.new_options = uia_id(db).options; - auop.new_options.issuer_permissions |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - // But able to propose - propose( auop ); - - // Unable to create a UIA with disable_bsrm_update permission bit - asset_create_operation acop; - acop.issuer = sam_id; - acop.symbol = "SAMCOIN"; - acop.precision = 2; - acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); - acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; - acop.common_options.market_fee_percent = 100; - acop.common_options.flags = charge_market_fee; - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_bsrm_update; - - trx.operations.clear(); - trx.operations.push_back( acop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Unable to propose either - BOOST_CHECK_THROW( propose( acop ), fc::exception ); - - // Able to create UIA without disable_bsrm_update permission bit - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; - trx.operations.clear(); - trx.operations.push_back( acop ); - PUSH_TX(db, trx, ~0); - - // Unable to create a PM with disable_bsrm_update permission bit - acop.symbol = "SAMPM"; - acop.precision = asset_id_type()(db).precision; - acop.is_prediction_market = true; - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle | disable_bsrm_update; - acop.bitasset_opts = bitasset_options(); - - trx.operations.clear(); - trx.operations.push_back( acop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Unable to propose either - BOOST_CHECK_THROW( propose( acop ), fc::exception ); - - // Unable to create a PM with BSRM in extensions - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; - acop.bitasset_opts->extensions.value.black_swan_response_method = 0; - - trx.operations.clear(); - trx.operations.push_back( acop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Unable to propose either - BOOST_CHECK_THROW( propose( acop ), fc::exception ); - - // Able to create PM with no disable_bsrm_update permission bit nor BSRM in extensions - acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle; - acop.bitasset_opts->extensions.value.black_swan_response_method.reset(); - trx.operations.clear(); - trx.operations.push_back( acop ); - PUSH_TX(db, trx, ~0); - - // Unable to update PM to set BSRM - asset_update_bitasset_operation aubop; - aubop.issuer = sam_id; - aubop.asset_to_update = pm_id; - aubop.new_options = pm_id(db).bitasset_data(db).options; - aubop.new_options.extensions.value.black_swan_response_method = 1; - - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - // Able to propose - propose( aubop ); - - generate_block(); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -/// Tests whether asset owner has permission to update bsrm -BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) -{ - try { - - // Advance to core-2467 hard fork - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_2467_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - ACTORS((sam)(feeder)); - - auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; - fund( sam, asset(init_amount) ); - fund( feeder, asset(init_amount) ); - - // create a MPA with a zero market_fee_percent - const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); - asset_id_type mpa_id = mpa.id; - - BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); - - BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - - using bsrm_type = bitasset_options::black_swan_response_type; - BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); - - // add a price feed publisher and publish a feed - update_feed_producers( mpa_id, { feeder_id } ); - - price_feed f; - f.settlement_price = price( asset(1,mpa_id), asset(1) ); - f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); - f.maintenance_collateral_ratio = 1850; - f.maximum_short_squeeze_ratio = 1250; - - uint16_t feed_icr = 1900; - - publish_feed( mpa_id, feeder_id, f, feed_icr ); - - // Prepare for asset update - asset_update_operation auop; - auop.issuer = sam_id; - auop.asset_to_update = mpa_id; - auop.new_options = mpa_id(db).options; - - // disable owner's permission to update bsrm - auop.new_options.issuer_permissions |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - PUSH_TX(db, trx, ~0); - - BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - - // check that owner can not update bsrm - asset_update_bitasset_operation aubop; - aubop.issuer = sam_id; - aubop.asset_to_update = mpa_id; - aubop.new_options = mpa_id(db).bitasset_data(db).options; - - aubop.new_options.extensions.value.black_swan_response_method = 1; - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.black_swan_response_method.reset(); - - BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - - // enable owner's permission to update bsrm - auop.new_options.issuer_permissions &= ~disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - PUSH_TX(db, trx, ~0); - - BOOST_CHECK( mpa_id(db).can_owner_update_bsrm() ); - - // check that owner can update bsrm - aubop.new_options.extensions.value.black_swan_response_method = 1; - trx.operations.clear(); - trx.operations.push_back( aubop ); - PUSH_TX(db, trx, ~0); - - BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - - BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); - BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); - - // check bsrm' valid range - aubop.new_options.extensions.value.black_swan_response_method = 4; - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - aubop.new_options.extensions.value.black_swan_response_method = 1; - - // Sam borrow some - borrow( sam, asset(1000, mpa_id), asset(2000) ); - - // disable owner's permission to update bsrm - auop.new_options.issuer_permissions |= disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - PUSH_TX(db, trx, ~0); - - BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - - // check that owner can not update bsrm - aubop.new_options.extensions.value.black_swan_response_method = 0; - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - aubop.new_options.extensions.value.black_swan_response_method.reset(); - trx.operations.clear(); - trx.operations.push_back( aubop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - aubop.new_options.extensions.value.black_swan_response_method = 1; - - // able to update other params that still has permission E.G. force_settlement_delay_sec - aubop.new_options.force_settlement_delay_sec += 1; - trx.operations.clear(); - trx.operations.push_back( aubop ); - PUSH_TX(db, trx, ~0); - - BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, - aubop.new_options.force_settlement_delay_sec ); - - BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method.valid() ); - - BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.black_swan_response_method, 1u ); - - // unable to enable the permission to update bsrm - auop.new_options.issuer_permissions &= ~disable_bsrm_update; - trx.operations.clear(); - trx.operations.push_back( auop ); - BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); - - BOOST_CHECK( !mpa_id(db).can_owner_update_bsrm() ); - - generate_block(); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - /// Tests margin calls when BSRM is no_settlement and call order is maker BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) { From 19c39fccff4e52063947cc27234340824db2f9eb Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 02:04:33 +0000 Subject: [PATCH 162/258] Fix rounding issue when matching limit with call and slightly refactor other code --- libraries/chain/db_market.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index b9ec5029c1..83fba4ad91 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1133,21 +1133,27 @@ database::match_result_type database::match( const limit_order_object& bid, cons bool cull_taker = false; + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + bool after_core_hardfork_2481 = HARDFORK_CORE_2481_PASSED( maint_time ); // Match settle orders with margin calls + const auto& feed_price = bitasset.current_feed.settlement_price; const auto& maintenance_collateral_ratio = bitasset.current_feed.maintenance_collateral_ratio; optional maintenance_collateralization; - if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME ) // call price caching issue + if( !before_core_hardfork_1270 ) maintenance_collateralization = bitasset.current_maintenance_collateralization; asset usd_for_sale = bid.amount_for_sale(); asset usd_to_buy = asset( ask.get_max_debt_to_cover( call_pays_price, feed_price, maintenance_collateral_ratio, maintenance_collateralization ), ask.debt_type() ); - asset call_pays, call_receives, order_pays, order_receives; + asset call_pays; + asset call_receives; + asset order_pays; + asset order_receives; if( usd_to_buy > usd_for_sale ) { // fill limit order order_receives = usd_for_sale * match_price; // round down here, in favor of call order - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later @@ -1159,6 +1165,11 @@ database::match_result_type database::match( const limit_order_object& bid, cons // If the order would receive 0 even at `match_price`, it would receive 0 at its own price, // so calling maybe_cull_small() will always cull it. call_receives = order_receives.multiply_and_round_up( match_price ); + if( after_core_hardfork_2481 ) + call_pays = call_receives * call_pays_price; // calculate with updated call_receives + else + // TODO add tests about CR change + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) cull_taker = true; } else @@ -1876,9 +1887,6 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( usd_to_buy > usd_for_sale ) { // fill order limit_receives = usd_for_sale * match_price; // round down, in favor of call order - if( !after_core_hardfork_2481 ) - // TODO add tests about CR change - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) // Be here, the limit order won't be paying something for nothing, since if it would, it would have // been cancelled elsewhere already (a maker limit order won't be paying something for nothing): @@ -1897,7 +1905,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // so calling maybe_cull_small() will always cull it. call_receives = limit_receives.multiply_and_round_up( match_price ); - if( after_core_hardfork_2481 ) + if( !after_core_hardfork_2481 ) + // TODO add tests about CR change + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + else { call_pays = call_receives * call_pays_price; // calculate with updated call_receives if( call_pays.amount >= call_order.collateral ) From d8a96e059c7dabf21e1449c04b37c66ab8199d62 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 02:08:44 +0000 Subject: [PATCH 163/258] Add no-settlement tests about small limit taker --- tests/tests/bsrm_no_settlement_tests.cpp | 269 ++++++++++++++++++++++- 1 file changed, 268 insertions(+), 1 deletion(-) diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index f9db05f030..e88672a24f 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -226,7 +226,7 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) // now feed price is 13:10 * (1000-111):(2750-111*275/100) // = 13:10 * 889:2445 = 11557:24450 // call order match price is 1300:1299 * 889:2445 = 0.363879089 - // sell_mid's price is 100/210 = 0.047619048 + // sell_mid's price is 100/210 = 0.47619048 // sell_mid got filled too BOOST_CHECK( !db.find( sell_mid_id ) ); @@ -307,6 +307,273 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) } } +/// Tests margin calls when BSRM is no_settlement and call order is maker and taker limit order is too small to fill +BOOST_AUTO_TEST_CASE( no_settlement_maker_small_limit_taker_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 100000 * (2000/125000) * 1.9 = 3040 + // 100000 * (22/1000) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower is unable to adjust debt position if it's still undercollateralized + // 100000 * (2000/125000) * 1.25 = 2000 + // 100000 * (22/1000) * 1.25 = 2750 + BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); + // borrower adjust debt position to right at MSSR + borrow( borrower, asset(0, mpa_id), asset(750) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Sam update MSSR and MCFR + // note: borrower's position is undercollateralized again due to the mssr change + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; + aubop.new_options.extensions.value.margin_call_fee_ratio = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(50000,mpa_id) ); + transfer( borrower3, seller2, asset(50000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(10000,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled in the beginning either + const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 250000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller sells some, this order will be filled + const limit_order_object* sell_low = create_sell_order( seller, asset(11100,mpa_id), asset(210) ); + BOOST_CHECK( !sell_low ); + + // call2 pays price = 210/10000 + // call2 match price = (210/10000) * (1299/1300) = 27279/1300000 + // sell_low receives = round_down(11100 * 27279/1300000)) = 232 + // sell_low pays = round_up(232 * 1300000/27279) = 11057, the rest is cancelled + // call2 receives = 11057 + // call2 pays = round_down(11057 * 210/10000) = 232, margin call fee = 0 + // now feed price = 13:10 * (100000-11057):(2100-232) = 13:10 * 88943:1868 = 1156259:18680 + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 238943 ); // 250000 - 11057 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price + == price( asset(1156259,mpa_id), asset(18680) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 88943 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1868 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller sells more + sell_low = create_sell_order( seller, asset(100000,mpa_id), asset(100) ); + + // call2 is fully filled + BOOST_CHECK( !db.find(call2_id) ); + // call2 pays 1868 + // call2 pays price = 1868/88943 + // call2 match price = (1868/88943) * (1299/1300) = 2426532/115625900 + // sell_low pays 88943 + // sell_low receives = round_up(88943 * 2426532/115625900)) = 1867 + // sell_low reminder = 100000-88943 = 11057 + + // sell_low is fully filled + BOOST_CHECK( !sell_low ); + // call pays price = 275/10000 + // call match price = (275/10000) * (1299/1300) = 357225/13000000 + // sell_low receives = round_down(11057 * 357225/13000000)) = 303 + // sell_low pays = round_up(303 * 13000000/357225) = 11027, the rest is cancelled + + auto final_check = [&] + { + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 138973 ); // 250000 - 11057 - 88943 - 11027 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2402 ); // 232 + 1867 + 303 + // call receives = 11027 + // call pays = round_down(11027 * 275/10000) = 303, margin call fee = 0 + // now feed price = 13:10 * (100000-11027):(2750-303) = 13:10 * 88973:2447 = 1156649:24470 + // call order match price is 1300:1299 * 88973:2447 = 36.38802348 + // sell_mid's price is 10000/210 = 47.619047619 + + // sell_mid got filled too + BOOST_CHECK( !db.find( sell_mid_id ) ); + + // sell_mid was selling 10000 MPA for 210 CORE as maker, matched at its price + // call pays round_down(210*1300/1299) = 210, fee = 0 + // call receives + // now feed price is 13:10 * (88973-10000):(2447-210) + // = 13:10 * 78973:2237 = 1026649:22370 + // call order match price is 1300:1299 * 78973:2237 = 35.330261612 + // sell_high's price is 10000/275 = 36.363636364 + + // sell_high got filled too + BOOST_CHECK( !db.find( sell_high_id ) ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); + // sell_mid was selling 10000 MPA for 210 CORE as maker, matched at its price + // sell_high was selling 10000 MPA for 275 CORE as maker, matched at its price + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 + // call pays round_down(275*1300/1299) = 275, fee = 0 + // now feed price is 13:10 * (78973-10000):(2237-275) + // = 13:10 * 68973:1962 = 896649:19620 (>1000/22) + // call order match price is 1300:1299 * 68973:1962 = 35.181496941 + // sell_highest's price is 10000/285 = 35.087719298, does not match + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(896649,mpa_id), asset(19620) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 68973 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1962 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /// Tests force settlements when BSRM is no_settlement and call order is maker BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) { From 64cbfd0200fff47391f4d022fa8c450ab3e11568 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 12:57:19 +0000 Subject: [PATCH 164/258] Fix rounding issue when matching settle with call --- libraries/chain/db_market.cpp | 45 ++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 83fba4ad91..aa9206a594 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1331,6 +1331,7 @@ asset database::match_impl( const force_settlement_object& settle, // the call order is a margin call, implies hf core-2481 else if( settle_pays == max_settlement ) // the settle order is larger, but the call order has TCR { + // Note: here settle_pays == call_receives call_pays = call_receives.multiply_and_round_up( match_price ); // round up, in favor of settle order settle_receives = call_receives.multiply_and_round_up( fill_price ); // round up // Note: here we do NOT stabilize call_receives since it is done in get_max_debt_to_cover(), @@ -1338,7 +1339,27 @@ asset database::match_impl( const force_settlement_object& settle, } else // the call order is a margin call, and the settle order is smaller { - // it was correct to round down call_pays. + // It was correct to round down call_pays. However, it is not the final result. + // For margin calls, due to margin call fee, it is fairer to calculate with fill_price first + const auto& calculate = [&settle_receives,&settle_pays,&fill_price,&call_receives,&call_pays,&match_price] + { + settle_receives = settle_pays * fill_price; // round down here, in favor of call order + if( settle_receives.amount != 0 ) + { + // round up to mitigate rounding issues (hf core-342) + call_receives = settle_receives.multiply_and_round_up( fill_price ); + // round down + call_pays = call_receives * match_price; + } + }; + + calculate(); + if( settle_receives.amount == 0 ) + { + cancel_settle_order( settle ); + // If the settle order is canceled, we just return, since nothing else can be done + return asset( 0, call_debt.asset_id ); + } // check whether the call order can be filled at match_price bool cap_price = false; @@ -1346,25 +1367,18 @@ asset database::match_impl( const force_settlement_object& settle, cap_price = true; else { - // round up to mitigate rounding issues (hf core-342) - call_receives = call_pays.multiply_and_round_up( match_price ); - auto new_collateral = call_collateral - call_pays; auto new_debt = call_debt - call_receives; // the result is positive due to math if( ( new_collateral / new_debt ) < call.collateralization() ) // if CR would decrease cap_price = true; } - if( !cap_price ) // match_price is good - { - settle_receives = call_receives * fill_price; // round down - } - else // match_price is not good + if( cap_price ) // match_price is not good, update match price and fill price, then calculate again { match_price = call_debt / call_collateral; - call_pays = settle_pays * match_price; // round down here, in favor of call order - // price changed, check if it is something-for-nothing again - if( call_pays.amount == 0 ) + fill_price = match_price / margin_call_pays_ratio; + calculate(); + if( settle_receives.amount == 0 ) { // Note: when it is a margin call, max_settlement is max_debt_to_cover. // if need to cap price here, max_debt_to_cover should be equal to call_debt. @@ -1373,14 +1387,7 @@ asset database::match_impl( const force_settlement_object& settle, // If the settle order is canceled, we just return, since nothing else can be done return asset( 0, call_debt.asset_id ); } - // price changed, update call_receives // round up to mitigate rounding issues (hf core-342) - call_receives = call_pays.multiply_and_round_up( match_price ); // round up - // update fill price and settle_receives - fill_price = match_price / margin_call_pays_ratio; - settle_receives = call_receives * fill_price; // round down here, in favor of call order } - if( settle_receives.amount == 0 ) - settle_receives.amount = 1; // reduce margin-call fee in this case. Note: here call_pays >= 1 } // end : if is_margin_call, else ... // be here, we should have: call_pays <= call_collateral From a432884610df9d8ed57b4a4e40470043fbfab8f6 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 12:58:40 +0000 Subject: [PATCH 165/258] Add no-settlement tests about small settle taker --- tests/tests/bsrm_no_settlement_tests.cpp | 207 +++++++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index e88672a24f..39f7e08dc6 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -854,6 +854,213 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_force_settle_test ) } } +/// Tests force settlements when BSRM is no_settlement and call order is maker and settle order is too small to fill +BOOST_AUTO_TEST_CASE( no_settlement_maker_small_settle_taker_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 100000 * (2000/125000) * 1.9 = 3040 + // 100000 * (22/1000) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower is unable to adjust debt position if it's still undercollateralized + // 100000 * (2000/125000) * 1.25 = 2000 + // 100000 * (22/1000) * 1.25 = 2750 + BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); + // borrower adjust debt position to right at MSSR + borrow( borrower, asset(0, mpa_id), asset(750) ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Sam update MSSR and MCFR + // note: borrower's position is undercollateralized again due to the mssr change + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1300; + aubop.new_options.extensions.value.margin_call_fee_ratio = 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).median_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK_EQUAL( mpa.bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1300u ); + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(130000,mpa_id), asset(2100) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(50000,mpa_id) ); + transfer( borrower3, seller2, asset(50000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + // seller2 sells some, due to MCFR, this order won't be filled in the beginning, but will be filled later + const limit_order_object* sell_mid = create_sell_order( seller2, asset(10000,mpa_id), asset(210) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled in the beginning either + const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 250000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + + auto final_check = [&] + { + BOOST_CHECK( !db.find(settle_id) ); + + // call2 pays price = 210/10000 + // call2 match price = (210/10000) * (1299/1300) = 27279/1300000 + // sell_low receives = round_down(11100 * 27279/1300000)) = 232 + // sell_low pays = round_up(232 * 1300000/27279) = 11057, the rest is cancelled + // call2 receives = 11057 + // call2 pays = round_down(11057 * 210/10000) = 232, margin call fee = 0 + // now feed price = 13:10 * (100000-11057):(2100-232) = 13:10 * 88943:1868 = 1156259:18680 + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 238943 ); // 250000 - 11057 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 232 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1156259,mpa_id), asset(18680) ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 88943 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1868 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /// Tests margin calls and force settlements when BSRM is no_settlement and call order is taker BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) { try { From edd73826af0e49e0aaddf7b3178bf7337a53f724 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 3 Sep 2021 13:58:45 +0000 Subject: [PATCH 166/258] Fix tests about matching call with settle --- tests/tests/force_settle_match_tests.cpp | 56 +++++++++++------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 97299f4c0a..db51ef4588 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -321,9 +321,9 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK( db.find( call_id ) != nullptr ); // check - share_type call_to_pay = expected_amount_to_settle * 11 / 100; // round down, favors call order - share_type call_to_cover = (call_to_pay * 100 + 10 ) / 11; // stabilize : 101 -> 100 - share_type call_to_settler = (call_to_cover * 107) / 1000; // round down, favors call order + share_type call_to_settler = expected_amount_to_settle * 107 / 1000; // round down, favors call order : 10 + share_type call_to_cover = (call_to_settler * 1000 + 106 ) / 107; // stabilize : 101 -> 94 + share_type call_to_pay = call_to_cover * 11 / 100; // round down, favors call order : 10, fee = 0 BOOST_CHECK_EQUAL( 100000 - call_to_cover.value, call.debt.value ); BOOST_CHECK_EQUAL( 15000 - call_to_pay.value, call.collateral.value ); idump( (call) ); @@ -332,7 +332,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99884, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100, the rest 1 be canceled + BOOST_CHECK_EQUAL( 99890, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94, the rest 7 be canceled int64_t expected_seller_core_balance = 1 + 1 + call_to_settler.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); @@ -352,9 +352,9 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK( db.find( call_id ) != nullptr ); // check - share_type call_to_pay2 = amount_to_settle2 * 11 / 100; // round down, favors call order - share_type call_to_cover2 = (call_to_pay2 * 100 + 10 ) / 11; // stabilize : 100 -> 100 (no change) - share_type call_to_settler2 = (call_to_cover2 * 107) / 1000; // round down, favors call order + share_type call_to_settler2 = amount_to_settle2 * 107 / 1000; // round down, favors call order : 10 + share_type call_to_cover2 = (call_to_settler2 * 1000 + 106 ) / 107; // stabilize : 100 -> 94 + share_type call_to_pay2 = call_to_cover2 * 11 / 100; // round down, favors call order : 10, fee = 0 BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value ); BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value ); idump( (call) ); @@ -363,7 +363,7 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99784, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 + BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94 expected_seller_core_balance += call_to_settler2.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); @@ -398,22 +398,18 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK( db.find( call_id ) != nullptr ); // check - share_type call_to_pay3 = amount_to_settle3 * 13 / 100; // round down, favors call order - share_type call_to_cover3 = (call_to_pay3 * 100 + 10 ) / 13; // stabilize : 9 -> 8 - share_type call_to_settler3 = (call_to_cover3 * 103) / 1000; // round down, favors call order + share_type call_to_settler3 = amount_to_settle3 * 103 / 1000; // round down, favors call order : 0 BOOST_CHECK_EQUAL( 0, call_to_settler3.value ); - call_to_settler3 = 1; // 0 -> 1 - BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value - call_to_cover3.value, - call.debt.value ); - BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value - call_to_pay3.value, - call.collateral.value ); + // the settle order will be cancelled + BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value ); idump( (call) ); // borrower's balance doesn't change BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99776, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 - 8 + BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94 expected_seller_core_balance += call_to_settler3.value; BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); @@ -433,16 +429,14 @@ BOOST_AUTO_TEST_CASE(hf2481_small_settle_call) BOOST_CHECK( db.find( call_id ) != nullptr ); // no data change - BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value - call_to_cover3.value, - call.debt.value ); - BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value - call_to_pay3.value, - call.collateral.value ); + BOOST_CHECK_EQUAL( 100000 - call_to_cover.value - call_to_cover2.value, call.debt.value ); + BOOST_CHECK_EQUAL( 15000 - call_to_pay.value - call_to_pay2.value, call.collateral.value ); idump( (call) ); // borrower's balance doesn't change BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); // check seller balance - BOOST_CHECK_EQUAL( 99776, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 100 - 100 - 8 + BOOST_CHECK_EQUAL( 99796, get_balance(seller, bitusd) ); // 100000 - 7 - 9 - 94 - 94 // expected_seller_core_balance does not change BOOST_CHECK_EQUAL( expected_seller_core_balance, get_balance(seller, core) ); @@ -1156,14 +1150,15 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // call2 is still in margin call territory after matched with limit order, now it matches with settle orders // the settle orders are too small to fill call2 share_type call2_to_cover1 = 39000; // 40000 - 1000 - share_type call2_to_pay1 = call2_to_cover1 * call2_copy.collateral / call2_copy.debt; // round down + share_type settle_receives2 = call2_to_cover1 * call2_copy.collateral * 107 + / (call2_copy.debt * 110); // round down share_type call2_to_cover1_old = call2_to_cover1; // stabilize - call2_to_cover1 = (call2_to_pay1 * call2_copy.debt + call2_copy.collateral - 1) / call2_copy.collateral; + call2_to_cover1 = (settle_receives2 * call2_copy.debt * 110 + call2_copy.collateral * 107 - 1) + / ( call2_copy.collateral * 107 ); + share_type call2_to_pay1 = call2_to_cover1 * call2_copy.collateral / call2_copy.debt; // round down share_type settle_refund = call2_to_cover1_old - call2_to_cover1; - share_type settle_receives2 = call2_to_cover1 * call2_copy.collateral * 107 - / (call2_copy.debt * 110); // round down share_type margin_call_fee_settle_2 = call2_to_pay1 - settle_receives2; expected_margin_call_fees += margin_call_fee_settle_2; @@ -1176,14 +1171,15 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) // call2 matches with the other settle order share_type call2_to_cover2 = 10000; - share_type call2_to_pay2 = call2_to_cover2 * call2_copy.collateral / call2_copy.debt; // round down + share_type settle2_receives2 = call2_to_cover2 * call2_copy.collateral * 107 + / (call2_copy.debt * 110); // round down share_type call2_to_cover2_old = call2_to_cover2; // stabilize - call2_to_cover2 = (call2_to_pay2 * call2_copy.debt + call2_copy.collateral - 1) / call2_copy.collateral; + call2_to_cover2 = (settle2_receives2 * call2_copy.debt * 110 + call2_copy.collateral * 107 - 1) + / ( call2_copy.collateral * 107 ); + share_type call2_to_pay2 = call2_to_cover2 * call2_copy.collateral / call2_copy.debt; // round down share_type settle2_refund = call2_to_cover2_old - call2_to_cover2; - share_type settle2_receives2 = call2_to_cover2 * call2_copy.collateral * 107 - / (call2_copy.debt * 110); // round down share_type margin_call_fee_settle2_2 = call2_to_pay2 - settle2_receives2; expected_margin_call_fees += margin_call_fee_settle2_2; From 0443f5468d51beeb4e4cd2177edc2a22343dfe3a Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 14:32:22 +0000 Subject: [PATCH 167/258] Add tests about updating BSRM after GS --- tests/tests/bsrm_basic_tests.cpp | 113 +++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 4257285d9a..5082d836ad 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -668,4 +668,117 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) } } +/// Tests whether it is able to update BSRM after GS +BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + + // Create asset + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + using bsrm_type = bitasset_options::black_swan_response_type; + + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed to trigger GS" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + + // Sam tries to update BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + for( uint16_t i = 1; i <= 3; ++i ) + { + idump( (i) ); + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + } + + // recheck + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + + // publish a new feed to revive the MPA + ilog( "Publish a new feed to revive MPA" ); + f.settlement_price = price( asset(1000,mpa_id), asset(3) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check - revived + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + // Sam tries to update BSRM + for( uint8_t i = 1; i <= 3; ++i ) + { + idump( (i) ); + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); + + if( i != 3 ) + { + aubop.new_options.extensions.value.black_swan_response_method = 0; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + } + } + + ilog( "Generate a block" ); + generate_block(); + + // final check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 8f71eb26d171c01ce7ae882d31f141c8cad78f82 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 15:13:14 +0000 Subject: [PATCH 168/258] Add BSRM update tests for indvd-settlement to fund --- tests/common/database_fixture.cpp | 3 + tests/tests/bsrm_basic_tests.cpp | 144 ++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 2c04862c0f..8b65780aef 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -514,7 +514,10 @@ void database_fixture_base::verify_asset_supplies( const database& db ) { const auto& bad = asset_obj.bitasset_data(db); total_balances[bad.options.short_backing_asset] += bad.settlement_fund; + total_balances[bad.options.short_backing_asset] += bad.individual_settlement_fund; total_balances[bad.options.short_backing_asset] += dasset_obj.accumulated_collateral_fees; + if( !bad.has_settlement() ) // Note: if asset has been globally settled, do not check total debt + total_debts[bad.asset_id] += bad.individual_settlement_debt; } total_balances[asset_obj.id] += dasset_obj.confidential_supply.value; } diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 5082d836ad..79a01c67e8 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -781,4 +781,148 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) } } +/// Tests whether it is able to update BSRM after individual settlement to fund +BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed to trigger settlement" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + // Sam tries to update BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + for( uint16_t i = 0; i <= 3; ++i ) + { + if( static_cast(i) == bsrm_type::individual_settlement_to_fund ) + continue; + idump( (i) ); + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + } + + // recheck + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + // Settle debt + ilog( "Settle" ); + force_settle( borrower2, asset(100000,mpa_id) ); + + // recheck + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + + // Sam tries to update BSRM + for( uint8_t i = 0; i <= 3; ++i ) + { + if( static_cast(i) == bsrm_type::individual_settlement_to_fund ) + continue; + idump( (i) ); + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); + if( i != 3 ) + { + aubop.new_options.extensions.value.black_swan_response_method = bsrm_value; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + } + } + + ilog( "Generate a block" ); + generate_block(); + + // final check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 0e207048feece1ab8a1b17ef9ba2b02bc802ee09 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 16:09:33 +0000 Subject: [PATCH 169/258] Add BSRM update tests for indvd-settle to order --- tests/common/database_fixture.cpp | 9 +- tests/tests/bsrm_basic_tests.cpp | 179 +++++++++++++++++++++++++++++- 2 files changed, 182 insertions(+), 6 deletions(-) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 8b65780aef..9218a90a63 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -493,10 +493,17 @@ void database_fixture_base::verify_asset_supplies( const database& db ) for( const limit_order_object& o : db.get_index_type().indices() ) { asset for_sale = o.amount_for_sale(); - if( for_sale.asset_id == asset_id_type() ) core_in_orders += for_sale.amount; + if( for_sale.asset_id == asset_id_type() && !o.is_settled_debt ) + // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders + core_in_orders += for_sale.amount; total_balances[for_sale.asset_id] += for_sale.amount; total_balances[asset_id_type()] += o.deferred_fee; total_balances[o.deferred_paid_fee.asset_id] += o.deferred_paid_fee.amount; + if( o.is_settled_debt ) + { + total_debts[o.receive_asset_id()] += o.sell_price.quote.amount; + BOOST_CHECK_EQUAL( o.sell_price.base.amount.value, for_sale.amount.value ); + } } for( const call_order_object& o : db.get_index_type().indices() ) { diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 79a01c67e8..c8192553c6 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -692,6 +692,9 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) using bsrm_type = bitasset_options::black_swan_response_type; BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -717,7 +720,10 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) publish_feed( mpa_id, feeder_id, f, feed_icr ); // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); // Sam tries to update BSRM @@ -736,8 +742,10 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) } // recheck - BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // publish a new feed to revive the MPA ilog( "Publish a new feed to revive MPA" ); @@ -746,6 +754,8 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) // check - revived BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 1; i <= 3; ++i ) @@ -772,8 +782,10 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) generate_block(); // final check - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); @@ -823,8 +835,9 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::individual_settlement_to_fund ); - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -855,6 +868,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -880,14 +894,16 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) == bsrm_type::individual_settlement_to_fund ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // Settle debt ilog( "Settle" ); force_settle( borrower2, asset(100000,mpa_id) ); // recheck - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 0; i <= 3; ++i ) @@ -916,8 +932,161 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) generate_block(); // final check - BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests whether it is able to update BSRM after individual settlement to order +BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed to trigger settlement" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + // Sam tries to update BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + + for( uint16_t i = 0; i <= 3; ++i ) + { + if( static_cast(i) == bsrm_type::individual_settlement_to_order ) + continue; + idump( (i) ); + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + } + + // recheck + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( db.find_individual_settlemnt_order(mpa_id) ); + + // Fill the individual settlement order + ilog( "Buy into the individual settlement order" ); + const limit_order_object* sell_ptr = create_sell_order( borrower2, asset(100000,mpa_id), asset(1) ); + BOOST_CHECK( !sell_ptr ); + + // recheck + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + + // Sam tries to update BSRM + for( uint8_t i = 0; i <= 3; ++i ) + { + if( static_cast(i) == bsrm_type::individual_settlement_to_order ) + continue; + idump( (i) ); + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); + if( i != 2 ) + { + aubop.new_options.extensions.value.black_swan_response_method = bsrm_value; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + } + } + + ilog( "Generate a block" ); + generate_block(); + + // final check + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(2) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); From cdcc4d6549c254c61c281fa86a4dc3684420fe8f Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 16:10:31 +0000 Subject: [PATCH 170/258] Add comments --- libraries/chain/db_market.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index aa9206a594..3201646177 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -370,6 +370,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, obj.is_settled_debt = true; } ); } + // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders } // call order is maker @@ -1107,6 +1108,7 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde asset(0, call_receives.asset_id), match_price, true ) ); // Update the maker order + // Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders if( maker_filled ) remove( maker ); else From d483ebbdc651a2069fda0b19db25780f810e15d0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 16:15:31 +0000 Subject: [PATCH 171/258] Rename a function --- libraries/chain/asset_evaluator.cpp | 2 +- libraries/chain/db_getter.cpp | 2 +- libraries/chain/db_market.cpp | 4 +-- .../chain/include/graphene/chain/database.hpp | 2 +- tests/tests/bsrm_basic_tests.cpp | 30 +++++++++---------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9b184814f0..f8c22536cb 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -744,7 +744,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( !current_bitasset_data.has_individual_settlement(), "Unable to update BSRM when the individual settlement pool is not empty" ); else if( bsrm_type::individual_settlement_to_order == old_bsrm ) - FC_ASSERT( !d.find_individual_settlemnt_order( op.asset_to_update ), + FC_ASSERT( !d.find_settled_debt_order( op.asset_to_update ), "Unable to update BSRM when there exists an individual settlement order" ); // Since we do not allow updating in some cases (above), only check no_settlement here diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index ed4fc84ac6..085b48f338 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -149,7 +149,7 @@ const witness_schedule_object& database::get_witness_schedule_object()const return *_p_witness_schedule_obj; } -const limit_order_object* database::find_individual_settlemnt_order( const asset_id_type& a )const +const limit_order_object* database::find_settled_debt_order( const asset_id_type& a )const { const auto& limit_index = get_index_type().indices().get(); auto itr = limit_index.lower_bound( std::make_tuple( true, a ) ); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 3201646177..12063c75b8 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -303,7 +303,7 @@ void database::globally_settle_asset_impl( const asset_object& mia, } // Move the individual settlement order to the GS fund - const limit_order_object* limit_ptr = find_individual_settlemnt_order( bitasset.asset_id ); + const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id ); if( limit_ptr ) { collateral_gathered.amount += limit_ptr->for_sale; @@ -351,7 +351,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } else // settle to order { - const limit_order_object* limit_ptr = find_individual_settlemnt_order( bitasset.asset_id ); + const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id ); if( limit_ptr ) { modify( *limit_ptr, [&order,&fund_receives]( limit_order_object& obj ) { diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index fecb6e6acc..9f8b438d95 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -213,7 +213,7 @@ namespace graphene { namespace chain { /// Find the limit order which is the individual settlement fund of the specified asset /// @param a ID of the asset /// @return nullptr if not found, pointer to the limit order if found - const limit_order_object* find_individual_settlemnt_order( const asset_id_type& a )const; + const limit_order_object* find_settled_debt_order( const asset_id_type& a )const; /// Find the call order with the least collateral ratio /// @param bitasset The bitasset object diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index c8192553c6..4b0a24b1e6 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -694,7 +694,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -723,7 +723,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); // Sam tries to update BSRM @@ -745,7 +745,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == bsrm_type::global_settlement ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // publish a new feed to revive the MPA ilog( "Publish a new feed to revive MPA" ); @@ -755,7 +755,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) // check - revived BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 1; i <= 3; ++i ) @@ -785,7 +785,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); @@ -837,7 +837,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) == bsrm_type::individual_settlement_to_fund ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -868,7 +868,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -894,7 +894,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) == bsrm_type::individual_settlement_to_fund ); BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Settle debt ilog( "Settle" ); @@ -903,7 +903,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) // recheck BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 0; i <= 3; ++i ) @@ -935,7 +935,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_fund ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(3) ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); @@ -987,7 +987,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) == bsrm_type::individual_settlement_to_order ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // add a price feed publisher and publish a feed update_feed_producers( mpa_id, { feeder_id } ); @@ -1018,7 +1018,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) // check BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); @@ -1044,7 +1044,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) == bsrm_type::individual_settlement_to_order ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); - BOOST_CHECK( db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); // Fill the individual settlement order ilog( "Buy into the individual settlement order" ); @@ -1054,7 +1054,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) // recheck BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); // Sam tries to update BSRM for( uint8_t i = 0; i <= 3; ++i ) @@ -1086,7 +1086,7 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() == static_cast(2) ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); - BOOST_CHECK( !db.find_individual_settlemnt_order(mpa_id) ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); } catch (fc::exception& e) { edump((e.to_detail_string())); From fa7e7d7f7dafb9d13a15c9c5f40c521411be8590 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 5 Sep 2021 17:11:41 +0000 Subject: [PATCH 172/258] Add tests about updating BSRM from no_settlement to other types when exists an undercollateralized debt position --- tests/tests/bsrm_basic_tests.cpp | 148 +++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 4b0a24b1e6..cb062a196a 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -1094,4 +1094,152 @@ BOOST_AUTO_TEST_CASE( update_bsrm_after_individual_settlement_to_order ) } } +/// Tests scenarios: +/// updating BSRM from no_settlement to others when the least collateralized short is actually undercollateralized +BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Several passes, update BSRM from no_settlement to different values + for( uint8_t i = 0; i <= 3; ++i ) + { + if( i == bsrm_value ) + continue; + idump( (i) ); + + ACTORS((sam)(feeder)(borrower)(borrower2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::no_settlement ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed so that the least collateralized short is undercollateralized" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + // Sam tries to update BSRM + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.black_swan_response_method = i; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + // check + const auto& check_result = [&] + { + switch( static_cast(i) ) + { + case bsrm_type::global_settlement: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + break; + case bsrm_type::individual_settlement_to_fund: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + break; + case bsrm_type::individual_settlement_to_order: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + break; + default: + BOOST_FAIL( "This should not happen" ); + break; + } + }; + + check_result(); + + ilog( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 7b82a6c08b11f06d5282b59ec07bcf03f7cc108a Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 6 Sep 2021 17:57:09 +0000 Subject: [PATCH 173/258] Simplify code --- libraries/chain/db_market.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 12063c75b8..8fdaa5bb4a 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -686,7 +686,7 @@ bool database::apply_order(const limit_order_object& new_order_object) // We only need to check if the new order will match with others if it is at the front of the book const auto& limit_price_idx = get_index_type().indices().get(); - auto limit_itr = limit_price_idx.lower_bound( boost::make_tuple( new_order_object.sell_price, order_id ) ); + auto limit_itr = limit_price_idx.iterator_to( new_order_object ); if( limit_itr != limit_price_idx.begin() ) { --limit_itr; @@ -766,13 +766,14 @@ bool database::apply_order(const limit_order_object& new_order_object) { auto call_min = price::min( recv_asset_id, sell_asset_id ); // check limit orders first, match the ones with better price in comparison to call orders - while( !finished && limit_itr != limit_end && limit_itr->sell_price > call_match_price ) + auto limit_itr_after_call = limit_price_idx.lower_bound( call_match_price ); + while( !finished && limit_itr != limit_itr_after_call ) { - auto old_limit_itr = limit_itr; + const limit_order_object& matching_limit_order = *limit_itr; ++limit_itr; // match returns 2 when only the old order was fully filled. // In this case, we keep matching; otherwise, we stop. - finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) + finished = ( match( new_order_object, matching_limit_order, matching_limit_order.sell_price ) != match_result_type::only_maker_filled ); } @@ -851,10 +852,10 @@ bool database::apply_order(const limit_order_object& new_order_object) // still need to check limit orders while( !finished && limit_itr != limit_end ) { - auto old_limit_itr = limit_itr; + const limit_order_object& matching_limit_order = *limit_itr; ++limit_itr; // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. - finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) + finished = ( match( new_order_object, matching_limit_order, matching_limit_order.sell_price ) != match_result_type::only_maker_filled ); } @@ -1966,12 +1967,9 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( usd_to_buy == usd_for_sale ) filled_limit = true; - else if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) - { + else if( filled_limit && before_hardfork_615 ) //NOTE: Multiple limit match problem (see issue 453, yes this happened) - if( before_hardfork_615 ) - _issue_453_affected_assets.insert( bitasset.asset_id ); - } + _issue_453_affected_assets.insert( bitasset.asset_id ); } limit_pays = call_receives; From e1f5547380ec3433110fc0edfe83ec5a2ea51473 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 6 Sep 2021 20:43:41 +0000 Subject: [PATCH 174/258] Refactor call_order_update_evaluator by moving some assertions from do_apply() to do_evaluator() --- .../graphene/chain/market_evaluator.hpp | 16 ++-- libraries/chain/market_evaluator.cpp | 86 +++++++++++-------- 2 files changed, 60 insertions(+), 42 deletions(-) diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 80a0c25eab..5dec716aea 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -37,7 +37,7 @@ namespace graphene { namespace chain { class limit_order_create_evaluator : public evaluator { public: - typedef limit_order_create_operation operation_type; + using operation_type = limit_order_create_operation; void_result do_evaluate( const limit_order_create_operation& o ); object_id_type do_apply( const limit_order_create_operation& o ); @@ -51,6 +51,7 @@ namespace graphene { namespace chain { */ virtual void pay_fee() override; + private: share_type _deferred_fee = 0; asset _deferred_paid_fee; const limit_order_create_operation* _op = nullptr; @@ -62,25 +63,29 @@ namespace graphene { namespace chain { class limit_order_cancel_evaluator : public evaluator { public: - typedef limit_order_cancel_operation operation_type; + using operation_type = limit_order_cancel_operation; void_result do_evaluate( const limit_order_cancel_operation& o ); asset do_apply( const limit_order_cancel_operation& o ); + private: const limit_order_object* _order; }; class call_order_update_evaluator : public evaluator { public: - typedef call_order_update_operation operation_type; + using operation_type = call_order_update_operation; void_result do_evaluate( const call_order_update_operation& o ); object_id_type do_apply( const call_order_update_operation& o ); + private: bool _closing_order = false; const asset_object* _debt_asset = nullptr; - const call_order_object* _order = nullptr; + const call_order_object* call_ptr = nullptr; + share_type new_debt; + share_type new_collateral; const asset_bitasset_data_object* _bitasset_data = nullptr; const asset_dynamic_data_object* _dynamic_data_obj = nullptr; }; @@ -88,11 +93,12 @@ namespace graphene { namespace chain { class bid_collateral_evaluator : public evaluator { public: - typedef bid_collateral_operation operation_type; + using operation_type = bid_collateral_operation; void_result do_evaluate( const bid_collateral_operation& o ); void_result do_apply( const bid_collateral_operation& o ); + private: const asset_object* _debt_asset = nullptr; const asset_bitasset_data_object* _bitasset_data = nullptr; const collateral_bid_object* _bid = nullptr; diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index e534af7f7a..c8ebb33115 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -180,7 +180,7 @@ asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& void_result call_order_update_evaluator::do_evaluate(const call_order_update_operation& o) { try { - database& d = db(); + const database& d = db(); auto next_maintenance_time = d.get_dynamic_global_properties().next_maintenance_time; @@ -214,6 +214,30 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset, "Collateral asset type should be same as backing asset of debt asset" ); + auto& call_idx = d.get_index_type().indices().get(); + auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) ); + if( itr != call_idx.end() ) // updating or closing debt position + { + call_ptr = &(*itr); + new_collateral = call_ptr->collateral + o.delta_collateral.amount; + new_debt = call_ptr->debt + o.delta_debt.amount; + if( new_debt == 0 ) + { + FC_ASSERT( new_collateral == 0, "Should claim all collateral when closing debt position" ); + _closing_order = true; + } + else + { + FC_ASSERT( new_collateral > 0 && new_debt > 0, + "Both collateral and debt should be positive after updated a debt position if not to close it" ); + } + } + else // creating new debt position + { + FC_ASSERT( o.delta_collateral.amount > 0, "Delta collateral amount of new debt position should be positive" ); + FC_ASSERT( o.delta_debt.amount > 0, "Delta debt amount of new debt position should be positive" ); + } + if( _bitasset_data->is_prediction_market ) FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount, "Debt amount and collateral amount should be same when updating debt position in a prediction market" ); @@ -245,7 +269,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope d.adjust_balance( o.funding_account, o.delta_debt ); // Deduct the debt paid from the total supply of the debt asset. - d.modify(*_dynamic_data_obj, [&](asset_dynamic_data_object& dynamic_asset) { + d.modify(*_dynamic_data_obj, [&o](asset_dynamic_data_object& dynamic_asset) { dynamic_asset.current_supply += o.delta_debt.amount; }); } @@ -266,20 +290,14 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_1270 = ( next_maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue - auto& call_idx = d.get_index_type().indices().get(); - auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) ); - const call_order_object* call_obj = nullptr; call_order_id_type call_order_id; optional old_collateralization; optional old_debt; - if( itr == call_idx.end() ) // creating new debt position + if( !call_ptr ) // creating new debt position { - FC_ASSERT( o.delta_collateral.amount > 0, "Delta collateral amount of new debt position should be positive" ); - FC_ASSERT( o.delta_debt.amount > 0, "Delta debt amount of new debt position should be positive" ); - - call_obj = &d.create( [&o,this,before_core_hardfork_1270]( call_order_object& call ){ + call_ptr = &d.create( [&o,this,before_core_hardfork_1270]( call_order_object& call ){ call.borrower = o.funding_account; call.collateral = o.delta_collateral.amount; call.debt = o.delta_debt.amount; @@ -290,19 +308,15 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope call.call_price = price( asset( 1, o.delta_collateral.asset_id ), asset( 1, o.delta_debt.asset_id ) ); call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; }); - call_order_id = call_obj->id; + call_order_id = call_ptr->id; } else // updating existing debt position { - call_obj = &*itr; - auto new_collateral = call_obj->collateral + o.delta_collateral.amount; - auto new_debt = call_obj->debt + o.delta_debt.amount; - call_order_id = call_obj->id; + call_order_id = call_ptr->id; - if( new_debt == 0 ) + if( _closing_order ) { - FC_ASSERT( new_collateral == 0, "Should claim all collateral when closing debt position" ); - d.remove( *call_obj ); + d.remove( *call_ptr ); // Update current_feed if needed const auto bsrm = _bitasset_data->get_black_swan_response_method(); @@ -312,13 +326,10 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope return call_order_id; } - FC_ASSERT( new_collateral > 0 && new_debt > 0, - "Both collateral and debt should be positive after updated a debt position if not to close it" ); - - old_collateralization = call_obj->collateralization(); - old_debt = call_obj->debt; + old_collateralization = call_ptr->collateralization(); + old_debt = call_ptr->debt; - d.modify( *call_obj, [&o,new_debt,new_collateral,this,before_core_hardfork_1270]( call_order_object& call ){ + d.modify( *call_ptr, [&o,this,before_core_hardfork_1270]( call_order_object& call ){ call.collateral = new_collateral; call.debt = new_debt; if( before_core_hardfork_1270 ) // don't update call_price after core-1270 hard fork @@ -339,7 +350,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // force settlement order. if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) { - FC_ASSERT( call_obj->collateralization() >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), + // Note: this will throw even when increasing CR + FC_ASSERT( call_ptr->collateralization() >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), "Could not create a debt position which would trigger a blackswan event instantly" ); } // check to see if the order needs to be margin called now, but don't allow black swans and require there to be @@ -349,23 +361,23 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope if( d.check_call_orders( *_debt_asset, false, false, _bitasset_data ) ) // don't allow black swan, // not for new limit order { - call_obj = d.find(call_order_id); + call_ptr = d.find(call_order_id); // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. // after hard fork core-583: we want to allow increasing collateral // Note: increasing collateral won't get the call order itself matched (instantly margin called) // if there is at least a call order get matched but didn't cause a black swan event, // current order must have got matched. in this case, it's OK if it's totally filled. GRAPHENE_ASSERT( - !call_obj, + !call_ptr, call_order_update_unfilled_margin_call, "Updating call order would trigger a margin call that cannot be fully filled" ); } else { - call_obj = d.find(call_order_id); + call_ptr = d.find(call_order_id); // we know no black swan event has occurred - FC_ASSERT( call_obj, "no margin call was executed and yet the call object was deleted" ); + FC_ASSERT( call_ptr, "no margin call was executed and yet the call object was deleted" ); // this HF must remain as-is, as the assert inside the "if" was triggered during push_proposal() if( d.head_block_time() <= HARDFORK_CORE_583_TIME ) { @@ -374,11 +386,11 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // were no matching orders. In the latter case, we throw. GRAPHENE_ASSERT( // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here - ~call_obj->call_price < _bitasset_data->current_feed.settlement_price, + ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price, call_order_update_unfilled_margin_call, "Updating call order would trigger a margin call that cannot be fully filled", // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here - ("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price) + ("a", ~call_ptr->call_price )("b", _bitasset_data->current_feed.settlement_price) ); } else // after hard fork core-583, always allow call order to be updated if collateral ratio @@ -396,19 +408,19 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // The `current_initial_collateralization` variable has been initialized according to the logic, // so we directly use it here. bool check = ( !before_core_hardfork_1270 - && call_obj->collateralization() > _bitasset_data->current_initial_collateralization ) + && call_ptr->collateralization() > _bitasset_data->current_initial_collateralization ) || ( before_core_hardfork_1270 - && ~call_obj->call_price < _bitasset_data->current_feed.settlement_price ) - || ( old_collateralization.valid() && call_obj->debt <= *old_debt - && call_obj->collateralization() > *old_collateralization ); + && ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) + || ( old_collateralization.valid() && call_ptr->debt <= *old_debt + && call_ptr->collateralization() > *old_collateralization ); FC_ASSERT( check, "Can only increase collateral ratio without increasing debt when the debt position's " "collateral ratio is lower than required initial collateral ratio (ICR), " "if not to trigger a margin call that be fully filled immediately", ("old_debt", old_debt) - ("new_debt", call_obj->debt) + ("new_debt", call_ptr->debt) ("old_collateralization", old_collateralization) - ("new_collateralization", call_obj->collateralization() ) + ("new_collateralization", call_ptr->collateralization() ) ); } } From fb90153f068fdc071db1780bc65ec6001f3dfa88 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 6 Sep 2021 21:00:45 +0000 Subject: [PATCH 175/258] Allow closing debt position even if no price feed --- libraries/chain/market_evaluator.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index c8ebb33115..1b65fe5def 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -165,11 +165,12 @@ asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& auto quote_asset = _order->sell_price.quote.asset_id; auto refunded = _order->amount_for_sale(); - d.cancel_limit_order(*_order, false /* don't create a virtual op*/); + d.cancel_limit_order( *_order, false ); // don't create a virtual op if( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_606_TIME ) { - // Possible optimization: order can be called by canceling a limit order iff the canceled order was at the top of the book. + // Possible optimization: + // order can be called by canceling a limit order if the canceled order was at the top of the book. // Do I need to check calls in both assets? d.check_call_orders(base_asset(d)); d.check_call_orders(quote_asset(d)); @@ -209,7 +210,8 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope /// if there is a settlement for this asset, then no further margin positions may be taken and /// all existing margin positions should have been closed va database::globally_settle_asset - FC_ASSERT( !_bitasset_data->has_settlement(), "Cannot update debt position when the asset has been globally settled" ); + FC_ASSERT( !_bitasset_data->has_settlement(), + "Cannot update debt position when the asset has been globally settled" ); FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset, "Collateral asset type should be same as backing asset of debt asset" ); @@ -240,8 +242,10 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope if( _bitasset_data->is_prediction_market ) FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount, - "Debt amount and collateral amount should be same when updating debt position in a prediction market" ); - else if( _bitasset_data->current_feed.settlement_price.is_null() ) + "Debt amount and collateral amount should be same when updating debt position in a prediction " + "market" ); + else if( _bitasset_data->current_feed.settlement_price.is_null() + && !( HARDFORK_CORE_2467_PASSED( next_maintenance_time ) && _closing_order ) ) FC_THROW_EXCEPTION(insufficient_feeds, "Cannot borrow asset with no price feed."); // Since hard fork core-973, check asset authorization limitations From ea5ffb88fc1c916e7a40dd62c5106c93859d229f Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 6 Sep 2021 21:35:19 +0000 Subject: [PATCH 176/258] Add tests about closing debt position when no feed --- tests/tests/bsrm_basic_tests.cpp | 76 ++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index cb062a196a..0eed9937a3 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -668,6 +668,82 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) } } +/// Tests closing debt position when there is no sufficient price feeds +BOOST_AUTO_TEST_CASE( close_debt_position_when_no_feed ) +{ + try { + + // Advance to a time before core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + + // Create asset + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // update price feed publisher list so that there is no valid feed + update_feed_producers( mpa_id, { sam_id } ); + + // no sufficient price feeds + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + // Unable to close debt position + BOOST_CHECK_THROW( cover( borrower, asset(100000, mpa_id), asset(2000) ), fc::exception ); + BOOST_CHECK( db.find( call_id ) ); + + // Go beyond the hard fork time + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + // Still no sufficient price feeds + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + // The debt position is there + BOOST_CHECK( db.find( call_id ) ); + + // Able to close debt position + cover( borrower_id(db), asset(100000, mpa_id), asset(2000) ); + BOOST_CHECK( !db.find( call_id ) ); + + ilog( "Generate a block" ); + generate_block(); + + // final check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + BOOST_CHECK( !db.find( call_id ) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /// Tests whether it is able to update BSRM after GS BOOST_AUTO_TEST_CASE( update_bsrm_after_gs ) { From 0a843b5250a7360de4dde21950d3104cb7967733 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 00:25:01 +0000 Subject: [PATCH 177/258] Remove Ubuntu 16 from Github Actions workflows --- .github/workflows/build-and-test.ubuntu-debug.yml | 2 +- .github/workflows/build-and-test.ubuntu-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.ubuntu-debug.yml b/.github/workflows/build-and-test.ubuntu-debug.yml index 6689f61c4a..960902bf3b 100644 --- a/.github/workflows/build-and-test.ubuntu-debug.yml +++ b/.github/workflows/build-and-test.ubuntu-debug.yml @@ -8,7 +8,7 @@ jobs: name: Build and test in Debug mode strategy: matrix: - os: [ ubuntu-16.04, ubuntu-18.04, ubuntu-20.04 ] + os: [ ubuntu-18.04, ubuntu-20.04 ] runs-on: ${{ matrix.os }} services: elasticsearch: diff --git a/.github/workflows/build-and-test.ubuntu-release.yml b/.github/workflows/build-and-test.ubuntu-release.yml index 8f310d8d0a..69e25a21ae 100644 --- a/.github/workflows/build-and-test.ubuntu-release.yml +++ b/.github/workflows/build-and-test.ubuntu-release.yml @@ -8,7 +8,7 @@ jobs: name: Build and test in Release mode strategy: matrix: - os: [ ubuntu-16.04, ubuntu-18.04, ubuntu-20.04 ] + os: [ ubuntu-18.04, ubuntu-20.04 ] runs-on: ${{ matrix.os }} services: elasticsearch: From 29e36f043afd9953982e5bf79805fc4367dd1f3d Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 00:27:00 +0000 Subject: [PATCH 178/258] Update cache keys in Github Actions MinGW workflow --- .github/workflows/build-and-test.win.yml | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-and-test.win.yml b/.github/workflows/build-and-test.win.yml index e1fad2984e..eeb942b79e 100644 --- a/.github/workflows/build-and-test.win.yml +++ b/.github/workflows/build-and-test.win.yml @@ -14,12 +14,15 @@ jobs: name: Build required 3rd-party libraries runs-on: ubuntu-latest steps: + # Get OS version to be used in cache key - see https://github.com/actions/cache/issues/543 + - run: | + echo "OS_VERSION=`lsb_release -sr`" >> $GITHUB_ENV - name: Load Cache id: cache-libs - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: libs - key: mingw64-libs-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }} + key: mingw64-libs-${{ env.OS_VERSION }}-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }} - name: Install dependencies if: steps.cache-libs.outputs.cache-hit != 'true' run: | @@ -113,11 +116,13 @@ jobs: - uses: actions/checkout@v2 with: submodules: recursive + - run: | + echo "OS_VERSION=`lsb_release -sr`" >> $GITHUB_ENV - name: Load external libraries - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: libs - key: mingw64-libs-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }} + key: mingw64-libs-${{ env.OS_VERSION }}-${{ env.BOOST_VERSION }}_${{ env.CURL_VERSION }}_${{ env.OPENSSL_VERSION }}_${{ env.ZLIB_VERSION }} - name: Configure run: | LIBS="`pwd`/libs" @@ -138,13 +143,13 @@ jobs: -D GRAPHENE_DISABLE_UNITY_BUILD=ON \ .. - name: Load Cache - uses: actions/cache@v1 + uses: actions/cache@v2 with: path: ccache - key: ccache-mingw64-${{ github.ref }}-${{ github.sha }} + key: ccache-mingw64-${{ env.OS_VERSION }}-${{ github.ref }}-${{ github.sha }} restore-keys: | - ccache-mingw64-${{ github.ref }}- - ccache-mingw64- + ccache-mingw64-${{ env.OS_VERSION }}-${{ github.ref }}- + ccache-mingw64-${{ env.OS_VERSION }}- - name: Build run: | export CCACHE_DIR="$GITHUB_WORKSPACE/ccache" From 30c64415dafa03f6cf32cbcecb31c37808f1e53b Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 14:04:57 +0000 Subject: [PATCH 179/258] Fix call_order_update issues about no_settlement * check call orders after updated/closed a debt position * always allow increasing CR if not to increasing debt * allow partial fill if triggers margin call and new CR > ICR --- libraries/chain/db_market.cpp | 2 +- libraries/chain/market_evaluator.cpp | 84 +++++++++++++++++++--------- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 8fdaa5bb4a..2cf41d4911 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -2033,7 +2033,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa // TODO perhaps improve performance by settling multiple call orders inside in one call while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) { - // do nothing + margin_called = true; } } else diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 1b65fe5def..e657106ffc 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -325,7 +325,15 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // Update current_feed if needed const auto bsrm = _bitasset_data->get_black_swan_response_method(); if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) + { + auto old_feed_price = _bitasset_data->current_feed.settlement_price; d.update_bitasset_current_feed( *_bitasset_data, true ); + if( !_bitasset_data->current_feed.settlement_price.is_null() + && _bitasset_data->current_feed.settlement_price != old_feed_price ) + { + d.check_call_orders( *_debt_asset, true, false, _bitasset_data ); + } + } return call_order_id; } @@ -352,34 +360,65 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // * if there is no force settlement order, it would trigger a blackswan event instantly, // * if there is a force settlement order, they will match at the call order's CR, but it is not fair for the // force settlement order. + auto call_collateralization = call_ptr->collateralization(); + bool increasing_cr = ( old_collateralization.valid() && call_ptr->debt <= *old_debt + && call_collateralization > *old_collateralization ); if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) { - // Note: this will throw even when increasing CR - FC_ASSERT( call_ptr->collateralization() >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), - "Could not create a debt position which would trigger a blackswan event instantly" ); + // Note: if it is to increase CR and is not increasing debt amount, it is allowed, + // because it implies BSRM == no_settlement + FC_ASSERT( increasing_cr + || call_collateralization >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), + "Could not create a debt position which would trigger a blackswan event instantly, " + "unless it is to increase collateral ratio of an existing debt position and " + "is not increasing its debt amount" ); } + // Update current_feed if needed + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) + d.update_bitasset_current_feed( *_bitasset_data, true ); + // check to see if the order needs to be margin called now, but don't allow black swans and require there to be // limit orders available that could be used to fill the order. // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork, // the first call order may be unable to be updated if the second one is undercollateralized. - if( d.check_call_orders( *_debt_asset, false, false, _bitasset_data ) ) // don't allow black swan, - // not for new limit order + // Note: check call orders, don't allow black swan, not for new limit order + bool called_some = d.check_call_orders( *_debt_asset, false, false, _bitasset_data ); + call_ptr = d.find(call_order_id); + if( called_some ) { - call_ptr = d.find(call_order_id); // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. // after hard fork core-583: we want to allow increasing collateral // Note: increasing collateral won't get the call order itself matched (instantly margin called) // if there is at least a call order get matched but didn't cause a black swan event, // current order must have got matched. in this case, it's OK if it's totally filled. - GRAPHENE_ASSERT( - !call_ptr, - call_order_update_unfilled_margin_call, - "Updating call order would trigger a margin call that cannot be fully filled" - ); + // after hard fork core-2467: when BSRM is no_settlement, it is possible that other call orders are matched + // in check_call_orders, also possible that increasing CR will get the call order itself matched + if( !HARDFORK_CORE_2467_PASSED( next_maint_time ) ) // before core-2467 hf + { + GRAPHENE_ASSERT( !call_ptr, call_order_update_unfilled_margin_call, + "Updating call order would trigger a margin call that cannot be fully filled" ); + } + // after core-2467 hf + else + { + // if the call order is totally filled, it is OK, + // if it is increasing CR, it is always ok, no matter if it or another another call order is called, + // otherwise, the remaining call order's CR need to be > ICR + // TODO: perhaps it makes sense to allow more cases, e.g. + // - when a position has ICR > CR > MCR, allow the owner to sell some collateral to increase CR + // - allow owners to sell collateral at price < MSSP (need to update code elsewhere) + FC_ASSERT( !call_ptr || increasing_cr + || call_ptr->collateralization() > _bitasset_data->current_initial_collateralization, + "Could not create a debt position which would trigger a margin call instantly, " + "unless the debt position is fully filled, or it is to increase collateral ratio of " + "an existing debt position and is not increasing its debt amount, " + "or the remaining debt position's collateral ratio is above required " + "initial collateral ratio (ICR)" ); + } } else { - call_ptr = d.find(call_order_id); // we know no black swan event has occurred FC_ASSERT( call_ptr, "no margin call was executed and yet the call object was deleted" ); // this HF must remain as-is, as the assert inside the "if" was triggered during push_proposal() @@ -411,27 +450,22 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // after BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). // The `current_initial_collateralization` variable has been initialized according to the logic, // so we directly use it here. - bool check = ( !before_core_hardfork_1270 - && call_ptr->collateralization() > _bitasset_data->current_initial_collateralization ) - || ( before_core_hardfork_1270 - && ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) - || ( old_collateralization.valid() && call_ptr->debt <= *old_debt - && call_ptr->collateralization() > *old_collateralization ); + bool check = ( increasing_cr + || ( !before_core_hardfork_1270 + && call_collateralization > _bitasset_data->current_initial_collateralization ) + || ( before_core_hardfork_1270 + && ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) ); FC_ASSERT( check, "Can only increase collateral ratio without increasing debt when the debt position's " - "collateral ratio is lower than required initial collateral ratio (ICR), " - "if not to trigger a margin call that be fully filled immediately", + "collateral ratio is lower than or equal to required initial collateral ratio (ICR), " + "if not to trigger a margin call immediately", ("old_debt", old_debt) ("new_debt", call_ptr->debt) ("old_collateralization", old_collateralization) - ("new_collateralization", call_ptr->collateralization() ) + ("new_collateralization", call_collateralization) ); } } - // Update current_feed if needed - const auto bsrm = _bitasset_data->get_black_swan_response_method(); - if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) - d.update_bitasset_current_feed( *_bitasset_data, true ); } return call_order_id; From 9866bb0eae6341b723d644723b8adddda7e1642f Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 14:35:46 +0000 Subject: [PATCH 180/258] Fix no_settlement tests wrt call_order_update --- tests/tests/bsrm_no_settlement_tests.cpp | 32 +++++++++--------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index 39f7e08dc6..f864522da2 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -124,12 +124,10 @@ BOOST_AUTO_TEST_CASE( no_settlement_maker_margin_call_test ) BOOST_REQUIRE( call3_ptr ); call_order_id_type call3_id = call3_ptr->id; - // borrower is unable to adjust debt position if it's still undercollateralized - // 1000 * (2000/1250) * 1.25 = 2000 - // 1000 * (22/10) * 1.25 = 2750 - BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); // borrower adjust debt position to right at MSSR - borrow( borrower, asset(0, mpa_id), asset(750) ); + // 1000 * (22/10) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CRid; - // borrower is unable to adjust debt position if it's still undercollateralized - // 100000 * (2000/125000) * 1.25 = 2000 - // 100000 * (22/1000) * 1.25 = 2750 - BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); // borrower adjust debt position to right at MSSR - borrow( borrower, asset(0, mpa_id), asset(750) ); + // 100000 * (22/1000) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CRid; - // borrower is unable to adjust debt position if it's still undercollateralized - // 1000 * (2000/1250) * 1.25 = 2000 - // 1000 * (22/10) * 1.25 = 2750 - BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); // borrower adjust debt position to right at MSSR - borrow( borrower, asset(0, mpa_id), asset(750) ); + // 1000 * (22/10) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CRid; - // borrower is unable to adjust debt position if it's still undercollateralized - // 100000 * (2000/125000) * 1.25 = 2000 - // 100000 * (22/1000) * 1.25 = 2750 - BOOST_CHECK_THROW( borrow( borrower, asset(0, mpa_id), asset(749) ), fc::exception ); // borrower adjust debt position to right at MSSR - borrow( borrower, asset(0, mpa_id), asset(750) ); + // 100000 * (22/1000) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CR Date: Tue, 7 Sep 2021 15:38:41 +0000 Subject: [PATCH 181/258] Fix tests about call_order_update partially fill --- tests/tests/force_settle_match_tests.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index db51ef4588..8cda2a54ae 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -640,11 +640,11 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // buy_low's price is too low that won't be matched BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); - // Can not reduce CR of a call order to trigger a margin call but not get fully filled - BOOST_CHECK_THROW( borrow( borrower_id(db), asset(10, usd_id), asset(0), 1700), fc::exception ); + // Can not reduce CR of a call order to trigger a margin call but not get fully filled and final CR <= ICR + BOOST_CHECK_THROW( borrow( borrower_id(db), asset(10000, usd_id), asset(160000), 1700), fc::exception ); - // Can not create a new call order that is partially called instantly - BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10, usd_id), asset(160), 1700), fc::exception ); + // Can not create a new call order that is partially called instantly if final CR <= ICR + BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10000, usd_id), asset(160000), 1700), fc::exception ); idump( (settle_id(db))(get_balance(seller, core)) ); @@ -661,6 +661,12 @@ BOOST_AUTO_TEST_CASE(tcr_test_hf2481_call_settle) // Can not create a new call order that would trigger a black swan event BOOST_CHECK_THROW( borrow( borrower4_id(db), asset(10000, usd_id), asset(10000) ), fc::exception ); + // Able to reduce CR of a call order to trigger a margin call if final CR is above ICR + borrow( borrower_id(db), asset(10, usd_id), asset(0), 1700 ); + + // Able to create a new call order that is partially called instantly if final CR is above ICR + borrow( borrower4_id(db), asset(10, usd_id), asset(160), 1700 ); + // generate a block generate_block(); From 186a4b6044cef955091c877cce0eee9862728f92 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 15:43:22 +0000 Subject: [PATCH 182/258] Fix code smells --- .../graphene/chain/market_evaluator.hpp | 6 +-- libraries/chain/market_evaluator.cpp | 48 +++++++++---------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 5dec716aea..8df92ff464 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -40,7 +40,7 @@ namespace graphene { namespace chain { using operation_type = limit_order_create_operation; void_result do_evaluate( const limit_order_create_operation& o ); - object_id_type do_apply( const limit_order_create_operation& o ); + object_id_type do_apply( const limit_order_create_operation& o ) const; /** override the default behavior defined by generic_evalautor */ @@ -66,7 +66,7 @@ namespace graphene { namespace chain { using operation_type = limit_order_cancel_operation; void_result do_evaluate( const limit_order_cancel_operation& o ); - asset do_apply( const limit_order_cancel_operation& o ); + asset do_apply( const limit_order_cancel_operation& o ) const; private: const limit_order_object* _order; @@ -96,7 +96,7 @@ namespace graphene { namespace chain { using operation_type = bid_collateral_operation; void_result do_evaluate( const bid_collateral_operation& o ); - void_result do_apply( const bid_collateral_operation& o ); + void_result do_apply( const bid_collateral_operation& o ) const; private: const asset_object* _debt_asset = nullptr; diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index e657106ffc..a90754f8c9 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -80,16 +80,12 @@ void limit_order_create_evaluator::convert_fee() { if( db().head_block_time() <= HARDFORK_CORE_604_TIME ) generic_evaluator::convert_fee(); - else - if( !trx_state->skip_fee ) - { - if( fee_asset->get_id() != asset_id_type() ) - { - db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { - d.fee_pool -= core_fee_paid; - }); - } - } + else if( !trx_state->skip_fee && fee_asset->get_id() != asset_id_type() ) + { + db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { + d.fee_pool -= core_fee_paid; + }); + } } void limit_order_create_evaluator::pay_fee() @@ -104,7 +100,7 @@ void limit_order_create_evaluator::pay_fee() } } -object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op) +object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op) const { try { if( op.amount_to_sell.asset_id == asset_id_type() ) { @@ -115,7 +111,7 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o db().adjust_balance(op.seller, -op.amount_to_sell); - const auto& new_order_object = db().create([&](limit_order_object& obj){ + const auto& new_order_object = db().create([this,&op](limit_order_object& obj){ obj.seller = _seller->id; obj.for_sale = op.amount_to_sell.amount; obj.sell_price = op.get_price(); @@ -140,7 +136,7 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o) { try { - database& d = db(); + const database& d = db(); _order = d.find( o.order ); @@ -157,7 +153,7 @@ void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_o return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o) +asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o) const { try { database& d = db(); @@ -202,7 +198,7 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount <= _debt_asset->options.max_supply, "Borrowing this quantity would exceed MAX_SUPPLY" ); } - + FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount >= 0, "This transaction would bring current supply below zero."); @@ -446,16 +442,16 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // be here, we know no margin call was executed, // so call_obj's collateral ratio should be set only by op // ------ - // Before BSIP77, CR of the new/updated position is required to be above MCR; - // after BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). + // Before BSIP77, CR of the new/updated position is required to be above MCR. + // After BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). // The `current_initial_collateralization` variable has been initialized according to the logic, // so we directly use it here. - bool check = ( increasing_cr - || ( !before_core_hardfork_1270 - && call_collateralization > _bitasset_data->current_initial_collateralization ) - || ( before_core_hardfork_1270 - && ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) ); - FC_ASSERT( check, + bool ok = increasing_cr; + if( !ok ) + ok = before_core_hardfork_1270 ? + ( ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) + : ( call_collateralization > _bitasset_data->current_initial_collateralization ); + FC_ASSERT( ok, "Can only increase collateral ratio without increasing debt when the debt position's " "collateral ratio is lower than or equal to required initial collateral ratio (ICR), " "if not to trigger a margin call immediately", @@ -473,7 +469,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation& o) { try { - database& d = db(); + const database& d = db(); FC_ASSERT( d.head_block_time() > HARDFORK_CORE_216_TIME, "Not yet!" ); @@ -527,7 +523,7 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o) +void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o) const { try { database& d = db(); @@ -538,7 +534,7 @@ void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o d.adjust_balance( o.bidder, -o.additional_collateral ); - _bid = &d.create([&]( collateral_bid_object& bid ) { + d.create([&o]( collateral_bid_object& bid ) { bid.bidder = o.bidder; bid.inv_swan_price = o.additional_collateral / o.debt_covered; }); From e1a6ac53d8add2d0f2aaa8de60143783cf53b778 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 7 Sep 2021 17:15:56 +0000 Subject: [PATCH 183/258] Refactor call_order_update_evaluator::do_apply() to reduce the number of levels of nested if, for, or while statements --- libraries/chain/market_evaluator.cpp | 260 +++++++++++++-------------- 1 file changed, 130 insertions(+), 130 deletions(-) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index a90754f8c9..4714e5a99d 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -287,11 +287,31 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } } + if( _closing_order ) // closing the debt position + { + auto call_order_id = call_ptr->id; + + d.remove( *call_ptr ); + + // Update current_feed if needed + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) + { + auto old_feed_price = _bitasset_data->current_feed.settlement_price; + d.update_bitasset_current_feed( *_bitasset_data, true ); + if( !_bitasset_data->current_feed.settlement_price.is_null() + && _bitasset_data->current_feed.settlement_price != old_feed_price ) + { + d.check_call_orders( *_debt_asset, true, false, _bitasset_data ); + } + } + + return call_order_id; + } + const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_1270 = ( next_maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue - call_order_id_type call_order_id; - optional old_collateralization; optional old_debt; @@ -308,32 +328,9 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope call.call_price = price( asset( 1, o.delta_collateral.asset_id ), asset( 1, o.delta_debt.asset_id ) ); call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; }); - call_order_id = call_ptr->id; } else // updating existing debt position { - call_order_id = call_ptr->id; - - if( _closing_order ) - { - d.remove( *call_ptr ); - - // Update current_feed if needed - const auto bsrm = _bitasset_data->get_black_swan_response_method(); - if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) - { - auto old_feed_price = _bitasset_data->current_feed.settlement_price; - d.update_bitasset_current_feed( *_bitasset_data, true ); - if( !_bitasset_data->current_feed.settlement_price.is_null() - && _bitasset_data->current_feed.settlement_price != old_feed_price ) - { - d.check_call_orders( *_debt_asset, true, false, _bitasset_data ); - } - } - - return call_order_id; - } - old_collateralization = call_ptr->collateralization(); old_debt = call_ptr->debt; @@ -349,118 +346,121 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope }); } + call_order_id_type call_order_id = call_ptr->id; + + if( _bitasset_data->is_prediction_market ) + return call_order_id; + // then we must check for margin calls and other issues - if( !_bitasset_data->is_prediction_market ) + + // After hf core-2481, we do not allow new position's CR to be <= ~max_short_squeeze_price, because + // * if there is no force settlement order, it would trigger a blackswan event instantly, + // * if there is a force settlement order, they will match at the call order's CR, but it is not fair for the + // force settlement order. + auto call_collateralization = call_ptr->collateralization(); + bool increasing_cr = ( old_collateralization.valid() && call_ptr->debt <= *old_debt + && call_collateralization > *old_collateralization ); + if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) + { + // Note: if it is to increase CR and is not increasing debt amount, it is allowed, + // because it implies BSRM == no_settlement + FC_ASSERT( increasing_cr + || call_collateralization >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), + "Could not create a debt position which would trigger a blackswan event instantly, " + "unless it is to increase collateral ratio of an existing debt position and " + "is not increasing its debt amount" ); + } + // Update current_feed if needed + const auto bsrm = _bitasset_data->get_black_swan_response_method(); + if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) + d.update_bitasset_current_feed( *_bitasset_data, true ); + + // check to see if the order needs to be margin called now, but don't allow black swans and require there to be + // limit orders available that could be used to fill the order. + // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork, + // the first call order may be unable to be updated if the second one is undercollateralized. + // Note: check call orders, don't allow black swan, not for new limit order + bool called_some = d.check_call_orders( *_debt_asset, false, false, _bitasset_data ); + call_ptr = d.find(call_order_id); + if( called_some ) { - // After hf core-2481, we do not allow new position's CR to be <= ~max_short_squeeze_price, because - // * if there is no force settlement order, it would trigger a blackswan event instantly, - // * if there is a force settlement order, they will match at the call order's CR, but it is not fair for the - // force settlement order. - auto call_collateralization = call_ptr->collateralization(); - bool increasing_cr = ( old_collateralization.valid() && call_ptr->debt <= *old_debt - && call_collateralization > *old_collateralization ); - if( HARDFORK_CORE_2481_PASSED( next_maint_time ) ) + // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. + // after hard fork core-583: we want to allow increasing collateral + // Note: increasing collateral won't get the call order itself matched (instantly margin called) + // if there is at least a call order get matched but didn't cause a black swan event, + // current order must have got matched. in this case, it's OK if it's totally filled. + // after hard fork core-2467: when BSRM is no_settlement, it is possible that other call orders are matched + // in check_call_orders, also possible that increasing CR will get the call order itself matched + if( !HARDFORK_CORE_2467_PASSED( next_maint_time ) ) // before core-2467 hf { - // Note: if it is to increase CR and is not increasing debt amount, it is allowed, - // because it implies BSRM == no_settlement - FC_ASSERT( increasing_cr - || call_collateralization >= ~( _bitasset_data->median_feed.max_short_squeeze_price() ), - "Could not create a debt position which would trigger a blackswan event instantly, " - "unless it is to increase collateral ratio of an existing debt position and " - "is not increasing its debt amount" ); + GRAPHENE_ASSERT( !call_ptr, call_order_update_unfilled_margin_call, + "Updating call order would trigger a margin call that cannot be fully filled" ); } - // Update current_feed if needed - const auto bsrm = _bitasset_data->get_black_swan_response_method(); - if( bitasset_options::black_swan_response_type::no_settlement == bsrm ) - d.update_bitasset_current_feed( *_bitasset_data, true ); - - // check to see if the order needs to be margin called now, but don't allow black swans and require there to be - // limit orders available that could be used to fill the order. - // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork, - // the first call order may be unable to be updated if the second one is undercollateralized. - // Note: check call orders, don't allow black swan, not for new limit order - bool called_some = d.check_call_orders( *_debt_asset, false, false, _bitasset_data ); - call_ptr = d.find(call_order_id); - if( called_some ) + // after core-2467 hf + else { - // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. - // after hard fork core-583: we want to allow increasing collateral - // Note: increasing collateral won't get the call order itself matched (instantly margin called) - // if there is at least a call order get matched but didn't cause a black swan event, - // current order must have got matched. in this case, it's OK if it's totally filled. - // after hard fork core-2467: when BSRM is no_settlement, it is possible that other call orders are matched - // in check_call_orders, also possible that increasing CR will get the call order itself matched - if( !HARDFORK_CORE_2467_PASSED( next_maint_time ) ) // before core-2467 hf - { - GRAPHENE_ASSERT( !call_ptr, call_order_update_unfilled_margin_call, - "Updating call order would trigger a margin call that cannot be fully filled" ); - } - // after core-2467 hf - else - { - // if the call order is totally filled, it is OK, - // if it is increasing CR, it is always ok, no matter if it or another another call order is called, - // otherwise, the remaining call order's CR need to be > ICR - // TODO: perhaps it makes sense to allow more cases, e.g. - // - when a position has ICR > CR > MCR, allow the owner to sell some collateral to increase CR - // - allow owners to sell collateral at price < MSSP (need to update code elsewhere) - FC_ASSERT( !call_ptr || increasing_cr - || call_ptr->collateralization() > _bitasset_data->current_initial_collateralization, - "Could not create a debt position which would trigger a margin call instantly, " - "unless the debt position is fully filled, or it is to increase collateral ratio of " - "an existing debt position and is not increasing its debt amount, " - "or the remaining debt position's collateral ratio is above required " - "initial collateral ratio (ICR)" ); - } + // if the call order is totally filled, it is OK, + // if it is increasing CR, it is always ok, no matter if it or another another call order is called, + // otherwise, the remaining call order's CR need to be > ICR + // TODO: perhaps it makes sense to allow more cases, e.g. + // - when a position has ICR > CR > MCR, allow the owner to sell some collateral to increase CR + // - allow owners to sell collateral at price < MSSP (need to update code elsewhere) + FC_ASSERT( !call_ptr || increasing_cr + || call_ptr->collateralization() > _bitasset_data->current_initial_collateralization, + "Could not create a debt position which would trigger a margin call instantly, " + "unless the debt position is fully filled, or it is to increase collateral ratio of " + "an existing debt position and is not increasing its debt amount, " + "or the remaining debt position's collateral ratio is above required " + "initial collateral ratio (ICR)" ); } - else + } + else + { + // we know no black swan event has occurred + FC_ASSERT( call_ptr, "no margin call was executed and yet the call object was deleted" ); + // this HF must remain as-is, as the assert inside the "if" was triggered during push_proposal() + if( d.head_block_time() <= HARDFORK_CORE_583_TIME ) { - // we know no black swan event has occurred - FC_ASSERT( call_ptr, "no margin call was executed and yet the call object was deleted" ); - // this HF must remain as-is, as the assert inside the "if" was triggered during push_proposal() - if( d.head_block_time() <= HARDFORK_CORE_583_TIME ) - { - // We didn't fill any call orders. This may be because we - // aren't in margin call territory, or it may be because there - // were no matching orders. In the latter case, we throw. - GRAPHENE_ASSERT( - // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here - ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price, - call_order_update_unfilled_margin_call, - "Updating call order would trigger a margin call that cannot be fully filled", - // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here - ("a", ~call_ptr->call_price )("b", _bitasset_data->current_feed.settlement_price) - ); - } - else // after hard fork core-583, always allow call order to be updated if collateral ratio - // is increased and debt is not increased - { - // We didn't fill any call orders. This may be because we - // aren't in margin call territory, or it may be because there - // were no matching orders. In the latter case, - // if collateral ratio is not increased or debt is increased, we throw. - // be here, we know no margin call was executed, - // so call_obj's collateral ratio should be set only by op - // ------ - // Before BSIP77, CR of the new/updated position is required to be above MCR. - // After BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). - // The `current_initial_collateralization` variable has been initialized according to the logic, - // so we directly use it here. - bool ok = increasing_cr; - if( !ok ) - ok = before_core_hardfork_1270 ? - ( ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) - : ( call_collateralization > _bitasset_data->current_initial_collateralization ); - FC_ASSERT( ok, - "Can only increase collateral ratio without increasing debt when the debt position's " - "collateral ratio is lower than or equal to required initial collateral ratio (ICR), " - "if not to trigger a margin call immediately", - ("old_debt", old_debt) - ("new_debt", call_ptr->debt) - ("old_collateralization", old_collateralization) - ("new_collateralization", call_collateralization) - ); - } + // We didn't fill any call orders. This may be because we + // aren't in margin call territory, or it may be because there + // were no matching orders. In the latter case, we throw. + GRAPHENE_ASSERT( + // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here + ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price, + call_order_update_unfilled_margin_call, + "Updating call order would trigger a margin call that cannot be fully filled", + // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here + ("a", ~call_ptr->call_price )("b", _bitasset_data->current_feed.settlement_price) + ); + } + else // after hard fork core-583, always allow call order to be updated if collateral ratio + // is increased and debt is not increased + { + // We didn't fill any call orders. This may be because we + // aren't in margin call territory, or it may be because there + // were no matching orders. In the latter case, + // if collateral ratio is not increased or debt is increased, we throw. + // be here, we know no margin call was executed, + // so call_obj's collateral ratio should be set only by op + // ------ + // Before BSIP77, CR of the new/updated position is required to be above MCR. + // After BSIP77, CR of the new/updated position is required to be above max(ICR,MCR). + // The `current_initial_collateralization` variable has been initialized according to the logic, + // so we directly use it here. + bool ok = increasing_cr; + if( !ok ) + ok = before_core_hardfork_1270 ? + ( ~call_ptr->call_price < _bitasset_data->current_feed.settlement_price ) + : ( call_collateralization > _bitasset_data->current_initial_collateralization ); + FC_ASSERT( ok, + "Can only increase collateral ratio without increasing debt when the debt position's " + "collateral ratio is lower than or equal to required initial collateral ratio (ICR), " + "if not to trigger a margin call immediately", + ("old_debt", old_debt) + ("new_debt", call_ptr->debt) + ("old_collateralization", old_collateralization) + ("new_collateralization", call_collateralization) + ); } } From c699e5304d83f39a6e2ca7394718505b92b6572b Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 18:23:26 +0000 Subject: [PATCH 184/258] Add call_order_update tests about no_settlement --- tests/tests/bsrm_no_settlement_tests.cpp | 478 ++++++++++++++++++++++- 1 file changed, 476 insertions(+), 2 deletions(-) diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index f864522da2..22780a8a2f 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -1334,8 +1334,6 @@ BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) BOOST_CHECK_EQUAL( call3_id(db).debt.value, 1000 ); BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); - //BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100 ); - if( 0 == i ) // no order is filled { // now feed price is 13:10 * 1000:2100 = 13:21 = 0.619047619 (> 10:22 = 0.454545455) @@ -2175,4 +2173,480 @@ BOOST_AUTO_TEST_CASE( no_settlement_taker_test ) } FC_CAPTURE_AND_RETHROW() } +/// Tests updating debt positions when BSRM is no_settlement +BOOST_AUTO_TEST_CASE( no_settlement_update_debt_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // Several passes + for( int i = 0; i <= 20; ++ i ) + { + idump( (i) ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::no_settlement); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == bsrm_type::no_settlement ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // borrowers borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == price( asset(125000,mpa_id), asset(2000) ) ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + + // borrower3 is unable to create debt position if its CR is below ICR which is calculated with median_feed + // 100000 * (2000/125000) * 1.9 = 3040 + // 100000 * (22/1000) * 1.9 = 4180 + BOOST_CHECK_THROW( borrow( borrower3, asset(100000, mpa_id), asset(4180) ), fc::exception ); + // borrower3 create debt position right above ICR + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(4181) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // borrower adjust debt position to right at MSSR + // 100000 * (22/1000) * 1.25 = 2750 + borrow( borrower, asset(0, mpa_id), asset(1) ); // can increase CR if not to increase debt, even if new CRid; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled in the beginning either + const limit_order_object* sell_high = create_sell_order( seller2, asset(10000,mpa_id), asset(275) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + + // seller2 sells more, this order won't be filled + const limit_order_object* sell_highest = create_sell_order( seller2, asset(10000,mpa_id), asset(285) ); + BOOST_REQUIRE( sell_highest ); + limit_order_id_type sell_highest_id = sell_highest->id; + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // update debt position + BOOST_TEST_MESSAGE( "Update debt position"); + if( 0 == i ) + { + // borrower2 slightly update the position and is not filled + borrow( borrower2, asset(0, mpa_id), asset(1) ); // to 100000:2101 + } + else if( 1 == i ) + { + // borrower2 update the position and is partially filled + borrow( borrower2, asset(0, mpa_id), asset(100) ); // to 100000:2200 + } + else if( 2 == i ) + { + // borrower2 update the position to smaller so it will be fully filled + borrow( borrower2, asset(-90000, mpa_id), asset(-1880) ); // to 10000:220 + } + else if( 3 == i ) + { + // borrower2 update the position to smaller so it will be fully filled, + // and borrower's position is partially filled + borrow( borrower2, asset(-91000, mpa_id), asset(-1880) ); // to 9000:220 + } + else if( 4 == i ) + { + // borrower2 update the position so that its CR is higher than borrower's, + // and borrower's position is partially filled + borrow( borrower2, asset(0, mpa_id), asset(651) ); // to 100000:2751 + } + else if( 5 == i ) + { + // borrower2 close the position, so borrwer's position is partially filled + borrow( borrower2, asset(-100000, mpa_id), asset(-2100) ); // to 10000:220 + } + else if( 6 == i ) + { + // borrower close the position, no order is filled + borrow( borrower, asset(-100000, mpa_id), asset(-2750) ); + } + else + { + BOOST_TEST_MESSAGE( "No more test cases so far" ); + break; + } + + // check result + auto check_result = [&] + { + BOOST_TEST_MESSAGE( "Check result"); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 4181 ); + + if( 0 == i ) // no order is filled + { + // now feed price is 13:10 * 100000:2101 = 130000:2101 = 61.875297477 (> 1000:22 = 45.454545455) + // call2 pays price = 100000:2101 + // match price = 100000:2101 * 1300:1289 = 48.002558167 + // sell_mid price = 10000:210 = 47.619047619 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130000,mpa_id), asset(2101) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2101 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2101 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else if( 1 == i ) + { + // now feed price is 13:10 * 100000:2200 = 1300:22 = 59.090909091 (> 1000:22 = 45.454545455) + // call2 pays price = 100000:2200 + // match price = 100000:2200 * 1300:1289 = 45.84244305 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // sell_mid receives 210 + // call2 pays round_down(10000*(210*1300/1289) = 211, margin call fee = 1 + // now feed price is 13:10 * (100000-10000):(2200-211) + // = 13:10 * 90000:1989 = 1170000:19890 = 58.823529412 (> 1000:22 = 45.454545455) + // call2 match price is 1300:1289 * 90000:1989 = 42.124625705 + // sell_high price = 10000:275 = 36.363636364 + + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1170000,mpa_id), asset(19890) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1989 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2200 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 2 == i ) + { + // now feed price is 13:10 * 10000:220 = 1300:22 = 59.090909091 (> 1000:22 = 45.454545455) + // call2 pays price = 10000:220 + // match price = 10000:220 * 1300:1289 = 45.84244305 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // sell_mid receives 210 + // call2 pays round_up(210*1300/1289) = 212, margin call fee = 2 + // call2 is filled + BOOST_REQUIRE( !db.find( call2_id ) ); + // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455) + // call match price is 1300:1289 * 100000:2750 = 36.67395444 + // sell_high price = 10000:275 = 36.363636364 + + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(13000,mpa_id), asset(275) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2750 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 212 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 3 == i ) + { + // now feed price is 13:10 * 9000:220 = 1170:22 = 53.181818182 (> 1000:22 = 45.454545455) + // call2 pays price = 9000:220 + // match price = 9000:220 * 1300:1289 = 41.258198745 + // sell_mid price = 10000:210 = 47.619047619 + // call2 is filled + BOOST_REQUIRE( !db.find( call2_id ) ); + // call2 receives 9000 + // sell_mid receives round_up(9000*210/10000) = 189 + // call2 pays round_up(9000*(210/10000)*(1300/1289)) = 191, margin call fee = 2 + + // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455) + // call match price is 1300:1289 * 100000:2750 = 36.67395444 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // call receives 1000 + // sell_mid receives round_down(1000*210/10000) = 21 + // call pays round_down(1000*(210/10000)*(1300/1289)) = 21, margin call fee = 0 + + // now feed price is 13:10 * (100000-1000):(2750-21) + // = 13:10 * 99000:2729 = 128700:2729 = 47.160131916 (> 1000:22 = 45.454545455) + // call pays price = 99000:2729 + // match price = 99000:2729 * 1300:1289 = 36.586603504 + // sell_high price = 10000:275 = 36.363636364 (does not match) + + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(128700,mpa_id), asset(2729) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 99000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2729 ); + + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 191 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); // 189 + 21 + } + else if( 4 == i ) + { + // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455) + // call match price is 1300:1289 * 100000:2750 = 36.67395444 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // call receives 10000 + // sell_mid receives round_down(10000*210/10000) = 210 + // call pays round_down(10000*(210/10000)*(1300/1289)) = 211, margin call fee = 1 + // call is now (100000-10000):(2750-211) = 90000:2539 = 35.447026388 + // call2 is 100000:2751 = 36.35041803 + + // now feed price is 13:10 * 100000:2751 = 130000:2751 = 47.255543439 (> 1000:22 = 45.454545455) + // call2 pays price = 100000:2751 + // match price = 100000:2751 * 1300:1289 = 36.660623304 + // sell_high price = 10000:275 = 36.363636364 (does not match) + + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130000,mpa_id), asset(2751) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 90000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2539 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2751 ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2751 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 210 ); + } + else if( 5 == i ) + { + BOOST_REQUIRE( !db.find( call2_id ) ); + // now feed price is 13:10 * 100000:2750 = 13000:275 = 47.272727273 (> 1000:22 = 45.454545455) + // call match price is 1300:1289 * 100000:2750 = 36.67395444 + // sell_mid price = 10000:210 = 47.619047619 + // sell_mid is filled + BOOST_REQUIRE( !db.find( sell_mid_id ) ); + // call receives 10000 + // sell_mid receives round_down(10000*210/10000) = 210 + // call pays round_down(10000*(210/10000)*(1300/1289)) = 211, margin call fee = 1 + // call is now (100000-10000):(2750-211) = 90000:2539 = 35.447026388 + + // now feed price is 13:10 * 90000:2539 = 117000:2539 = 46.081134305 (> 1000:22 = 45.454545455) + // call pays price = 90000:2539 + // match price = 90000:2539 * 1300:1289 = 35.749522347 + // sell_high price = 10000:275 = 36.363636364 + // sell_high is filled + BOOST_REQUIRE( !db.find( sell_high_id ) ); + // call receives 10000 + // sell_mid receives round_down(10000*275/10000) = 275 + // call pays round_down(10000*(275/10000)*(1300/1289)) = 277, margin call fee = 2 + // call is now (100000-20000):(2750-211-277) = 80000:2262 = 35.366931919 + // now feed price is 13:10 * 80000:2262 = 104000:2262 = 45.977011494 (> 1000:22 = 45.454545455) + // call pays price = 80000:2262 + // match price = 80000:2262 * 1300:1289 = 35.668744371 + // sell_highest price = 10000:285 = 35.087719298 (does not match) + + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(104000,mpa_id), asset(2262) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 80000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2262 ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2750 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 485 ); // 210 + 275 + } + else if( 6 == i ) + { + BOOST_REQUIRE( !db.find( call_id ) ); + // now feed price is 13:10 * 100000:2100 = 130000:2100 = 61.904761905 (> 1000:22 = 45.454545455) + // call2 pays price = 100000:2100 + // match price = 100000:2100 * 1300:1289 = 48.025416528 + // sell_mid price = 10000:210 = 47.619047619 + BOOST_REQUIRE( db.find( sell_mid_id ) ); + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_REQUIRE( db.find( sell_highest_id ) ); + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 10000 ); + BOOST_CHECK_EQUAL( sell_highest_id(db).for_sale.value, 10000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130000,mpa_id), asset(2100) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 20000 ); // 50000 - 10000 - 10000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + } + else + { + BOOST_FAIL( "to be fixed" ); + } + }; + check_result(); + + // generate a block + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + // check again + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 6abdc1ef6a3d858f262773e1a2e7c9fabc2ac736 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 19:13:23 +0000 Subject: [PATCH 185/258] Add tests about manual GS for different BSRM types --- tests/tests/bsrm_basic_tests.cpp | 195 +++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 0eed9937a3..b947ec6899 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -1318,4 +1318,199 @@ BOOST_AUTO_TEST_CASE( undercollateralized_and_update_bsrm_from_no_settlement ) } FC_CAPTURE_AND_RETHROW() } +/// Tests scenarios: +/// manually trigger global settlement via asset_global_settle_operation on each BSRM type +BOOST_AUTO_TEST_CASE( manual_gs_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + using bsrm_type = bitasset_options::black_swan_response_type; + + // Several passes for each BSRM type + for( uint8_t i = 0; i <= 3; ++i ) + { + idump( (i) ); + + ACTORS((sam)(feeder)(borrower)(borrower2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = i; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() == static_cast(i) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // borrow some + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(8000) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // publish a new feed so that borrower's debt position is undercollateralized + ilog( "Publish a new feed so that the least collateralized short is undercollateralized" ); + f.settlement_price = price( asset(1000,mpa_id), asset(22) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // check + const auto& check_result = [&] + { + switch( static_cast(i) ) + { + case bsrm_type::global_settlement: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + // can not globally settle again + BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception ); + break; + case bsrm_type::no_settlement: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::no_settlement ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + // can not globally settle at real price since the least collateralized short's CR is too low + BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception ); + break; + case bsrm_type::individual_settlement_to_fund: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + // MSSR = 1250, MCFR = 11, fee = round_down(2000 * 11 / 1250) = 17, fund = 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + break; + case bsrm_type::individual_settlement_to_order: + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( db.find( call2_id ) ); + + BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->amount_to_receive().amount.value, 100000 ); + break; + default: + BOOST_FAIL( "This should not happen" ); + break; + } + }; + + check_result(); + + ilog( "Generate a block" ); + generate_block(); + + check_result(); + + // globally settle + if( bsrm_type::no_settlement == static_cast(i) ) + force_global_settle( mpa_id(db), price( asset(1000,mpa_id), asset(18) ) ); + else if( bsrm_type::individual_settlement_to_fund == static_cast(i) + || bsrm_type::individual_settlement_to_order == static_cast(i) ) + force_global_settle( mpa_id(db), price( asset(1000,mpa_id), asset(22) ) ); + + // check + const auto& check_result2 = [&] + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() + == bsrm_type::global_settlement ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + + switch( static_cast(i) ) + { + case bsrm_type::global_settlement: + break; + case bsrm_type::no_settlement: + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 3600 ); // 1800 * 2 + break; + case bsrm_type::individual_settlement_to_fund: + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 4183 ); // 1983 + 2200 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + break; + case bsrm_type::individual_settlement_to_order: + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).settlement_fund.value, 4183 ); // 1983 + 2200 + break; + default: + BOOST_FAIL( "This should not happen" ); + break; + } + }; + + check_result2(); + + ilog( "Generate a block" ); + generate_block(); + + check_result2(); + + // reset + db.pop_block(); + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From cb0069626cfe3c16ee5d11b1d2de49628b71a6e3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 19:33:41 +0000 Subject: [PATCH 186/258] Update comments --- libraries/chain/db_getter.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 085b48f338..8bdcfd8b30 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -174,7 +174,7 @@ const call_order_object* database::find_least_collateralized_short( const asset_ { const auto& call_price_index = get_index_type().indices().get(); auto call_itr = call_price_index.lower_bound( call_min ); - if( call_itr != call_price_index.end() ) // no call order + if( call_itr != call_price_index.end() ) // found a call order call_ptr = &(*call_itr); } else // after core-1270 hard fork, check with collateralization @@ -182,12 +182,12 @@ const call_order_object* database::find_least_collateralized_short( const asset_ // Note: it is safe to check here even if there is no call order due to individual settlements const auto& call_collateral_index = get_index_type().indices().get(); auto call_itr = call_collateral_index.lower_bound( call_min ); - if( call_itr != call_collateral_index.end() ) // no call order + if( call_itr != call_collateral_index.end() ) // found a call order call_ptr = &(*call_itr); } - if( !call_ptr ) + if( !call_ptr ) // not found return nullptr; - if( call_ptr->debt_type() != bitasset.asset_id ) // no call order + if( call_ptr->debt_type() != bitasset.asset_id ) // call order is of another asset return nullptr; return call_ptr; } From 1a8f51d77e74b2114e314ec2c0b5cf1aa2676f9a Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 20:01:45 +0000 Subject: [PATCH 187/258] Update manual GS tests for different BSRM types check capping and uncapping of current feed price --- tests/tests/bsrm_basic_tests.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index b947ec6899..71a7ce788a 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -1397,6 +1397,7 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) // check const auto& check_result = [&] { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); switch( static_cast(i) ) { case bsrm_type::global_settlement: @@ -1407,6 +1408,8 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); // can not globally settle again BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception ); break; @@ -1418,6 +1421,9 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(1250,mpa_id), asset(20) ) ); // can not globally settle at real price since the least collateralized short's CR is too low BOOST_CHECK_THROW( force_global_settle( mpa_id(db), f.settlement_price ), fc::exception ); break; @@ -1433,6 +1439,11 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); // MSSR = 1250, MCFR = 11, fee = round_down(2000 * 11 / 1250) = 17, fund = 2000 - 17 = 1983 BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + // current feed = 100000:1983 * (1250-11):1000 + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(123900,mpa_id), asset(1983) ) ); break; case bsrm_type::individual_settlement_to_order: BOOST_CHECK( mpa_id(db).bitasset_data(db).get_black_swan_response_method() @@ -1442,6 +1453,8 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( db.find( call2_id ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->for_sale.value, 1983 ); BOOST_CHECK_EQUAL( db.find_settled_debt_order(mpa_id)->amount_to_receive().amount.value, 100000 ); @@ -1476,6 +1489,9 @@ BOOST_AUTO_TEST_CASE( manual_gs_test ) BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); BOOST_CHECK( !db.find( call_id ) ); BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); switch( static_cast(i) ) { From c0c7c572c1e2cc5a08dffdcd18bf7aabc6394865 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 10 Sep 2021 21:03:02 +0000 Subject: [PATCH 188/258] Slightly refactor asset_global_settle_evaluator by using database::find_least_collateralized_short() --- libraries/chain/asset_evaluator.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index f8c22536cb..a1fa87e2fe 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1057,17 +1057,16 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle FC_ASSERT( !_bitasset_data.has_settlement(), "This asset has been globally settled, cannot globally settle again" ); - // Note: there can be no debt position due to individual settlements, processed below - const auto& idx = d.get_index_type().indices().get(); - auto itr = idx.lower_bound( price::min( _bitasset_data.options.short_backing_asset, op.asset_to_settle ) ); - if( itr != idx.end() && itr->debt_type() == op.asset_to_settle ) + // Note: after core-2467 hard fork, there can be no debt position due to individual settlements, so we check here + const call_order_object* least_collateralized_short = d.find_least_collateralized_short( _bitasset_data, true ); + if( least_collateralized_short ) { - const call_order_object& least_collateralized_short = *itr; - FC_ASSERT( ( least_collateralized_short.get_debt() * op.settle_price ) - <= least_collateralized_short.get_collateral(), - "Cannot globally settle at supplied price: least collateralized short lacks " - "sufficient collateral to settle." ); + FC_ASSERT( ( least_collateralized_short->get_debt() * op.settle_price ) + <= least_collateralized_short->get_collateral(), + "Cannot globally settle at supplied price: least collateralized short lacks " + "sufficient collateral to settle." ); } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } From a070eeb079cd5bf433c25d508adab375c684ca14 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 12 Sep 2021 16:52:57 +0000 Subject: [PATCH 189/258] Add tests for individual_settlement_to_order --- tests/tests/bsrm_indvd_settlement_tests.cpp | 334 ++++++++++++++++++++ tests/tests/bsrm_no_settlement_tests.cpp | 1 - 2 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 tests/tests/bsrm_indvd_settlement_tests.cpp diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp new file mode 100644 index 0000000000..07669bfd17 --- /dev/null +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2021 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) + +/// Tests individual settlement to order +BOOST_AUTO_TEST_CASE( individual_settlement_to_order_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + fund( borrower4, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // undercollateralization price = 100000:3000 * 1250:1000 = 100000:1760 + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // undercollateralization price = 100000:4000 * 1250:1000 = 100000:2400 + const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); + BOOST_REQUIRE( call4_ptr ); + call_order_id_type call4_id = call4_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(100000,mpa_id) ); + transfer( borrower4, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 400000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + + // check + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + // order match price = 100000 / 1983 = 50.428643469 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // seller sells some + const limit_order_object* limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + // call2 is partially filled + // limit order gets round_down(10000*(1650/100000)*(1239/1000)) = 204 + // limit order pays round_up(204*(100000/1650)*(1000/1239)) = 9979 + // call2 gets 9979 + // call2 pays round_down(9979*(1650/100000)*(1250/1000)) = 205, margin call fee = 1 + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 90021 ); // 100000 - 9979 + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 1895 ); // 2100 - 205 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 390021 ); // 400000 - 9979 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 204 ); + + // publish a new feed so that 2 other debt positions are undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1800) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 + // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789 + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // call2: margin call fee deducted = round_down(1895*11/1250) = 16, + // fund receives 1895 - 16 = 1879 + // call3: margin call fee deducted = round_down(2200*11/1250) = 19, + // fund receives 2200 - 19 = 2181 + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // 1983 + 1879 + 2181 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // 100000 + 90021 + 100000 + // order match price = 290021 / 6043 = 47.992884329 + + // borrower buys at higher price + const limit_order_object* buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) ); + BOOST_CHECK( buy_high ); + limit_order_id_type buy_high_id = buy_high->id; + + // seller sells some, this will match buy_high, + // and when it matches call4, it will be cancelled since it is too small + limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + // buy_high is filled + BOOST_CHECK( !db.find( buy_high_id ) ); + + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // no change + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // no change + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389921 ); // 400000 - 9979 - 100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 214 ); // 204 + 10 + + // publish a new feed so that the settled debt order is in the front of the order book + f.settlement_price = price( asset(100000,mpa_id), asset(1600) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1600 * 1000:1250 = 100000:2000 = 50 + // call match price = 100000:1600 * 1000:1239 = 100000:1982.4 = 50.443906376 + + // borrower buys at higher price + buy_high = create_sell_order( borrower, asset(10), asset(100,mpa_id) ); + BOOST_CHECK( buy_high ); + buy_high_id = buy_high->id; + + // seller sells some, this will match buy_high, + // and when it matches the settled debt, it will be cancelled since it is too small + limit_ptr = create_sell_order( seller, asset(120,mpa_id), asset(1) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + // buy_high is filled + BOOST_CHECK( !db.find( buy_high_id ) ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 6043 ); // no change + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 290021 ); // no change + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 389821 ); // 400000 - 9979 - 100 - 100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 224 ); // 204 + 10 + 10 + + // seller sells some + limit_ptr = create_sell_order( seller, asset(10000,mpa_id), asset(100) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + // the settled debt is partially filled + // limit order receives = round_down(10000*6043/290021) = 208 + // settled debt receives = round_up(208*290021/6043) = 9983 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 5835 ); // 6043 - 208 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 280038 ); // 290021 - 9983 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 379838 ); // 400000 - 9979 - 100 - 100 - 9983 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 432 ); // 204 + 10 + 10 + 208 + + // seller sells some + limit_ptr = create_sell_order( seller, asset(300000,mpa_id), asset(3000) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + auto final_check = [&] + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + + // the settled debt is fully filled + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + // limit order reminder = 300000 - 280038 = 19962 + // call4 is partially filled + // limit order gets round_down(19962*(1600/100000)*(1239/1000)) = 395 + // limit order pays round_up(395*(100000/1600)*(1000/1239)) = 19926 + // call4 gets 19926 + // call4 pays round_down(19926*(1600/100000)*(1250/1000)) = 398, margin call fee = 3 + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 79874 ); // 400000 - 9979 - 100 - 100 - 9983 + // - 280038 - 19926 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 6662 ); // 204 + 10 + 10 + 208 + 5835 + 395 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 80074 ); // 100000 - 19926 + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2102 ); // 2500 - 398 + + }; + + final_check(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + final_check(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/bsrm_no_settlement_tests.cpp b/tests/tests/bsrm_no_settlement_tests.cpp index 22780a8a2f..4487c9050b 100644 --- a/tests/tests/bsrm_no_settlement_tests.cpp +++ b/tests/tests/bsrm_no_settlement_tests.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include From 5a5c0bc2e77d98dfbda8dd224607764eb6f9462d Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 15 Sep 2021 16:56:41 +0000 Subject: [PATCH 190/258] Add tests about cancelling settle order on no call --- tests/tests/bsrm_indvd_settlement_tests.cpp | 180 ++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index 07669bfd17..a1fea75c8e 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -331,4 +331,184 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_test ) } } +/// Tests a scenario that force settlements get cancelled on expiration when there is no debt position +/// due to individual settlement to order +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_debt_position ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_order); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 86400; + acop.bitasset_opts->force_settlement_delay_sec = 600; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + + acop.symbol = "SAMMPA2"; + acop.bitasset_opts->force_settlement_delay_sec = 60000; + trx.operations.clear(); + trx.operations.push_back( acop ); + ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa2 = db.get(ptx.operation_results[0].get()); + asset_id_type mpa2_id = mpa2.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + update_feed_producers( mpa2_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + price_feed f2; + f2.settlement_price = price( asset(100,mpa2_id), asset(1) ); + f2.core_exchange_rate = price( asset(100,mpa2_id), asset(1) ); + f2.maintenance_collateral_ratio = 1850; + f2.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa2_id, feeder_id, f2, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower, asset(100000, mpa2_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower, seller, asset(100000,mpa2_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + // order match price = 100000 / 1983 = 50.428643469 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + result = force_settle( seller, asset(11100,mpa2_id) ); + force_settlement_id_type settle2_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle2_id) ); + + BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the first settle order expire + generate_blocks( db.head_block_time() + fc::seconds(600) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 1983 ); // no change + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 100000 ); + + // the first settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + // no change to the second settle order + BOOST_REQUIRE( db.find(settle2_id) ); + BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa2_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 7729a5169c3129d9f095a704b1a6e692f96a774c Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 15 Sep 2021 17:36:22 +0000 Subject: [PATCH 191/258] Add more tests about cancelling settle orders --- tests/tests/settle_tests.cpp | 474 +++++++++++++++++++++++++++++++++++ 1 file changed, 474 insertions(+) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 4bad2a299b..c900b35688 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -2004,6 +2004,480 @@ BOOST_AUTO_TEST_CASE( global_settle_ticker_test ) } } +/// Tests a scenario that force settlements get cancelled on the block when the asset is globally settled +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_gs ) +{ + try { + + // Advance to a desired hard fork time + // Note: this test doesn't apply after hf2481 + generate_blocks( HARDFORK_CORE_1780_TIME ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 800; + acop.bitasset_opts->force_settlement_delay_sec = 600; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa_id, feeder_id, f ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // Transfer funds to seller + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + // generate a block + generate_block(); + + // check again + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // publish a new feed so that the asset is globally settled + f.settlement_price = price( asset(100,mpa_id), asset(10) ); + publish_feed( mpa_id, feeder_id, f ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // generate a block + generate_block(); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_settlement() ); + + // the settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests a scenario that force settlements get cancelled on expiration when there is no sufficient feed +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_feed ) +{ + try { + + // Advance to a desired hard fork time + if(hf2467) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + generate_blocks( HARDFORK_CORE_1780_TIME ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 400; + acop.bitasset_opts->force_settlement_delay_sec = 600; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa_id, feeder_id, f ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // Transfer funds to seller + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the price feed expire + generate_blocks( db.head_block_time() + fc::seconds(400) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the settle order expire + generate_blocks( db.head_block_time() + fc::seconds(200) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + // the settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests a scenario that force settlements get cancelled on expiration when the amount is zero +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_zero_amount ) +{ + try { + + // Advance to a desired hard fork time + if(hf2467) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + generate_blocks( HARDFORK_CORE_1780_TIME ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 800; + acop.bitasset_opts->force_settlement_delay_sec = 600; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa_id, feeder_id, f ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // Transfer funds to seller + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(0,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 0 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // generate a block + generate_block(); + + // check again + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 0 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the settle order expire + generate_blocks( db.head_block_time() + fc::seconds(600) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // the settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests a scenario that force settlements get cancelled on expiration when offset is 100% +BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_100_percent_offset ) +{ + try { + + // Advance to a desired hard fork time + if(hf2467) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + } + else + generate_blocks( HARDFORK_CORE_1780_TIME ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 800; + acop.bitasset_opts->force_settlement_delay_sec = 600; + acop.bitasset_opts->force_settlement_offset_percent = GRAPHENE_100_PERCENT; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + publish_feed( mpa_id, feeder_id, f ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // Transfer funds to seller + transfer( borrower, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // seller settles some + auto result = force_settle( seller, asset(11100,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // generate a block + generate_block(); + + // check again + BOOST_REQUIRE( db.find(settle_id) ); + + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 11100 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 88900 ); // 100000 - 11100 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + // let the settle order expire + generate_blocks( db.head_block_time() + fc::seconds(600) ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + + // the settle order is cancelled + BOOST_REQUIRE( !db.find(settle_id) ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_no_feed_after_hf_2467) +{ try { + hf2467 = true; + INVOKE(settle_order_cancel_due_to_no_feed); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_zero_amount_after_hf_2467) +{ try { + hf2467 = true; + INVOKE(settle_order_cancel_due_to_zero_amount); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(settle_order_cancel_due_to_100_percent_offset_after_hf_2467) +{ try { + hf2467 = true; + INVOKE(settle_order_cancel_due_to_100_percent_offset); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(settle_rounding_test_after_hf_1270) { try { hf1270 = true; From 3170106441933a8dd70831cd09f5202e201c5791 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 16 Sep 2021 21:42:45 +0000 Subject: [PATCH 192/258] Fix check_call_orders() : check limit orders again after filled a call order with a settle order --- libraries/chain/db_market.cpp | 205 ++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 97 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 2cf41d4911..d0fde8e7a1 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -86,22 +86,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( !call_ptr ) // no call order return false; - using bsrm_type = bitasset_options::black_swan_response_type; - const auto bsrm = bitasset.get_black_swan_response_method(); - - price highest = settle_price; - // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here - // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. - // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. - // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. - if( bsrm_type::individual_settlement_to_fund == bsrm ) - highest = bitasset.median_feed.max_short_squeeze_price(); - else if( !before_core_hardfork_1270 ) - highest = bitasset.current_feed.max_short_squeeze_price(); - else if( maint_time > HARDFORK_CORE_338_TIME ) - highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); - // else do nothing - const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); @@ -115,39 +99,62 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); - if( limit_itr != limit_end ) + using bsrm_type = bitasset_options::black_swan_response_type; + const auto bsrm = bitasset.get_black_swan_response_method(); + + // when BSRM is individual settlement, we loop multiple times + bool settled_some = false; + while( true ) { - FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); - auto call_pays_price = limit_itr->sell_price; - if( after_core_hardfork_2481 ) + settle_price = bitasset.current_feed.settlement_price; + price highest = settle_price; + // Due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + // * If BSRM is individual_settlement_to_fund, check with median_feed to decide whether to settle. + // * If BSRM is no_settlement, check with current_feed to NOT trigger global settlement. + // * If BSRM is global_settlement or individual_settlement_to_order, median_feed == current_feed. + if( bsrm_type::individual_settlement_to_fund == bsrm ) + highest = bitasset.median_feed.max_short_squeeze_price(); + else if( !before_core_hardfork_1270 ) + highest = bitasset.current_feed.max_short_squeeze_price(); + else if( maint_time > HARDFORK_CORE_338_TIME ) + highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); + // else do nothing + + if( limit_itr != limit_end ) { - // due to margin call fee, we check with MCPP (margin call pays price) here - call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); + FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); + auto call_pays_price = limit_itr->sell_price; + if( after_core_hardfork_2481 ) + { + // due to margin call fee, we check with MCPP (margin call pays price) here + call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); + } + highest = std::max( call_pays_price, highest ); } - highest = std::max( call_pays_price, highest ); - } - // The variable `highest` after hf_338: - // * if no limit order, it is expected to be the black swan price; if the call order with the least CR - // has CR below or equal to the black swan price, we trigger GS, - // * if there exists at least one limit order and the price is higher, we use the limit order's price, - // which means we will match the margin call orders with the limit order first. - // - // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered - // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got - // caught by an additional check in check_call_orders(). - // This bug is fixed in hf_2481. Actually, after hf_2481, - // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), - // * if there is no force settlement, we check here with margin call fee in consideration. - - auto least_collateral = call_ptr->collateralization(); - // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, - // if the matching limit order is smaller, due to rounding, it is still possible that the - // call order's collateralization would increase and become higher than ~highest after matched. - // However, for simplicity, we only compare the prices here. - bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) : ( ~least_collateral >= highest ); - if( is_blackswan ) - { + // The variable `highest` after hf_338: + // * if no limit order, it is expected to be the black swan price; if the call order with the least CR + // has CR below or equal to the black swan price, we trigger GS, + // * if there exists at least one limit order and the price is higher, we use the limit order's price, + // which means we will match the margin call orders with the limit order first. + // + // However, there was a bug: after hf_bsip74 and before hf_2481, margin call fee was not considered + // when calculating highest, which means some blackswans weren't got caught here. Fortunately they got + // caught by an additional check in check_call_orders(). + // This bug is fixed in hf_2481. Actually, after hf_2481, + // * if there is a force settlement, we totally rely on the additional checks in check_call_orders(), + // * if there is no force settlement, we check here with margin call fee in consideration. + + auto least_collateral = call_ptr->collateralization(); + // Note: strictly speaking, even when the call order's collateralization is lower than ~highest, + // if the matching limit order is smaller, due to rounding, it is still possible that the + // call order's collateralization would increase and become higher than ~highest after matched. + // However, for simplicity, we only compare the prices here. + bool is_blackswan = after_core_hardfork_2481 ? ( ~least_collateral > highest ) + : ( ~least_collateral >= highest ); + if( !is_blackswan ) + return settled_some; + wdump( (*call_ptr) ); elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" " Least collateralized call: ${lc} ${~lc}\n" @@ -166,6 +173,11 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) { individually_settle( bitasset, *call_ptr ); + call_ptr = find_least_collateralized_short( bitasset, true ); + if( !call_ptr ) // no call order + return true; + settled_some = true; + continue; } // Global settlement or no settlement, but we should not be here if BSRM is no_settlement else if( after_core_hardfork_2481 ) @@ -195,7 +207,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s globally_settle_asset(mia, ~least_collateral ); return true; } - return false; } /** @@ -609,6 +620,7 @@ bool maybe_cull_small_order( database& db, const limit_order_object& order ) return false; } +// Note: optimizations have been done in apply_order(...) bool database::apply_order_before_hardfork_625(const limit_order_object& new_order_object) { auto order_id = new_order_object.id; @@ -625,7 +637,7 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord const auto& limit_price_idx = get_index_type().indices().get(); - // TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to + // it should be possible to simply check the NEXT/PREV iterator after new_order_object to // determine whether or not this order has "changed the book" in a way that requires us to // check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs // constant time check. Potential optimization. @@ -644,8 +656,8 @@ bool database::apply_order_before_hardfork_625(const limit_order_object& new_ord != match_result_type::only_maker_filled ); } - // TODO Possible optimization: only check calls if the new order completely filled some old order. - // Do I need to check both assets? + // Possible optimization: only check calls if the new order completely filled some old order. + // Do I need to check both assets? check_call_orders(sell_asset); // after the new limit order filled some orders on the book, // if a call order matches another order, the call order is taker check_call_orders(receive_asset); // the other side, same as above @@ -1821,12 +1833,26 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool update_current_feed = ( bsrm_type::no_settlement == bsrm && bitasset.is_current_feed_price_capped() ); - // If update_current_feed is true and we filled a call order with a settle order, + const auto& settlement_index = get_index_type().indices().get(); + + // If we filled a call order with a settle order, // we need to check limit orders again, in this case we loop multiple times. // In other cases we only need to loop once. while( true ) { + // If BSRM is individual settlement, check for blackswan first + if( ( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) + // Note: only call when conditions above are true + && check_for_blackswan( mia, enable_black_swan, &bitasset ) ) // TODO perhaps improve performance + // by passing in iterators + { + margin_called = true; + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + if( bsrm_type::individual_settlement_to_fund == bsrm ) + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + } + // match call orders with limit orders while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance // by passing in iterators && limit_itr != limit_end @@ -2003,46 +2029,42 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) limit_itr = next_limit_itr; - } // while call_itr != call_end + } // while !blackswan and call_itr != call_end and limit_itr != limit_end + + // If already GSed, or there is no call order, we return. + // Note: if GSed, the iterators used in has_call_order() should have been invalidated + if( bitasset.has_settlement() || !has_call_order() ) + return margin_called; + + // If no need to process force settlements, we return + if( skip_matching_settle_orders || !after_core_hardfork_2481 ) + return margin_called; + + // If feed protected, we return + if( bitasset.current_maintenance_collateralization < call_collateral_itr->collateralization() ) + return margin_called; + + // If no force settlements, we return + auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); + if( settle_itr == settlement_index.end() || settle_itr->balance.asset_id != bitasset.asset_id ) + return margin_called; // Check margin calls against force settlements - if( !skip_matching_settle_orders && after_core_hardfork_2481 && !bitasset.has_settlement() ) + // Note: we always need to recheck limit orders after processed call-settle match, + // in case when the least collateralized short was undercollateralized. + if( match_force_settlements( bitasset ) ) { - // Be here, there exists at least one margin call not processed - bool called_some = match_force_settlements( bitasset ); - if( called_some ) - { - margin_called = true; - // if called some, it means the call order is updated or removed, - // in this case, if update_current_feed is true, - // it is possible that there are limit orders able to get filled, - // so we need to check again - if( update_current_feed ) - { - limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); - call_collateral_itr = call_collateral_index.lower_bound( call_min ); - update_current_feed = bitasset.is_current_feed_price_capped(); - continue; - } - } - // At last, check for blackswan // TODO perhaps improve performance by passing in iterators - if( bsrm_type::individual_settlement_to_fund == bsrm - || bsrm_type::individual_settlement_to_order == bsrm ) + margin_called = true; + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + if( update_current_feed ) { - // Run multiple times, each time one call order gets settled - // TODO perhaps improve performance by settling multiple call orders inside in one call - while( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) - { - margin_called = true; - } + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + update_current_feed = bitasset.is_current_feed_price_capped(); } - else - check_for_blackswan( mia, enable_black_swan, &bitasset ); } - break; + // else : no more force settlements, or feed protected, both will be handled in the next loop } // while true - return margin_called; } FC_CAPTURE_AND_RETHROW() } bool database::match_force_settlements( const asset_bitasset_data_object& bitasset ) @@ -2074,13 +2096,6 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // It is in debt/collateral . price call_pays_price = bitasset.current_feed.max_short_squeeze_price(); - // Note: when BSRM is no_settlement, current_feed can change after filled a call order, - // so we recalculate inside the loop - using bsrm_type = bitasset_options::black_swan_response_type; - bool update_call_price = ( bitasset.get_black_swan_response_method() == bsrm_type::no_settlement - && bitasset.is_current_feed_price_capped() ); - - bool margin_called = false; while( settle_itr != settle_end && call_itr != call_end ) { const force_settlement_object& settle_order = *settle_itr; @@ -2088,7 +2103,7 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 if( bitasset.current_maintenance_collateralization < call_order.collateralization() ) - return margin_called; + return false; // TCR applies here asset max_debt_to_cover( call_order.get_max_debt_to_cover( call_pays_price, @@ -2103,19 +2118,15 @@ bool database::match_force_settlements( const asset_bitasset_data_object& bitass auto result = match( call_order, settle_order, call_pays_price, bitasset, max_debt_to_cover, call_match_price ); // if result.amount > 0, it means the call order got updated or removed + // in this case, we need to check limit orders first, so we return if( result.amount > 0 ) - { - // if update_call_price is true, we need to check limit orders first, so we return - if( update_call_price ) - return true; - margin_called = true; - } + return true; // else : result.amount == 0, it means the settle order got canceled directly and the call order did not change settle_itr = settlement_index.lower_bound( bitasset.asset_id ); call_itr = call_collateral_index.lower_bound( call_min ); } - return margin_called; + return false; } void database::pay_order( const account_object& receiver, const asset& receives, const asset& pays ) From 2b38112e8e6c2fa69127356299ec3cc0430557c7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 16 Sep 2021 21:48:10 +0000 Subject: [PATCH 193/258] Add tests wrt matching call with settle then limit --- tests/tests/force_settle_match_tests.cpp | 155 +++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/tests/tests/force_settle_match_tests.cpp b/tests/tests/force_settle_match_tests.cpp index 8cda2a54ae..a0b030a2e9 100644 --- a/tests/tests/force_settle_match_tests.cpp +++ b/tests/tests/force_settle_match_tests.cpp @@ -1323,4 +1323,159 @@ BOOST_AUTO_TEST_CASE(call_settle_blackswan) } FC_LOG_AND_RETHROW() } +/*** + * Match taker call orders with maker settle orders, + * then it is able to match taker call orders with maker limit orders again + */ +BOOST_AUTO_TEST_CASE(call_settle_limit_settle) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2481_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(seller2)(borrower)(borrower2)(borrower3)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id; + + { + // set margin call fee ratio + asset_update_bitasset_operation uop; + uop.issuer = usd_id(db).issuer; + uop.asset_to_update = usd_id; + uop.new_options = usd_id(db).bitasset_data(db).options; + uop.new_options.extensions.value.margin_call_fee_ratio = 30; // 3% + + trx.clear(); + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + } + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/175 CORE/USD = 60/700, tcr 170% is lower than 175% + const call_order_object& call = *borrow( borrower, bitusd.amount(100000), asset(15000), 1700); + call_order_id_type call_id = call.id; + // create another position with 360% collateral, call price is 18/175 CORE/USD = 72/700, no tcr + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(18000) ); + call_order_id_type call2_id = call2.id; + // create yet another position with 800% collateral, call price is 40/175 CORE/USD = 160/700, no tcr + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(40000) ); + call_order_id_type call3_id = call3.id; + + transfer(borrower, seller, bitusd.amount(100000)); + transfer(borrower2, seller, bitusd.amount(100000)); + transfer(borrower3, seller2, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 100000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call2.debt.value ); + BOOST_CHECK_EQUAL( 18000, call2.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 40000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 200000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 100000, get_balance(seller2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 18000, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); + + // Create a sell order which will trigger a blackswan event if matched, price 100/16 + limit_order_id_type sell_swan = create_sell_order(seller2, bitusd.amount(10000), core.amount(1600) )->id; + BOOST_CHECK_EQUAL( db.find( sell_swan )->for_sale.value, 10000 ); + + // Create a force settlement, will be matched with several call orders later + auto result = force_settle( seller, bitusd.amount(200000) ); + BOOST_REQUIRE( result.is_type() ); + BOOST_REQUIRE( result.get().value.new_objects.valid() ); + BOOST_REQUIRE( !result.get().value.new_objects->empty() ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_CHECK( db.find( settle_id ) != nullptr ); + BOOST_CHECK_EQUAL( 200000, settle_id(db).balance.amount.value ); + + // Check balances + BOOST_CHECK_EQUAL( 0, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 90000, get_balance(seller2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) ); + + // adjust price feed to get call and call2 (but not call3) into margin call territory + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(16); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 100/16, mssp = 1000/176, mcop = 100/16 * 100/107 = 625/107, mcpr = 110/107 + + const auto& check_result = [&] + { + // matching call with sell_swan would trigger a black swan event, so it's skipped + // so matching call with settle + // the settle order is bigger so call is fully filled + BOOST_CHECK( !db.find( call_id ) ); + // call pays 15000, gets 100000 + // settle receives round_up(15000 * 107 / 110) = 14591, margin call fee = 409 + + // now it is able to match call2 with sell_swan + // call2 is bigger, sell_swan is fully filled + BOOST_CHECK( !db.find( sell_swan ) ); + // sell_swan pays 10000, gets 1600 + // call2 pays round_down(1600 * 110 / 107) = 1644, margin call fee = 44 + + // now match call2 with settle + // the settle order is bigger so call2 is fully filled + BOOST_CHECK( !db.find( call2_id ) ); + // call2 gets 90000, pays round_up(90000 * (16/100) * (11/10)) = 15840 + // settle receives round_up(90000 * (16/100) * (107/100)) = 15408, margin call fee = 432 + + // the settle order is not fully filled + BOOST_CHECK_EQUAL( 10000, settle_id(db).balance.amount.value ); + + // no change to call3 + BOOST_CHECK_EQUAL( 100000, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 40000, call3_id(db).collateral.value ); + + // blackswan event did not occur + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + + // check balances + BOOST_CHECK_EQUAL( 0, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 14591+15408, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 90000, get_balance(seller2_id, usd_id) ); + BOOST_CHECK_EQUAL( 1600, get_balance(seller2_id, core_id) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower_id, core_id) ); + BOOST_CHECK_EQUAL( init_balance - 1644 - 15840, get_balance(borrower2_id, core_id) ); + BOOST_CHECK_EQUAL( init_balance - 40000, get_balance(borrower3_id, core_id) ); + }; + + // check + check_result(); + + // generate a block + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + BOOST_TEST_MESSAGE( "Check again" ); + + // check + check_result(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 9b2bfffe2fd150222d79f5b13012f8f7fe78e431 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 17 Sep 2021 13:23:22 +0000 Subject: [PATCH 194/258] Remove an unused variable --- libraries/chain/include/graphene/chain/market_evaluator.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 8df92ff464..f9d94ddd5f 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -54,7 +54,6 @@ namespace graphene { namespace chain { private: share_type _deferred_fee = 0; asset _deferred_paid_fee; - const limit_order_create_operation* _op = nullptr; const account_object* _seller = nullptr; const asset_object* _sell_asset = nullptr; const asset_object* _receive_asset = nullptr; From c253f40e9ffbef06a5986f68cff5dca77c24c5d5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 17 Sep 2021 14:11:44 +0000 Subject: [PATCH 195/258] Check call orders after paid from individual fund --- libraries/chain/asset_evaluator.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index a1fa87e2fe..0082ab0a88 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -1229,8 +1229,17 @@ static extendable_operation_result pay_settle_from_individual_pool( database& d, d.adjust_balance( op.account, settled_amount ); // Update current_feed since fund price changed + auto old_feed_price = bitasset.current_feed.settlement_price; d.update_bitasset_current_feed( bitasset, true ); + // When current_feed is updated, it is possible that there are limit orders able to get filled, + // so we need to call check_call_orders() + // Note: theoretically, if the fund is still not empty, its new CR should be >= old CR, + // in this case, calling check_call_orders() should not change anything. + // Note: there should be no existing force settlements + if( 0 == bitasset.individual_settlement_debt && old_feed_price != bitasset.current_feed.settlement_price ) + d.check_call_orders( asset_to_settle, true, false, &bitasset ); + extendable_operation_result result; result.value.paid = vector({ pays }); From d357023fbb8d399244400a8d0008f71e31636ded Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 18 Sep 2021 15:47:35 +0000 Subject: [PATCH 196/258] Add tests for individual settlement to order --- tests/tests/bsrm_indvd_settlement_tests.cpp | 308 +++++++++++++++++++- 1 file changed, 304 insertions(+), 4 deletions(-) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index a1fea75c8e..6baa2fc359 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -35,8 +35,308 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) -/// Tests individual settlement to order -BOOST_AUTO_TEST_CASE( individual_settlement_to_order_test ) +/// Tests individual settlement (to order or fund) : how call orders are being processed when price drops +BOOST_AUTO_TEST_CASE( individual_settlement_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // two passes, one for individual settlement to order, the other for individual settlement to fund + for( int i = 0; i < 2; ++ i ) + { + idump( (i) ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller)(seller2)(seller3)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + fund( borrower4, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = (i == 0) ? static_cast(bsrm_type::individual_settlement_to_order) + : static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + if( 0 == i ) + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_order ); + else if( 1 == i ) + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // 100000 / 2000 = 50 + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // 100000 / 2100 = 47.619047619 + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // 100000 / 2200 = 45.454545455 + // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760 + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // 100000 / 2500 = 40 + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 + const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); + BOOST_REQUIRE( call4_ptr ); + call_order_id_type call4_id = call4_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller, asset(100000,mpa_id) ); + transfer( borrower4, seller2, asset(50000,mpa_id) ); + transfer( borrower4, seller3, asset(50000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 300000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 50000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 50000 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 ); + + // seller sells some + const limit_order_object* sell_low = create_sell_order( seller, asset(10000,mpa_id), asset(190) ); + BOOST_REQUIRE( sell_low ); + limit_order_id_type sell_low_id = sell_low->id; + BOOST_CHECK_EQUAL( sell_low_id(db).for_sale.value, 10000 ); + + // seller sells some + const limit_order_object* sell_mid = create_sell_order( seller, asset(100000,mpa_id), asset(2000) ); + BOOST_REQUIRE( sell_mid ); + limit_order_id_type sell_mid_id = sell_mid->id; + BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100000 ); + + // seller sells some + const limit_order_object* sell_high = create_sell_order( seller, asset(100000,mpa_id), asset(2400) ); + BOOST_REQUIRE( sell_high ); + limit_order_id_type sell_high_id = sell_high->id; + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + + // seller2 settles + auto result = force_settle( seller2, asset(50000,mpa_id) ); + force_settlement_id_type settle_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find( settle_id ) ); + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 50000 ); + + // seller3 settles + result = force_settle( seller3, asset(10000,mpa_id) ); + force_settlement_id_type settle2_id = *result.get().value.new_objects->begin(); + BOOST_REQUIRE( db.find( settle2_id ) ); + BOOST_CHECK_EQUAL( settle2_id(db).balance.amount.value, 10000 ); + + // check + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // 300000 - 10000 - 100000 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 0 ); // 50000 - 50000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40000 ); // 50000 - 10000 + BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 ); + + // publish a new feed so that call, call2 and call3 are undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1800) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 + // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789 + + auto check_result = [&] + { + // sell_low price is 10000/190 = 52.631578947 + // call is matched with sell_low + // call pays price is (10000/190) * (1239/1250) + // sell_low is smaller thus fully filled + BOOST_CHECK( !db.find( sell_low_id ) ); + // sell_low gets 190, pays 10000 + // call gets 10000, pays round_down(190 * 1250/1239) = 191, margin call fee = 1 + // call is now (100000-10000):(2000-191) = 90000:1809 = 49.751243781 (< 50) + + // sell_mid price is 100000/2000 = 50 + // call is matched with sell_mid + // call pays price is (100000/2000) * (1239/1250) + // call is smaller + // call gets 90000, pays round_up(90000 * (2000/100000) * (1250/1239)) = 1815 + // 1815 > 1809, unable to fill + + // call is matched with settle + // settle is smaller thus fully filled + BOOST_CHECK( !db.find( settle_id ) ); + // unable to pay at MSSP + // call pays at its own price + // settle receives round_down(50000 * (1809/90000) * (1239/1250)) = 996 + // settle pays round_up(996 * (90000/1809) * (1250/1239)) = 49993, refund 7 + // call receives 49993 + // call pays round_down(49993 * 1809/90000) = 1004, margin call fee = 1004 - 996 = 8 + // call is now (90000-49993):(1809-1004) = 40007:805 = 49.698136646 (< 90000:1809) + + // call is matched with sell_mid again + // call gets 40007, pays round_up(40007 * (2000/100000) * (1250/1239)) = 807 + // 807 > 805, unable to fill + + // call is matched with settle2 + // settle2 is smaller thus fully filled + BOOST_CHECK( !db.find( settle2_id ) ); + // unable to pay at MSSP + // call pays at its own price + // settle2 receives round_down(10000 * (805/40007) * (1239/1250)) = 199 + // settle2 pays round_up(199 * (40007/805) * (1250/1239)) = 9978, refund 22 + // call receives 9978 + // call pays round_down(9978 * 805/40007) = 200, margin call fee = 200 - 199 = 1 + // call is now (40007-9978):(805-200) = 30029:605 = 49.634710744 (< 40007:805) + + // call is matched with sell_mid again + // call gets 30029, pays round_up(30029 * (2000/100000) * (1250/1239)) = 606 + // 606 > 605, unable to fill + + // no settle order + // call is individually settled + BOOST_CHECK( !db.find( call_id ) ); + // fund gets round_up(605 * 1239/1250) = 600, margin call fee = 605 - 600 = 5 + // fund debt = 30029 + + if( 0 == i ) // to order + { + // call2 is matched with sell_mid + // the size is the same, consider call2 as smaller + // call2 gets 100000, pays round_up(100000 * (2000/100000) * (1250/1239)) = 2018, margin call fee = 18 + // 2018 < 2100, able to fill + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( sell_mid_id ) ); + + // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + + // no settle order + // call3 is individually settled + BOOST_CHECK( !db.find( call3_id ) ); + // fund gets round_up(2200 * 1239/1250) = 2181, margin call fee = 2200 - 2181 = 19 + // fund debt += 100000 + + // call4 is not undercollateralized + BOOST_REQUIRE( db.find( call4_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); + BOOST_REQUIRE( settled_debt ); + + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 2781 ); // 600 + 2181 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 130029 ); // 30029 + 100000 + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2018 ); // refund 82 + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 7 ); // refund 7 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22 + BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 ); + } + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + +/// Tests individual settlement to order : settles when price drops, and how orders are being matched after settled +BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) { try { @@ -113,12 +413,12 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_order_test ) BOOST_REQUIRE( call2_ptr ); call_order_id_type call2_id = call2_ptr->id; - // undercollateralization price = 100000:3000 * 1250:1000 = 100000:1760 + // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760 const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); BOOST_REQUIRE( call3_ptr ); call_order_id_type call3_id = call3_ptr->id; - // undercollateralization price = 100000:4000 * 1250:1000 = 100000:2400 + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); BOOST_REQUIRE( call4_ptr ); call_order_id_type call4_id = call4_ptr->id; From 7f9e15b9581d74e4e06c0b433d8fd79992495eb3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 18 Sep 2021 18:58:41 +0000 Subject: [PATCH 197/258] Fix code smells --- libraries/chain/db_market.cpp | 7 ++++--- libraries/chain/db_notify.cpp | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index d0fde8e7a1..a8c4a8133e 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1159,8 +1159,9 @@ database::match_result_type database::match( const limit_order_object& bid, cons maintenance_collateralization = bitasset.current_maintenance_collateralization; asset usd_for_sale = bid.amount_for_sale(); - asset usd_to_buy = asset( ask.get_max_debt_to_cover( call_pays_price, feed_price, - maintenance_collateral_ratio, maintenance_collateralization ), ask.debt_type() ); + asset usd_to_buy( ask.get_max_debt_to_cover( call_pays_price, feed_price, maintenance_collateral_ratio, + maintenance_collateralization ), + ask.debt_type() ); asset call_pays; asset call_receives; @@ -2298,7 +2299,7 @@ asset database::pay_force_settle_fees(const asset_object& collecting_asset, cons auto value = detail::calculate_percent(collat_receives.amount, *collecting_bitasset_opts.extensions.value.force_settle_fee_percent); - asset settle_fee = asset{ value, collat_receives.asset_id }; + asset settle_fee( value, collat_receives.asset_id ); // Deposit fee in asset's dynamic data object: if( value > 0) { diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index a2c2c87210..8f33f8a15d 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -391,8 +391,7 @@ struct get_impacted_account_visitor void operation_get_impacted_accounts( const operation& op, flat_set& result, bool ignore_custom_op_required_auths ) { - detail::get_impacted_account_visitor vtor = detail::get_impacted_account_visitor( result, - ignore_custom_op_required_auths ); + detail::get_impacted_account_visitor vtor( result, ignore_custom_op_required_auths ); op.visit( vtor ); } From 21c93b475930dfd2afeae0f8afd2e2a07b3cbdef Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 21 Sep 2021 21:42:46 +0000 Subject: [PATCH 198/258] Fix individual_settlement_to_fund Settle undercollateralized debt position when unable to match with a limit order --- libraries/chain/db_market.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index a8c4a8133e..354d00f6b2 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -129,7 +129,14 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // due to margin call fee, we check with MCPP (margin call pays price) here call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); } - highest = std::max( call_pays_price, highest ); + if( bsrm_type::individual_settlement_to_fund != bsrm ) + highest = std::max( call_pays_price, highest ); + // for individual_settlement_to_fund, if call_pays_price < current_feed.max_short_squeeze_price(), + // we don't match the least collateralized short with the limit order + // even if call_pays_price >= median_feed.max_short_squeeze_price() + else if( call_pays_price >= bitasset.current_feed.max_short_squeeze_price() ) + highest = call_pays_price; + // else highest is median_feed.max_short_squeeze_price() } // The variable `highest` after hf_338: From 9cecdb566366442f00edc1bd649d652aec1a5f02 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 21 Sep 2021 22:06:39 +0000 Subject: [PATCH 199/258] Add tests for individual settlement to fund --- tests/tests/bsrm_indvd_settlement_tests.cpp | 68 +++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index 6baa2fc359..fcea75d284 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -312,6 +312,74 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 7 ); // refund 7 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 ); + BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22 + BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 ); + } + else if( 1 == i ) // to fund + { + // sell_mid price is 100000/2000 = 50 + // call pays price is (100000/2000) * (1239:1250) = 49.56 + + // median feed is 100000/1800 = 55.555555556 + // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 + // call match price = 100000:1800 * 1000:1239 = 100000:2230.2 = 44.83902789 + + // fund collateral = 600 + // fund debt = 30029 + // current feed is capped at (30029:600) * (1239:1000) = 62.009885 + // call pays price is (30029:600) * (1239:1250) = 49.607908 + // call match price is 30029/600 = 50.048333333 (> sell_mid.price) + + // call2 will not match with sell_mid + // call2 is individually settled + BOOST_CHECK( !db.find( call2_id ) ); + // fund gets round_up(2100 * 1239/1250) = 2082, margin call fee = 2100 - 2082 = 18 + // fund debt += 100000 + + // fund collateral = 600 + 2082 = 2682 + // fund debt = 30029 + 100000 = 130029 + // current feed is capped at (130029:2682) * (1239:1000) = 60.069325503 + // call pays price is (130029:2682) * (1239:1250) = 48.055460403 + // call match price is 130029/2682 = 48.482102908 + + // call3 is matched with sell_mid + // the size is the same, consider call3 as smaller + // call3 gets 100000, pays round_up(100000 * (2000/100000) * (1250/1239)) = 2018, margin call fee = 18 + // 2018 < 2200, able to fill + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK( !db.find( sell_mid_id ) ); + + // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + + // call4 is not undercollateralized + BOOST_REQUIRE( db.find( call4_id ) ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 2682 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 130029 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(130029*1239,mpa_id), asset(2682*1000) ) ); + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 7 ); // refund 7 From 9240fa74c5ba5fa2c9d454b4519edc35a3c11f01 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 22 Sep 2021 20:24:38 +0000 Subject: [PATCH 200/258] Refactor database::check_call_orders() and fix individual settlements: always check limit orders again after individually settled some debt positions --- libraries/chain/db_market.cpp | 267 +++++++++++++++++----------------- 1 file changed, 134 insertions(+), 133 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 354d00f6b2..2e5c878ddc 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1843,41 +1843,36 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const auto& settlement_index = get_index_type().indices().get(); - // If we filled a call order with a settle order, - // we need to check limit orders again, in this case we loop multiple times. - // In other cases we only need to loop once. - while( true ) + while( has_call_order() ) { - // If BSRM is individual settlement, check for blackswan first - if( ( bsrm_type::individual_settlement_to_fund == bsrm || bsrm_type::individual_settlement_to_order == bsrm ) - // Note: only call when conditions above are true - && check_for_blackswan( mia, enable_black_swan, &bitasset ) ) // TODO perhaps improve performance - // by passing in iterators + // check for blackswan first // TODO perhaps improve performance by passing in iterators + bool settled_some = check_for_blackswan( mia, enable_black_swan, &bitasset ); + if( bitasset.has_settlement() ) + return margin_called; + + if( settled_some ) // which implies that BSRM is individual settlement to fund or to order { - margin_called = true; call_collateral_itr = call_collateral_index.lower_bound( call_min ); + if( call_collateral_itr == call_collateral_end ) + return true; + margin_called = true; if( bsrm_type::individual_settlement_to_fund == bsrm ) limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); } - // match call orders with limit orders - while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance - // by passing in iterators - && limit_itr != limit_end - && has_call_order() ) - { - bool filled_call = false; - - const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); + // be here, there exists at least one call order + const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); - // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - bool feed_protected = before_core_hardfork_1270 ? - ( after_hardfork_436 - && bitasset.current_feed.settlement_price > ~call_order.call_price ) - : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); - if( feed_protected ) - return margin_called; + // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 + bool feed_protected = before_core_hardfork_1270 ? + ( after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) + : ( bitasset.current_maintenance_collateralization < call_order.collateralization() ); + if( feed_protected ) + return margin_called; + // match call orders with limit orders + if( limit_itr != limit_end ) + { const limit_order_object& limit_order = *limit_itr; price match_price = limit_order.sell_price; @@ -1928,130 +1923,136 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa asset usd_for_sale = limit_order.amount_for_sale(); asset call_pays, call_receives, limit_pays, limit_receives; - if( usd_to_buy > usd_for_sale ) - { // fill order - limit_receives = usd_for_sale * match_price; // round down, in favor of call order - - // Be here, the limit order won't be paying something for nothing, since if it would, it would have - // been cancelled elsewhere already (a maker limit order won't be paying something for nothing): - // * after hard fork core-625, the limit order will be always a maker if entered this function; - // * before hard fork core-625, - // * when the limit order is a taker, it could be paying something for nothing only when - // the call order is smaller and is too small - // * when the limit order is a maker, it won't be paying something for nothing - - if( before_core_hardfork_342 ) - call_receives = usd_for_sale; - else - // The remaining amount in the limit order would be too small, - // so we should cull the order in fill_limit_order() below. - // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, - // so calling maybe_cull_small() will always cull it. - call_receives = limit_receives.multiply_and_round_up( match_price ); - - if( !after_core_hardfork_2481 ) - // TODO add tests about CR change - call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) - else - { - call_pays = call_receives * call_pays_price; // calculate with updated call_receives - if( call_pays.amount >= call_order.collateral ) - break; - auto new_collateral = call_order.get_collateral() - call_pays; - auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math - if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease - break; - } - filled_limit = true; + struct UndercollateralizationException {}; + try { // throws UndercollateralizationException if the call order is undercollateralized + + bool filled_call = false; + + if( usd_to_buy > usd_for_sale ) + { // fill order + limit_receives = usd_for_sale * match_price; // round down, in favor of call order + + // Be here, the limit order won't be paying something for nothing, since if it would, it would have + // been cancelled elsewhere already (a maker limit order won't be paying something for nothing): + // * after hard fork core-625, the limit order will be always a maker if entered this function; + // * before hard fork core-625, + // * when the limit order is a taker, it could be paying something for nothing only when + // the call order is smaller and is too small + // * when the limit order is a maker, it won't be paying something for nothing + + if( before_core_hardfork_342 ) + call_receives = usd_for_sale; + else + // The remaining amount in the limit order would be too small, + // so we should cull the order in fill_limit_order() below. + // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, + // so calling maybe_cull_small() will always cull it. + call_receives = limit_receives.multiply_and_round_up( match_price ); + + if( !after_core_hardfork_2481 ) + // TODO add tests about CR change + call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + else + { + call_pays = call_receives * call_pays_price; // calculate with updated call_receives + if( call_pays.amount >= call_order.collateral ) + throw UndercollateralizationException(); + auto new_collateral = call_order.get_collateral() - call_pays; + auto new_debt = call_order.get_debt() - call_receives; // the result is positive due to math + if( ( new_collateral / new_debt ) < call_order.collateralization() ) // if CR would decrease + throw UndercollateralizationException(); + } + + filled_limit = true; - } else { // fill call, could be partial fill due to TCR - call_receives = usd_to_buy; + } else { // fill call, could be partial fill due to TCR + call_receives = usd_to_buy; - if( before_core_hardfork_342 ) - { - limit_receives = usd_to_buy * match_price; // round down, in favor of call order - call_pays = limit_receives; - } else { - call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. - // Note: Due to different rounding, this could potentialy be - // one satoshi more than the blackswan check above - if( call_pays.amount > call_order.collateral ) + if( before_core_hardfork_342 ) { - if( after_core_hardfork_2481 ) - break; - if( mute_exceptions ) - call_pays.amount = call_order.collateral; + limit_receives = usd_to_buy * match_price; // round down, in favor of call order + call_pays = limit_receives; + } else { + call_pays = usd_to_buy.multiply_and_round_up( call_pays_price ); // BSIP74; excess is fee. + // Note: Due to different rounding, this could potentialy be + // one satoshi more than the blackswan check above + if( call_pays.amount > call_order.collateral ) + { + if( after_core_hardfork_2481 ) + throw UndercollateralizationException(); + if( mute_exceptions ) + call_pays.amount = call_order.collateral; + } + // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher + // than the old CR, so no additional check for potential blackswan here + + limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, favors limit order + if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 + limit_receives.amount = call_order.collateral; + // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more + // debt asset, it means the call order could be receiving a bit too much less than its value. + // It is a sad thing for the call order, but it is the rule + // -- when a call order is margin called, it does not get more than it borrowed. + // On the other hand, if the call order is not being closed (due to TCR), + // it means get_max_debt_to_cover() did not return a perfect result, maybe we can improve it. } - // Note: if it is a partial fill due to TCR, the math guarantees that the new CR will be higher - // than the old CR, so no additional check for potential blackswan here - - limit_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order - if( limit_receives.amount > call_order.collateral ) // implies !after_hf_2481 - limit_receives.amount = call_order.collateral; - // Note: here we don't re-assign call_receives with (orders_receives * match_price) to receive more - // debt asset, it means the call order could be receiving a bit too much less than its value. - // It is a sad thing for the call order, but it is the rule - // -- when a call order is margin called, it does not get more than it borrowed. - // On the other hand, if the call order is not being closed (due to TCR), - // it means get_max_debt_to_cover() did not return a perfect result, probably we can improve it. + + filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) + + if( usd_to_buy == usd_for_sale ) + filled_limit = true; + else if( filled_limit && before_hardfork_615 ) + //NOTE: Multiple limit match problem (see issue 453, yes this happened) + _issue_453_affected_assets.insert( bitasset.asset_id ); } + limit_pays = call_receives; - filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hf core-343) + // BSIP74: Margin call fee + FC_ASSERT(call_pays >= limit_receives); + const asset margin_call_fee = call_pays - limit_receives; - if( usd_to_buy == usd_for_sale ) - filled_limit = true; - else if( filled_limit && before_hardfork_615 ) - //NOTE: Multiple limit match problem (see issue 453, yes this happened) - _issue_453_affected_assets.insert( bitasset.asset_id ); - } - limit_pays = call_receives; + if( filled_call && before_core_hardfork_343 ) + ++call_price_itr; - // BSIP74: Margin call fee - FC_ASSERT(call_pays >= limit_receives); - const asset margin_call_fee = call_pays - limit_receives; + // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker + fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); - if( filled_call && before_core_hardfork_343 ) - ++call_price_itr; + // Update current_feed after filled call order if needed + if( update_current_feed ) + { + update_bitasset_current_feed( bitasset, true ); + limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); + update_current_feed = bitasset.is_current_feed_price_capped(); + } - // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker - fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order, margin_call_fee); + if( !before_core_hardfork_1270 ) + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + else if( !before_core_hardfork_343 ) + call_price_itr = call_price_index.lower_bound( call_min ); - // Update current_feed after filled call order if needed - if( update_current_feed ) - { - update_bitasset_current_feed( bitasset, true ); - limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); - update_current_feed = bitasset.is_current_feed_price_capped(); + auto next_limit_itr = std::next( limit_itr ); + // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker + bool really_filled = fill_limit_order( limit_order, limit_pays, limit_receives, true, + match_price, !for_new_limit_order ); + if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) + limit_itr = next_limit_itr; + + continue; // check for blackswan again + + } catch( const UndercollateralizationException& ) { + // Nothing to do here } + } // if there is a matching limit order - if( !before_core_hardfork_1270 ) - call_collateral_itr = call_collateral_index.lower_bound( call_min ); - else if( !before_core_hardfork_343 ) - call_price_itr = call_price_index.lower_bound( call_min ); - - auto next_limit_itr = std::next( limit_itr ); - // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker - bool really_filled = fill_limit_order( limit_order, limit_pays, limit_receives, true, - match_price, !for_new_limit_order ); - if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) - limit_itr = next_limit_itr; - - } // while !blackswan and call_itr != call_end and limit_itr != limit_end - - // If already GSed, or there is no call order, we return. - // Note: if GSed, the iterators used in has_call_order() should have been invalidated - if( bitasset.has_settlement() || !has_call_order() ) - return margin_called; + // be here, it is unable to fill a limit order due to undercollateralization (and there is a force settlement), + // or there is no matching limit order due to MSSR, or no limit order at all // If no need to process force settlements, we return + // Note: before core-2481/2467 hf, or BSRM is no_settlement and processing a new force settlement if( skip_matching_settle_orders || !after_core_hardfork_2481 ) return margin_called; - // If feed protected, we return - if( bitasset.current_maintenance_collateralization < call_collateral_itr->collateralization() ) - return margin_called; - // If no force settlements, we return auto settle_itr = settlement_index.lower_bound( bitasset.asset_id ); if( settle_itr == settlement_index.end() || settle_itr->balance.asset_id != bitasset.asset_id ) @@ -2071,8 +2072,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } } // else : no more force settlements, or feed protected, both will be handled in the next loop - } // while true - + } // while there exists a call order + return margin_called; } FC_CAPTURE_AND_RETHROW() } bool database::match_force_settlements( const asset_bitasset_data_object& bitasset ) From 8ec1e03e1fbb26fca2c8d2f1288af3e8264576f0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 22 Sep 2021 22:05:44 +0000 Subject: [PATCH 201/258] Add more tests about individual settlements --- tests/tests/bsrm_indvd_settlement_tests.cpp | 98 ++++++++++++++++++--- 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index fcea75d284..d8414567f4 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -51,7 +51,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) set_expiration( db, trx ); - ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(seller)(seller2)(seller3)); + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(seller)(seller2)(seller3)(seller4)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); @@ -60,6 +60,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) fund( borrower2, asset(init_amount) ); fund( borrower3, asset(init_amount) ); fund( borrower4, asset(init_amount) ); + fund( borrower5, asset(init_amount) ); using bsrm_type = bitasset_options::black_swan_response_type; uint8_t bsrm_value = (i == 0) ? static_cast(bsrm_type::individual_settlement_to_order) @@ -108,6 +109,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); @@ -137,12 +139,19 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_REQUIRE( call4_ptr ); call_order_id_type call4_id = call4_ptr->id; + // 100000 / 2240 = 44.642857143 + // undercollateralization price = 100000:2240 * 1250:1000 = 100000:1792 + const call_order_object* call5_ptr = borrow( borrower5, asset(1000000, mpa_id), asset(22400) ); + BOOST_REQUIRE( call5_ptr ); + call_order_id_type call5_id = call5_ptr->id; + // Transfer funds to sellers transfer( borrower, seller, asset(100000,mpa_id) ); transfer( borrower2, seller, asset(100000,mpa_id) ); transfer( borrower3, seller, asset(100000,mpa_id) ); transfer( borrower4, seller2, asset(50000,mpa_id) ); transfer( borrower4, seller3, asset(50000,mpa_id) ); + transfer( borrower5, seller4, asset(1000000,mpa_id) ); BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); @@ -152,11 +161,14 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + BOOST_CHECK_EQUAL( call5_id(db).debt.value, 1000000 ); + BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 22400 ); BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 300000 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); @@ -164,6 +176,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 50000 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 1000000 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 0 ); // seller sells some const limit_order_object* sell_low = create_sell_order( seller, asset(10000,mpa_id), asset(190) ); @@ -177,6 +191,12 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) limit_order_id_type sell_mid_id = sell_mid->id; BOOST_CHECK_EQUAL( sell_mid_id(db).for_sale.value, 100000 ); + // seller4 sells some + const limit_order_object* sell_mid2 = create_sell_order( seller4, asset(20000,mpa_id), asset(439) ); + BOOST_REQUIRE( sell_mid2 ); + limit_order_id_type sell_mid2_id = sell_mid2->id; + BOOST_CHECK_EQUAL( sell_mid2_id(db).for_sale.value, 20000 ); + // seller sells some const limit_order_object* sell_high = create_sell_order( seller, asset(100000,mpa_id), asset(2400) ); BOOST_REQUIRE( sell_high ); @@ -204,6 +224,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + BOOST_CHECK_EQUAL( call5_id(db).debt.value, 1000000 ); + BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 22400 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // 300000 - 10000 - 100000 - 100000 BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); @@ -211,8 +233,10 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40000 ); // 50000 - 10000 BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // 1000000 - 20000 + BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 0 ); - // publish a new feed so that call, call2 and call3 are undercollateralized + // publish a new feed so that call, call2, call3 and call5 are undercollateralized f.settlement_price = price( asset(100000,mpa_id), asset(1800) ); publish_feed( mpa_id, feeder_id, f, feed_icr ); // call pays price = 100000:1800 * 1000:1250 = 100000:2250 = 44.444444444 @@ -281,16 +305,32 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( !db.find( call2_id ) ); BOOST_CHECK( !db.find( sell_mid_id ) ); - // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) - BOOST_REQUIRE( db.find( sell_high_id ) ); - BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + // sell_mid2 price is 20000/439 = 45.55808656 + // call pays price is (20000/439) * (1239/1250) = 45.157175399 - // no settle order + // call3 is 100000/2200 = 45.454545455 (>45.157175399), so unable to fill // call3 is individually settled BOOST_CHECK( !db.find( call3_id ) ); // fund gets round_up(2200 * 1239/1250) = 2181, margin call fee = 2200 - 2181 = 19 // fund debt += 100000 + // call5 is 1000000/22400 = 44.642857143 (<45.157175399) + // call5 is matched with sell_mid2 + // sell_mid2 is smaller thus fully filled + BOOST_CHECK( !db.find( sell_mid2_id ) ); + // sell_mid2 gets 439, pays 20000 + // call5 gets 20000, pays round_down(439 * 1250/1239) = 442, margin call fee = 3 + // call5 is now (1000000-20000):(22400-442) = 980000:21958 = 44.63065853 (> MSSP 44.444444444) + + // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) + BOOST_REQUIRE( db.find( sell_high_id ) ); + BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); + + // call5 is individually settled + BOOST_CHECK( !db.find( call5_id ) ); + // fund gets round_up(21958 * 1239/1250) = 21765, margin call fee = 21958 - 21765 = 193 + // fund debt += 980000 + // call4 is not undercollateralized BOOST_REQUIRE( db.find( call4_id ) ); BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); @@ -299,18 +339,20 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); const limit_order_object* settled_debt = db.find_settled_debt_order(mpa_id); BOOST_REQUIRE( settled_debt ); - BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 2781 ); // 600 + 2181 - BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 130029 ); // 30029 + 100000 + BOOST_CHECK_EQUAL( settled_debt->for_sale.value, 24546 ); // 600 + 2181 + 21765 + BOOST_CHECK_EQUAL( settled_debt->amount_to_receive().amount.value, 1110029 ); // 30029 + 100000 + 980000 BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2018 ); // refund 82 BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2200 ); BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 @@ -318,6 +360,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22 BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 439 ); // 439 } else if( 1 == i ) // to fund { @@ -344,7 +388,7 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) // fund debt = 30029 + 100000 = 130029 // current feed is capped at (130029:2682) * (1239:1000) = 60.069325503 // call pays price is (130029:2682) * (1239:1250) = 48.055460403 - // call match price is 130029/2682 = 48.482102908 + // call match price is 130029/2682 = 48.482102908 (< sell_mid.price) // call3 is matched with sell_mid // the size is the same, consider call3 as smaller @@ -353,14 +397,36 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( !db.find( call3_id ) ); BOOST_CHECK( !db.find( sell_mid_id ) ); + // sell_mid2 price is 20000/439 = 45.55808656 + // call match price is 130029/2682 = 48.482102908 (> sell_mid2.price) + + // call5 will not match with sell_mid2 + // call5 is individually settled + BOOST_CHECK( !db.find( call5_id ) ); + // fund gets round_up(22400 * 1239/1250) = 22203, margin call fee = 22400 - 22203 = 197 + // fund debt += 1000000 + + // fund collateral = 600 + 2082 + 22203 = 24885 + // fund debt = 30029 + 100000 + 100000 = 1130029 + // current feed is capped at (1130029:24885) * (1239:1000) = 56.263047257 + // call pays price is (1130029:24885) * (1239:1250) = 45.010437806 + // call match price is 1130029/24885 = 45.410046213 (< sell_mid2.price) + + // sell_mid2 is matched with call4 + // sell_mid2 is smaller thus fully filled + BOOST_CHECK( !db.find( sell_mid2_id ) ); + // sell_mid2 gets 439, pays 20000 + // call4 gets 20000, pays round_down(439 * 1250/1239) = 442, margin call fee = 3 + // call4 is now (100000-20000):(2500-442) = 80000:2058 = 38.872691934 (< MSSP 44.444444444) + // sell_high price is 100000/2400 = 41.666666667 (< call match price, so will not match) BOOST_REQUIRE( db.find( sell_high_id ) ); BOOST_CHECK_EQUAL( sell_high_id(db).for_sale.value, 100000 ); // call4 is not undercollateralized BOOST_REQUIRE( db.find( call4_id ) ); - BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); - BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 80000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2058 ); // check BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); @@ -369,16 +435,18 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); - BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 2682 ); - BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 130029 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 24885 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 1130029 ); BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price - == price( asset(130029*1239,mpa_id), asset(2682*1000) ) ); + == price( asset(1130029*1239,mpa_id), asset(24885*1000) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2100 ); BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + BOOST_CHECK_EQUAL( get_balance( borrower5_id, asset_id_type() ), init_amount - 22400 ); BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 90000 ); // no change BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 2190 ); // 190 + 2000 @@ -386,6 +454,8 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 996 ); BOOST_CHECK_EQUAL( get_balance( seller3_id, mpa_id ), 40022 ); // refund 22 BOOST_CHECK_EQUAL( get_balance( seller3_id, asset_id_type() ), 199 ); + BOOST_CHECK_EQUAL( get_balance( seller4_id, mpa_id ), 980000 ); // no change + BOOST_CHECK_EQUAL( get_balance( seller4_id, asset_id_type() ), 439 ); // 439 } }; From 63909453dcc6b44b57032048fad47c5d53dc2bab Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Sep 2021 20:22:23 +0000 Subject: [PATCH 202/258] Add disable_force_settle tests for indvd settle --- tests/tests/bsrm_indvd_settlement_tests.cpp | 259 ++++++++++++++++++++ 1 file changed, 259 insertions(+) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index d8414567f4..5ff96a93ed 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -473,6 +473,265 @@ BOOST_AUTO_TEST_CASE( individual_settlement_test ) } FC_CAPTURE_AND_RETHROW() } +/// Tests individual settlement to fund : if disable_force_settle flag is set, +/// * able to settle if the fund is not empty, +/// * and settle order is cancelled when the fund becomes empty +BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_test ) +{ try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // two passes, + // i == 0 : with valid feed, + // i == 1 : no feed + for( int i = 0; i < 2; ++ i ) + { + idump( (i) ); + + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_id_type samcoin_id = create_user_issued_asset( "SAMCOIN", sam_id(db), charge_market_fee, + price(asset(1, asset_id_type(1)), asset(1)), + 2, 100 ).id; // fee 1% + issue_uia( borrower, asset(init_amount, samcoin_id) ); + issue_uia( borrower2, asset(init_amount, samcoin_id) ); + + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee | disable_force_settle; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->feed_lifetime_sec = 300; + acop.bitasset_opts->short_backing_asset = samcoin_id; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + acop.bitasset_opts->extensions.value.force_settle_fee_percent = 300; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1,samcoin_id) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000, samcoin_id) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2500, samcoin_id) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 0 ); + + // Unable to settle when the fund is empty and disable_force_settle is set + BOOST_CHECK_THROW( force_settle( seller, asset(1000,mpa_id) ), fc::exception ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650,samcoin_id) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(100000*1239,mpa_id), asset(1983*1000,samcoin_id) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + if( 1 == i ) // let the feed expire + { + generate_blocks( db.head_block_time() + fc::seconds(350) ); + set_expiration( db, trx ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + } + + // seller settles some : allowed when fund is not empty + auto result = force_settle( seller_id(db), asset(10000,mpa_id) ); + auto op_result = result.get().value; + // seller gets round_down(10000*1983/100000) = 198, market fee 1, finally gets 197 + // seller pays round_up(198*100000/1983) = 9985 + BOOST_CHECK( !op_result.new_objects.valid() ); // no delayed force settlement + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 9985, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 197, samcoin_id ) ); + BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 1, samcoin_id ) ); + // fund is now (100000-9985):(1983-198) = 90015:1785 + + // check + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1785 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 90015 ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + if( 0 == i ) + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(90015*1239,mpa_id), asset(1785*1000,samcoin_id) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + } + else if( 1 == i ) + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + } + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 190015 ); // 200000 - 9985 + BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 197 ); + + // seller settles more, more than debt in the fund + result = force_settle( seller_id(db), asset(150000,mpa_id) ); + op_result = result.get().value; + + auto check_result = [&] + { + // seller gets 99041 + // seller pays 1964 + BOOST_CHECK( !op_result.new_objects.valid() ); // no delayed force settlement + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 90015, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1768, samcoin_id ) ); + BOOST_REQUIRE( op_result.fees.valid() && 1U == op_result.fees->size() ); + BOOST_CHECK( *op_result.fees->begin() == asset( 17, samcoin_id ) ); + // fund is now empty + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + if( 0 == i ) + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + } + else if( 1 == i ) + { + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price.is_null() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price.is_null() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + } + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); // 200000 - 9985 - 90015 + BOOST_CHECK_EQUAL( get_balance( seller_id, samcoin_id ), 1965 ); // 197 + 1768 + + // Unable to settle when the fund is empty and disable_force_settle is set + BOOST_CHECK_THROW( force_settle( seller, asset(1000,mpa_id) ), fc::exception ); + + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + // reset + db.pop_block(); + + } // for i + +} FC_CAPTURE_AND_RETHROW() } + /// Tests individual settlement to order : settles when price drops, and how orders are being matched after settled BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) { From 4dbaa3043bf408beab07fe9b7b67a8596ddd4ba5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Sep 2021 22:41:29 +0000 Subject: [PATCH 203/258] Add taking tests for individual settlement to fund --- tests/tests/bsrm_indvd_settlement_tests.cpp | 265 ++++++++++++++++++++ 1 file changed, 265 insertions(+) diff --git a/tests/tests/bsrm_indvd_settlement_tests.cpp b/tests/tests/bsrm_indvd_settlement_tests.cpp index 5ff96a93ed..53b7b352a6 100644 --- a/tests/tests/bsrm_indvd_settlement_tests.cpp +++ b/tests/tests/bsrm_indvd_settlement_tests.cpp @@ -732,6 +732,271 @@ BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_disable_force_settle_tes } FC_CAPTURE_AND_RETHROW() } +/// Tests individual settlement to fund : settles when price drops, and how taker orders would match after that +BOOST_AUTO_TEST_CASE( individual_settlement_to_fund_and_taking_test ) +{ + try { + + // Advance to core-2467 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2467_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(borrower2)(borrower3)(borrower4)(borrower5)(seller)(seller2)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( borrower2, asset(init_amount) ); + fund( borrower3, asset(init_amount) ); + fund( borrower4, asset(init_amount) ); + fund( borrower5, asset(init_amount) ); + + using bsrm_type = bitasset_options::black_swan_response_type; + uint8_t bsrm_value = static_cast(bsrm_type::individual_settlement_to_fund); + + // Create asset + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMMPA"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; // 1% + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + acop.bitasset_opts = bitasset_options(); + acop.bitasset_opts->minimum_feeds = 1; + acop.bitasset_opts->extensions.value.black_swan_response_method = bsrm_value; + acop.bitasset_opts->extensions.value.margin_call_fee_ratio = 11; + + trx.operations.clear(); + trx.operations.push_back( acop ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& mpa = db.get(ptx.operation_results[0].get()); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa.bitasset_data(db).get_black_swan_response_method() + == bsrm_type::individual_settlement_to_fund ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(100,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(100,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + BOOST_CHECK( mpa.bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( mpa.bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa.bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // borrowers borrow some + // undercollateralization price = 100000:2000 * 1250:1000 = 100000:1600 + const call_order_object* call_ptr = borrow( borrower, asset(100000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + + // undercollateralization price = 100000:2100 * 1250:1000 = 100000:1680 + const call_order_object* call2_ptr = borrow( borrower2, asset(100000, mpa_id), asset(2100) ); + BOOST_REQUIRE( call2_ptr ); + call_order_id_type call2_id = call2_ptr->id; + + // undercollateralization price = 100000:2200 * 1250:1000 = 100000:1760 + const call_order_object* call3_ptr = borrow( borrower3, asset(100000, mpa_id), asset(2200) ); + BOOST_REQUIRE( call3_ptr ); + call_order_id_type call3_id = call3_ptr->id; + + // undercollateralization price = 100000:2500 * 1250:1000 = 100000:2000 + const call_order_object* call4_ptr = borrow( borrower4, asset(100000, mpa_id), asset(2500) ); + BOOST_REQUIRE( call4_ptr ); + call_order_id_type call4_id = call4_ptr->id; + + // Transfer funds to sellers + transfer( borrower, seller, asset(100000,mpa_id) ); + transfer( borrower2, seller, asset(100000,mpa_id) ); + transfer( borrower3, seller2, asset(100000,mpa_id) ); + transfer( borrower4, seller2, asset(100000,mpa_id) ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // publish a new feed so that borrower's debt position is undercollateralized + f.settlement_price = price( asset(100000,mpa_id), asset(1650) ); + publish_feed( mpa_id, feeder_id, f, feed_icr ); + // call pays price = 100000:1650 * 1000:1250 = 100000:2062.5 = 48.484848485 + // call match price = 100000:1650 * 1000:1239 = 100000:2048.75 = 48.915303153 + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + // call: margin call fee deducted = round_down(2000*11/1250) = 17, + // fund receives 2000 - 17 = 1983 + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 1983 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 100000 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price + == price( asset(100000*1239,mpa_id), asset(1983*1000) ) ); + BOOST_CHECK( mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + // call pays price = 100000:1983 * 1239:1250 = 49.984871407 + // call match price = 100000:1983 = 50.428643469 + + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 2100 ); + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + // borrower5 is unable to borrow if CR <= real ICR + // for median_feed: 1650 * 1.9 = 3135 + // for current_feed: 1983 * 1.9 / 1.239 = 3040.9 + BOOST_CHECK_THROW( borrow( borrower5, asset(100000, mpa_id), asset(3135) ), fc::exception ); + const call_order_object* call5_ptr = borrow( borrower5, asset(100000, mpa_id), asset(3136) ); + BOOST_REQUIRE( call5_ptr ); + call_order_id_type call5_id = call5_ptr->id; + + BOOST_CHECK_EQUAL( call5_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call5_id(db).collateral.value, 3136 ); + + // seller sells some + const limit_order_object* limit_ptr = create_sell_order( seller, asset(80000,mpa_id), asset(100) ); + // the limit order is filled + BOOST_CHECK( !limit_ptr ); + + // call2 is partially filled + // limit order gets round_down(80000*(1983/100000)) = 1586 + // limit order pays round_up(1586*(100000/1983)) = 79980 + // call2 gets 79980 + // call2 pays round_down(79980*(1983/100000)*(1250/1239)) = 1600, margin call fee = 14 + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK_EQUAL( call2_id(db).debt.value, 20020 ); // 100000 - 79980 + BOOST_CHECK_EQUAL( call2_id(db).collateral.value, 500 ); // 2100 - 1600 + // 20020 / 500 = 40.04 + BOOST_CHECK_EQUAL( call3_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call3_id(db).collateral.value, 2200 ); + // 100000 / 2200 = 45.454545455 + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 100000 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 2500 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 120020 ); // 200000 - 79980 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 1586 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 200000 ); + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 0 ); + + // seller sells more, this order is below MSSP so will not be matched right now + limit_ptr = create_sell_order( seller, asset(100000,mpa_id), asset(2000) ); + // the limit order is not filled + BOOST_REQUIRE( limit_ptr ); + limit_order_id_type limit_id = limit_ptr->id; + + BOOST_CHECK_EQUAL( limit_ptr->for_sale.value, 100000 ); + + // unable to settle too little amount + BOOST_CHECK_THROW( force_settle( seller2, asset(50,mpa_id) ), fc::exception ); + + // seller2 settles + auto result = force_settle( seller2, asset(150000,mpa_id) ); + auto op_result = result.get().value; + + auto check_result = [&] + { + // seller2 gets 1983 + // seller2 pays 100000 + BOOST_REQUIRE( op_result.new_objects.valid() ); // force settlement order created + force_settlement_id_type settle_id = *op_result.new_objects->begin(); + + BOOST_REQUIRE( op_result.paid.valid() && 1U == op_result.paid->size() ); + BOOST_CHECK( *op_result.paid->begin() == asset( 100000, mpa_id ) ); + BOOST_REQUIRE( op_result.received.valid() && 1U == op_result.received->size() ); + BOOST_CHECK( *op_result.received->begin() == asset( 1983 ) ); + // fund is now empty + + // check + BOOST_CHECK( mpa_id(db).bitasset_data(db).median_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).has_individual_settlement() ); + BOOST_CHECK( !db.find_settled_debt_order(mpa_id) ); + + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_fund.value, 0 ); + BOOST_CHECK_EQUAL( mpa_id(db).bitasset_data(db).individual_settlement_debt.value, 0 ); + + BOOST_CHECK( mpa_id(db).bitasset_data(db).current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).is_current_feed_price_capped() ); + + // call3 is the least collateralized short, matched with the limit order, both filled + BOOST_CHECK( !db.find(call3_id) ); + BOOST_CHECK( !db.find(limit_id) ); + // same size, consider call3 as smaller + // call3 match price 100000:2000 + // call3 gets 100000, pays round_up(2000 * 1250/1239) = 2018, margin call fee 18 + + // settle order is matched with call2 + // call2 is smaller + // call2 gets 20020, pays round_up(20020 * (1650/100000) * (1250/1000)) = 413 + // settle order gets round_up(20020 * (1650/100000) * (1239/1000)) = 410, margin call fee = 3 + + // settle order is matched with call4 + // settle order is smaller + BOOST_CHECK( !db.find(settle_id) ); + // settle order gets round_down((50000-20020) * (1650/100000) * (1239/1000)) = 612 + // settle order pays round_up(612 * (100000/1650) * (1000/1239)) = 29937 + // call4 gets 29937 + // call4 pays round_down(29937 * (1650/100000) * (1250/1000)) = 617, margin call fee = 5 + // call4 is now (100000-29937):(2500-617) = 70063:1883 + BOOST_CHECK_EQUAL( call4_id(db).debt.value, 70063 ); + BOOST_CHECK_EQUAL( call4_id(db).collateral.value, 1883 ); + + BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 20020 ); // 200000 - 79980 - 100000 + BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 3586 ); // 1586 + 2000 + BOOST_CHECK_EQUAL( get_balance( seller2_id, mpa_id ), 50043 ); // 200000 - 100000 - 20020 - 29937 + BOOST_CHECK_EQUAL( get_balance( seller2_id, asset_id_type() ), 3005 ); // 1983 + 410 + 612 + + BOOST_CHECK_EQUAL( get_balance( borrower_id, asset_id_type() ), init_amount - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2_id, asset_id_type() ), init_amount - 2013 ); // refund some + BOOST_CHECK_EQUAL( get_balance( borrower3_id, asset_id_type() ), init_amount - 2018 ); // refund some + BOOST_CHECK_EQUAL( get_balance( borrower4_id, asset_id_type() ), init_amount - 2500 ); + + }; + + check_result(); + + BOOST_TEST_MESSAGE( "Generate a block" ); + generate_block(); + + check_result(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /// Tests individual settlement to order : settles when price drops, and how orders are being matched after settled BOOST_AUTO_TEST_CASE( individual_settlement_to_order_and_taking_test ) { From f9446a727b11824368a04fba411af30a72262aa8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Sep 2021 23:15:17 +0000 Subject: [PATCH 204/258] Add tests about force-settling when no feed --- tests/tests/settle_tests.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index c900b35688..b6e0ee735f 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -2230,6 +2230,11 @@ BOOST_AUTO_TEST_CASE( settle_order_cancel_due_to_no_feed ) BOOST_CHECK_EQUAL( get_balance( seller_id, mpa_id ), 100000 ); BOOST_CHECK_EQUAL( get_balance( seller_id, asset_id_type() ), 0 ); + // unable to settle when no feed + BOOST_CHECK_THROW( force_settle( seller, asset(11100,mpa_id) ), fc::exception ); + + generate_block(); + } catch (fc::exception& e) { edump((e.to_detail_string())); throw; From 270a506496c04ea13e41e7ae385d000d2681e131 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Sep 2021 23:45:55 +0000 Subject: [PATCH 205/258] Fix a comment in a test case --- tests/tests/market_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 92e73dbcff..3cbfcb84bf 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1497,7 +1497,7 @@ BOOST_AUTO_TEST_CASE(gs_price_test) // No margin call at this moment - // This order is right at of the first debt position + // This order is right at MSSP of the first debt position limit_order_id_type sell_mid = create_sell_order(seller, bitusd.amount(2000), core.amount(30000))->id; BOOST_CHECK_EQUAL( 2000, sell_mid(db).for_sale.value ); From 01bcfa5c93fd2fde3109bf7e0ad201a725b45ccf Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 27 Sep 2021 16:06:27 +0000 Subject: [PATCH 206/258] Update a comment --- libraries/chain/include/graphene/chain/asset_object.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 5f6ef1cbbb..55744113a9 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -276,7 +276,7 @@ namespace graphene { namespace chain { /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; - /// @return whether @ref median_feed and @ref current_feed is different + /// @return whether @ref current_feed is different from @ref median_feed bool is_current_feed_price_capped()const { return ( median_feed.settlement_price != current_feed.settlement_price ); } From d3da53f7cff507026b88f623f1b6e90ac93d21f1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 27 Sep 2021 16:30:49 +0000 Subject: [PATCH 207/258] Update comments --- .../protocol/include/graphene/protocol/asset_ops.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index cb14713a68..136728b431 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -108,7 +108,7 @@ namespace graphene { namespace protocol { */ struct bitasset_options { - /// Defines how to response to black swan events + /// Defines how a BitAsset would respond to black swan events enum class black_swan_response_type { /// All debt positions are closed, all or some collateral is moved to a global-settlement fund. @@ -118,15 +118,19 @@ namespace graphene { namespace protocol { /// No debt position is closed, and the derived settlement price is dynamically capped at the collateral /// ratio of the debt position with the least collateral ratio so that all debt positions are able to pay /// off their debt when being margin called or force-settled. + /// It is allowed to create new debt positions and update existing debt positions. /// Also known as "Global Settlement Protection". no_settlement = 1, /// Only the undercollateralized debt positions are closed and their collateral is moved to a fund which /// can be claimed via force-settlement. The derived settlement price is capped at the fund's collateral - /// ratio so that remaining debt positions will not be margin called or force-settled at a worse price. + /// ratio so that remaining debt positions will not be margin called or force-settled at a worse price + /// when the fund is not empty. + /// It is allowed to create new debt positions and update existing debt positions. individual_settlement_to_fund = 2, /// Only the undercollateralized debt positions are closed and their collateral is moved to a limit order /// on the order book which can be bought. The derived settlement price is NOT capped, which means remaining /// debt positions could be margin called at a worse price. + /// It is allowed to create new debt positions and update existing debt positions. individual_settlement_to_order = 3, /// Total number of available black swan response methods BSRM_TYPE_COUNT = 4 From c38ad74be67b336c2316f16a998ced69fba37401 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 29 Sep 2021 18:39:15 +0000 Subject: [PATCH 208/258] Slightly optimize check_for_blackswan() and etc --- libraries/chain/db_market.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 2e5c878ddc..045082562a 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -99,6 +99,17 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); + price call_pays_price; + if( limit_itr != limit_end ) + { + call_pays_price = limit_itr->sell_price; + if( after_core_hardfork_2481 ) + { + // due to margin call fee, we check with MCPP (margin call pays price) here + call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); + } + } + using bsrm_type = bitasset_options::black_swan_response_type; const auto bsrm = bitasset.get_black_swan_response_method(); @@ -123,12 +134,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( limit_itr != limit_end ) { FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); - auto call_pays_price = limit_itr->sell_price; - if( after_core_hardfork_2481 ) - { - // due to margin call fee, we check with MCPP (margin call pays price) here - call_pays_price = call_pays_price * bitasset.get_margin_call_pays_ratio(); - } if( bsrm_type::individual_settlement_to_fund != bsrm ) highest = std::max( call_pays_price, highest ); // for individual_settlement_to_fund, if call_pays_price < current_feed.max_short_squeeze_price(), @@ -380,11 +385,11 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } else { - create< limit_order_object >( [&order,&fund_receives]( limit_order_object& obj ) { + create< limit_order_object >( [&order_debt,&fund_receives]( limit_order_object& obj ) { obj.expiration = time_point_sec::maximum(); obj.seller = GRAPHENE_NULL_ACCOUNT; obj.for_sale = fund_receives.amount; - obj.sell_price = fund_receives / order.get_debt(); + obj.sell_price = fund_receives / order_debt; obj.is_settled_debt = true; } ); } @@ -392,7 +397,7 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } // call order is maker - FC_ASSERT( fill_call_order( order, order.get_collateral(), order_debt, + FC_ASSERT( fill_call_order( order, order_collateral, order_debt, fund_receives_price, true, margin_call_fee, false ), "Internal error: unable to close margin call ${o}", ("o", order) ); @@ -783,7 +788,6 @@ bool database::apply_order(const limit_order_object& new_order_object) bool feed_price_updated = false; // whether current_feed.settlement_price has been updated if( to_check_call_orders ) { - auto call_min = price::min( recv_asset_id, sell_asset_id ); // check limit orders first, match the ones with better price in comparison to call orders auto limit_itr_after_call = limit_price_idx.lower_bound( call_match_price ); while( !finished && limit_itr != limit_itr_after_call ) @@ -796,6 +800,7 @@ bool database::apply_order(const limit_order_object& new_order_object) != match_result_type::only_maker_filled ); } + auto call_min = price::min( recv_asset_id, sell_asset_id ); if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hf { // check if there are margin calls From 67d6624f4b7aa236683b0dc33ba6fd6e9457dd95 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 29 Sep 2021 21:24:39 +0000 Subject: [PATCH 209/258] Update comments --- libraries/chain/db_market.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 045082562a..877e8a6bb1 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1188,16 +1188,17 @@ database::match_result_type database::match( const limit_order_object& bid, cons if( order_receives.amount == 0 ) return match_result_type::only_taker_filled; - // The remaining amount in the limit order could be too small, - // so we should cull the order in fill_limit_order() below. - // If the order would receive 0 even at `match_price`, it would receive 0 at its own price, - // so calling maybe_cull_small() will always cull it. call_receives = order_receives.multiply_and_round_up( match_price ); if( after_core_hardfork_2481 ) call_pays = call_receives * call_pays_price; // calculate with updated call_receives else // TODO add tests about CR change call_pays = usd_for_sale * call_pays_price; // (same as match_price until BSIP-74) + + // The remaining amount (if any) in the limit order would be too small, + // so we should cull the order in fill_limit_order() below. + // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, + // so calling maybe_cull_small() will always cull it. cull_taker = true; } else From 5ba086c30bb7e9ded69b9cc5e59e0f25016d3831 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 29 Sep 2021 21:46:31 +0000 Subject: [PATCH 210/258] Add a comment --- libraries/chain/db_market.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 877e8a6bb1..c2b1c8649f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -2073,6 +2073,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa call_collateral_itr = call_collateral_index.lower_bound( call_min ); if( update_current_feed ) { + // Note: we do not call update_bitasset_current_feed() here, + // because it's called in match_impl() in match() in match_force_settlements() limit_end = limit_price_index.upper_bound( bitasset.get_margin_call_order_price() ); update_current_feed = bitasset.is_current_feed_price_capped(); } From ad734be4aae0857eb478a003bb36ac9356b8fce9 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 30 Sep 2021 08:10:11 +0000 Subject: [PATCH 211/258] Shorten a variable name --- .../chain/include/graphene/chain/credit_offer_object.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/include/graphene/chain/credit_offer_object.hpp b/libraries/chain/include/graphene/chain/credit_offer_object.hpp index ab8ce3be1f..24b26479f0 100644 --- a/libraries/chain/include/graphene/chain/credit_offer_object.hpp +++ b/libraries/chain/include/graphene/chain/credit_offer_object.hpp @@ -204,7 +204,7 @@ struct by_offer_borrower; // for protocol /** * @ingroup object_index */ -using credit_deal_summary_multi_index_type = multi_index_container< +using credit_deal_summary_index_type = multi_index_container< credit_deal_summary_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, @@ -220,7 +220,7 @@ using credit_deal_summary_multi_index_type = multi_index_container< /** * @ingroup object_index */ -using credit_deal_summary_index = generic_index; +using credit_deal_summary_index = generic_index; } } // graphene::chain From 0c3cc6794f69c31be1b1dc9b3c28a92c98b8cede Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 30 Sep 2021 08:14:45 +0000 Subject: [PATCH 212/258] Change type of a variable to avoid precision loss --- libraries/protocol/fee_schedule_calc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/protocol/fee_schedule_calc.cpp b/libraries/protocol/fee_schedule_calc.cpp index 7ab5483f7b..ccb976da4c 100644 --- a/libraries/protocol/fee_schedule_calc.cpp +++ b/libraries/protocol/fee_schedule_calc.cpp @@ -33,7 +33,7 @@ namespace graphene { namespace protocol { using result_type = uint64_t; const fee_schedule& param; - const int current_op; + const operation::tag_type current_op; calc_fee_visitor( const fee_schedule& p, const operation& op ):param(p),current_op(op.which()) { /* Nothing else to do */ } From f9711309ca620ef62f60cfaec224d9da6705e86d Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 30 Sep 2021 10:02:15 +0000 Subject: [PATCH 213/258] Fix code smells --- libraries/app/application.cpp | 178 ++++++++++++++++++++------------- libraries/app/database_api.cpp | 60 +++++------ 2 files changed, 136 insertions(+), 102 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 1b8292bed2..bbd73efbb2 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -126,13 +126,13 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) _p2p_network->load_configuration(data_dir / "p2p"); _p2p_network->set_node_delegate(shared_from_this()); - if( _options->count("seed-node") ) + if( _options->count("seed-node") > 0 ) { auto seeds = _options->at("seed-node").as>(); _p2p_network->add_seed_nodes(seeds); } - if( _options->count("seed-nodes") ) + if( _options->count("seed-nodes") > 0 ) { auto seeds_str = _options->at("seed-nodes").as(); auto seeds = fc::json::from_string(seeds_str).as>(2); @@ -147,7 +147,7 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) _p2p_network->add_seed_nodes(seeds); } - if( _options->count("p2p-endpoint") ) + if( _options->count("p2p-endpoint") > 0 ) _p2p_network->listen_on_endpoint(fc::ip::endpoint::from_string(_options->at("p2p-endpoint").as()), true); else _p2p_network->listen_on_port(0, false); @@ -194,11 +194,11 @@ void application_impl::new_connection( const fc::http::websocket_connection_ptr& void application_impl::reset_websocket_server() { try { - if( !_options->count("rpc-endpoint") ) + if( _options->count("rpc-endpoint") == 0 ) return; string proxy_forward_header; - if( _options->count("proxy-forwarded-for-header") ) + if( _options->count("proxy-forwarded-for-header") > 0 ) proxy_forward_header = _options->at("proxy-forwarded-for-header").as(); _websocket_server = std::make_shared( proxy_forward_header ); @@ -211,19 +211,20 @@ void application_impl::reset_websocket_server() void application_impl::reset_websocket_tls_server() { try { - if( !_options->count("rpc-tls-endpoint") ) + if( _options->count("rpc-tls-endpoint") == 0 ) return; - if( !_options->count("server-pem") ) + if( _options->count("server-pem") == 0 ) { wlog( "Please specify a server-pem to use rpc-tls-endpoint" ); return; } string proxy_forward_header; - if( _options->count("proxy-forwarded-for-header") ) + if( _options->count("proxy-forwarded-for-header") > 0 ) proxy_forward_header = _options->at("proxy-forwarded-for-header").as(); - string password = _options->count("server-pem-password") ? _options->at("server-pem-password").as() : ""; + string password = ( _options->count("server-pem-password") > 0 ) ? + _options->at("server-pem-password").as() : ""; _websocket_tls_server = std::make_shared( _options->at("server-pem").as(), password, proxy_forward_header ); _websocket_tls_server->on_connection( std::bind(&application_impl::new_connection, this, std::placeholders::_1) ); @@ -297,108 +298,141 @@ void application_impl::initialize(const fc::path& data_dir, shared_ptrcount("api-limit-get-account-history-operations")) { - _app_options.api_limit_get_account_history_operations = _options->at("api-limit-get-account-history-operations").as(); + if (_options->count("api-limit-get-account-history-operations") > 0) { + _app_options.api_limit_get_account_history_operations = + _options->at("api-limit-get-account-history-operations").as(); } - if(_options->count("api-limit-get-account-history")){ - _app_options.api_limit_get_account_history = _options->at("api-limit-get-account-history").as(); + if(_options->count("api-limit-get-account-history") > 0){ + _app_options.api_limit_get_account_history = + _options->at("api-limit-get-account-history").as(); } - if(_options->count("api-limit-get-grouped-limit-orders")){ - _app_options.api_limit_get_grouped_limit_orders = _options->at("api-limit-get-grouped-limit-orders").as(); + if(_options->count("api-limit-get-grouped-limit-orders") > 0){ + _app_options.api_limit_get_grouped_limit_orders = + _options->at("api-limit-get-grouped-limit-orders").as(); } - if(_options->count("api-limit-get-relative-account-history")){ - _app_options.api_limit_get_relative_account_history = _options->at("api-limit-get-relative-account-history").as(); + if(_options->count("api-limit-get-relative-account-history") > 0){ + _app_options.api_limit_get_relative_account_history = + _options->at("api-limit-get-relative-account-history").as(); } - if(_options->count("api-limit-get-account-history-by-operations")){ - _app_options.api_limit_get_account_history_by_operations = _options->at("api-limit-get-account-history-by-operations").as(); + if(_options->count("api-limit-get-account-history-by-operations") > 0){ + _app_options.api_limit_get_account_history_by_operations = + _options->at("api-limit-get-account-history-by-operations").as(); } - if(_options->count("api-limit-get-asset-holders")){ - _app_options.api_limit_get_asset_holders = _options->at("api-limit-get-asset-holders").as(); + if(_options->count("api-limit-get-asset-holders") > 0){ + _app_options.api_limit_get_asset_holders = + _options->at("api-limit-get-asset-holders").as(); } - if(_options->count("api-limit-get-key-references")){ - _app_options.api_limit_get_key_references = _options->at("api-limit-get-key-references").as(); + if(_options->count("api-limit-get-key-references") > 0){ + _app_options.api_limit_get_key_references = + _options->at("api-limit-get-key-references").as(); } - if(_options->count("api-limit-get-htlc-by")) { - _app_options.api_limit_get_htlc_by = _options->at("api-limit-get-htlc-by").as(); + if(_options->count("api-limit-get-htlc-by") > 0) { + _app_options.api_limit_get_htlc_by = + _options->at("api-limit-get-htlc-by").as(); } - if(_options->count("api-limit-get-full-accounts")) { - _app_options.api_limit_get_full_accounts = _options->at("api-limit-get-full-accounts").as(); + if(_options->count("api-limit-get-full-accounts") > 0) { + _app_options.api_limit_get_full_accounts = + _options->at("api-limit-get-full-accounts").as(); } - if(_options->count("api-limit-get-full-accounts-lists")) { - _app_options.api_limit_get_full_accounts_lists = _options->at("api-limit-get-full-accounts-lists").as(); + if(_options->count("api-limit-get-full-accounts-lists") > 0) { + _app_options.api_limit_get_full_accounts_lists = + _options->at("api-limit-get-full-accounts-lists").as(); } - if(_options->count("api-limit-get-top-voters")) { - _app_options.api_limit_get_top_voters = _options->at("api-limit-get-top-voters").as(); + if(_options->count("api-limit-get-top-voters") > 0) { + _app_options.api_limit_get_top_voters = + _options->at("api-limit-get-top-voters").as(); } - if(_options->count("api-limit-get-call-orders")) { - _app_options.api_limit_get_call_orders = _options->at("api-limit-get-call-orders").as(); + if(_options->count("api-limit-get-call-orders") > 0) { + _app_options.api_limit_get_call_orders = + _options->at("api-limit-get-call-orders").as(); } - if(_options->count("api-limit-get-settle-orders")) { - _app_options.api_limit_get_settle_orders = _options->at("api-limit-get-settle-orders").as(); + if(_options->count("api-limit-get-settle-orders") > 0) { + _app_options.api_limit_get_settle_orders = + _options->at("api-limit-get-settle-orders").as(); } - if(_options->count("api-limit-get-assets")) { - _app_options.api_limit_get_assets = _options->at("api-limit-get-assets").as(); + if(_options->count("api-limit-get-assets") > 0) { + _app_options.api_limit_get_assets = + _options->at("api-limit-get-assets").as(); } - if(_options->count("api-limit-get-limit-orders")){ - _app_options.api_limit_get_limit_orders = _options->at("api-limit-get-limit-orders").as(); + if(_options->count("api-limit-get-limit-orders") > 0){ + _app_options.api_limit_get_limit_orders = + _options->at("api-limit-get-limit-orders").as(); } - if(_options->count("api-limit-get-limit-orders-by-account")){ - _app_options.api_limit_get_limit_orders_by_account = _options->at("api-limit-get-limit-orders-by-account").as(); + if(_options->count("api-limit-get-limit-orders-by-account") > 0){ + _app_options.api_limit_get_limit_orders_by_account = + _options->at("api-limit-get-limit-orders-by-account").as(); } - if(_options->count("api-limit-get-order-book")){ - _app_options.api_limit_get_order_book = _options->at("api-limit-get-order-book").as(); + if(_options->count("api-limit-get-order-book") > 0){ + _app_options.api_limit_get_order_book = + _options->at("api-limit-get-order-book").as(); } - if(_options->count("api-limit-list-htlcs")){ - _app_options.api_limit_list_htlcs = _options->at("api-limit-list-htlcs").as(); + if(_options->count("api-limit-list-htlcs") > 0){ + _app_options.api_limit_list_htlcs = + _options->at("api-limit-list-htlcs").as(); } - if(_options->count("api-limit-lookup-accounts")) { - _app_options.api_limit_lookup_accounts = _options->at("api-limit-lookup-accounts").as(); + if(_options->count("api-limit-lookup-accounts") > 0) { + _app_options.api_limit_lookup_accounts = + _options->at("api-limit-lookup-accounts").as(); } - if(_options->count("api-limit-lookup-witness-accounts")) { - _app_options.api_limit_lookup_witness_accounts = _options->at("api-limit-lookup-witness-accounts").as(); + if(_options->count("api-limit-lookup-witness-accounts") > 0) { + _app_options.api_limit_lookup_witness_accounts = + _options->at("api-limit-lookup-witness-accounts").as(); } - if(_options->count("api-limit-lookup-committee-member-accounts")) { - _app_options.api_limit_lookup_committee_member_accounts = _options->at("api-limit-lookup-committee-member-accounts").as(); + if(_options->count("api-limit-lookup-committee-member-accounts") > 0) { + _app_options.api_limit_lookup_committee_member_accounts = + _options->at("api-limit-lookup-committee-member-accounts").as(); } - if(_options->count("api-limit-lookup-vote-ids")) { - _app_options.api_limit_lookup_vote_ids = _options->at("api-limit-lookup-vote-ids").as(); + if(_options->count("api-limit-lookup-vote-ids") > 0) { + _app_options.api_limit_lookup_vote_ids = + _options->at("api-limit-lookup-vote-ids").as(); } - if(_options->count("api-limit-get-account-limit-orders")) { - _app_options.api_limit_get_account_limit_orders = _options->at("api-limit-get-account-limit-orders").as(); + if(_options->count("api-limit-get-account-limit-orders") > 0) { + _app_options.api_limit_get_account_limit_orders = + _options->at("api-limit-get-account-limit-orders").as(); } - if(_options->count("api-limit-get-collateral-bids")) { - _app_options.api_limit_get_collateral_bids = _options->at("api-limit-get-collateral-bids").as(); + if(_options->count("api-limit-get-collateral-bids") > 0) { + _app_options.api_limit_get_collateral_bids = + _options->at("api-limit-get-collateral-bids").as(); } - if(_options->count("api-limit-get-top-markets")) { - _app_options.api_limit_get_top_markets = _options->at("api-limit-get-top-markets").as(); + if(_options->count("api-limit-get-top-markets") > 0) { + _app_options.api_limit_get_top_markets = + _options->at("api-limit-get-top-markets").as(); } - if(_options->count("api-limit-get-trade-history")) { - _app_options.api_limit_get_trade_history = _options->at("api-limit-get-trade-history").as(); + if(_options->count("api-limit-get-trade-history") > 0) { + _app_options.api_limit_get_trade_history = + _options->at("api-limit-get-trade-history").as(); } - if(_options->count("api-limit-get-trade-history-by-sequence")) { - _app_options.api_limit_get_trade_history_by_sequence = _options->at("api-limit-get-trade-history-by-sequence").as(); + if(_options->count("api-limit-get-trade-history-by-sequence") > 0) { + _app_options.api_limit_get_trade_history_by_sequence = + _options->at("api-limit-get-trade-history-by-sequence").as(); } - if(_options->count("api-limit-get-withdraw-permissions-by-giver")) { - _app_options.api_limit_get_withdraw_permissions_by_giver = _options->at("api-limit-get-withdraw-permissions-by-giver").as(); + if(_options->count("api-limit-get-withdraw-permissions-by-giver") > 0) { + _app_options.api_limit_get_withdraw_permissions_by_giver = + _options->at("api-limit-get-withdraw-permissions-by-giver").as(); } - if(_options->count("api-limit-get-withdraw-permissions-by-recipient")) { - _app_options.api_limit_get_withdraw_permissions_by_recipient = _options->at("api-limit-get-withdraw-permissions-by-recipient").as(); + if(_options->count("api-limit-get-withdraw-permissions-by-recipient") > 0) { + _app_options.api_limit_get_withdraw_permissions_by_recipient = + _options->at("api-limit-get-withdraw-permissions-by-recipient").as(); } if(_options->count("api-limit-get-tickets") > 0) { - _app_options.api_limit_get_tickets = _options->at("api-limit-get-tickets").as(); + _app_options.api_limit_get_tickets = + _options->at("api-limit-get-tickets").as(); } if(_options->count("api-limit-get-liquidity-pools") > 0) { - _app_options.api_limit_get_liquidity_pools = _options->at("api-limit-get-liquidity-pools").as(); + _app_options.api_limit_get_liquidity_pools = + _options->at("api-limit-get-liquidity-pools").as(); } if(_options->count("api-limit-get-liquidity-pool-history") > 0) { _app_options.api_limit_get_liquidity_pool_history = _options->at("api-limit-get-liquidity-pool-history").as(); } if(_options->count("api-limit-get-samet-funds") > 0) { - _app_options.api_limit_get_samet_funds = _options->at("api-limit-get-samet-funds").as(); + _app_options.api_limit_get_samet_funds = + _options->at("api-limit-get-samet-funds").as(); } if(_options->count("api-limit-get-credit-offers") > 0) { - _app_options.api_limit_get_credit_offers = _options->at("api-limit-get-credit-offers").as(); + _app_options.api_limit_get_credit_offers = + _options->at("api-limit-get-credit-offers").as(); } } @@ -608,7 +642,7 @@ bool application_impl::handle_block(const graphene::net::block_message& blk_msg, "Rejecting block with timestamp in the future", ); try { - const uint32_t skip = (_is_block_producer | _force_validate) ? + const uint32_t skip = (_is_block_producer || _force_validate) ? database::skip_nothing : database::skip_transaction_signatures; bool result = valve.do_serial( [this,&blk_msg,skip] () { _chain_db->precompute_parallel( blk_msg.block, skip ).wait(); diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 7875320f9c..8692cd5417 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -751,8 +751,8 @@ map database_api_impl::lookup_accounts( const string& lo // In addition to the common auto-subscription rules, here we auto-subscribe if only look for one account bool to_subscribe = (limit == 1 && get_whether_to_subscribe( subscribe )); for( auto itr = accounts_by_name.lower_bound(lower_bound_name); - limit-- && itr != accounts_by_name.end(); - ++itr ) + limit > 0 && itr != accounts_by_name.end(); + ++itr, --limit ) { result.insert(make_pair(itr->name, itr->get_id())); if( to_subscribe ) @@ -943,8 +943,8 @@ vector database_api_impl::list_assets(const string& lower if( lower_bound_symbol == "" ) itr = assets_by_symbol.begin(); - while(limit-- && itr != assets_by_symbol.end()) - result.emplace_back( extend_asset( *itr++ ) ); + for( ; limit > 0 && itr != assets_by_symbol.end(); ++itr, --limit ) + result.emplace_back( extend_asset( *itr ) ); return result; } @@ -1304,7 +1304,7 @@ vector database_api::get_collateral_bids( const std::stri return my->get_collateral_bids( asset, limit, start ); } -vector database_api_impl::get_collateral_bids( const std::string& asset, +vector database_api_impl::get_collateral_bids( const std::string& asset_id_or_symbol, uint32_t limit, uint32_t skip )const { try { FC_ASSERT( _app_options, "Internal error" ); @@ -1313,28 +1313,20 @@ vector database_api_impl::get_collateral_bids( const std: "limit can not be greater than ${configured_limit}", ("configured_limit", configured_limit) ); - const asset_id_type asset_id = get_asset_from_string(asset)->id; - const asset_object& swan = asset_id(_db); - FC_ASSERT( swan.is_market_issued() ); - const asset_bitasset_data_object& bad = swan.bitasset_data(_db); - const asset_object& back = bad.options.short_backing_asset(_db); - const auto& idx = _db.get_index_type(); - const auto& aidx = idx.indices().get(); - auto start = aidx.lower_bound( boost::make_tuple( asset_id, - price::max(back.id, asset_id), - collateral_bid_id_type() ) ); - auto end = aidx.lower_bound( boost::make_tuple( asset_id, - price::min(back.id, asset_id), - collateral_bid_id_type(GRAPHENE_DB_MAX_INSTANCE_ID) ) ); + const asset_object& swan = *get_asset_from_string(asset_id_or_symbol); + FC_ASSERT( swan.is_market_issued(), "Asset is not a MPA" ); + const asset_id_type asset_id = swan.id; + const auto& idx = _db.get_index_type().indices().get(); + auto itr = idx.lower_bound( asset_id ); + auto end = idx.upper_bound( asset_id ); vector result; - while( skip-- > 0 && start != end ) { ++start; } - while( start != end && limit-- > 0) + while( skip > 0 && itr != end ) { ++itr; --skip; } + for( ; itr != end && limit > 0; ++itr, --limit ) { - result.push_back(*start); - ++start; + result.push_back(*itr); } return result; -} FC_CAPTURE_AND_RETHROW( (asset)(limit)(skip) ) } +} FC_CAPTURE_AND_RETHROW( (asset_id_or_symbol)(limit)(skip) ) } void database_api::subscribe_to_market( std::function callback, const std::string& a, const std::string& b ) @@ -2298,6 +2290,7 @@ map database_api_impl::lookup_witness_accounts( const s // get all the names and look them all up, sort them, then figure out what // records to return. This could be optimized, but we expect the // number of witnesses to be few and the frequency of calls to be rare + // TODO optimize std::map witnesses_by_account_name; for (const witness_object& witness : witnesses_by_id) if (auto account_iter = _db.find(witness.witness_account)) @@ -2305,8 +2298,11 @@ map database_api_impl::lookup_witness_accounts( const s witnesses_by_account_name.insert(std::make_pair(account_iter->name, witness.id)); auto end_iter = witnesses_by_account_name.begin(); - while (end_iter != witnesses_by_account_name.end() && limit--) - ++end_iter; + while( end_iter != witnesses_by_account_name.end() && limit > 0 ) + { + ++end_iter; + --limit; + } witnesses_by_account_name.erase(end_iter, witnesses_by_account_name.end()); return witnesses_by_account_name; } @@ -2385,6 +2381,7 @@ map database_api_impl::lookup_committee_member // get all the names and look them all up, sort them, then figure out what // records to return. This could be optimized, but we expect the // number of committee_members to be few and the frequency of calls to be rare + // TODO optimize std::map committee_members_by_account_name; for (const committee_member_object& committee_member : committee_members_by_id) if (auto account_iter = _db.find(committee_member.committee_member_account)) @@ -2392,8 +2389,11 @@ map database_api_impl::lookup_committee_member committee_members_by_account_name.insert(std::make_pair(account_iter->name, committee_member.id)); auto end_iter = committee_members_by_account_name.begin(); - while (end_iter != committee_members_by_account_name.end() && limit--) - ++end_iter; + while( end_iter != committee_members_by_account_name.end() && limit > 0 ) + { + ++end_iter; + --limit; + } committee_members_by_account_name.erase(end_iter, committee_members_by_account_name.end()); return committee_members_by_account_name; } @@ -3262,7 +3262,7 @@ vector database_api_impl::get_limit_orders( const asset_id_t bool database_api_impl::is_impacted_account( const flat_set& accounts) { - if( !_subscribed_accounts.size() || !accounts.size() ) + if( _subscribed_accounts.size() == 0 || accounts.size() == 0 ) return false; return std::any_of(accounts.begin(), accounts.end(), [this](const account_id_type& account) { @@ -3272,7 +3272,7 @@ bool database_api_impl::is_impacted_account( const flat_set& ac void database_api_impl::broadcast_updates( const vector& updates ) { - if( updates.size() && _subscribe_callback ) { + if( updates.size() > 0 && _subscribe_callback ) { auto capture_this = shared_from_this(); fc::async([capture_this,updates](){ if(capture_this->_subscribe_callback) @@ -3283,7 +3283,7 @@ void database_api_impl::broadcast_updates( const vector& updates ) void database_api_impl::broadcast_market_updates( const market_queue_type& queue) { - if( queue.size() ) + if( queue.size() > 0 ) { auto capture_this = shared_from_this(); fc::async([capture_this, this, queue](){ From 6db0d53f3e19008d4ebdeb326efa64d5e66a3be7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 30 Sep 2021 11:35:53 +0000 Subject: [PATCH 214/258] Fix code smells --- libraries/chain/account_evaluator.cpp | 3 ++- libraries/chain/asset_evaluator.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 16e6e776bd..b9070c91a9 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -76,7 +76,8 @@ void verify_account_votes( const database& db, const account_options& options ) for( auto id : options.votes ) { FC_ASSERT( id < max_vote_id, "Can not vote for ${id} which does not exist.", ("id",id) ); - has_worker_votes |= (id.type() == vote_id_type::worker); + if( id.type() == vote_id_type::worker ) + has_worker_votes = true; } if( has_worker_votes && (db.head_block_time() >= HARDFORK_607_TIME) ) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 0082ab0a88..e233d7c88b 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -252,8 +252,8 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o void asset_create_evaluator::pay_fee() { - fee_is_odd = core_fee_paid.value & 1; - core_fee_paid -= core_fee_paid.value/2; + fee_is_odd = ( ( core_fee_paid.value % 2 ) != 0 ); + core_fee_paid -= ( core_fee_paid.value / 2 ); generic_evaluator::pay_fee(); } From dc23d97ab0255902e9a6165d5e9213aaab01a46f Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 30 Sep 2021 11:47:37 +0000 Subject: [PATCH 215/258] Fix code smells --- libraries/chain/db_balance.cpp | 42 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index a75464e474..bcd5d1004a 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -165,38 +165,34 @@ optional< vesting_balance_id_type > database::deposit_lazy_vesting( fc::time_point_sec now = head_block_time(); - while( true ) + if( ovbid.valid() ) { - if( !ovbid.valid() ) - break; const vesting_balance_object& vbo = (*ovbid)(*this); - if( vbo.owner != req_owner ) - break; - if( !vbo.policy.is_type< cdd_vesting_policy >() ) - break; - if( vbo.policy.get< cdd_vesting_policy >().vesting_seconds != req_vesting_seconds ) - break; - modify( vbo, [&]( vesting_balance_object& _vbo ) + if( vbo.owner == req_owner && vbo.policy.is_type< cdd_vesting_policy >() + && vbo.policy.get< cdd_vesting_policy >().vesting_seconds == req_vesting_seconds ) { - if( require_vesting ) - _vbo.deposit(now, amount); - else - _vbo.deposit_vested(now, amount); - } ); - return optional< vesting_balance_id_type >(); + modify( vbo, [require_vesting, &now, &amount]( vesting_balance_object& _vbo ) + { + if( require_vesting ) + _vbo.deposit(now, amount); + else + _vbo.deposit_vested(now, amount); + } ); + return optional< vesting_balance_id_type >(); + } } - const vesting_balance_object& vbo = create< vesting_balance_object >( [&]( vesting_balance_object& _vbo ) + cdd_vesting_policy policy; + policy.vesting_seconds = req_vesting_seconds; + policy.coin_seconds_earned = require_vesting ? 0 : amount.value * policy.vesting_seconds; + policy.coin_seconds_earned_last_update = now; + + const vesting_balance_object& vbo = create< vesting_balance_object >( + [&req_owner, &amount, &balance_type, &policy ]( vesting_balance_object& _vbo ) { _vbo.owner = req_owner; _vbo.balance = amount; _vbo.balance_type = balance_type; - - cdd_vesting_policy policy; - policy.vesting_seconds = req_vesting_seconds; - policy.coin_seconds_earned = require_vesting ? 0 : amount.value * policy.vesting_seconds; - policy.coin_seconds_earned_last_update = now; - _vbo.policy = policy; } ); From 13e8052a07d807bc0ee1f9f05cd676d8cb8523a2 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 30 Sep 2021 19:06:10 +0000 Subject: [PATCH 216/258] Fix code smells --- libraries/chain/db_maint.cpp | 6 +++--- libraries/chain/db_management.cpp | 2 +- libraries/chain/db_market.cpp | 2 +- libraries/chain/db_update.cpp | 8 ++++---- libraries/chain/fork_database.cpp | 2 +- .../chain/include/graphene/chain/is_authorized_asset.hpp | 3 +-- libraries/chain/include/graphene/chain/vote_count.hpp | 4 ++-- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 89f1bb2082..96051faf84 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -268,7 +268,7 @@ void database::update_active_witnesses() // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, // then I want to keep the most significant 16 bits of what's left. - int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + uint8_t bits_to_drop = std::max(int8_t(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); for( const auto& weight : weights ) { // Ensure that everyone has at least one vote. Zero weights aren't allowed. @@ -369,7 +369,7 @@ void database::update_active_committee_members() // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, // then I want to keep the most significant 16 bits of what's left. - int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + uint8_t bits_to_drop = std::max(int8_t(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); for( const auto& weight : weights ) { // Ensure that everyone has at least one vote. Zero weights aren't allowed. @@ -924,7 +924,7 @@ void database::process_bitassets() { const auto &asset = get( o.asset_id ); auto flags = asset.options.flags; - if ( ( flags & ( witness_fed_asset | committee_fed_asset ) ) && + if ( ( 0 != ( flags & ( witness_fed_asset | committee_fed_asset ) ) ) && o.options.feed_lifetime_sec < head_epoch_seconds ) // if smartcoin && check overflow { fc::time_point_sec calculated = head_time - o.options.feed_lifetime_sec; diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 7a7e8f2723..d78051ecc1 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -92,7 +92,7 @@ void database::reindex( fc::path data_dir ) if( block.valid() ) { if( block->timestamp >= last_block->timestamp - gpo.parameters.maximum_time_until_expiration ) - skip &= ~skip_transaction_dupe_check; + skip &= (uint32_t)(~skip_transaction_dupe_check); blocks.emplace( processed_block_size, std::move(*block), fc::future() ); std::get<2>(blocks.back()) = precompute_parallel( std::get<1>(blocks.back()), skip ); } diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index c2b1c8649f..35230ecb88 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -2236,7 +2236,7 @@ asset database::pay_market_fees(const account_object* seller, const asset_object if ( is_rewards_allowed() ) { const auto reward_percent = recv_asset.options.extensions.value.reward_percent; - if ( reward_percent && *reward_percent ) + if ( reward_percent && (*reward_percent) > 0 ) { const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent); if ( reward_value > 0 && is_authorized_asset(*this, seller->registrar(*this), recv_asset) ) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index c3ce21888f..c59c921d26 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -50,7 +50,7 @@ void database::update_global_dynamic_data( const signed_block& b, const uint32_t const uint32_t block_num = b.block_num(); if( BOOST_UNLIKELY( block_num == 1 ) ) dgp.recently_missed_count = 0; - else if( _checkpoints.size() && _checkpoints.rbegin()->first >= block_num ) + else if( _checkpoints.size() > 0 && _checkpoints.rbegin()->first >= block_num ) dgp.recently_missed_count = 0; else if( missed_blocks ) dgp.recently_missed_count += GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT*missed_blocks; @@ -69,7 +69,7 @@ void database::update_global_dynamic_data( const signed_block& b, const uint32_t dgp.current_aslot += missed_blocks+1; }); - if( !(get_node_properties().skip_flags & skip_undo_history_check) ) + if( 0 == (get_node_properties().skip_flags & skip_undo_history_check) ) { GRAPHENE_ASSERT( _dgp.head_block_number - _dgp.last_irreversible_block_num < GRAPHENE_MAX_UNDO_HISTORY, undo_database_exception, "The database does not have enough undo history to support a blockchain with so many missed blocks. " @@ -556,8 +556,8 @@ void database::update_maintenance_flag( bool new_maintenance_flag ) { auto maintenance_flag = dynamic_global_property_object::maintenance_flag; dpo.dynamic_flags = - (dpo.dynamic_flags & ~maintenance_flag) - | (new_maintenance_flag ? maintenance_flag : 0); + (dpo.dynamic_flags & (uint32_t)(~maintenance_flag)) + | (new_maintenance_flag ? (uint32_t)maintenance_flag : 0U); } ); return; } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index c77b7ee58e..f357442a40 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -92,7 +92,7 @@ void fork_database::_push_block(const item_ptr& item) _head = item; uint32_t min_num = _head->num - std::min( _max_size, _head->num ); auto& num_idx = _index.get(); - while( num_idx.size() && (*num_idx.begin())->num < min_num ) + while( num_idx.size() > 0 && (*num_idx.begin())->num < min_num ) num_idx.erase( num_idx.begin() ); } } diff --git a/libraries/chain/include/graphene/chain/is_authorized_asset.hpp b/libraries/chain/include/graphene/chain/is_authorized_asset.hpp index 3d99ae0ff8..2128e3597a 100644 --- a/libraries/chain/include/graphene/chain/is_authorized_asset.hpp +++ b/libraries/chain/include/graphene/chain/is_authorized_asset.hpp @@ -42,8 +42,7 @@ bool _is_authorized_asset(const database& d, const account_object& acct, const a inline bool is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj) { - bool fast_check = !(asset_obj.options.flags & white_list); - fast_check &= !(acct.allowed_assets.valid()); + bool fast_check = ( ( 0 == (asset_obj.options.flags & white_list) ) && !(acct.allowed_assets.valid()) ); if( fast_check ) return true; diff --git a/libraries/chain/include/graphene/chain/vote_count.hpp b/libraries/chain/include/graphene/chain/vote_count.hpp index ed8ed130fe..787c51b1cc 100644 --- a/libraries/chain/include/graphene/chain/vote_count.hpp +++ b/libraries/chain/include/graphene/chain/vote_count.hpp @@ -41,8 +41,8 @@ struct vote_counter assert( votes <= last_votes ); last_votes = votes; if( bitshift == -1 ) - bitshift = std::max(int(boost::multiprecision::detail::find_msb( votes )) - 15, 0); - uint64_t scaled_votes = std::max( votes >> bitshift, uint64_t(1) ); + bitshift = std::max(int8_t(boost::multiprecision::detail::find_msb( votes )) - 15, 0); + uint64_t scaled_votes = std::max( votes >> (uint8_t)bitshift, uint64_t(1) ); assert( scaled_votes <= std::numeric_limits::max() ); total_votes += scaled_votes; assert( total_votes <= std::numeric_limits::max() ); From 02f21109a63c9b76dd30eefa6d418b40a0fc1f0a Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 30 Sep 2021 20:30:50 +0000 Subject: [PATCH 217/258] Fix code smells --- libraries/chain/db_block.cpp | 34 ++++++++--------- libraries/db/include/graphene/db/index.hpp | 12 +++--- .../account_history_plugin.cpp | 14 +++---- libraries/protocol/account.cpp | 6 ++- libraries/protocol/asset.cpp | 37 +++++++++---------- libraries/protocol/block.cpp | 4 +- .../include/graphene/protocol/object_id.hpp | 2 +- .../include/graphene/protocol/types.hpp | 2 +- libraries/protocol/memo.cpp | 4 +- libraries/protocol/transaction.cpp | 2 +- libraries/utilities/elasticsearch.cpp | 2 +- libraries/wallet/wallet_debug.cpp | 4 +- libraries/wallet/wallet_voting.cpp | 16 ++++---- programs/js_operation_serializer/main.cpp | 4 +- 14 files changed, 70 insertions(+), 73 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 97b4bee493..28b5dbf9a3 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -139,7 +139,7 @@ bool database::_push_block(const signed_block& new_block) // verify that the block signer is in the current set of active witnesses. shared_ptr prev_block = _fork_db.fetch_block( new_block.previous ); GRAPHENE_ASSERT( prev_block, unlinkable_block_exception, "block does not link to known chain" ); - if( prev_block->scheduled_witnesses && !(skip&(skip_witness_schedule_check|skip_witness_signature)) ) + if( prev_block->scheduled_witnesses && 0 == (skip&(skip_witness_schedule_check|skip_witness_signature)) ) verify_signing_witness( new_block, *prev_block ); } @@ -414,7 +414,7 @@ signed_block database::_generate_block( _pending_tx_session.reset(); // Check witness signing key - if( !(skip & skip_witness_signature) ) + if( 0 == (skip & skip_witness_signature) ) { // Note: if this check failed (which won't happen in normal situations), // we would have temporarily broken the invariant that @@ -497,7 +497,7 @@ signed_block database::_generate_block( pending_block.transaction_merkle_root = pending_block.calculate_merkle_root(); pending_block.witness = witness_id; - if( !(skip & skip_witness_signature) ) + if( 0 == (skip & skip_witness_signature) ) pending_block.sign( block_signing_private_key ); push_block( pending_block, skip | skip_transaction_signatures ); // skip authority check when pushing self-generated blocks @@ -563,7 +563,7 @@ const vector >& database::get_applied_opera void database::apply_block( const signed_block& next_block, uint32_t skip ) { auto block_num = next_block.block_num(); - if( _checkpoints.size() && _checkpoints.rbegin()->second != block_id_type() ) + if( _checkpoints.size() > 0 && _checkpoints.rbegin()->second != block_id_type() ) { auto itr = _checkpoints.find( block_num ); if( itr != _checkpoints.end() ) @@ -586,7 +586,7 @@ void database::_apply_block( const signed_block& next_block ) uint32_t skip = get_node_properties().skip_flags; _applied_ops.clear(); - if( !(skip & skip_block_size_check) ) + if( 0 == (skip & skip_block_size_check) ) { FC_ASSERT( fc::raw::pack_size(next_block) <= get_global_properties().parameters.maximum_block_size ); } @@ -691,7 +691,7 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx auto& trx_idx = get_mutable_index_type(); const chain_id_type& chain_id = get_chain_id(); - if( !(skip & skip_transaction_dupe_check) ) + if( 0 == (skip & skip_transaction_dupe_check) ) { GRAPHENE_ASSERT( trx_idx.indices().get().find(trx.id()) == trx_idx.indices().get().end(), duplicate_transaction, @@ -702,7 +702,7 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx const chain_parameters& chain_parameters = get_global_properties().parameters; eval_state._trx = &trx; - if( !(skip & skip_transaction_signatures) ) + if( 0 == (skip & skip_transaction_signatures) ) { bool allow_non_immediate_owner = ( head_block_time() >= HARDFORK_CORE_584_TIME ); auto get_active = [this]( account_id_type id ) { return &id(*this).active; }; @@ -720,7 +720,7 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx //expired, and TaPoS makes no sense as no blocks exist. if( BOOST_LIKELY(head_block_num() > 0) ) { - if( !(skip & skip_tapos_check) ) + if( 0 == (skip & skip_tapos_check) ) { const auto& tapos_block_summary = block_summary_id_type( trx.ref_block_num )(*this); @@ -733,14 +733,14 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx FC_ASSERT( trx.expiration <= now + chain_parameters.maximum_time_until_expiration, "", ("trx.expiration",trx.expiration)("now",now)("max_til_exp",chain_parameters.maximum_time_until_expiration)); FC_ASSERT( now <= trx.expiration, "", ("now",now)("trx.exp",trx.expiration) ); - if ( !(skip & skip_block_size_check ) ) // don't waste time on replay + if ( 0 == (skip & skip_block_size_check ) ) // don't waste time on replay FC_ASSERT( head_block_time() <= HARDFORK_CORE_1573_TIME || trx.get_packed_size() <= chain_parameters.maximum_transaction_size, "Transaction exceeds maximum transaction size." ); } //Insert transaction into unique transactions database. - if( !(skip & skip_transaction_dupe_check) ) + if( 0 == (skip & skip_transaction_dupe_check) ) { create([&trx](transaction_history_object& transaction) { transaction.trx_id = trx.id(); @@ -789,10 +789,10 @@ const witness_object& database::validate_block_header( uint32_t skip, const sign FC_ASSERT( head_block_time() < next_block.timestamp, "", ("head_block_time",head_block_time())("next",next_block.timestamp)("blocknum",next_block.block_num()) ); const witness_object& witness = next_block.witness(*this); - if( !(skip&skip_witness_signature) ) + if( 0 == (skip&skip_witness_signature) ) FC_ASSERT( next_block.validate_signee( witness.signing_key ) ); - if( !(skip&skip_witness_schedule_check) ) + if( 0 == (skip&skip_witness_schedule_check) ) { uint32_t slot_num = get_slot_at_time( next_block.timestamp ); FC_ASSERT( slot_num > 0 ); @@ -835,11 +835,11 @@ void database::_precompute_parallel( const Trx* trx, const size_t count, const u for( size_t i = 0; i < count; ++i, ++trx ) { trx->validate(); // TODO - parallelize wrt confidential operations - if ( !(skip & skip_block_size_check) ) + if( 0 == (skip & skip_block_size_check) ) trx->get_packed_size(); - if( !(skip&skip_transaction_dupe_check) ) + if( 0 == (skip&skip_transaction_dupe_check) ) trx->id(); - if( !(skip&skip_transaction_signatures) ) + if( 0 == (skip&skip_transaction_signatures) ) trx->get_signature_keys( get_chain_id() ); } } @@ -865,9 +865,9 @@ fc::future database::precompute_parallel( const signed_block& block, const } } - if( !(skip&skip_witness_signature) ) + if( 0 == (skip&skip_witness_signature) ) workers.push_back( fc::do_parallel( [&block] () { block.signee(); } ) ); - if( !(skip&skip_merkle_check) ) + if( 0 == (skip&skip_merkle_check) ) block.calculate_merkle_root(); block.id(); diff --git a/libraries/db/include/graphene/db/index.hpp b/libraries/db/include/graphene/db/index.hpp index a5f4ff1e7a..e6cf0d8cbf 100644 --- a/libraries/db/include/graphene/db/index.hpp +++ b/libraries/db/include/graphene/db/index.hpp @@ -208,7 +208,7 @@ namespace graphene { namespace db { // private static const size_t MAX_HOLE = 100; - static const size_t _mask = ((1 << chunkbits) - 1); + static const size_t _mask = ((1ULL << chunkbits) - 1); uint64_t next = 0; vector< vector< const Object* > > content; std::stack< object_id_type > ids_being_modified; @@ -225,10 +225,10 @@ namespace graphene { namespace db { uint64_t instance = obj.id.instance(); if( instance == next ) { - if( !(next & _mask) ) + if( 0 == (next & _mask) ) { content.resize((next >> chunkbits) + 1); - content[next >> chunkbits].resize( 1 << chunkbits, nullptr ); + content[next >> chunkbits].resize( 1ULL << chunkbits, nullptr ); } next++; } @@ -237,10 +237,10 @@ namespace graphene { namespace db { else // instance > next, allow small "holes" { FC_ASSERT( instance <= next + MAX_HOLE, "Out-of-order insert: {id} > {next}!", ("id",obj.id)("next",next) ); - if( !(next & _mask) || (next & (~_mask)) != (instance & (~_mask)) ) + if( 0 == (next & _mask) || (next & (~_mask)) != (instance & (~_mask)) ) { content.resize((instance >> chunkbits) + 1); - content[instance >> chunkbits].resize( 1 << chunkbits, nullptr ); + content[instance >> chunkbits].resize( 1ULL << chunkbits, nullptr ); } while( next <= instance ) { @@ -294,7 +294,7 @@ namespace graphene { namespace db { FC_ASSERT( id.space() == Object::space_id, "Space ID mismatch!" ); FC_ASSERT( id.type() == Object::type_id, "Type_ID mismatch!" ); if( id.instance() >= next ) return nullptr; - return content[id.instance() >> chunkbits][id.instance() & ((1 << chunkbits) - 1)]; + return content[id.instance() >> chunkbits][id.instance() & ((1ULL << chunkbits) - 1)]; }; }; diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 189a2a8b3c..94dc3eede7 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -210,7 +210,8 @@ void account_history_plugin_impl::update_account_histories( const signed_block& } } -void account_history_plugin_impl::add_account_history( const account_id_type account_id, const operation_history_id_type op_id ) +void account_history_plugin_impl::add_account_history( const account_id_type account_id, + const operation_history_id_type op_id ) { graphene::chain::database& db = database(); const auto& stats_obj = account_id(db).statistics(db); @@ -226,15 +227,10 @@ void account_history_plugin_impl::add_account_history( const account_id_type acc obj.total_ops = ath.sequence; }); // Amount of history to keep depends on if account is in the "extended history" list - bool extended_hist = false; - for ( auto eh_account_id : _extended_history_accounts ) { - extended_hist |= (account_id == eh_account_id); - } - if ( _extended_history_registrars.size() > 0 ) { + bool extended_hist = ( _extended_history_accounts.find( account_id ) != _extended_history_accounts.end() ); + if( !extended_hist && _extended_history_registrars.size() > 0 ) { const account_id_type registrar_id = account_id(db).registrar; - for ( auto eh_registrar_id : _extended_history_registrars ) { - extended_hist |= (registrar_id == eh_registrar_id); - } + extended_hist = ( _extended_history_registrars.find( registrar_id ) != _extended_history_registrars.end() ); } // _max_ops_per_account is guaranteed to be non-zero outside; max_ops_to_keep // will likewise be non-zero, and also non-negative (it is unsigned). diff --git a/libraries/protocol/account.cpp b/libraries/protocol/account.cpp index e1822308be..9b77d8eca6 100644 --- a/libraries/protocol/account.cpp +++ b/libraries/protocol/account.cpp @@ -159,10 +159,12 @@ void account_options::validate() const auto needed_committee = num_committee; for( vote_id_type id : votes ) - if( id.type() == vote_id_type::witness && needed_witnesses ) + { + if( id.type() == vote_id_type::witness && needed_witnesses > 0 ) --needed_witnesses; - else if ( id.type() == vote_id_type::committee && needed_committee ) + else if ( id.type() == vote_id_type::committee && needed_committee > 0 ) --needed_committee; + } FC_ASSERT( needed_witnesses == 0 && needed_committee == 0, "May not specify fewer witnesses or committee members than the number voted for."); diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index af811d8bb1..b3f11848ea 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -30,7 +30,6 @@ namespace graphene { namespace protocol { using fc::uint128_t; - using fc::int128_t; bool operator == ( const price& a, const price& b ) { @@ -112,38 +111,38 @@ namespace graphene { namespace protocol { if( r.numerator() == r.denominator() ) return p; - boost::rational p128( p.base.amount.value, p.quote.amount.value ); - boost::rational r128( r.numerator(), r.denominator() ); + boost::rational p128( p.base.amount.value, p.quote.amount.value ); + boost::rational r128( r.numerator(), r.denominator() ); auto cp = p128 * r128; auto ocp = cp; bool shrinked = false; bool using_max = false; - static const int128_t max( GRAPHENE_MAX_SHARE_SUPPLY ); + static const uint128_t max( GRAPHENE_MAX_SHARE_SUPPLY ); while( cp.numerator() > max || cp.denominator() > max ) { if( cp.numerator() == 1 ) { - cp = boost::rational( 1, max ); + cp = boost::rational( 1, max ); using_max = true; break; } else if( cp.denominator() == 1 ) { - cp = boost::rational( max, 1 ); + cp = boost::rational( max, 1 ); using_max = true; break; } else { - cp = boost::rational( cp.numerator() >> 1, cp.denominator() >> 1 ); + cp = boost::rational( cp.numerator() >> 1, cp.denominator() >> 1 ); shrinked = true; } } if( shrinked ) // maybe not accurate enough due to rounding, do additional checks here { - int128_t num = ocp.numerator(); - int128_t den = ocp.denominator(); + uint128_t num = ocp.numerator(); + uint128_t den = ocp.denominator(); if( num > den ) { num /= den; @@ -158,15 +157,15 @@ namespace graphene { namespace protocol { den = max; num = 1; } - boost::rational ncp( num, den ); + boost::rational ncp( num, den ); if( num == max || den == max ) // it's on the edge, we know it's accurate enough cp = ncp; else { // from the accurate ocp, now we have ncp and cp. use the one which is closer to ocp. // TODO improve performance - auto diff1 = abs( ncp - ocp ); - auto diff2 = abs( cp - ocp ); + auto diff1 = (ncp >= ocp) ? (ncp - ocp) : (ocp - ncp); + auto diff2 = (cp >= ocp) ? (cp - ocp) : (ocp - cp); if( diff1 < diff2 ) cp = ncp; } } @@ -211,12 +210,12 @@ namespace graphene { namespace protocol { */ price price::call_price( const asset& debt, const asset& collateral, uint16_t collateral_ratio) { try { - boost::rational swan(debt.amount.value,collateral.amount.value); - boost::rational ratio( collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM ); + boost::rational swan(debt.amount.value,collateral.amount.value); + boost::rational ratio( collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM ); auto cp = swan * ratio; while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY ) - cp = boost::rational( (cp.numerator() >> 1)+1, (cp.denominator() >> 1)+1 ); + cp = boost::rational( (cp.numerator() >> 1)+1, (cp.denominator() >> 1)+1 ); return ( asset( static_cast(cp.denominator()), collateral.asset_id ) / asset( static_cast(cp.numerator()), debt.asset_id ) ); @@ -278,13 +277,13 @@ namespace graphene { namespace protocol { price price_feed::max_short_squeeze_price_before_hf_1270()const { // settlement price is in debt/collateral - boost::rational sp( settlement_price.base.amount.value, settlement_price.quote.amount.value ); - boost::rational ratio( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio ); + boost::rational sp( settlement_price.base.amount.value, settlement_price.quote.amount.value ); + boost::rational ratio( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio ); auto cp = sp * ratio; while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY ) - cp = boost::rational( (cp.numerator() >> 1)+(cp.numerator()&1), - (cp.denominator() >> 1)+(cp.denominator()&1) ); + cp = boost::rational( (cp.numerator() >> 1)+(cp.numerator()&1U), + (cp.denominator() >> 1)+(cp.denominator()&1U) ); return ( asset( static_cast(cp.numerator()), settlement_price.base.asset_id ) / asset( static_cast(cp.denominator()), settlement_price.quote.asset_id ) ); diff --git a/libraries/protocol/block.cpp b/libraries/protocol/block.cpp index 307b592d4e..bf3c7bbf8b 100644 --- a/libraries/protocol/block.cpp +++ b/libraries/protocol/block.cpp @@ -40,7 +40,7 @@ namespace graphene { namespace protocol { const block_id_type& signed_block_header::id()const { - if( !_block_id._hash[0].value() ) + if( 0 == _block_id._hash[0].value() ) { auto tmp = fc::sha224::hash( *this ); tmp._hash[0] = boost::endian::endian_reverse(block_num()); // store the block num in the ID, 160 bits is plenty for the hash @@ -73,7 +73,7 @@ namespace graphene { namespace protocol { if( transactions.size() == 0 ) return empty_checksum; - if( !_calculated_merkle_root._hash[0].value() ) + if( 0 == _calculated_merkle_root._hash[0].value() ) { vector ids; ids.resize( transactions.size() ); diff --git a/libraries/protocol/include/graphene/protocol/object_id.hpp b/libraries/protocol/include/graphene/protocol/object_id.hpp index 34aa51c481..b44f208be9 100644 --- a/libraries/protocol/include/graphene/protocol/object_id.hpp +++ b/libraries/protocol/include/graphene/protocol/object_id.hpp @@ -70,7 +70,7 @@ namespace graphene { namespace db { template< typename T > bool is() const { - return (number >> 48) == ((T::space_id << 8) | (T::type_id)); + return (number >> 48) == ((uint64_t)(T::space_id << 8) | (T::type_id)); } template< typename T > diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 675de8e350..efb5814f85 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -248,7 +248,7 @@ const static uint16_t PERMISSION_ONLY_MASK = | disable_mssr_update | disable_bsrm_update; // The bits that can be used in flags for non-UIA assets -const static uint16_t VALID_FLAGS_MASK = ASSET_ISSUER_PERMISSION_MASK & ~PERMISSION_ONLY_MASK; +const static uint16_t VALID_FLAGS_MASK = ASSET_ISSUER_PERMISSION_MASK & (uint16_t)(~PERMISSION_ONLY_MASK); // the bits that can be used in flags for UIA assets const static uint16_t UIA_VALID_FLAGS_MASK = UIA_ASSET_ISSUER_PERMISSION_MASK; diff --git a/libraries/protocol/memo.cpp b/libraries/protocol/memo.cpp index f68585c548..859f7ef96e 100644 --- a/libraries/protocol/memo.cpp +++ b/libraries/protocol/memo.cpp @@ -39,8 +39,8 @@ void memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::pub { uint64_t entropy = fc::sha224::hash(fc::ecc::private_key::generate())._hash[0].value(); entropy <<= 32; - entropy &= 0xff00000000000000; - nonce = (fc::time_point::now().time_since_epoch().count() & 0x00ffffffffffffff) | entropy; + entropy &= 0xff00000000000000ULL; + nonce = ((uint64_t)(fc::time_point::now().time_since_epoch().count()) & 0x00ffffffffffffffULL) | entropy; } else nonce = custom_nonce; auto secret = priv.get_shared_secret(pub); diff --git a/libraries/protocol/transaction.cpp b/libraries/protocol/transaction.cpp index 43ec06e164..82f5111e5a 100644 --- a/libraries/protocol/transaction.cpp +++ b/libraries/protocol/transaction.cpp @@ -433,7 +433,7 @@ set signed_transaction::minimize_required_signatures( const transaction_id_type& precomputable_transaction::id()const { - if( !_tx_id_buffer._hash[0].value() ) + if( 0 == _tx_id_buffer._hash[0].value() ) transaction::id(); return _tx_id_buffer; } diff --git a/libraries/utilities/elasticsearch.cpp b/libraries/utilities/elasticsearch.cpp index ce4228f43a..0782c71490 100644 --- a/libraries/utilities/elasticsearch.cpp +++ b/libraries/utilities/elasticsearch.cpp @@ -99,7 +99,7 @@ bool handleBulkResponse(long http_code, const std::string& CurlReadBuffer) // all good, but check errors in response fc::variant j = fc::json::from_string(CurlReadBuffer); bool errors = j["errors"].as_bool(); - if(errors == true) { + if( errors ) { elog( "ES returned 200 but with errors: ${e}", ("e", CurlReadBuffer) ); return false; } diff --git a/libraries/wallet/wallet_debug.cpp b/libraries/wallet/wallet_debug.cpp index dc0a7c49cc..fabd9530f2 100644 --- a/libraries/wallet/wallet_debug.cpp +++ b/libraries/wallet/wallet_debug.cpp @@ -28,7 +28,7 @@ namespace graphene { namespace wallet { namespace detail { void wallet_api_impl::dbg_make_uia(string creator, string symbol) { asset_options opts; - opts.flags &= ~(white_list | disable_force_settle | global_settle); + opts.flags &= (uint16_t)( ~(white_list | disable_force_settle | global_settle) ); opts.issuer_permissions = opts.flags; opts.core_exchange_rate = price(asset(1), asset(1,asset_id_type(1))); create_asset(get_account(creator).name, symbol, 2, opts, {}, true); @@ -37,7 +37,7 @@ namespace graphene { namespace wallet { namespace detail { void wallet_api_impl::dbg_make_mia(string creator, string symbol) { asset_options opts; - opts.flags &= ~white_list; + opts.flags &= (uint16_t)(~white_list); opts.issuer_permissions = opts.flags; opts.core_exchange_rate = price(asset(1), asset(1,asset_id_type(1))); bitasset_options bopts; diff --git a/libraries/wallet/wallet_voting.cpp b/libraries/wallet/wallet_voting.cpp index ee8352b347..1dc911e22b 100644 --- a/libraries/wallet/wallet_voting.cpp +++ b/libraries/wallet/wallet_voting.cpp @@ -37,7 +37,7 @@ namespace graphene { namespace wallet { namespace detail { from_variant( worker_settings, result, GRAPHENE_MAX_NESTED_OBJECTS ); return result; } - + signed_transaction wallet_api_impl::update_worker_votes( string account, worker_vote_delta delta, @@ -102,7 +102,7 @@ namespace graphene { namespace wallet { namespace detail { return sign_transaction( tx, broadcast ); } - signed_transaction wallet_api_impl::create_committee_member(string owner_account, string url, + signed_transaction wallet_api_impl::create_committee_member(string owner_account, string url, bool broadcast ) { try { @@ -296,8 +296,8 @@ namespace graphene { namespace wallet { namespace detail { } else { - unsigned votes_removed = voting_account_object.options.votes.erase(committee_member_obj->vote_id); - if (!votes_removed) + auto votes_removed = voting_account_object.options.votes.erase(committee_member_obj->vote_id); + if( 0 == votes_removed ) FC_THROW("Account ${account} is already not voting for committee_member ${committee_member}", ("account", voting_account)("committee_member", committee_member)); } @@ -330,8 +330,8 @@ namespace graphene { namespace wallet { namespace detail { } else { - unsigned votes_removed = voting_account_object.options.votes.erase(witness_obj->vote_id); - if (!votes_removed) + auto votes_removed = voting_account_object.options.votes.erase(witness_obj->vote_id); + if( 0 == votes_removed ) FC_THROW("Account ${account} is already not voting for witness ${witness}", ("account", voting_account)("witness", witness)); } @@ -347,7 +347,7 @@ namespace graphene { namespace wallet { namespace detail { return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (voting_account)(witness)(approve)(broadcast) ) } - signed_transaction wallet_api_impl::set_voting_proxy(string account_to_modify, + signed_transaction wallet_api_impl::set_voting_proxy(string account_to_modify, optional voting_account, bool broadcast ) { try { account_object account_object_to_modify = get_account(account_to_modify); @@ -466,7 +466,7 @@ namespace graphene { namespace wallet { namespace detail { size_t n = key.size(); for( size_t i=0; i struct serializer { - static_assert( fc::reflector::is_defined::value == false, "invalid template arguments" ); + static_assert( !fc::reflector::is_defined::value, "invalid template arguments" ); static void init() {} @@ -341,7 +341,7 @@ class register_member_visitor template struct serializer { - static_assert( fc::reflector::is_defined::value == reflected, "invalid template arguments" ); + static_assert( fc::reflector::is_defined::value, "invalid template arguments" ); static void init() { auto name = js_name::name(); From a4cc1f2a03c94c53870962e70d0962b4baec9482 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 30 Sep 2021 23:27:39 +0000 Subject: [PATCH 218/258] Update macOS OpenSSL root dir for Github Actions --- .github/workflows/build-and-test.mac.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.mac.yml b/.github/workflows/build-and-test.mac.yml index 6557de8603..021e73783c 100644 --- a/.github/workflows/build-and-test.mac.yml +++ b/.github/workflows/build-and-test.mac.yml @@ -28,7 +28,7 @@ jobs: -D CMAKE_C_COMPILER_LAUNCHER=ccache \ -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ -D BOOST_ROOT=/usr/local/opt/boost@1.69 \ - -D OPENSSL_ROOT_DIR=/usr/local/opt/openssl \ + -D OPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 \ .. - name: Load Cache uses: actions/cache@v2 From 1abf54561e9132e950505dbcb64421dbc38e94cd Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 1 Oct 2021 08:59:09 +0000 Subject: [PATCH 219/258] Fix code smells --- libraries/app/application.cpp | 6 +++--- libraries/app/database_api.cpp | 7 ++++--- libraries/chain/asset_evaluator.cpp | 5 +++-- libraries/chain/db_block.cpp | 2 +- libraries/chain/db_maint.cpp | 18 ++++++++++++------ libraries/chain/db_update.cpp | 2 +- .../account_history/account_history_plugin.cpp | 2 +- libraries/protocol/asset.cpp | 10 ++++------ libraries/protocol/memo.cpp | 13 ++++++++----- 9 files changed, 37 insertions(+), 28 deletions(-) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index bbd73efbb2..6c2a57ecec 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -194,7 +194,7 @@ void application_impl::new_connection( const fc::http::websocket_connection_ptr& void application_impl::reset_websocket_server() { try { - if( _options->count("rpc-endpoint") == 0 ) + if( 0 == _options->count("rpc-endpoint") ) return; string proxy_forward_header; @@ -211,9 +211,9 @@ void application_impl::reset_websocket_server() void application_impl::reset_websocket_tls_server() { try { - if( _options->count("rpc-tls-endpoint") == 0 ) + if( 0 == _options->count("rpc-tls-endpoint") ) return; - if( _options->count("server-pem") == 0 ) + if( 0 == _options->count("server-pem") ) { wlog( "Please specify a server-pem to use rpc-tls-endpoint" ); return; diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 8692cd5417..fca286df78 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -943,7 +943,8 @@ vector database_api_impl::list_assets(const string& lower if( lower_bound_symbol == "" ) itr = assets_by_symbol.begin(); - for( ; limit > 0 && itr != assets_by_symbol.end(); ++itr, --limit ) + auto end = assets_by_symbol.end(); + for( ; limit > 0 && itr != end; ++itr, --limit ) result.emplace_back( extend_asset( *itr ) ); return result; @@ -3262,7 +3263,7 @@ vector database_api_impl::get_limit_orders( const asset_id_t bool database_api_impl::is_impacted_account( const flat_set& accounts) { - if( _subscribed_accounts.size() == 0 || accounts.size() == 0 ) + if( _subscribed_accounts.empty() || accounts.empty() ) return false; return std::any_of(accounts.begin(), accounts.end(), [this](const account_id_type& account) { @@ -3272,7 +3273,7 @@ bool database_api_impl::is_impacted_account( const flat_set& ac void database_api_impl::broadcast_updates( const vector& updates ) { - if( updates.size() > 0 && _subscribe_callback ) { + if( !updates.empty() && _subscribe_callback ) { auto capture_this = shared_from_this(); fc::async([capture_this,updates](){ if(capture_this->_subscribe_callback) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index e233d7c88b..23cb00081d 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -252,8 +252,9 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o void asset_create_evaluator::pay_fee() { - fee_is_odd = ( ( core_fee_paid.value % 2 ) != 0 ); - core_fee_paid -= ( core_fee_paid.value / 2 ); + constexpr int64_t two = 2; + fee_is_odd = ( ( core_fee_paid.value % two ) != 0 ); + core_fee_paid -= core_fee_paid.value / two; generic_evaluator::pay_fee(); } diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 28b5dbf9a3..9d2c50bf71 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -563,7 +563,7 @@ const vector >& database::get_applied_opera void database::apply_block( const signed_block& next_block, uint32_t skip ) { auto block_num = next_block.block_num(); - if( _checkpoints.size() > 0 && _checkpoints.rbegin()->second != block_id_type() ) + if( !_checkpoints.empty() && _checkpoints.rbegin()->second != block_id_type() ) { auto itr = _checkpoints.find( block_num ); if( itr != _checkpoints.end() ) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 96051faf84..8aba0f8a93 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -268,11 +268,13 @@ void database::update_active_witnesses() // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, // then I want to keep the most significant 16 bits of what's left. - uint8_t bits_to_drop = std::max(int8_t(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + uint64_t votes_msb = boost::multiprecision::detail::find_msb(total_votes); + constexpr uint8_t bits_to_keep_minus_one = 15; + uint64_t bits_to_drop = (votes_msb > bits_to_keep_minus_one) ? (votes_msb - bits_to_keep_minus_one) : 0; for( const auto& weight : weights ) { // Ensure that everyone has at least one vote. Zero weights aren't allowed. - uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); + uint16_t votes = std::max((uint16_t)(weight.second >> bits_to_drop), uint16_t(1) ); a.active.account_auths[weight.first] += votes; a.active.weight_threshold += votes; } @@ -322,7 +324,8 @@ void database::update_active_committee_members() const chain_property_object& cpo = get_chain_properties(); - committee_member_count = std::max( committee_member_count*2+1, (size_t)cpo.immutable_parameters.min_committee_member_count ); + committee_member_count = std::max( committee_member_count*2+1, + (size_t)cpo.immutable_parameters.min_committee_member_count ); auto committee_members = sort_votable_objects( committee_member_count ); auto update_committee_member_total_votes = [this]( const committee_member_object& cm ) { @@ -367,13 +370,16 @@ void database::update_active_committee_members() total_votes += _vote_tally_buffer[cm.vote_id]; } - // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, + // total_votes is 64 bits. + // Subtract the number of leading low bits from 64 to get the number of useful bits, // then I want to keep the most significant 16 bits of what's left. - uint8_t bits_to_drop = std::max(int8_t(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + uint64_t votes_msb = boost::multiprecision::detail::find_msb(total_votes); + constexpr uint8_t bits_to_keep_minus_one = 15; + uint64_t bits_to_drop = (votes_msb > bits_to_keep_minus_one) ? (votes_msb - bits_to_keep_minus_one) : 0; for( const auto& weight : weights ) { // Ensure that everyone has at least one vote. Zero weights aren't allowed. - uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); + uint16_t votes = std::max((uint16_t)(weight.second >> bits_to_drop), uint16_t(1) ); a.active.account_auths[weight.first] += votes; a.active.weight_threshold += votes; } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index c59c921d26..551ce42ea8 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -50,7 +50,7 @@ void database::update_global_dynamic_data( const signed_block& b, const uint32_t const uint32_t block_num = b.block_num(); if( BOOST_UNLIKELY( block_num == 1 ) ) dgp.recently_missed_count = 0; - else if( _checkpoints.size() > 0 && _checkpoints.rbegin()->first >= block_num ) + else if( !_checkpoints.empty() && _checkpoints.rbegin()->first >= block_num ) dgp.recently_missed_count = 0; else if( missed_blocks ) dgp.recently_missed_count += GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT*missed_blocks; diff --git a/libraries/plugins/account_history/account_history_plugin.cpp b/libraries/plugins/account_history/account_history_plugin.cpp index 94dc3eede7..9561315ebf 100644 --- a/libraries/plugins/account_history/account_history_plugin.cpp +++ b/libraries/plugins/account_history/account_history_plugin.cpp @@ -228,7 +228,7 @@ void account_history_plugin_impl::add_account_history( const account_id_type acc }); // Amount of history to keep depends on if account is in the "extended history" list bool extended_hist = ( _extended_history_accounts.find( account_id ) != _extended_history_accounts.end() ); - if( !extended_hist && _extended_history_registrars.size() > 0 ) { + if( !extended_hist && !_extended_history_registrars.empty() ) { const account_id_type registrar_id = account_id(db).registrar; extended_hist = ( _extended_history_registrars.find( registrar_id ) != _extended_history_registrars.end() ); } diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index b3f11848ea..7eae1c4fb7 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -146,15 +146,13 @@ namespace graphene { namespace protocol { if( num > den ) { num /= den; - if( num > max ) - num = max; + num = std::min( num, max ); den = 1; } else { den /= num; - if( den > max ) - den = max; + den = std::min( den, max ); num = 1; } boost::rational ncp( num, den ); @@ -175,8 +173,8 @@ namespace graphene { namespace protocol { if( shrinked || using_max ) { - if( ( r.numerator() > r.denominator() && np < p ) - || ( r.numerator() < r.denominator() && np > p ) ) + bool flipped = ( r.numerator() > r.denominator() ) ? ( np < p ) : ( np > p ); + if( flipped ) // even with an accurate result, if p is out of valid range, return it np = p; } diff --git a/libraries/protocol/memo.cpp b/libraries/protocol/memo.cpp index 859f7ef96e..a9fa015791 100644 --- a/libraries/protocol/memo.cpp +++ b/libraries/protocol/memo.cpp @@ -35,17 +35,20 @@ void memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::pub { from = priv.get_public_key(); to = pub; - if( custom_nonce == 0 ) + if( 0 == custom_nonce ) { uint64_t entropy = fc::sha224::hash(fc::ecc::private_key::generate())._hash[0].value(); - entropy <<= 32; - entropy &= 0xff00000000000000ULL; - nonce = ((uint64_t)(fc::time_point::now().time_since_epoch().count()) & 0x00ffffffffffffffULL) | entropy; + constexpr uint64_t half_size = 32; + constexpr uint64_t high_bits = 0xff00000000000000ULL; + constexpr uint64_t low_bits = 0x00ffffffffffffffULL; + entropy <<= half_size; + entropy &= high_bits; + nonce = ((uint64_t)(fc::time_point::now().time_since_epoch().count()) & low_bits) | entropy; } else nonce = custom_nonce; auto secret = priv.get_shared_secret(pub); auto nonce_plus_secret = fc::sha512::hash(fc::to_string(nonce) + secret.str()); - string text = memo_message(digest_type::hash(msg)._hash[0].value(), msg).serialize(); + string text = memo_message((uint32_t)digest_type::hash(msg)._hash[0].value(), msg).serialize(); message = fc::aes_encrypt( nonce_plus_secret, vector(text.begin(), text.end()) ); } else From 622a0995192ba2d8c892d7cd967c17febcf1cc1f Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 1 Oct 2021 09:27:47 +0000 Subject: [PATCH 220/258] Fix code smells --- libraries/protocol/asset.cpp | 46 ++++++++++++------- .../include/graphene/protocol/asset.hpp | 18 +++----- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index 7eae1c4fb7..485a9d2ac6 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -80,18 +80,21 @@ namespace graphene { namespace protocol { if( a.asset_id == b.base.asset_id ) { FC_ASSERT( b.base.amount.value > 0 ); - uint128_t result = (uint128_t(a.amount.value) * b.quote.amount.value + b.base.amount.value - 1)/b.base.amount.value; + uint128_t result = ( ( ( uint128_t(a.amount.value) * b.quote.amount.value ) + b.base.amount.value ) - 1 ) + / b.base.amount.value; FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY ); return asset( static_cast(result), b.quote.asset_id ); } else if( a.asset_id == b.quote.asset_id ) { FC_ASSERT( b.quote.amount.value > 0 ); - uint128_t result = (uint128_t(a.amount.value) * b.base.amount.value + b.quote.amount.value - 1)/b.quote.amount.value; + uint128_t result = ( ( ( uint128_t(a.amount.value) * b.base.amount.value ) + b.quote.amount.value ) - 1 ) + / b.quote.amount.value; FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY ); return asset( static_cast(result), b.base.asset_id ); } - FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset::multiply_and_round_up(price)", ("asset",a)("price",b) ); + FC_THROW_EXCEPTION( fc::assert_exception, + "invalid asset::multiply_and_round_up(price)", ("asset",a)("price",b) ); } price operator / ( const asset& base, const asset& quote ) @@ -100,8 +103,11 @@ namespace graphene { namespace protocol { return price{base,quote}; } FC_CAPTURE_AND_RETHROW( (base)(quote) ) } - price price::max( asset_id_type base, asset_id_type quote ) { return asset( share_type(GRAPHENE_MAX_SHARE_SUPPLY), base ) / asset( share_type(1), quote); } - price price::min( asset_id_type base, asset_id_type quote ) { return asset( 1, base ) / asset( GRAPHENE_MAX_SHARE_SUPPLY, quote); } + price price::max( asset_id_type base, asset_id_type quote ) + { return asset( share_type(GRAPHENE_MAX_SHARE_SUPPLY), base ) / asset( share_type(1), quote); } + + price price::min( asset_id_type base, asset_id_type quote ) + { return asset( share_type(1), base ) / asset( share_type(GRAPHENE_MAX_SHARE_SUPPLY), quote); } price operator * ( const price& p, const ratio_type& r ) { try { @@ -121,13 +127,13 @@ namespace graphene { namespace protocol { static const uint128_t max( GRAPHENE_MAX_SHARE_SUPPLY ); while( cp.numerator() > max || cp.denominator() > max ) { - if( cp.numerator() == 1 ) + if( 1 == cp.numerator() ) { cp = boost::rational( 1, max ); using_max = true; break; } - else if( cp.denominator() == 1 ) + else if( 1 == cp.denominator() ) { cp = boost::rational( max, 1 ); using_max = true; @@ -341,26 +347,32 @@ namespace graphene { namespace protocol { // compile-time table of powers of 10 using template metaprogramming -template< int N > +template< size_t N > struct p10 { - static const int64_t v = 10 * p10::v; + static constexpr int64_t v = 10 * p10::v; }; template<> struct p10<0> { - static const int64_t v = 1; + static constexpr int64_t v = 1; }; -const int64_t scaled_precision_lut[19] = +share_type asset::scaled_precision( uint8_t precision ) { - p10< 0 >::v, p10< 1 >::v, p10< 2 >::v, p10< 3 >::v, - p10< 4 >::v, p10< 5 >::v, p10< 6 >::v, p10< 7 >::v, - p10< 8 >::v, p10< 9 >::v, p10< 10 >::v, p10< 11 >::v, - p10< 12 >::v, p10< 13 >::v, p10< 14 >::v, p10< 15 >::v, - p10< 16 >::v, p10< 17 >::v, p10< 18 >::v -}; + FC_ASSERT( precision < 19 ); + static constexpr std::array scaled_precision_lut = + { + p10< 0 >::v, p10< 1 >::v, p10< 2 >::v, p10< 3 >::v, + p10< 4 >::v, p10< 5 >::v, p10< 6 >::v, p10< 7 >::v, + p10< 8 >::v, p10< 9 >::v, p10< 10 >::v, p10< 11 >::v, + p10< 12 >::v, p10< 13 >::v, p10< 14 >::v, p10< 15 >::v, + p10< 16 >::v, p10< 17 >::v, p10< 18 >::v + }; + + return scaled_precision_lut[ precision ]; +} } } // graphene::protocol diff --git a/libraries/protocol/include/graphene/protocol/asset.hpp b/libraries/protocol/include/graphene/protocol/asset.hpp index 29a8e7e803..3072a0d90f 100644 --- a/libraries/protocol/include/graphene/protocol/asset.hpp +++ b/libraries/protocol/include/graphene/protocol/asset.hpp @@ -26,8 +26,6 @@ namespace graphene { namespace protocol { - extern const int64_t scaled_precision_lut[]; - struct price; struct asset @@ -90,11 +88,7 @@ namespace graphene { namespace protocol { return asset( a.amount + b.amount, a.asset_id ); } - static share_type scaled_precision( uint8_t precision ) - { - FC_ASSERT( precision < 19 ); - return scaled_precision_lut[ precision ]; - } + static share_type scaled_precision( uint8_t precision ); asset multiply_and_round_up( const price& p )const; ///< Multiply and round up }; @@ -155,9 +149,9 @@ namespace graphene { namespace protocol { price operator / ( const price& p, const ratio_type& r ); inline price& operator *= ( price& p, const ratio_type& r ) - { return p = p * r; } + { p = p * r; return p; } inline price& operator /= ( price& p, const ratio_type& r ) - { return p = p / r; } + { p = p / r; return p; } /** * @class price_feed @@ -208,7 +202,7 @@ namespace graphene { namespace protocol { * * The Maximum Short Squeeze Price is computed as follows, in units of DEBT per COLLATERAL: * - * MSSP = settlement_price / MSSR; + * MSSP = settlement_price / MSSR * * @return The MSSP in units of DEBT per COLLATERAL. */ @@ -237,11 +231,11 @@ namespace graphene { namespace protocol { * determined by the Maximum Short Squeeze Ratio (MSSR) and the Margin Call Fee Ratio (MCFR) * as follows, in units of DEBT per COLLATERAL: * - * MCOP = settlement_price / (MSSR - MCFR); + * MCOP = settlement_price / (MSSR - MCFR) * * Compare with Maximum Short Squeeze Price (MSSP), which is computed as follows: * - * MSSP = settlement_price / MSSR; + * MSSP = settlement_price / MSSR * * Since BSIP-74, we distinguish between Maximum Short Squeeze Price (MSSP) and Margin Call * Order Price (MCOP). Margin calls previously offered collateral at the MSSP, but now they From 45e687cb50987e716aa2f9846320eaee9b394c3f Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 1 Oct 2021 10:12:43 +0000 Subject: [PATCH 221/258] Fix code smells --- libraries/chain/db_maint.cpp | 32 ++++++++++++++++--------------- libraries/chain/fork_database.cpp | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 8aba0f8a93..112a6a1a27 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -208,7 +208,7 @@ void database::update_active_witnesses() /// accounts that vote for 0 or 1 witness do not get to express an opinion on /// the number of witnesses to have (they abstain and are non-voting accounts) - share_type stake_tally = 0; + share_type stake_tally = 0; size_t witness_count = 0; if( stake_target > 0 ) @@ -306,8 +306,9 @@ void database::update_active_witnesses() void database::update_active_committee_members() { try { - assert( _committee_count_histogram_buffer.size() > 0 ); - share_type stake_target = (_total_voting_stake[0]-_committee_count_histogram_buffer[0]) / 2; + assert( !_committee_count_histogram_buffer.empty() ); + constexpr size_t two = 2; + share_type stake_target = (_total_voting_stake[0]-_committee_count_histogram_buffer[0]) / two; /// accounts that vote for 0 or 1 committee member do not get to express an opinion on /// the number of committee members to have (they abstain and are non-voting accounts) @@ -324,7 +325,7 @@ void database::update_active_committee_members() const chain_property_object& cpo = get_chain_properties(); - committee_member_count = std::max( committee_member_count*2+1, + committee_member_count = std::max( ( committee_member_count * two ) + 1, (size_t)cpo.immutable_parameters.min_committee_member_count ); auto committee_members = sort_votable_objects( committee_member_count ); @@ -955,7 +956,7 @@ void database::process_bitassets() /**** * @brief a one-time data process to correct max_supply - * + * * NOTE: while exceeding max_supply happened in mainnet, it seemed to have corrected * itself before HF 1465. But this method must remain to correct some assets in testnet */ @@ -970,12 +971,13 @@ void process_hf_1465( database& db ) graphene::chain::share_type max_supply = current_asset.options.max_supply; if (current_supply > max_supply && max_supply != GRAPHENE_MAX_SHARE_SUPPLY) { - wlog( "Adjusting max_supply of ${asset} because current_supply (${current_supply}) is greater than ${old}.", - ("asset", current_asset.symbol) + wlog( "Adjusting max_supply of ${asset} because current_supply (${current_supply}) is greater than ${old}.", + ("asset", current_asset.symbol) ("current_supply", current_supply.value) ("old", max_supply)); db.modify( current_asset, [current_supply](asset_object& obj) { - obj.options.max_supply = graphene::chain::share_type(std::min(current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY)); + obj.options.max_supply = graphene::chain::share_type(std::min(current_supply.value, + GRAPHENE_MAX_SHARE_SUPPLY)); }); } } @@ -1203,7 +1205,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g optional delegator_recalc_times; vote_tally_helper( database& db ) - : d(db), props( d.get_global_properties() ), dprops( d.get_dynamic_global_properties() ), + : d(db), props( d.get_global_properties() ), dprops( d.get_dynamic_global_properties() ), now( d.head_block_time() ), hf2103_passed( HARDFORK_CORE_2103_PASSED( now ) ), hf2262_passed( HARDFORK_CORE_2262_PASSED( now ) ), pob_activated( dprops.total_pob > 0 || dprops.total_inactive > 0 ) @@ -1315,17 +1317,17 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g vp_all = vp_active = voting_stake[2]; if( !directly_voting ) { - vp_active = voting_stake[2] = detail::vote_recalc_options::delegator().get_recalced_voting_stake( + vp_active = voting_stake[2] = detail::vote_recalc_options::delegator().get_recalced_voting_stake( voting_stake[2], stats.last_vote_time, *delegator_recalc_times ); } - vp_witness = voting_stake[1] = detail::vote_recalc_options::witness().get_recalced_voting_stake( + vp_witness = voting_stake[1] = detail::vote_recalc_options::witness().get_recalced_voting_stake( voting_stake[2], opinion_account_stats.last_vote_time, *witness_recalc_times ); - vp_committee = voting_stake[0] = detail::vote_recalc_options::committee().get_recalced_voting_stake( + vp_committee = voting_stake[0] = detail::vote_recalc_options::committee().get_recalced_voting_stake( voting_stake[2], opinion_account_stats.last_vote_time, *committee_recalc_times ); num_committee_voting_stake = voting_stake[0]; if( opinion_account.num_committee_voted > 1 ) voting_stake[0] /= opinion_account.num_committee_voted; - vp_worker = voting_stake[2] = detail::vote_recalc_options::worker().get_recalced_voting_stake( + vp_worker = voting_stake[2] = detail::vote_recalc_options::worker().get_recalced_voting_stake( voting_stake[2], opinion_account_stats.last_vote_time, *worker_recalc_times ); } @@ -1347,7 +1349,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g update_stats.vp_committee += vp_committee; update_stats.vp_witness += vp_witness; update_stats.vp_worker += vp_worker; - // update_stats.vote_tally_time = now; + // update_stats.vote_tally_time = now; } }); @@ -1382,7 +1384,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g } tally_helper(*this); perform_account_maintenance( tally_helper ); - + struct clear_canary { clear_canary(vector& target): target(target){} ~clear_canary() { target.clear(); } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index f357442a40..7b4c9b98b6 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -92,7 +92,7 @@ void fork_database::_push_block(const item_ptr& item) _head = item; uint32_t min_num = _head->num - std::min( _max_size, _head->num ); auto& num_idx = _index.get(); - while( num_idx.size() > 0 && (*num_idx.begin())->num < min_num ) + while( !num_idx.empty() && (*num_idx.begin())->num < min_num ) num_idx.erase( num_idx.begin() ); } } From aa2832b07b656d8df0496d63f9227ad48dc9ef93 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 1 Oct 2021 13:01:08 +0000 Subject: [PATCH 222/258] Fix code smells --- libraries/chain/db_block.cpp | 3 +- libraries/chain/db_maint.cpp | 295 ++++++++++-------- .../chain/include/graphene/chain/database.hpp | 6 +- 3 files changed, 174 insertions(+), 130 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 9d2c50bf71..0c54dbf0f9 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -599,7 +599,6 @@ void database::_apply_block( const signed_block& next_block ) ("id",next_block.id()) ); const witness_object& signing_witness = validate_block_header(skip, next_block); - const auto& global_props = get_global_properties(); const auto& dynamic_global_props = get_dynamic_global_properties(); bool maint_needed = (dynamic_global_props.next_maintenance_time <= next_block.timestamp); @@ -638,7 +637,7 @@ void database::_apply_block( const signed_block& next_block ) // Are we at the maintenance interval? if( maint_needed ) - perform_chain_maintenance(next_block, global_props); + perform_chain_maintenance( next_block ); create_block_summary(next_block); clear_expired_transactions(); diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 112a6a1a27..30becbe491 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -202,8 +202,10 @@ void database::pay_workers( share_type& budget ) void database::update_active_witnesses() { try { - assert( _witness_count_histogram_buffer.size() > 0 ); - share_type stake_target = (_total_voting_stake[1]-_witness_count_histogram_buffer[0]) / 2; + assert( !_witness_count_histogram_buffer.empty() ); + constexpr size_t two = 2; + constexpr size_t vid_witness = static_cast( vote_id_type::witness ); // 1 + share_type stake_target = (_total_voting_stake[vid_witness]-_witness_count_histogram_buffer[0]) / two; /// accounts that vote for 0 or 1 witness do not get to express an opinion on /// the number of witnesses to have (they abstain and are non-voting accounts) @@ -222,7 +224,8 @@ void database::update_active_witnesses() const chain_property_object& cpo = get_chain_properties(); - witness_count = std::max( witness_count*2+1, (size_t)cpo.immutable_parameters.min_witness_count ); + witness_count = std::max( ( witness_count * two ) + 1, + (size_t)cpo.immutable_parameters.min_witness_count ); auto wits = sort_votable_objects( witness_count ); const global_property_object& gpo = get_global_properties(); @@ -279,7 +282,7 @@ void database::update_active_witnesses() a.active.weight_threshold += votes; } - a.active.weight_threshold /= 2; + a.active.weight_threshold /= two; a.active.weight_threshold += 1; } else @@ -308,7 +311,8 @@ void database::update_active_committee_members() { try { assert( !_committee_count_histogram_buffer.empty() ); constexpr size_t two = 2; - share_type stake_target = (_total_voting_stake[0]-_committee_count_histogram_buffer[0]) / two; + constexpr size_t vid_committee = static_cast( vote_id_type::committee ); // 0 + share_type stake_target = (_total_voting_stake[vid_committee]-_committee_count_histogram_buffer[0]) / two; /// accounts that vote for 0 or 1 committee member do not get to express an opinion on /// the number of committee members to have (they abstain and are non-voting accounts) @@ -385,7 +389,7 @@ void database::update_active_committee_members() a.active.weight_threshold += votes; } - a.active.weight_threshold /= 2; + a.active.weight_threshold /= two; a.active.weight_threshold += 1; } else @@ -483,7 +487,8 @@ void database::process_budget() // voting on changes to block interval). // assert( gpo.parameters.block_interval > 0 ); - uint64_t blocks_to_maint = (uint64_t(time_to_maint) + gpo.parameters.block_interval - 1) / gpo.parameters.block_interval; + uint64_t blocks_to_maint = ( ( uint64_t(time_to_maint) + gpo.parameters.block_interval ) - 1 ) + / gpo.parameters.block_interval; // blocks_to_maint > 0 because time_to_maint > 0, // which means numerator is at least equal to block_interval @@ -500,7 +505,8 @@ void database::process_budget() fc::uint128_t worker_budget_u128 = gpo.parameters.worker_budget_per_day.value; worker_budget_u128 *= uint64_t(time_to_maint); - worker_budget_u128 /= 60*60*24; + constexpr uint64_t seconds_per_day = 86400; + worker_budget_u128 /= seconds_per_day; share_type worker_budget; if( worker_budget_u128 >= static_cast(available_funds.value) ) @@ -515,13 +521,14 @@ void database::process_budget() rec.leftover_worker_funds = leftover_worker_funds; available_funds += leftover_worker_funds; - rec.supply_delta = rec.witness_budget - + rec.worker_budget - - rec.leftover_worker_funds - - rec.from_accumulated_fees + rec.supply_delta = ((( rec.witness_budget + + rec.worker_budget ) + - rec.leftover_worker_funds ) + - rec.from_accumulated_fees ) - rec.from_unused_witness_budget; - modify(core, [&]( asset_dynamic_data_object& _core ) + modify(core, [&rec,&witness_budget,&worker_budget,&leftover_worker_funds,&dpo] + ( asset_dynamic_data_object& _core ) { _core.current_supply = (_core.current_supply + rec.supply_delta ); @@ -535,7 +542,7 @@ void database::process_budget() _core.accumulated_fees = 0; }); - modify(dpo, [&]( dynamic_global_property_object& _dpo ) + modify(dpo, [&witness_budget, &now]( dynamic_global_property_object& _dpo ) { // Since initial witness_budget was rolled into // available_funds, we replace it with witness_budget @@ -545,7 +552,7 @@ void database::process_budget() }); rec.current_supply = core.current_supply; - create< budget_record_object >( [&]( budget_record_object& _rec ) + create< budget_record_object >( [this,&rec]( budget_record_object& _rec ) { _rec.time = head_block_time(); _rec.record = rec; @@ -578,8 +585,7 @@ void visit_special_authorities( const database& db, Visitor visit ) void update_top_n_authorities( database& db ) { - visit_special_authorities( db, - [&]( const account_object& acct, bool is_owner, const special_authority& auth ) + visit_special_authorities( db, [&db]( const account_object& acct, bool is_owner, const special_authority& auth ) { if( auth.is_type< top_holders_special_authority >() ) { @@ -589,7 +595,7 @@ void update_top_n_authorities( database& db ) vote_counter vc; const auto& bal_idx = db.get_index_type< account_balance_index >().indices().get< by_asset_balance >(); uint8_t num_needed = tha.num_top_holders; - if( num_needed == 0 ) + if( 0 == num_needed ) return; // find accounts @@ -601,15 +607,16 @@ void update_top_n_authorities( database& db ) continue; vc.add( bal.owner, bal.balance.value ); --num_needed; - if( num_needed == 0 ) + if( 0 == num_needed ) break; } - db.modify( acct, [&]( account_object& a ) + db.modify( acct, [&vc,&is_owner]( account_object& a ) { vc.finish( is_owner ? a.owner : a.active ); if( !vc.is_empty() ) - a.top_n_control_flags |= (is_owner ? account_object::top_n_control_owner : account_object::top_n_control_active); + a.top_n_control_flags |= (is_owner ? account_object::top_n_control_owner + : account_object::top_n_control_active); } ); } } ); @@ -623,21 +630,23 @@ void split_fba_balance( uint16_t designated_asset_issuer_pct ) { - FC_ASSERT( uint32_t(network_pct) + uint32_t(designated_asset_buyback_pct) + uint32_t(designated_asset_issuer_pct) == GRAPHENE_100_PERCENT ); + FC_ASSERT( ( uint32_t(network_pct) + designated_asset_buyback_pct ) + designated_asset_issuer_pct + == GRAPHENE_100_PERCENT ); const fba_accumulator_object& fba = fba_accumulator_id_type( fba_id )(db); - if( fba.accumulated_fba_fees == 0 ) + if( 0 == fba.accumulated_fba_fees ) return; const asset_dynamic_data_object& core_dd = db.get_core_dynamic_data(); if( !fba.is_configured(db) ) { - ilog( "${n} core given to network at block ${b} due to non-configured FBA", ("n", fba.accumulated_fba_fees)("b", db.head_block_time()) ); - db.modify( core_dd, [&]( asset_dynamic_data_object& _core_dd ) + ilog( "${n} core given to network at block ${b} due to non-configured FBA", + ("n", fba.accumulated_fba_fees)("b", db.head_block_time()) ); + db.modify( core_dd, [&fba]( asset_dynamic_data_object& _core_dd ) { _core_dd.current_supply -= fba.accumulated_fba_fees; } ); - db.modify( fba, [&]( fba_accumulator_object& _fba ) + db.modify( fba, []( fba_accumulator_object& _fba ) { _fba.accumulated_fba_fees = 0; } ); @@ -688,7 +697,7 @@ void split_fba_balance( db.push_applied_operation(vop); } - db.modify( fba, [&]( fba_accumulator_object& _fba ) + db.modify( fba, []( fba_accumulator_object& _fba ) { _fba.accumulated_fba_fees = 0; } ); @@ -696,15 +705,20 @@ void split_fba_balance( void distribute_fba_balances( database& db ) { - split_fba_balance( db, fba_accumulator_id_transfer_to_blind , 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); - split_fba_balance( db, fba_accumulator_id_blind_transfer , 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); - split_fba_balance( db, fba_accumulator_id_transfer_from_blind, 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); + constexpr uint16_t twenty = 20; + constexpr uint16_t twenty_percent = twenty * GRAPHENE_1_PERCENT; + constexpr uint16_t sixty = 60; + constexpr uint16_t sixty_percent = sixty * GRAPHENE_1_PERCENT; + split_fba_balance( db, fba_accumulator_id_transfer_to_blind , twenty_percent, sixty_percent, twenty_percent ); + split_fba_balance( db, fba_accumulator_id_blind_transfer , twenty_percent, sixty_percent, twenty_percent ); + split_fba_balance( db, fba_accumulator_id_transfer_from_blind, twenty_percent, sixty_percent, twenty_percent ); } void create_buyback_orders( database& db ) { const auto& bbo_idx = db.get_index_type< buyback_index >().indices().get(); - const auto& bal_idx = db.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >(); + const auto& bal_idx = db.get_index_type< primary_index< account_balance_index > >() + .get_secondary_index< balances_by_account_index >(); for( const buyback_object& bbo : bbo_idx ) { @@ -715,7 +729,8 @@ void create_buyback_orders( database& db ) if( !buyback_account.allowed_assets.valid() ) { - wlog( "skipping buyback account ${b} at block ${n} because allowed_assets does not exist", ("b", buyback_account)("n", db.head_block_num()) ); + wlog( "skipping buyback account ${b} at block ${n} because allowed_assets does not exist", + ("b", buyback_account)("n", db.head_block_num()) ); continue; } @@ -730,7 +745,8 @@ void create_buyback_orders( database& db ) continue; if( buyback_account.allowed_assets->find( asset_to_sell ) == buyback_account.allowed_assets->end() ) { - wlog( "buyback account ${b} not selling disallowed holdings of asset ${a} at block ${n}", ("b", buyback_account)("a", asset_to_sell)("n", db.head_block_num()) ); + wlog( "buyback account ${b} not selling disallowed holdings of asset ${a} at block ${n}", + ("b", buyback_account)("a", asset_to_sell)("n", db.head_block_num()) ); continue; } @@ -761,9 +777,12 @@ void create_buyback_orders( database& db ) } catch( const fc::exception& e ) { - // we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account - wlog( "Skipping buyback processing selling ${as} for ${ab} for buyback account ${b} at block ${n}; exception was ${e}", - ("as", asset_to_sell)("ab", asset_to_buy)("b", buyback_account)("n", db.head_block_num())("e", e.to_detail_string()) ); + // we can in fact get here, + // e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account + wlog( "Skipping buyback processing selling ${as} for ${ab} for buyback account ${b} at block ${n}; " + "exception was ${e}", + ("as", asset_to_sell)("ab", asset_to_buy)("b", buyback_account) + ("n", db.head_block_num())("e", e.to_detail_string()) ); continue; } } @@ -812,7 +831,7 @@ void database::process_bids( const asset_bitasset_data_object& bad ) const asset_dynamic_data_object& bdd = to_revive.dynamic_data( *this ); const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get(); - const auto start = bid_idx.lower_bound( boost::make_tuple( to_revive_id, price::max( bad.options.short_backing_asset, to_revive_id ), collateral_bid_id_type() ) ); + const auto start = bid_idx.lower_bound( to_revive_id ); share_type covered = 0; auto itr = start; @@ -824,7 +843,8 @@ void database::process_bids( const asset_bitasset_data_object& bad ) debt_in_bid.amount = bdd.current_supply; asset total_collateral = debt_in_bid * bad.settlement_price; total_collateral += bid.inv_swan_price.base; - price call_price = price::call_price( debt_in_bid, total_collateral, bad.current_feed.maintenance_collateral_ratio ); + price call_price = price::call_price( debt_in_bid, total_collateral, + bad.current_feed.maintenance_collateral_ratio ); if( ~call_price >= bad.current_feed.settlement_price ) break; covered += debt_in_bid.amount; ++itr; @@ -834,7 +854,8 @@ void database::process_bids( const asset_bitasset_data_object& bad ) const auto end = itr; share_type to_cover = bdd.current_supply; share_type remaining_fund = bad.settlement_fund; - for( itr = start; itr != end; ) + itr = start; + while( itr != end ) { const collateral_bid_object& bid = *itr; ++itr; @@ -935,7 +956,9 @@ void database::process_bitassets() o.options.feed_lifetime_sec < head_epoch_seconds ) // if smartcoin && check overflow { fc::time_point_sec calculated = head_time - o.options.feed_lifetime_sec; - for( auto itr = o.feeds.rbegin(); itr != o.feeds.rend(); ) // loop feeds + auto itr = o.feeds.rbegin(); + auto end = o.feeds.rend(); + while( itr != end ) // loop feeds { auto feed_time = itr->second.first; std::advance( itr, 1 ); @@ -964,7 +987,8 @@ void process_hf_1465( database& db ) { // for each market issued asset const auto& asset_idx = db.get_index_type().indices().get(); - for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_idx.end(); ++asset_itr ) + auto asset_end = asset_idx.end(); + for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_end; ++asset_itr ) { const auto& current_asset = *asset_itr; graphene::chain::share_type current_supply = current_asset.dynamic_data(db).current_supply; @@ -1021,15 +1045,15 @@ static void update_bitasset_current_feeds(database& db) * https://github.com/bitshares/bitshares-core/issues/890 * * @param db the database - * @param skip_check_call_orders true if check_call_orders() should not be called */ // NOTE: Unable to remove this function for testnet nor mainnet. Unfortunately, bad // feeds were found. -void process_hf_868_890( database& db, bool skip_check_call_orders ) +void process_hf_868_890( database& db ) { // for each market issued asset const auto& asset_idx = db.get_index_type().indices().get(); - for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_idx.end(); ++asset_itr ) + auto asset_end = asset_idx.end(); + for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_end; ++asset_itr ) { const auto& current_asset = *asset_itr; // Incorrect witness & committee feeds can simply be removed. @@ -1084,7 +1108,7 @@ void process_hf_868_890( database& db, bool skip_check_call_orders ) * @brief Remove any custom active authorities whose expiration dates are in the past * @param db A mutable database reference */ -void delete_expired_custom_authorities( database& db ) +void delete_expired_custom_auths( database& db ) { const auto& index = db.get_index_type().indices().get(); while (!index.empty() && index.begin()->valid_to < db.head_block_time()) @@ -1123,7 +1147,8 @@ namespace detail { total_recalc_seconds = ( recalc_steps - 1 ) * seconds_per_step; // should not overflow power_percents_to_subtract.reserve( recalc_steps - 1 ); for( uint32_t i = 1; i < recalc_steps; ++i ) - power_percents_to_subtract.push_back( GRAPHENE_100_PERCENT * i / recalc_steps ); // should not overflow + // should not overflow + power_percents_to_subtract.push_back( (uint16_t)( ( GRAPHENE_100_PERCENT * i ) / recalc_steps ) ); } vote_recalc_times get_vote_recalc_times( const time_point_sec now ) const @@ -1137,10 +1162,10 @@ namespace detail { uint32_t total_recalc_seconds; vector power_percents_to_subtract; - static const vote_recalc_options witness(); - static const vote_recalc_options committee(); - static const vote_recalc_options worker(); - static const vote_recalc_options delegator(); + static const vote_recalc_options& witness(); + static const vote_recalc_options& committee(); + static const vote_recalc_options& worker(); + static const vote_recalc_options& delegator(); // return the stake that is "recalced to X" uint64_t get_recalced_voting_stake( const uint64_t stake, const time_point_sec last_vote_time, @@ -1159,29 +1184,29 @@ namespace detail { } }; - const vote_recalc_options vote_recalc_options::witness() + const vote_recalc_options& vote_recalc_options::witness() { static const vote_recalc_options o( 360*86400, 8, 45*86400 ); return o; } - const vote_recalc_options vote_recalc_options::committee() + const vote_recalc_options& vote_recalc_options::committee() { static const vote_recalc_options o( 360*86400, 8, 45*86400 ); return o; } - const vote_recalc_options vote_recalc_options::worker() + const vote_recalc_options& vote_recalc_options::worker() { static const vote_recalc_options o( 360*86400, 8, 45*86400 ); return o; } - const vote_recalc_options vote_recalc_options::delegator() + const vote_recalc_options& vote_recalc_options::delegator() { static const vote_recalc_options o( 360*86400, 8, 45*86400 ); return o; } } -void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) +void database::perform_chain_maintenance( const signed_block& next_block ) { const auto& gpo = get_global_properties(); const auto& dgpo = get_dynamic_global_properties(); @@ -1198,23 +1223,27 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g const bool hf2103_passed; const bool hf2262_passed; const bool pob_activated; + const size_t two = 2; + const size_t vid_committee = static_cast( vote_id_type::committee ); // 0 + const size_t vid_witness = static_cast( vote_id_type::witness ); // 1 + const size_t vid_worker = static_cast( vote_id_type::worker ); // 2 optional witness_recalc_times; optional committee_recalc_times; optional worker_recalc_times; optional delegator_recalc_times; - vote_tally_helper( database& db ) + explicit vote_tally_helper( database& db ) : d(db), props( d.get_global_properties() ), dprops( d.get_dynamic_global_properties() ), now( d.head_block_time() ), hf2103_passed( HARDFORK_CORE_2103_PASSED( now ) ), hf2262_passed( HARDFORK_CORE_2262_PASSED( now ) ), pob_activated( dprops.total_pob > 0 || dprops.total_inactive > 0 ) { d._vote_tally_buffer.resize( props.next_available_vote_id, 0 ); - d._witness_count_histogram_buffer.resize( props.parameters.maximum_witness_count / 2 + 1, 0 ); - d._committee_count_histogram_buffer.resize( props.parameters.maximum_committee_count / 2 + 1, 0 ); - d._total_voting_stake[0] = 0; - d._total_voting_stake[1] = 0; + d._witness_count_histogram_buffer.resize( (props.parameters.maximum_witness_count / two) + 1, 0 ); + d._committee_count_histogram_buffer.resize( (props.parameters.maximum_committee_count / two) + 1, 0 ); + d._total_voting_stake[vid_committee] = 0; + d._total_voting_stake[vid_witness] = 0; if( hf2103_passed ) { witness_recalc_times = detail::vote_recalc_options::witness().get_vote_recalc_times( now ); @@ -1239,16 +1268,17 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g const account_object& opinion_account = ( directly_voting ? stake_account : d.get(stake_account.options.voting_account) ); - uint64_t voting_stake[3]; // 0=committee, 1=witness, 2=worker, as in vote_id_type::vote_type + std::array voting_stake; // 0=committee, 1=witness, 2=worker, as in vote_id_type::vote_type uint64_t num_committee_voting_stake; // number of committee members - voting_stake[2] = ( pob_activated ? 0 : stats.total_core_in_orders.value ) - + ( ( !hf2262_passed && stake_account.cashback_vb.valid() ) ? - (*stake_account.cashback_vb)(d).balance.amount.value : 0 ) - + ( hf2262_passed ? 0 : stats.core_in_balance.value ); + voting_stake[vid_worker] = pob_activated ? 0 : stats.total_core_in_orders.value; + voting_stake[vid_worker] += ( !hf2262_passed && stake_account.cashback_vb.valid() ) ? + (*stake_account.cashback_vb)(d).balance.amount.value : 0; + voting_stake[vid_worker] += hf2262_passed ? 0 : stats.core_in_balance.value; // voting power stats uint64_t vp_all = 0; ///< all voting power. - uint64_t vp_active = 0; ///< the voting power of the proxy, if there is no attenuation, it is equal to vp_all. + /// the voting power of the proxy, if there is no attenuation, it is equal to vp_all. + uint64_t vp_active = 0; uint64_t vp_committee = 0; ///< the final voting power for the committees. uint64_t vp_witness = 0; ///< the final voting power for the witnesses. uint64_t vp_worker = 0; ///< the final voting power for the workers. @@ -1258,81 +1288,92 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g const uint64_t pol_value = stats.total_pol_value.value; const uint64_t pob_amount = stats.total_core_pob.value; const uint64_t pob_value = stats.total_pob_value.value; - if( pob_amount == 0 ) + if( 0 == pob_amount ) { - voting_stake[2] += pol_value; + voting_stake[vid_worker] += pol_value; } - else if( pol_amount == 0 ) // and pob_amount > 0 + else if( 0 == pol_amount ) // and pob_amount > 0 { - if( pob_amount <= voting_stake[2] ) + if( pob_amount <= voting_stake[vid_worker] ) { - voting_stake[2] += ( pob_value - pob_amount ); + voting_stake[vid_worker] += ( pob_value - pob_amount ); } else { - auto base_value = static_cast( voting_stake[2] ) * pob_value / pob_amount; - voting_stake[2] = static_cast( base_value ); + auto base_value = ( static_cast( voting_stake[vid_worker] ) * pob_value ) + / pob_amount; + voting_stake[vid_worker] = static_cast( base_value ); } } else if( pob_amount <= pol_amount ) // pob_amount > 0 && pol_amount > 0 { - auto base_value = static_cast( pob_value ) * pol_value / pol_amount; - auto diff_value = static_cast( pob_amount ) * pol_value / pol_amount; + auto base_value = ( static_cast( pob_value ) * pol_value ) / pol_amount; + auto diff_value = ( static_cast( pob_amount ) * pol_value ) / pol_amount; base_value += ( pol_value - diff_value ); - voting_stake[2] += static_cast( base_value ); + voting_stake[vid_worker] += static_cast( base_value ); } else // pob_amount > pol_amount > 0 { - auto base_value = static_cast( pol_value ) * pob_value / pob_amount; + auto base_value = ( static_cast( pol_value ) * pob_value ) / pob_amount; fc::uint128_t diff_amount = pob_amount - pol_amount; - if( diff_amount <= voting_stake[2] ) + if( diff_amount <= voting_stake[vid_worker] ) { - auto diff_value = static_cast( pol_amount ) * pob_value / pob_amount; + auto diff_value = ( static_cast( pol_amount ) * pob_value ) / pob_amount; base_value += ( pob_value - diff_value ); - voting_stake[2] += static_cast( base_value - diff_amount ); + voting_stake[vid_worker] += static_cast( base_value - diff_amount ); } - else // diff_amount > voting_stake[2] + else // diff_amount > voting_stake[vid_worker] { - base_value += static_cast( voting_stake[2] ) * pob_value / pob_amount; - voting_stake[2] = static_cast( base_value ); + base_value += ( static_cast( voting_stake[vid_worker] ) * pob_value ) / pob_amount; + voting_stake[vid_worker] = static_cast( base_value ); } } // Shortcut - if( voting_stake[2] == 0 ) + if( 0 == voting_stake[vid_worker] ) return; - const account_statistics_object& opinion_account_stats = ( directly_voting ? stats : opinion_account.statistics( d ) ); + const auto& opinion_account_stats = ( directly_voting ? stats : opinion_account.statistics( d ) ); // Recalculate votes if( !hf2103_passed ) { - voting_stake[0] = voting_stake[2]; - voting_stake[1] = voting_stake[2]; - num_committee_voting_stake = voting_stake[2]; - vp_all = vp_active = vp_committee = vp_witness = vp_worker = voting_stake[2]; + voting_stake[vid_committee] = voting_stake[vid_worker]; + voting_stake[vid_witness] = voting_stake[vid_worker]; + num_committee_voting_stake = voting_stake[vid_worker]; + vp_all = voting_stake[vid_worker]; + vp_active = voting_stake[vid_worker]; + vp_committee = voting_stake[vid_worker]; + vp_witness = voting_stake[vid_worker]; + vp_worker = voting_stake[vid_worker]; } else { - vp_all = vp_active = voting_stake[2]; + vp_all = voting_stake[vid_worker]; + vp_active = voting_stake[vid_worker]; if( !directly_voting ) { - vp_active = voting_stake[2] = detail::vote_recalc_options::delegator().get_recalced_voting_stake( - voting_stake[2], stats.last_vote_time, *delegator_recalc_times ); + voting_stake[vid_worker] = detail::vote_recalc_options::delegator().get_recalced_voting_stake( + voting_stake[vid_worker], stats.last_vote_time, *delegator_recalc_times ); + vp_active = voting_stake[vid_worker]; } - vp_witness = voting_stake[1] = detail::vote_recalc_options::witness().get_recalced_voting_stake( - voting_stake[2], opinion_account_stats.last_vote_time, *witness_recalc_times ); - vp_committee = voting_stake[0] = detail::vote_recalc_options::committee().get_recalced_voting_stake( - voting_stake[2], opinion_account_stats.last_vote_time, *committee_recalc_times ); - num_committee_voting_stake = voting_stake[0]; + voting_stake[vid_witness] = detail::vote_recalc_options::witness().get_recalced_voting_stake( + voting_stake[vid_worker], opinion_account_stats.last_vote_time, *witness_recalc_times ); + vp_witness = voting_stake[vid_witness]; + voting_stake[vid_committee] = detail::vote_recalc_options::committee().get_recalced_voting_stake( + voting_stake[vid_worker], opinion_account_stats.last_vote_time, *committee_recalc_times ); + vp_committee = voting_stake[vid_committee]; + num_committee_voting_stake = voting_stake[vid_committee]; if( opinion_account.num_committee_voted > 1 ) - voting_stake[0] /= opinion_account.num_committee_voted; - vp_worker = voting_stake[2] = detail::vote_recalc_options::worker().get_recalced_voting_stake( - voting_stake[2], opinion_account_stats.last_vote_time, *worker_recalc_times ); + voting_stake[vid_committee] /= opinion_account.num_committee_voted; + voting_stake[vid_worker] = detail::vote_recalc_options::worker().get_recalced_voting_stake( + voting_stake[vid_worker], opinion_account_stats.last_vote_time, *worker_recalc_times ); + vp_worker = voting_stake[vid_worker]; } // update voting power - d.modify( opinion_account_stats, [=]( account_statistics_object& update_stats ) { + d.modify( opinion_account_stats, [vp_all,vp_active,vp_committee,vp_witness,vp_worker,this] + ( account_statistics_object& update_stats ) { if (update_stats.vote_tally_time != now) { update_stats.vp_all = vp_all; @@ -1349,7 +1390,6 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g update_stats.vp_committee += vp_committee; update_stats.vp_witness += vp_witness; update_stats.vp_worker += vp_worker; - // update_stats.vote_tally_time = now; } }); @@ -1363,37 +1403,40 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g } // votes for a number greater than maximum_witness_count are skipped here - if( voting_stake[1] > 0 + if( voting_stake[vid_witness] > 0 && opinion_account.options.num_witness <= props.parameters.maximum_witness_count ) { - uint16_t offset = opinion_account.options.num_witness / 2; - d._witness_count_histogram_buffer[offset] += voting_stake[1]; + uint16_t offset = opinion_account.options.num_witness / two; + d._witness_count_histogram_buffer[offset] += voting_stake[vid_witness]; } // votes for a number greater than maximum_committee_count are skipped here if( num_committee_voting_stake > 0 && opinion_account.options.num_committee <= props.parameters.maximum_committee_count ) { - uint16_t offset = opinion_account.options.num_committee / 2; + uint16_t offset = opinion_account.options.num_committee / two; d._committee_count_histogram_buffer[offset] += num_committee_voting_stake; } - d._total_voting_stake[0] += num_committee_voting_stake; - d._total_voting_stake[1] += voting_stake[1]; + d._total_voting_stake[vid_committee] += num_committee_voting_stake; + d._total_voting_stake[vid_witness] += voting_stake[vid_witness]; } } - } tally_helper(*this); + }; + + vote_tally_helper tally_helper(*this); perform_account_maintenance( tally_helper ); struct clear_canary { - clear_canary(vector& target): target(target){} + explicit clear_canary(vector& target): target(target){} + clear_canary( const clear_canary& ) = delete; ~clear_canary() { target.clear(); } private: vector& target; }; - clear_canary a(_witness_count_histogram_buffer), - b(_committee_count_histogram_buffer), - c(_vote_tally_buffer); + clear_canary a(_witness_count_histogram_buffer); + clear_canary b(_committee_count_histogram_buffer); + clear_canary c(_vote_tally_buffer); update_top_n_authorities(*this); update_active_witnesses(); @@ -1402,7 +1445,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g modify(gpo, [&dgpo](global_property_object& p) { // Remove scaling of account registration fee - p.parameters.get_mutable_fees().get().basic_fee >>= p.parameters.account_fee_scale_bitshifts * + p.parameters.get_mutable_fees().get().basic_fee >>= + p.parameters.account_fee_scale_bitshifts * (dgpo.accounts_registered_this_interval / p.parameters.accounts_per_fee_scale); if( p.pending_parameters ) @@ -1417,7 +1461,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if( next_maintenance_time <= next_block.timestamp ) { - if( next_block.block_num() == 1 ) + if( 1 == next_block.block_num() ) next_maintenance_time = time_point_sec() + (((next_block.timestamp.sec_since_epoch() / maintenance_interval) + 1) * maintenance_interval); else @@ -1438,7 +1482,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g // So this k suffices. // auto y = (head_block_time() - next_maintenance_time).to_seconds() / maintenance_interval; - next_maintenance_time += (y+1) * maintenance_interval; + next_maintenance_time += (uint32_t)( (y+1) * maintenance_interval ); } } @@ -1446,18 +1490,19 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g deprecate_annual_members(*this); // To reset call_price of all call orders, then match by new rule, for hard fork core-343 - bool to_update_and_match_call_orders_for_hf_343 = false; + bool to_process_hf_343 = false; if( (dgpo.next_maintenance_time <= HARDFORK_CORE_343_TIME) && (next_maintenance_time > HARDFORK_CORE_343_TIME) ) - to_update_and_match_call_orders_for_hf_343 = true; + to_process_hf_343 = true; // Process inconsistent price feeds - if( (dgpo.next_maintenance_time <= HARDFORK_CORE_868_890_TIME) && (next_maintenance_time > HARDFORK_CORE_868_890_TIME) ) - process_hf_868_890( *this, to_update_and_match_call_orders_for_hf_343 ); + if( (dgpo.next_maintenance_time <= HARDFORK_CORE_868_890_TIME) + && (next_maintenance_time > HARDFORK_CORE_868_890_TIME) ) + process_hf_868_890( *this ); // To reset call_price of all call orders, then match by new rule, for hard fork core-1270 - bool to_update_and_match_call_orders_for_hf_1270 = false; + bool to_process_hf_1270 = false; if( (dgpo.next_maintenance_time <= HARDFORK_CORE_1270_TIME) && (next_maintenance_time > HARDFORK_CORE_1270_TIME) ) - to_update_and_match_call_orders_for_hf_1270 = true; + to_process_hf_1270 = true; // make sure current_supply is less than or equal to max_supply if ( dgpo.next_maintenance_time <= HARDFORK_CORE_1465_TIME && next_maintenance_time > HARDFORK_CORE_1465_TIME ) @@ -1483,14 +1528,14 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g }); // We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-343 - if( to_update_and_match_call_orders_for_hf_343 ) + if( to_process_hf_343 ) { update_call_orders_hf_343(*this); match_call_orders(*this); } // We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-1270. - if( to_update_and_match_call_orders_for_hf_1270 ) + if( to_process_hf_1270 ) { update_call_orders_hf_1270(*this); update_bitasset_current_feeds(*this); @@ -1504,7 +1549,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g } process_bitassets(); - delete_expired_custom_authorities(*this); + delete_expired_custom_auths(*this); // process_budget needs to run at the bottom because // it needs to know the next_maintenance_time diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 9f8b438d95..f4f4fa2200 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -734,7 +734,7 @@ namespace graphene { namespace chain { void initialize_budget_record( fc::time_point_sec now, budget_record& rec )const; void process_budget(); void pay_workers( share_type& budget ); - void perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props); + void perform_chain_maintenance( const signed_block& next_block ); void update_active_witnesses(); void update_active_committee_members(); void update_worker_votes(); @@ -776,8 +776,8 @@ namespace graphene { namespace chain { vector _vote_tally_buffer; vector _witness_count_histogram_buffer; vector _committee_count_histogram_buffer; - uint64_t _total_voting_stake[2]; // 0=committee, 1=witness, - // as in vote_id_type::vote_type + std::array _total_voting_stake; // 0=committee, 1=witness, + // as in vote_id_type::vote_type flat_map _checkpoints; From 8632c78b3e66e0d72f4b5017fa0d1f192ea32f7b Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 1 Oct 2021 18:17:08 +0000 Subject: [PATCH 223/258] Fix code smells --- libraries/chain/db_maint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 30becbe491..469e924fab 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -204,7 +204,7 @@ void database::update_active_witnesses() { try { assert( !_witness_count_histogram_buffer.empty() ); constexpr size_t two = 2; - constexpr size_t vid_witness = static_cast( vote_id_type::witness ); // 1 + constexpr auto vid_witness = static_cast( vote_id_type::witness ); // 1 share_type stake_target = (_total_voting_stake[vid_witness]-_witness_count_histogram_buffer[0]) / two; /// accounts that vote for 0 or 1 witness do not get to express an opinion on @@ -311,7 +311,7 @@ void database::update_active_committee_members() { try { assert( !_committee_count_histogram_buffer.empty() ); constexpr size_t two = 2; - constexpr size_t vid_committee = static_cast( vote_id_type::committee ); // 0 + constexpr auto vid_committee = static_cast( vote_id_type::committee ); // 0 share_type stake_target = (_total_voting_stake[vid_committee]-_committee_count_histogram_buffer[0]) / two; /// accounts that vote for 0 or 1 committee member do not get to express an opinion on From 9b6bf01e836cd253e3795027b2d75b5e0f7e11f2 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 1 Oct 2021 22:30:56 +0000 Subject: [PATCH 224/258] Remove unneeded code --- libraries/app/database_api.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index fca286df78..328c0b766b 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -939,10 +939,6 @@ vector database_api_impl::list_assets(const string& lower result.reserve(limit); auto itr = assets_by_symbol.lower_bound(lower_bound_symbol); - - if( lower_bound_symbol == "" ) - itr = assets_by_symbol.begin(); - auto end = assets_by_symbol.end(); for( ; limit > 0 && itr != end; ++itr, --limit ) result.emplace_back( extend_asset( *itr ) ); From ce49c4fd4abcf240c61a2ee1c1035887e704f319 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 1 Oct 2021 23:23:08 +0000 Subject: [PATCH 225/258] Fix code smells --- libraries/app/database_api.cpp | 50 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 328c0b766b..6dfed13bb7 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -3164,40 +3164,52 @@ const account_object* database_api_impl::get_account_from_string( const std::str bool throw_if_not_found ) const { // TODO cache the result to avoid repeatly fetching from db - FC_ASSERT( name_or_id.size() > 0); - const account_object* account = nullptr; - if (std::isdigit(name_or_id[0])) - account = _db.find(fc::variant(name_or_id, 1).as(1)); + if( name_or_id.empty() ) + { + if( throw_if_not_found ) + FC_THROW_EXCEPTION( fc::assert_exception, "no such account" ); + else + return nullptr; + } + const account_object* account_ptr = nullptr; + if( 0 != std::isdigit(name_or_id[0]) ) + account_ptr = _db.find(fc::variant(name_or_id, 1).as(1)); else { const auto& idx = _db.get_index_type().indices().get(); auto itr = idx.find(name_or_id); if (itr != idx.end()) - account = &*itr; + account_ptr = &(*itr); } if(throw_if_not_found) - FC_ASSERT( account, "no such account" ); - return account; + FC_ASSERT( account_ptr, "no such account" ); + return account_ptr; } const asset_object* database_api_impl::get_asset_from_string( const std::string& symbol_or_id, bool throw_if_not_found ) const { // TODO cache the result to avoid repeatly fetching from db - FC_ASSERT( symbol_or_id.size() > 0); - const asset_object* asset = nullptr; - if (std::isdigit(symbol_or_id[0])) - asset = _db.find(fc::variant(symbol_or_id, 1).as(1)); + if( symbol_or_id.empty() ) + { + if( throw_if_not_found ) + FC_THROW_EXCEPTION( fc::assert_exception, "no such asset" ); + else + return nullptr; + } + const asset_object* asset_ptr = nullptr; + if( 0 != std::isdigit(symbol_or_id[0]) ) + asset_ptr = _db.find(fc::variant(symbol_or_id, 1).as(1)); else { const auto& idx = _db.get_index_type().indices().get(); auto itr = idx.find(symbol_or_id); if (itr != idx.end()) - asset = &*itr; + asset_ptr = &(*itr); } if(throw_if_not_found) - FC_ASSERT( asset, "no such asset" ); - return asset; + FC_ASSERT( asset_ptr, "no such asset" ); + return asset_ptr; } // helper function @@ -3280,7 +3292,7 @@ void database_api_impl::broadcast_updates( const vector& updates ) void database_api_impl::broadcast_market_updates( const market_queue_type& queue) { - if( queue.size() > 0 ) + if( !queue.empty() ) { auto capture_this = shared_from_this(); fc::async([capture_this, this, queue](){ @@ -3357,11 +3369,11 @@ void database_api_impl::handle_object_changed( bool force_notify, } } - if( updates.size() ) + if( !updates.empty() ) broadcast_updates(updates); } - if( _market_subscriptions.size() ) + if( !_market_subscriptions.empty() ) { market_queue_type broadcast_queue; @@ -3382,7 +3394,7 @@ void database_api_impl::handle_object_changed( bool force_notify, } } - if( broadcast_queue.size() ) + if( !broadcast_queue.empty() ) broadcast_market_updates(broadcast_queue); } } @@ -3401,7 +3413,7 @@ void database_api_impl::on_applied_block() }); } - if(_market_subscriptions.size() == 0) + if( _market_subscriptions.empty() ) return; const auto& ops = _db.get_applied_operations(); From 00f2d21ab8914ba13a20a81cf25cdcbb38a311f9 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 1 Oct 2021 23:43:20 +0000 Subject: [PATCH 226/258] Fix code smells --- libraries/chain/db_market.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 35230ecb88..5eadbe9f56 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -2236,7 +2236,7 @@ asset database::pay_market_fees(const account_object* seller, const asset_object if ( is_rewards_allowed() ) { const auto reward_percent = recv_asset.options.extensions.value.reward_percent; - if ( reward_percent && (*reward_percent) > 0 ) + if ( reward_percent.valid() && (*reward_percent) > 0 ) { const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent); if ( reward_value > 0 && is_authorized_asset(*this, seller->registrar(*this), recv_asset) ) From 005b427ba379fc0118f28a57edc41e6eae0517c7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 3 Oct 2021 22:54:53 +0000 Subject: [PATCH 227/258] Allow MPA owner to disable collateral bidding --- libraries/chain/asset_evaluator.cpp | 52 +++++++++++++++++-- libraries/chain/db_maint.cpp | 29 +++++++++++ libraries/chain/db_market.cpp | 6 +-- libraries/chain/hardfork.d/CORE_2281.hf | 6 +++ .../include/graphene/chain/asset_object.hpp | 2 + libraries/chain/market_evaluator.cpp | 9 +++- libraries/chain/proposal_evaluator.cpp | 17 +++++- libraries/protocol/asset_ops.cpp | 7 ++- .../include/graphene/protocol/asset_ops.hpp | 2 +- .../include/graphene/protocol/market.hpp | 2 +- .../include/graphene/protocol/types.hpp | 12 +++-- 11 files changed, 126 insertions(+), 18 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_2281.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 23cb00081d..40ff95b2ec 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -144,6 +144,20 @@ namespace detail { "Collateral-denominated fees are not yet active and therefore cannot be claimed." ); } + void check_asset_options_hf_core2281( const fc::time_point_sec& next_maint_time, const asset_options& options) + { + // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: + if ( !HARDFORK_CORE_2281_PASSED(next_maint_time) ) + { + // new issuer permissions should not be set until activation of the hardfork + FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_collateral_bidding), + "New asset issuer permission bit 'disable_collateral_bidding' should not be set " + "before Hardfork core-2281" ); + // Note: checks about flags are more complicated due to old bugs, + // and likely can not be removed after hardfork, so do not put them here + } + } + void check_asset_options_hf_core2467(const fc::time_point_sec& next_maint_time, const asset_options& options) { // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: @@ -151,7 +165,8 @@ namespace detail { { // new issuer permissions should not be set until activation of the hardfork FC_ASSERT( 0 == (options.issuer_permissions & asset_issuer_permission_flags::disable_bsrm_update), - "New asset issuer permission bits should not be set before Hardfork core-2467" ); + "New asset issuer permission bit 'disable_bsrm_update' should not be set " + "before Hardfork core-2467" ); } } @@ -178,6 +193,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o detail::check_asset_options_hf_1774(now, op.common_options); detail::check_asset_options_hf_bsip_48_75(now, op.common_options); detail::check_asset_options_hf_bsip81(now, op.common_options); + detail::check_asset_options_hf_core2281( next_maint_time, op.common_options ); // HF_REMOVABLE detail::check_asset_options_hf_core2467( next_maint_time, op.common_options ); // HF_REMOVABLE if( op.bitasset_opts ) { detail::check_bitasset_options_hf_bsip_48_75( now, *op.bitasset_opts ); @@ -188,10 +204,15 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o } // TODO move as many validations as possible to validate() if not triggered before hardfork - if( HARDFORK_BSIP_48_75_PASSED( now ) ) + if( HARDFORK_CORE_2281_PASSED( next_maint_time ) ) { op.common_options.validate_flags( op.bitasset_opts.valid() ); } + else if( HARDFORK_BSIP_48_75_PASSED( now ) ) + { + // do not allow the 'disable_collateral_bidding' bit + op.common_options.validate_flags( op.bitasset_opts.valid(), false ); + } const auto& chain_parameters = d.get_global_properties().parameters; FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); @@ -430,10 +451,12 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) detail::check_asset_options_hf_1774(now, o.new_options); detail::check_asset_options_hf_bsip_48_75(now, o.new_options); detail::check_asset_options_hf_bsip81(now, o.new_options); + detail::check_asset_options_hf_core2281( next_maint_time, o.new_options ); // HF_REMOVABLE detail::check_asset_options_hf_core2467( next_maint_time, o.new_options ); // HF_REMOVABLE detail::check_asset_update_extensions_hf_bsip_48_75( now, o.extensions.value ); bool hf_bsip_48_75_passed = ( HARDFORK_BSIP_48_75_PASSED( now ) ); + bool hf_core_2281_passed = ( HARDFORK_CORE_2281_PASSED( next_maint_time ) ); bool hf_core_2467_passed = ( HARDFORK_CORE_2467_PASSED( next_maint_time ) ); const asset_object& a = o.asset_to_update(d); @@ -502,17 +525,25 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) } } + // If an invalid bit was set in flags, it should be unset // TODO move as many validations as possible to validate() if not triggered before hardfork - if( hf_bsip_48_75_passed ) + if( hf_core_2281_passed ) { o.new_options.validate_flags( a.is_market_issued() ); } + else if( hf_bsip_48_75_passed ) + { + // do not allow the 'disable_collateral_bidding' bit + o.new_options.validate_flags( a.is_market_issued(), false ); + } // changed flags must be subset of old issuer permissions if( hf_bsip_48_75_passed ) { // Note: if an invalid bit was set, it can be unset regardless of the permissions - uint16_t check_bits = ( a.is_market_issued() ? VALID_FLAGS_MASK : UIA_VALID_FLAGS_MASK ); + uint16_t valid_flags_mask = hf_core_2281_passed ? VALID_FLAGS_MASK + : (VALID_FLAGS_MASK & (uint16_t)(~disable_collateral_bidding)); + uint16_t check_bits = a.is_market_issued() ? valid_flags_mask : UIA_VALID_FLAGS_MASK; FC_ASSERT( 0 == ( (o.new_options.flags ^ a.options.flags) & check_bits & (uint16_t)(~enabled_issuer_permissions_mask) ), @@ -578,6 +609,19 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) d.cancel_settle_order(*itr); } + // If we are now disabling collateral bidding, cancel all open collateral bids + if( 0 != (o.new_options.flags & disable_collateral_bidding) && asset_to_update->can_bid_collateral() ) + { + const auto& bid_idx = d.get_index_type< collateral_bid_index >().indices().get(); + auto itr = bid_idx.lower_bound( o.asset_to_update ); + while( itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == o.asset_to_update ) + { + const collateral_bid_object& bid = *itr; + ++itr; + d.cancel_bid( bid ); + } + } + // For market-issued assets, if core exchange rate changed, update flag in bitasset data if( !o.extensions.value.skip_core_exchange_rate.valid() && asset_to_update->is_market_issued() && asset_to_update->options.core_exchange_rate != o.new_options.core_exchange_rate ) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 469e924fab..4582bf310a 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -1131,6 +1131,31 @@ void process_hf_2262( database& db ) } } +/// A one-time data process to cancel all collateral bids for assets that disabled collateral bidding already +void process_hf_2281( database& db ) +{ + const auto& bid_idx = db.get_index_type< collateral_bid_index >().indices().get(); + auto bid_itr = bid_idx.begin(); + auto bid_end = bid_idx.end(); + + asset_id_type current_asset_id; + bool can_bid_collateral = true; + + while( bid_itr != bid_end ) + { + const collateral_bid_object& bid = *bid_itr; + ++bid_itr; + if( current_asset_id != bid.inv_swan_price.quote.asset_id ) + { + current_asset_id = bid.inv_swan_price.quote.asset_id; + can_bid_collateral = current_asset_id(db).can_bid_collateral(); + } + if( !can_bid_collateral ) + db.cancel_bid( bid ); + } + +} + namespace detail { struct vote_recalc_times @@ -1516,6 +1541,10 @@ void database::perform_chain_maintenance( const signed_block& next_block ) if ( dgpo.next_maintenance_time <= HARDFORK_CORE_2262_TIME && next_maintenance_time > HARDFORK_CORE_2262_TIME ) process_hf_2262(*this); + // Cancel all collateral bids on assets which disabled collateral bidding already + if ( dgpo.next_maintenance_time <= HARDFORK_CORE_2281_TIME && next_maintenance_time > HARDFORK_CORE_2281_TIME ) + process_hf_2281(*this); + // To check call orders and potential match them with force settlements, for hard fork core-2481 bool match_call_orders_for_hf_2481 = false; if( (dgpo.next_maintenance_time <= HARDFORK_CORE_2481_TIME) && (next_maintenance_time > HARDFORK_CORE_2481_TIME) ) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 5eadbe9f56..52d659c5c6 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -439,10 +439,8 @@ void database::_cancel_bids_and_revive_mpa( const asset_object& bitasset, const // cancel remaining bids const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get(); - auto itr = bid_idx.lower_bound( boost::make_tuple( bitasset.id, - price::max( bad.options.short_backing_asset, bitasset.id ), - collateral_bid_id_type() ) ); - while( itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == bitasset.id ) + auto itr = bid_idx.lower_bound( bad.asset_id ); + while( itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == bad.asset_id ) { const collateral_bid_object& bid = *itr; ++itr; diff --git a/libraries/chain/hardfork.d/CORE_2281.hf b/libraries/chain/hardfork.d/CORE_2281.hf new file mode 100644 index 0000000000..e4e47833fd --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2281.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2281 Add option for MPA owners to disable collateral bidding +#ifndef HARDFORK_CORE_2281_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2281_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2281_PASSED(now) (now > HARDFORK_CORE_2281_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 55744113a9..fcd675a829 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -112,6 +112,8 @@ namespace graphene { namespace chain { bool can_owner_update_mssr()const { return (0 == (options.issuer_permissions & disable_mssr_update)); } /// @return true if the asset owner can change black swan response method bool can_owner_update_bsrm()const { return (0 == (options.issuer_permissions & disable_bsrm_update)); } + /// @return true if can bid collateral for the asset + bool can_bid_collateral()const { return (0 == (options.flags & disable_collateral_bidding)); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 4714e5a99d..28fe567ec4 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -471,6 +471,7 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation { try { const database& d = db(); + // TODO cleanup: remove the assertion and related test cases after hardfork FC_ASSERT( d.head_block_time() > HARDFORK_CORE_216_TIME, "Not yet!" ); // Note: bidder is the fee payer thus exists in the database @@ -478,9 +479,15 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.", ("sym", _debt_asset->symbol) ); + const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; + // Note: due to old bugs, an asset can have the flag set before the hardfork, so we need the hardfork check here + // TODO review after hardfork to see if we can remove the check + if( HARDFORK_CORE_2281_PASSED( next_maint_time ) ) + FC_ASSERT( _debt_asset->can_bid_collateral(), "Collateral bidding is disabled for this asset" ); + _bitasset_data = &_debt_asset->bitasset_data(d); - FC_ASSERT( _bitasset_data->has_settlement() ); + FC_ASSERT( _bitasset_data->has_settlement(), "Cannot bid since the asset is not globally settled" ); FC_ASSERT( o.additional_collateral.asset_id == _bitasset_data->options.short_backing_asset ); diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index da4e959e63..0e16e1d904 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -51,6 +51,7 @@ namespace detail { void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op); // HF_REMOVABLE + void check_asset_options_hf_core2281(const fc::time_point_sec& next_maint_time, const asset_options& options); void check_asset_options_hf_core2467(const fc::time_point_sec& next_maint_time, const asset_options& options); void check_bitasset_opts_hf_core2467(const fc::time_point_sec& next_maint_time, const bitasset_options& options); } @@ -72,6 +73,7 @@ struct proposal_operation_hardfork_visitor detail::check_asset_options_hf_1774(block_time, v.common_options); detail::check_asset_options_hf_bsip_48_75(block_time, v.common_options); detail::check_asset_options_hf_bsip81(block_time, v.common_options); + detail::check_asset_options_hf_core2281( next_maintenance_time, v.common_options ); // HF_REMOVABLE detail::check_asset_options_hf_core2467( next_maintenance_time, v.common_options ); // HF_REMOVABLE if( v.bitasset_opts.valid() ) { detail::check_bitasset_options_hf_bsip_48_75( block_time, *v.bitasset_opts ); @@ -82,25 +84,36 @@ struct proposal_operation_hardfork_visitor } // TODO move as many validations as possible to validate() if not triggered before hardfork - if( HARDFORK_BSIP_48_75_PASSED( block_time ) ) + if( HARDFORK_CORE_2281_PASSED( next_maintenance_time ) ) { v.common_options.validate_flags( v.bitasset_opts.valid() ); } + else if( HARDFORK_BSIP_48_75_PASSED( block_time ) ) + { + // do not allow the 'disable_collateral_bidding' bit + v.common_options.validate_flags( v.bitasset_opts.valid(), false ); + } } void operator()(const graphene::chain::asset_update_operation &v) const { detail::check_asset_options_hf_1774(block_time, v.new_options); detail::check_asset_options_hf_bsip_48_75(block_time, v.new_options); detail::check_asset_options_hf_bsip81(block_time, v.new_options); + detail::check_asset_options_hf_core2281( next_maintenance_time, v.new_options ); // HF_REMOVABLE detail::check_asset_options_hf_core2467( next_maintenance_time, v.new_options ); // HF_REMOVABLE detail::check_asset_update_extensions_hf_bsip_48_75( block_time, v.extensions.value ); // TODO move as many validations as possible to validate() if not triggered before hardfork - if( HARDFORK_BSIP_48_75_PASSED( block_time ) ) + if( HARDFORK_CORE_2281_PASSED( next_maintenance_time ) ) { v.new_options.validate_flags( true ); } + else if( HARDFORK_BSIP_48_75_PASSED( block_time ) ) + { + // do not allow the 'disable_collateral_bidding' bit + v.new_options.validate_flags( true, false ); + } } diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 264f8f0649..7eacd2ceab 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -308,10 +308,15 @@ void asset_options::validate()const FC_ASSERT( *extensions.value.reward_percent <= GRAPHENE_100_PERCENT ); } -void asset_options::validate_flags( bool is_market_issued )const +// Note: this function is only called after the BSIP 48/75 hardfork +void asset_options::validate_flags( bool is_market_issued, bool allow_disable_collateral_bid )const { FC_ASSERT( 0 == (flags & (uint16_t)(~ASSET_ISSUER_PERMISSION_MASK)), "Can not set an unknown bit in flags" ); + if( !allow_disable_collateral_bid ) // before core-2281 hf, can not set the disable_collateral_bidding bit + FC_ASSERT( 0 == (flags & disable_collateral_bidding), + "Can not set the 'disable_collateral_bidding' bit in flags between the core-2281 hardfork " + "and the BSIP_48_75 hardfork" ); // Note: global_settle is checked in validate(), so do not check again here FC_ASSERT( 0 == (flags & disable_mcr_update), "Can not set disable_mcr_update flag, it is for issuer permission only" ); diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 136728b431..180c10fce0 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -98,7 +98,7 @@ namespace graphene { namespace protocol { /// Perform checks about @ref flags. /// @throws fc::exception if any check fails - void validate_flags( bool is_market_issued )const; + void validate_flags( bool is_market_issued, bool allow_disable_collateral_bid = true )const; }; /** diff --git a/libraries/protocol/include/graphene/protocol/market.hpp b/libraries/protocol/include/graphene/protocol/market.hpp index b29d8b4fa3..b91aaf478d 100644 --- a/libraries/protocol/include/graphene/protocol/market.hpp +++ b/libraries/protocol/include/graphene/protocol/market.hpp @@ -172,7 +172,7 @@ namespace graphene { namespace protocol { /** * @ingroup operations * - * This operation can be used after a black swan to bid collateral for + * This operation can be used after a global settlement to bid collateral for * taking over part of the debt and the settlement_fund (see BSIP-0018). */ struct bid_collateral_operation : public base_operation diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index efb5814f85..94328130aa 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -159,7 +159,7 @@ enum asset_issuer_permission_flags { override_authority = 0x04, ///< issuer may transfer asset back to himself transfer_restricted = 0x08, ///< require the issuer to be one party to every transfer disable_force_settle = 0x10, ///< disable force settling - global_settle = 0x20, ///< allow the bitasset owner to force a global settling, permission only + global_settle = 0x20, ///< allow the bitasset owner to force a global settlement, permission only disable_confidential = 0x40, ///< disallow the asset to be used with confidential transactions witness_fed_asset = 0x80, ///< the bitasset is to be fed by witnesses committee_fed_asset = 0x100, ///< the bitasset is to be fed by the committee @@ -180,7 +180,8 @@ enum asset_issuer_permission_flags { disable_mcr_update = 0x800, ///< the bitasset owner can not update MCR, permisison only disable_icr_update = 0x1000, ///< the bitasset owner can not update ICR, permisison only disable_mssr_update = 0x2000, ///< the bitasset owner can not update MSSR, permisison only - disable_bsrm_update = 0x4000 ///< the bitasset owner can not update BSRM, permission only + disable_bsrm_update = 0x4000, ///< the bitasset owner can not update BSRM, permission only + disable_collateral_bidding = 0x8000 ///< Can not bid collateral after a global settlement ///@} ///@} }; @@ -201,7 +202,8 @@ const static uint16_t ASSET_ISSUER_PERMISSION_MASK = | disable_mcr_update | disable_icr_update | disable_mssr_update - | disable_bsrm_update; + | disable_bsrm_update + | disable_collateral_bidding; // The "enable" bits for non-UIA assets const static uint16_t ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK = charge_market_fee @@ -220,7 +222,8 @@ const static uint16_t ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK = | disable_mcr_update | disable_icr_update | disable_mssr_update - | disable_bsrm_update; + | disable_bsrm_update + | disable_collateral_bidding; // The bits that can be used in asset issuer permissions for UIA assets const static uint16_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee @@ -365,6 +368,7 @@ FC_REFLECT_ENUM(graphene::protocol::asset_issuer_permission_flags, (disable_icr_update) (disable_mssr_update) (disable_bsrm_update) + (disable_collateral_bidding) ) namespace fc { namespace raw { From b3d2fe45c2166c565781faf4220b135e7eb8a563 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 4 Oct 2021 17:01:12 +0000 Subject: [PATCH 228/258] Update old tests for disable_collateral_bidding --- tests/common/database_fixture.hpp | 2 +- tests/tests/bsip48_75_tests.cpp | 17 ++++++++-- tests/tests/bsrm_basic_tests.cpp | 54 +++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 445520d913..89f17df53d 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -219,7 +219,7 @@ struct database_fixture_base { bool skip_key_index_test = false; uint32_t anon_acct_count; bool hf1270 = false; - bool hf2467 = false; + bool hf2467 = false; // Note: used by hf core-2281 too, assuming hf core-2281 and core-2467 occur at the same time bool hf2481 = false; bool bsip77 = false; diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 6861b9e233..84f37106ec 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -36,7 +36,7 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( bsip48_75_tests, database_fixture ) -BOOST_AUTO_TEST_CASE( hardfork_protection_test ) +BOOST_AUTO_TEST_CASE( bsip48_75_hardfork_protection_test ) { try { @@ -1062,7 +1062,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // advance to bsip48/75 hard fork generate_blocks( HARDFORK_BSIP_48_75_TIME ); - if( hf2467 ) + if( hf2467 ) // Note: tests hf core-2281 too, assumes hf core-2281 and hf core-2467 occur at the same time { auto mi = db.get_global_properties().parameters.maintenance_interval; generate_blocks(HARDFORK_CORE_2467_TIME - mi); @@ -1103,6 +1103,8 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // take a look at flags of MPA uint16_t valid_bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; + if( !hf2467 ) // Note: actually it is for hf core-2281 + valid_bitflag = valid_bitflag & ~disable_collateral_bidding; BOOST_CHECK( sambit_id(db).options.flags != valid_bitflag ); // Try to update MPA but leave some invalid flags, should fail @@ -1163,8 +1165,9 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // Unable to create a new MPA with an unknown bit in flags acop2.symbol = "NEWSAMBIT"; // With all possible bits in permissions set to 1 + // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time acop2.common_options.issuer_permissions = hf2467 ? ASSET_ISSUER_PERMISSION_MASK - : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update ); + : ( ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update & ~disable_collateral_bidding ); for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { acop2.common_options.flags = valid_bitflag | bit; @@ -1190,10 +1193,17 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_CHECK( !newsambit_id(db).can_owner_update_icr() ); BOOST_CHECK( !newsambit_id(db).can_owner_update_mcr() ); BOOST_CHECK( !newsambit_id(db).can_owner_update_mssr() ); + // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time if( hf2467 ) + { BOOST_CHECK( !newsambit_id(db).can_owner_update_bsrm() ); + BOOST_CHECK( !newsambit_id(db).can_bid_collateral() ); + } else + { BOOST_CHECK( newsambit_id(db).can_owner_update_bsrm() ); + BOOST_CHECK( newsambit_id(db).can_bid_collateral() ); + } // Able to propose too propose( acop2 ); @@ -1208,6 +1218,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_AUTO_TEST_CASE( invalid_flags_in_asset_after_hf2467 ) { + // Note: tests hf core-2281 too, assumes hf core-2281 and hf core-2467 occur at the same time hf2467 = true; INVOKE( invalid_flags_in_asset ); } diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index 71a7ce788a..fd1bc8395a 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -37,7 +37,7 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( bsrm_tests, database_fixture ) /// Tests scenarios that unable to have BSDM-related asset issuer permission or extensions before hardfork -BOOST_AUTO_TEST_CASE( hardfork_protection_test ) +BOOST_AUTO_TEST_CASE( bsrm_hardfork_protection_test ) { try { @@ -51,10 +51,13 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; - uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update & ~disable_collateral_bidding; + uint16_t new_bitmask1 = ASSET_ISSUER_PERMISSION_MASK; + uint16_t new_bitmask2 = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; + uint16_t new_bitmask3 = ASSET_ISSUER_PERMISSION_MASK & ~disable_collateral_bidding; - uint16_t bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; + uint16_t old_bitflag = VALID_FLAGS_MASK & ~committee_fed_asset & ~disable_collateral_bidding; vector ops; @@ -66,7 +69,7 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; acop.common_options.market_fee_percent = 100; - acop.common_options.flags = bitflag; + acop.common_options.flags = old_bitflag; acop.common_options.issuer_permissions = old_bitmask; acop.bitasset_opts = bitasset_options(); acop.bitasset_opts->minimum_feeds = 3; @@ -78,7 +81,13 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) auto& op = trx.operations.front().get(); // Unable to set new permission bit - op.common_options.issuer_permissions = new_bitmask; + op.common_options.issuer_permissions = new_bitmask1; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.common_options.issuer_permissions = new_bitmask2; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.common_options.issuer_permissions = new_bitmask3; BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); ops.push_back( op ); op.common_options.issuer_permissions = old_bitmask; @@ -117,7 +126,13 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) op.new_options.market_fee_percent = 200; // Unable to set new permission bit - op.new_options.issuer_permissions = new_bitmask; + op.new_options.issuer_permissions = new_bitmask1; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.issuer_permissions = new_bitmask2; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.issuer_permissions = new_bitmask3; BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); ops.push_back( op ); op.new_options.issuer_permissions = old_bitmask; @@ -174,6 +189,7 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) generate_block(); // Advance to core-2467 hard fork + // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time auto mi = db.get_global_properties().parameters.maintenance_interval; generate_blocks(HARDFORK_CORE_2467_TIME - mi); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); @@ -205,8 +221,11 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); - uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; - uint16_t new_bitmask = ASSET_ISSUER_PERMISSION_MASK; + // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time + uint16_t old_bitmask = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update & ~disable_collateral_bidding; + uint16_t new_bitmask1 = ASSET_ISSUER_PERMISSION_MASK; + uint16_t new_bitmask2 = ASSET_ISSUER_PERMISSION_MASK & ~disable_bsrm_update; + uint16_t new_bitmask3 = ASSET_ISSUER_PERMISSION_MASK & ~disable_collateral_bidding; uint16_t uiamask = UIA_ASSET_ISSUER_PERMISSION_MASK; uint16_t uiaflag = uiamask & ~disable_new_supply; // Allow creating new supply @@ -258,13 +277,18 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); // Advance to core-2467 hard fork + // Note: tests hf core-2281 too, assumes hf core-2281 and core-2267 occur at the same time auto mi = db.get_global_properties().parameters.maintenance_interval; generate_blocks(HARDFORK_CORE_2467_TIME - mi); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); // Still able to propose - auop.new_options.issuer_permissions = new_bitmask; + auop.new_options.issuer_permissions = new_bitmask1; + propose( auop ); + auop.new_options.issuer_permissions = new_bitmask2; + propose( auop ); + auop.new_options.issuer_permissions = new_bitmask3; propose( auop ); // But no longer able to update directly @@ -298,6 +322,11 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.issuer_permissions = uiamask | disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unset the non-UIA bits in issuer permissions, should succeed auop.new_options.issuer_permissions = uiamask; trx.operations.clear(); @@ -343,6 +372,11 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.issuer_permissions = uiamask | disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + generate_block(); } catch (fc::exception& e) { edump((e.to_detail_string())); From 9fc118fcbe2268dbd2b9286f81cf4f54353dd4b6 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 4 Oct 2021 17:14:35 +0000 Subject: [PATCH 229/258] Wrap long lines --- tests/tests/swan_tests.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index 92999679b6..e074f66a4f 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -287,8 +287,10 @@ BOOST_AUTO_TEST_CASE( black_swan_issue_346 ) set_price( bitusd, bitusd.amount(40) / core.amount(1000) ); // $0.04 borrow( borrower, bitusd.amount(100), asset(5000) ); // 2x collat transfer( borrower, seller, bitusd.amount(100) ); - limit_order_id_type oid_019 = create_sell_order( seller, bitusd.amount(39), core.amount(2000) )->id; // this order is at $0.019, we should not be able to match against it - limit_order_id_type oid_020 = create_sell_order( seller, bitusd.amount(40), core.amount(2000) )->id; // this order is at $0.020, we should be able to match against it + // this order is at $0.019, we should not be able to match against it + limit_order_id_type oid_019 = create_sell_order( seller, bitusd.amount(39), core.amount(2000) )->id; + // this order is at $0.020, we should be able to match against it + limit_order_id_type oid_020 = create_sell_order( seller, bitusd.amount(40), core.amount(2000) )->id; set_price( bitusd, bitusd.amount(21) / core.amount(1000) ); // $0.021 // // We attempt to match against $0.019 order and black swan, @@ -360,7 +362,8 @@ BOOST_AUTO_TEST_CASE( recollateralize ) // can't bid zero collateral GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(0), swan().amount(100) ), fc::exception ); // can't bid more than we have - GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(b2_balance + 100), swan().amount(100) ), fc::exception ); + GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(b2_balance + 100), swan().amount(100) ), + fc::exception ); trx.operations.clear(); // can't bid on a live bitasset From ac61626fe462a56c0fe653135b9b0d575e2f3c4e Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 4 Oct 2021 21:07:14 +0000 Subject: [PATCH 230/258] Fix asset issuer permissions check --- libraries/protocol/asset_ops.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 7eacd2ceab..0e7c3bafff 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -112,9 +112,9 @@ void asset_create_operation::validate()const FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( is_valid_symbol(symbol) ); common_options.validate(); - if( 0 != ( common_options.issuer_permissions - & (disable_force_settle|global_settle - |disable_mcr_update|disable_icr_update|disable_mssr_update|disable_bsrm_update) ) ) + // TODO fix the missing check for witness_fed_asset and committee_fed_asset with a hard fork + if( 0 != ( common_options.issuer_permissions & NON_UIA_ONLY_ISSUER_PERMISSION_MASK + & (uint16_t)( ~(witness_fed_asset|committee_fed_asset) ) ) ) FC_ASSERT( bitasset_opts.valid() ); if( is_prediction_market ) { From 3b57511a1baf9d0dc8ff2e4d92d7208d15249832 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 4 Oct 2021 21:09:02 +0000 Subject: [PATCH 231/258] Add tests for disable_collateral_bidding --- tests/tests/bsrm_basic_tests.cpp | 7 +- tests/tests/swan_tests.cpp | 416 ++++++++++++++++++++++++++++++- 2 files changed, 415 insertions(+), 8 deletions(-) diff --git a/tests/tests/bsrm_basic_tests.cpp b/tests/tests/bsrm_basic_tests.cpp index fd1bc8395a..d3ce89aa59 100644 --- a/tests/tests/bsrm_basic_tests.cpp +++ b/tests/tests/bsrm_basic_tests.cpp @@ -385,7 +385,7 @@ BOOST_AUTO_TEST_CASE( uia_issuer_permissions_update_test ) } /// Tests what kind of assets can have BSRM-related flags / issuer permissions / extensions -BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) +BOOST_AUTO_TEST_CASE( bsrm_asset_permissions_flags_extensions_test ) { try { @@ -395,11 +395,10 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); - ACTORS((sam)(feeder)); + ACTORS((sam)); auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; fund( sam, asset(init_amount) ); - fund( feeder, asset(init_amount) ); // Unable to create a PM with the disable_bsrm_update bit in flags BOOST_CHECK_THROW( create_prediction_market( "TESTPM", sam_id, 0, disable_bsrm_update ), fc::exception ); @@ -555,7 +554,7 @@ BOOST_AUTO_TEST_CASE( asset_permissions_flags_extensions_test ) } /// Tests whether asset owner has permission to update bsrm -BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_bsrm ) +BOOST_AUTO_TEST_CASE( bsrm_asset_owner_permissions_update_bsrm ) { try { diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index e074f66a4f..a31888375f 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -45,9 +45,9 @@ using namespace graphene::chain::test; namespace graphene { namespace chain { struct swan_fixture : database_fixture { - limit_order_id_type init_standard_swan(share_type amount = 1000) { + limit_order_id_type init_standard_swan(share_type amount = 1000, bool disable_bidding = false) { standard_users(); - standard_asset(); + standard_asset(disable_bidding); return trigger_swan(amount, amount); } @@ -62,9 +62,23 @@ struct swan_fixture : database_fixture { transfer(committee_account, borrower2_id, asset(init_balance)); } - void standard_asset() { + void standard_asset(bool disable_bidding = false) { set_expiration( db, trx ); - const auto& bitusd = create_bitasset("USDBIT", _feedproducer); + const asset_object* bitusd_ptr; + if( !disable_bidding ) + bitusd_ptr = &create_bitasset("USDBIT", _feedproducer); + else + { + auto cop = make_bitasset("USDBIT", _feedproducer); + cop.common_options.flags |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back(cop); + trx.validate(); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + bitusd_ptr = &db.get(ptx.operation_results[0].get()); + } + const auto& bitusd = *bitusd_ptr; _swan = bitusd.id; _back = asset_id_type(); update_feed_producers(swan(), {_feedproducer}); @@ -686,4 +700,398 @@ BOOST_AUTO_TEST_CASE( overflow ) BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); } FC_LOG_AND_RETHROW() } +/// Tests what kind of assets can have the disable_collateral_bidding flag / issuer permission +BOOST_AUTO_TEST_CASE( hf2281_asset_permissions_flags_test ) +{ + try { + + // Advance to core-2281 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2281_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + // Able to create a PM with the disable_collateral_bidding bit in flags + create_prediction_market( "TESTPMTEST", sam_id, 0, disable_collateral_bidding ); + + // Able to create a MPA with the disable_collateral_bidding bit in flags + create_bitasset( "TESTBITTEST", sam_id, 0, disable_collateral_bidding ); + + // Unable to create a UIA with the disable_collateral_bidding bit in flags + BOOST_CHECK_THROW( create_user_issued_asset( "TESTUIA", sam_id(db), disable_collateral_bidding ), + fc::exception ); + + // create a PM with a zero market_fee_percent + const asset_object& pm = create_prediction_market( "TESTPM", sam_id, 0, charge_market_fee ); + asset_id_type pm_id = pm.id; + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + // create a UIA with a zero market_fee_percent + const asset_object& uia = create_user_issued_asset( "TESTUIA", sam_id(db), charge_market_fee ); + asset_id_type uia_id = uia.id; + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + + // Able to set disable_collateral_bidding bit in flags for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.flags |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + // Able to propose + propose( auop ); + + // Able to set disable_collateral_bidding bit in flags for MPA + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + auop.new_options.flags |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + // Able to propose + propose( auop ); + + // Unable to set disable_collateral_bidding bit in flags for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.flags |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Able to propose + propose( auop ); + + // Able to set disable_collateral_bidding bit in issuer_permissions for PM + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.issuer_permissions |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + // Able to propose + propose( auop ); + + // Able to set disable_collateral_bidding bit in issuer_permissions for MPA + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + auop.new_options.issuer_permissions |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + // Able to propose + propose( auop ); + + // Unable to set disable_collateral_bidding bit in issuer_permissions for UIA + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.issuer_permissions |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // But able to propose + propose( auop ); + + // Unable to create a UIA with disable_collateral_bidding permission bit + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = charge_market_fee; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_collateral_bidding; + + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + + // Able to create UIA without disable_collateral_bidding permission bit + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Able to create a MPA with disable_collateral_bidding permission bit + acop.symbol = "SAMMPA"; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | disable_collateral_bidding; + acop.bitasset_opts = bitasset_options(); + + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Able to propose + propose( acop ); + + // Able to create a PM with disable_collateral_bidding permission bit + acop.symbol = "SAMPM"; + acop.precision = asset_id_type()(db).precision; + acop.is_prediction_market = true; + acop.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK | global_settle + | disable_collateral_bidding; + acop.bitasset_opts = bitasset_options(); + + trx.operations.clear(); + trx.operations.push_back( acop ); + PUSH_TX(db, trx, ~0); + + // Able to propose + propose( acop ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests whether asset owner has permission to update the disable_collateral_bidding flag and the permission +BOOST_AUTO_TEST_CASE( hf2281_asset_owner_permission_test ) +{ + try { + + // Advance to core-2281 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2281_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa_id(db).can_bid_collateral() ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // Prepare for asset update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + + // update disable_collateral_bidding flag + auop.new_options.flags |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK( !mpa_id(db).can_bid_collateral() ); + + // disable owner's permission to update the disable_collateral_bidding flag + auop.new_options.issuer_permissions |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK( !mpa_id(db).can_bid_collateral() ); + + // check that owner can not update the disable_collateral_bidding flag + auop.new_options.flags &= ~disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options = mpa_id(db).options; + + // check + BOOST_CHECK( !mpa_id(db).can_bid_collateral() ); + + // enable owner's permission to update the disable_collateral_bidding flag + auop.new_options.issuer_permissions &= ~disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK( !mpa_id(db).can_bid_collateral() ); + + // check that owner can update the disable_collateral_bidding flag + auop.new_options.flags &= ~disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK( mpa_id(db).can_bid_collateral() ); + + // Sam borrow some + borrow( sam, asset(1000, mpa_id), asset(2000) ); + + // disable owner's permission to update the disable_collateral_bidding flag + auop.new_options.issuer_permissions |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + // check + BOOST_CHECK( mpa_id(db).can_bid_collateral() ); + + // check that owner can not update the disable_collateral_bidding flag + auop.new_options.flags |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options = mpa_id(db).options; + + // check + BOOST_CHECK( mpa_id(db).can_bid_collateral() ); + + // unable to enable the permission due to non-zero supply + auop.new_options.issuer_permissions &= ~disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // check + BOOST_CHECK( mpa_id(db).can_bid_collateral() ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/// Tests the disable_collateral_bidding bit in asset flags +BOOST_AUTO_TEST_CASE( disable_collateral_bidding_test ) +{ try { + init_standard_swan( 2000 ); + + // Advance to core-2281 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2281_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + BOOST_CHECK( swan().can_bid_collateral() ); + + bid_collateral( borrower(), back().amount(3000), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(300), swan().amount(600) ); + + graphene::app::database_api db_api( db, &( app.get_options() )); + auto swan_symbol = swan().symbol; + vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( bids.size(), 2u ); + + // Disable collateral bidding + asset_update_operation auop; + auop.issuer = swan().issuer; + auop.asset_to_update = swan().id; + auop.new_options = swan().options; + auop.new_options.flags |= disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !swan().can_bid_collateral() ); + + // Check that existing bids are cancelled + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( bids.size(), 0u ); + + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + // Unable to bid + BOOST_CHECK_THROW( bid_collateral( borrower(), back().amount(3000), swan().amount(700) ), fc::exception ); + + // Enable collateral bidding + auop.new_options.flags &= ~disable_collateral_bidding; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( swan().can_bid_collateral() ); + + // Able to bid again + bid_collateral( borrower(), back().amount(3000), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(300), swan().amount(600) ); + + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( bids.size(), 2u ); + + generate_block(); + + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + +} FC_LOG_AND_RETHROW() } + +/// Tests cancelling of collateral bids at hard fork time if the disable_collateral_bidding bit in asset flags was +/// already set due to a bug +BOOST_AUTO_TEST_CASE( disable_collateral_bidding_cross_hardfork_test ) +{ try { + init_standard_swan( 2000, true ); + + wait_for_hf_core_216(); + + BOOST_CHECK( !swan().can_bid_collateral() ); + + bid_collateral( borrower(), back().amount(3000), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(300), swan().amount(600) ); + + graphene::app::database_api db_api( db, &( app.get_options() )); + auto swan_symbol = swan().symbol; + vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( bids.size(), 2u ); + + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + // Advance to core-2281 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2281_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + BOOST_CHECK( !swan().can_bid_collateral() ); + + // Check that existing bids are cancelled + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( bids.size(), 0u ); + + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + // Unable to bid + BOOST_CHECK_THROW( bid_collateral( borrower(), back().amount(3000), swan().amount(700) ), fc::exception ); + + generate_block(); + + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 9ba584623ef8f7048e75c1126a8353338ee9fdff Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 6 Oct 2021 14:52:23 +0000 Subject: [PATCH 232/258] Revive GSed bitassets based on ICR rather than MCR --- libraries/chain/asset_evaluator.cpp | 71 ++++++++++--------- libraries/chain/db_maint.cpp | 57 ++++++++------- libraries/chain/db_market.cpp | 21 +++--- libraries/chain/db_update.cpp | 2 + libraries/chain/hardfork.d/CORE_2290.hf | 6 ++ .../chain/include/graphene/chain/database.hpp | 2 +- 6 files changed, 93 insertions(+), 66 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_2290.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 40ff95b2ec..6e9d337ac1 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -614,7 +614,8 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) { const auto& bid_idx = d.get_index_type< collateral_bid_index >().indices().get(); auto itr = bid_idx.lower_bound( o.asset_to_update ); - while( itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == o.asset_to_update ) + const auto end = bid_idx.upper_bound( o.asset_to_update ); + while( itr != end ) { const collateral_bid_object& bid = *itr; ++itr; @@ -993,6 +994,7 @@ static bool update_bitasset_object_options( const auto old_feed = bdo.current_feed; // skip recalculating median feed if it is not needed db.update_bitasset_current_feed( bdo, !should_update_feeds ); + // Note: we don't try to revive the bitasset here if it was GSed // TODO probably we should do it // We need to call check_call_orders if the settlement price changes after hardfork core-868-890 feed_actually_changed = ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); @@ -1081,6 +1083,8 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f } }); d.update_bitasset_current_feed( bitasset_to_update ); + // Note: we don't try to revive the bitasset here if it was GSed // TODO probably we should do it + // Process margin calls, allow black swan, not for a new limit order d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); @@ -1233,6 +1237,7 @@ static extendable_operation_result pay_settle_from_gs_fund( database& d, d.modify( mia_dyn, [&pays]( asset_dynamic_data_object& obj ){ obj.current_supply -= pays.amount; }); + // Note: we don't revive the asset here if current_supply become zero, but only do it on a new feed extendable_operation_result result; @@ -1420,41 +1425,43 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope }); d.update_bitasset_current_feed( bad ); - if( !old_feed.margin_call_params_equal(bad.current_feed) ) - { - // Check whether need to revive the asset and proceed if need - if( bad.has_settlement() // has globally settled, implies head_block_time > HARDFORK_CORE_216_TIME - && !bad.current_feed.settlement_price.is_null() ) // has a valid feed + if( old_feed.margin_call_params_equal(bad.current_feed) ) + return void_result(); + + // Feed changed, check whether need to revive the asset and proceed if need + if( bad.has_settlement() // has globally settled, implies head_block_time > HARDFORK_CORE_216_TIME + && !bad.current_feed.settlement_price.is_null() ) // has a valid feed + { + bool should_revive = false; + const auto& mia_dyn = base.dynamic_asset_data_id(d); + if( mia_dyn.current_supply == 0 ) // if current supply is zero, revive the asset + should_revive = true; + // if current supply is not zero, revive the asset when collateral ratio of settlement fund + // is greater than ( MCR if before HF core-2290, ICR if after) + else if( next_maint_time <= HARDFORK_CORE_1270_TIME ) { - bool should_revive = false; - const auto& mia_dyn = base.dynamic_asset_data_id(d); - if( mia_dyn.current_supply == 0 ) // if current supply is zero, revive the asset - should_revive = true; - else // if current supply is not zero, when collateral ratio of settlement fund is greater than MCR, revive the asset - { - if( next_maint_time <= HARDFORK_CORE_1270_TIME ) - { - // before core-1270 hard fork, calculate call_price and compare to median feed - if( ~price::call_price( asset(mia_dyn.current_supply, o.asset_id), - asset(bad.settlement_fund, bad.options.short_backing_asset), - bad.current_feed.maintenance_collateral_ratio ) < bad.current_feed.settlement_price ) - should_revive = true; - } - else - { - // after core-1270 hard fork, calculate collateralization and compare to maintenance_collateralization - if( price( asset( bad.settlement_fund, bad.options.short_backing_asset ), - asset( mia_dyn.current_supply, o.asset_id ) ) > bad.current_maintenance_collateralization ) - should_revive = true; - } - } - if( should_revive ) - d.revive_bitasset(base); + // before core-1270 hard fork, calculate call_price and compare to median feed + auto fund_call_price = ~price::call_price( asset(mia_dyn.current_supply, o.asset_id), + asset(bad.settlement_fund, bad.options.short_backing_asset), + bad.current_feed.maintenance_collateral_ratio ); + should_revive = ( fund_call_price < bad.current_feed.settlement_price ); + } + else + { + // after core-1270 hard fork, calculate collateralization and compare to maintenance_collateralization + price fund_collateralization( asset( bad.settlement_fund, bad.options.short_backing_asset ), + asset( mia_dyn.current_supply, o.asset_id ) ); + should_revive = HARDFORK_CORE_2290_PASSED( next_maint_time ) ? + ( fund_collateralization > bad.current_initial_collateralization ) + : ( fund_collateralization > bad.current_maintenance_collateralization ); } - // Process margin calls, allow black swan, not for a new limit order - d.check_call_orders( base, true, false, bitasset_ptr ); + if( should_revive ) + d.revive_bitasset( base, bad ); } + // Process margin calls, allow black swan, not for a new limit order + d.check_call_orders( base, true, false, bitasset_ptr ); + return void_result(); } FC_CAPTURE_AND_RETHROW((o)) } diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 4582bf310a..cd4c36b46d 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -823,19 +823,30 @@ void deprecate_annual_members( database& db ) void database::process_bids( const asset_bitasset_data_object& bad ) { - if( bad.is_prediction_market ) return; - if( bad.current_feed.settlement_price.is_null() ) return; + if( bad.is_prediction_market || bad.current_feed.settlement_price.is_null() ) + return; - asset_id_type to_revive_id = (asset( 0, bad.options.short_backing_asset ) * bad.settlement_price).asset_id; + asset_id_type to_revive_id = bad.asset_id; const asset_object& to_revive = to_revive_id( *this ); const asset_dynamic_data_object& bdd = to_revive.dynamic_data( *this ); + if( 0 == bdd.current_supply ) // shortcut + { + _cancel_bids_and_revive_mpa( to_revive, bad ); + return; + } + + bool after_hf_core_2290 = HARDFORK_CORE_2290_PASSED( get_dynamic_global_properties().next_maintenance_time ); + const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get(); const auto start = bid_idx.lower_bound( to_revive_id ); + auto end = bid_idx.upper_bound( to_revive_id ); share_type covered = 0; auto itr = start; - while( covered < bdd.current_supply && itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == to_revive_id ) + auto revive_ratio = after_hf_core_2290 ? bad.current_feed.initial_collateral_ratio + : bad.current_feed.maintenance_collateral_ratio; + while( covered < bdd.current_supply && itr != end ) { const collateral_bid_object& bid = *itr; asset debt_in_bid = bid.inv_swan_price.quote; @@ -843,15 +854,14 @@ void database::process_bids( const asset_bitasset_data_object& bad ) debt_in_bid.amount = bdd.current_supply; asset total_collateral = debt_in_bid * bad.settlement_price; total_collateral += bid.inv_swan_price.base; - price call_price = price::call_price( debt_in_bid, total_collateral, - bad.current_feed.maintenance_collateral_ratio ); + price call_price = price::call_price( debt_in_bid, total_collateral, revive_ratio ); if( ~call_price >= bad.current_feed.settlement_price ) break; covered += debt_in_bid.amount; ++itr; } if( covered < bdd.current_supply ) return; - const auto end = itr; + end = itr; share_type to_cover = bdd.current_supply; share_type remaining_fund = bad.settlement_fund; itr = start; @@ -943,29 +953,28 @@ void database::process_bitassets() uint32_t head_epoch_seconds = head_time.sec_since_epoch(); bool after_hf_core_518 = ( head_time >= HARDFORK_CORE_518_TIME ); // clear expired feeds - const auto update_bitasset = [this,head_time,head_epoch_seconds,after_hf_core_518]( asset_bitasset_data_object &o ) + const auto& update_bitasset = [this,&head_time,head_epoch_seconds,after_hf_core_518] + ( asset_bitasset_data_object &o ) { o.force_settled_volume = 0; // Reset all BitAsset force settlement volumes to zero - // clear expired feeds - if( after_hf_core_518 ) + // clear expired feeds if smartcoin (witness_fed or committee_fed) && check overflow + if( after_hf_core_518 && o.options.feed_lifetime_sec < head_epoch_seconds + && ( 0 != ( o.asset_id(*this).options.flags & ( witness_fed_asset | committee_fed_asset ) ) ) ) { - const auto &asset = get( o.asset_id ); - auto flags = asset.options.flags; - if ( ( 0 != ( flags & ( witness_fed_asset | committee_fed_asset ) ) ) && - o.options.feed_lifetime_sec < head_epoch_seconds ) // if smartcoin && check overflow + fc::time_point_sec calculated = head_time - o.options.feed_lifetime_sec; + auto itr = o.feeds.rbegin(); + auto end = o.feeds.rend(); + while( itr != end ) // loop feeds { - fc::time_point_sec calculated = head_time - o.options.feed_lifetime_sec; - auto itr = o.feeds.rbegin(); - auto end = o.feeds.rend(); - while( itr != end ) // loop feeds - { - auto feed_time = itr->second.first; - std::advance( itr, 1 ); - if( feed_time < calculated ) - o.feeds.erase( itr.base() ); // delete expired feed - } + auto feed_time = itr->second.first; + std::advance( itr, 1 ); + if( feed_time < calculated ) + o.feeds.erase( itr.base() ); // delete expired feed } + // Note: we don't update current_feed here, and the update_expired_feeds() call is a bit too late, + // so theoretically there could be an inconsistency between active feeds and current_feed. + // And note that the next step "process_bids()" is based on current_feed. } }; diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 52d659c5c6..49cd4aad63 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -407,22 +407,23 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset, } -void database::revive_bitasset( const asset_object& bitasset ) +void database::revive_bitasset( const asset_object& bitasset, const asset_bitasset_data_object& bad ) { try { FC_ASSERT( bitasset.is_market_issued() ); - const asset_bitasset_data_object& bad = bitasset.bitasset_data(*this); + FC_ASSERT( bitasset.id == bad.asset_id ); FC_ASSERT( bad.has_settlement() ); - const asset_dynamic_data_object& bdd = bitasset.dynamic_asset_data_id(*this); FC_ASSERT( !bad.is_prediction_market ); FC_ASSERT( !bad.current_feed.settlement_price.is_null() ); + const asset_dynamic_data_object& bdd = bitasset.dynamic_asset_data_id(*this); if( bdd.current_supply > 0 ) { // Create + execute a "bid" with 0 additional collateral - const collateral_bid_object& pseudo_bid = create([&](collateral_bid_object& bid) { + const collateral_bid_object& pseudo_bid = create( + [&bitasset,&bad,&bdd](collateral_bid_object& bid) { bid.bidder = bitasset.issuer; bid.inv_swan_price = asset(0, bad.options.short_backing_asset) - / asset(bdd.current_supply, bitasset.id); + / asset(bdd.current_supply, bad.asset_id); }); execute_bid( pseudo_bid, bdd.current_supply, bad.settlement_fund, bad.current_feed ); } else @@ -440,7 +441,8 @@ void database::_cancel_bids_and_revive_mpa( const asset_object& bitasset, const // cancel remaining bids const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get(); auto itr = bid_idx.lower_bound( bad.asset_id ); - while( itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == bad.asset_id ) + const auto end = bid_idx.upper_bound( bad.asset_id ); + while( itr != end ) { const collateral_bid_object& bid = *itr; ++itr; @@ -448,7 +450,7 @@ void database::_cancel_bids_and_revive_mpa( const asset_object& bitasset, const } // revive - modify( bad, [&]( asset_bitasset_data_object& obj ){ + modify( bad, []( asset_bitasset_data_object& obj ){ obj.settlement_price = price(); obj.settlement_fund = 0; }); @@ -472,7 +474,8 @@ void database::cancel_bid(const collateral_bid_object& bid, bool create_virtual_ void database::execute_bid( const collateral_bid_object& bid, share_type debt_covered, share_type collateral_from_fund, const price_feed& current_feed ) { - const call_order_object& call_obj = create( [&](call_order_object& call ){ + const call_order_object& call_obj = create( + [&bid, &debt_covered, &collateral_from_fund, ¤t_feed, this](call_order_object& call ){ call.borrower = bid.bidder; call.collateral = bid.inv_swan_price.base.amount + collateral_from_fund; call.debt = debt_covered; @@ -489,7 +492,7 @@ void database::execute_bid( const collateral_bid_object& bid, share_type debt_co // Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders if( bid.inv_swan_price.base.asset_id == asset_id_type() ) - modify( get_account_stats_by_owner(bid.bidder), [&](account_statistics_object& stats) { + modify( get_account_stats_by_owner(bid.bidder), [&call_obj](account_statistics_object& stats) { stats.total_core_in_orders += call_obj.collateral; }); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 551ce42ea8..71d203384e 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -492,6 +492,8 @@ void database::update_expired_feeds() auto old_median_feed = b.current_feed; const asset_object& asset_obj = b.asset_id( *this ); update_bitasset_current_feed( b ); + // Note: we don't try to revive the bitasset here if it was GSed // TODO probably we should do it + if( !b.current_feed.settlement_price.is_null() && !b.current_feed.margin_call_params_equal( old_median_feed ) ) { diff --git a/libraries/chain/hardfork.d/CORE_2290.hf b/libraries/chain/hardfork.d/CORE_2290.hf new file mode 100644 index 0000000000..65d2a6347d --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2290.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2290 Revival of globally-settled assets should depend on ICR but not MCR +#ifndef HARDFORK_CORE_2290_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2290_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2290_PASSED(now) (now > HARDFORK_CORE_2290_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index f4f4fa2200..f51574dcb0 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -316,7 +316,7 @@ namespace graphene { namespace chain { void cancel_limit_order( const limit_order_object& order, bool create_virtual_op = true, bool skip_cancel_fee = false ); - void revive_bitasset( const asset_object& bitasset ); + void revive_bitasset( const asset_object& bitasset, const asset_bitasset_data_object& bad ); void cancel_bid(const collateral_bid_object& bid, bool create_virtual_op = true); void execute_bid( const collateral_bid_object& bid, share_type debt_covered, share_type collateral_from_fund, const price_feed& current_feed ); From 58fb1aafeb3d6227fc91ba90a9942847ed5fb797 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 6 Oct 2021 16:54:43 +0000 Subject: [PATCH 233/258] Add tests for ICR-based revival of GSed bitassets --- tests/tests/swan_tests.cpp | 294 ++++++++++++++++++++++++++++++++++++- 1 file changed, 291 insertions(+), 3 deletions(-) diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index a31888375f..4877a600f2 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -118,11 +118,12 @@ struct swan_fixture : database_fixture { return oid; } - void set_feed(share_type usd, share_type core) { + // Note: need to set MCR explicitly, testnet has a different default + void set_feed(share_type usd, share_type core, uint16_t mcr = 1750, const optional& icr = {}) { price_feed feed; - feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default + feed.maintenance_collateral_ratio = mcr; feed.settlement_price = swan().amount(usd) / back().amount(core); - publish_feed(swan(), feedproducer(), feed); + publish_feed(swan(), feedproducer(), feed, icr); } void expire_feed() { @@ -197,6 +198,9 @@ BOOST_AUTO_TEST_CASE( black_swan ) BOOST_TEST_MESSAGE( "Verify that we cannot borrow after black swan" ); GRAPHENE_REQUIRE_THROW( borrow(borrower(), swan().amount(1000), back().amount(2000)), fc::exception ) trx.operations.clear(); + + generate_block(); + } catch( const fc::exception& e) { edump((e.to_detail_string())); throw; @@ -339,6 +343,178 @@ BOOST_AUTO_TEST_CASE( revive_recovered ) BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); set_feed( 701, 800 ); BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + + graphene::app::database_api db_api( db, &( app.get_options() )); + auto swan_symbol = _swan(db).symbol; + vector calls = db_api.get_call_orders(swan_symbol, 100); + BOOST_REQUIRE_EQUAL( 1u, calls.size() ); + BOOST_CHECK( calls[0].borrower == swan().issuer ); + BOOST_CHECK_EQUAL( calls[0].debt.value, 1400 ); + BOOST_CHECK_EQUAL( calls[0].collateral.value, 2800 ); + + generate_block(); + +} catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/** Creates a black swan, place bids, recover price feed - asset should be revived + */ +BOOST_AUTO_TEST_CASE( revive_recovered_with_bids ) +{ try { + init_standard_swan( 700 ); + + if(hf2481) + wait_for_hf_core_2481(); + else if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); + + // price not good enough for recovery + set_feed( 700, 800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + bid_collateral( borrower(), back().amount(10510), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + graphene::app::database_api db_api( db, &( app.get_options() )); + auto swan_symbol = _swan(db).symbol; + vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( 2u, bids.size() ); + + // revive after price recovers + set_feed( 701, 800 ); + BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK( bids.empty() ); + + vector calls = db_api.get_call_orders(swan_symbol, 100); + BOOST_REQUIRE_EQUAL( 1u, calls.size() ); + BOOST_CHECK( calls[0].borrower == swan().issuer ); + BOOST_CHECK_EQUAL( calls[0].debt.value, 1400 ); + BOOST_CHECK_EQUAL( calls[0].collateral.value, 2800 ); + + generate_block(); +} catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/** Creates a black swan, place bids, recover price feed with ICR, before the core-2290 hard fork, + * asset should be revived based on MCR + */ +BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_not_by_icr_before_hf_core_2290 ) +{ try { + init_standard_swan( 700 ); + + // Advance to a time before core-2290 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2290_TIME - mi * 2); + set_expiration( db, trx ); + + BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); + BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); + + BOOST_REQUIRE( HARDFORK_BSIP_77_PASSED( db.head_block_time() ) ); + + // price not good enough for recovery + set_feed( 700, 800, 1750, 1800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + bid_collateral( borrower(), back().amount(10510), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + graphene::app::database_api db_api( db, &( app.get_options() )); + auto swan_symbol = _swan(db).symbol; + vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( 2u, bids.size() ); + + // good feed price + set_feed( 701, 800, 1750, 1800 ); + BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK( bids.empty() ); + + vector calls = db_api.get_call_orders(swan_symbol, 100); + BOOST_REQUIRE_EQUAL( 1u, calls.size() ); + BOOST_CHECK( calls[0].borrower == swan().issuer ); + BOOST_CHECK_EQUAL( calls[0].debt.value, 1400 ); + BOOST_CHECK_EQUAL( calls[0].collateral.value, 2800 ); + + generate_block(); +} catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/** Creates a black swan, place bids, recover price feed with ICR, after the core-2290 hard fork, + * asset should be revived based on ICR + */ +BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_by_icr_after_hf_core_2290 ) +{ try { + init_standard_swan( 700 ); + + // Advance to a time before core-2290 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2290_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); + BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); + + // price not good enough for recovery + set_feed( 700, 800, 1750, 1800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + bid_collateral( borrower(), back().amount(10510), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(21000), swan().amount(1399) ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + graphene::app::database_api db_api( db, &( app.get_options() )); + auto swan_symbol = _swan(db).symbol; + vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( 2u, bids.size() ); + + // price still not good enough for recovery + set_feed( 701, 800, 1750, 1800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( 2u, bids.size() ); + + // price still not good enough for recovery + set_feed( 720, 800, 1750, 1800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( 2u, bids.size() ); + + // good feed price + set_feed( 721, 800, 1750, 1800 ); + BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK( bids.empty() ); + + vector calls = db_api.get_call_orders(swan_symbol, 100); + BOOST_REQUIRE_EQUAL( 1u, calls.size() ); + BOOST_CHECK( calls[0].borrower == swan().issuer ); + BOOST_CHECK_EQUAL( calls[0].debt.value, 1400 ); + BOOST_CHECK_EQUAL( calls[0].collateral.value, 2800 ); + + generate_block(); } catch( const fc::exception& e) { edump((e.to_detail_string())); throw; @@ -447,6 +623,104 @@ BOOST_AUTO_TEST_CASE( recollateralize ) } } +/** Creates a black swan, recover price feed with ICR, before the core-2290 hard fork, + * asset should be revived based on MCR + */ +BOOST_AUTO_TEST_CASE( recollateralize_not_by_icr_before_hf_core_2290 ) +{ try { + init_standard_swan( 700 ); + + // Advance to a time before core-2290 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2290_TIME - mi * 2); + set_expiration( db, trx ); + + BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); + BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); + + BOOST_REQUIRE( HARDFORK_BSIP_77_PASSED( db.head_block_time() ) ); + + set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800 + // works + bid_collateral( borrower(), back().amount(1051), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(2100), swan().amount(1399) ); + + graphene::app::database_api db_api( db, &( app.get_options() )); + auto swan_symbol = _swan(db).symbol; + vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( 2u, bids.size() ); + + // revive + wait_for_maintenance(); + BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK( bids.empty() ); + BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); +} catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/** Creates a black swan, recover price feed with ICR, after the core-2290 hard fork, + * asset should be revived based on ICR + */ +BOOST_AUTO_TEST_CASE( recollateralize_by_icr_after_hf_core_2290 ) +{ try { + init_standard_swan( 700 ); + + // Advance to core-2290 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2290_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); + BOOST_CHECK( swan().bitasset_data(db).settlement_fund == 2800 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); + + set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800 + // doesn't happen if some bids have a bad swan price + bid_collateral( borrower(), back().amount(1051), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(2100), swan().amount(1399) ); + wait_for_maintenance(); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800 + // doesn't happen if some bids have a bad swan price + bid_collateral( borrower(), back().amount(1120), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(1122), swan().amount(700) ); + wait_for_maintenance(); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + set_feed(1, 2, 1750, 1800); // MCR = 1750, ICR = 1800 + // works + bid_collateral( borrower(), back().amount(1121), swan().amount(700) ); + bid_collateral( borrower2(), back().amount(1122), swan().amount(700) ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + graphene::app::database_api db_api( db, &( app.get_options() )); + auto swan_symbol = _swan(db).symbol; + vector bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK_EQUAL( 2u, bids.size() ); + + // revive + wait_for_maintenance(); + BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); + + bids = db_api.get_collateral_bids(swan_symbol, 100, 0); + BOOST_CHECK( bids.empty() ); + BOOST_CHECK( swan().dynamic_data(db).current_supply == 1400 ); +} catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /** Creates a black swan, bid, adjust bid before/after hf_1692 */ BOOST_AUTO_TEST_CASE( bid_issue_1692 ) @@ -604,6 +878,13 @@ BOOST_AUTO_TEST_CASE(revive_recovered_hf1270) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(revive_recovered_with_bids_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_recovered_with_bids); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(recollateralize_hf1270) { try { hf1270 = true; @@ -648,6 +929,13 @@ BOOST_AUTO_TEST_CASE(revive_recovered_hf2481) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(revive_recovered_with_bids_hf2481) +{ try { + hf2481 = true; + INVOKE(revive_recovered_with_bids); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(recollateralize_hf2481) { try { hf2481 = true; From 94b3c23770885e0f46c50433b2e0a1551c22da5f Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 6 Oct 2021 17:07:30 +0000 Subject: [PATCH 234/258] Add comments --- tests/tests/swan_tests.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index 4877a600f2..27573b1b06 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -426,7 +426,7 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_not_by_icr_before_hf_core_2290 BOOST_REQUIRE( HARDFORK_BSIP_77_PASSED( db.head_block_time() ) ); // price not good enough for recovery - set_feed( 700, 800, 1750, 1800 ); + set_feed( 700, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); bid_collateral( borrower(), back().amount(10510), swan().amount(700) ); @@ -439,7 +439,7 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_not_by_icr_before_hf_core_2290 BOOST_CHECK_EQUAL( 2u, bids.size() ); // good feed price - set_feed( 701, 800, 1750, 1800 ); + set_feed( 701, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); @@ -477,7 +477,7 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_by_icr_after_hf_core_2290 ) BOOST_CHECK( swan().bitasset_data(db).current_feed.settlement_price.is_null() ); // price not good enough for recovery - set_feed( 700, 800, 1750, 1800 ); + set_feed( 700, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); bid_collateral( borrower(), back().amount(10510), swan().amount(700) ); @@ -490,19 +490,19 @@ BOOST_AUTO_TEST_CASE( revive_recovered_with_bids_by_icr_after_hf_core_2290 ) BOOST_CHECK_EQUAL( 2u, bids.size() ); // price still not good enough for recovery - set_feed( 701, 800, 1750, 1800 ); + set_feed( 701, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK_EQUAL( 2u, bids.size() ); // price still not good enough for recovery - set_feed( 720, 800, 1750, 1800 ); + set_feed( 720, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); BOOST_CHECK_EQUAL( 2u, bids.size() ); // good feed price - set_feed( 721, 800, 1750, 1800 ); + set_feed( 721, 800, 1750, 1800 ); // MCR = 1750, ICR = 1800 BOOST_CHECK( !swan().bitasset_data(db).has_settlement() ); bids = db_api.get_collateral_bids(swan_symbol, 100, 0); From 2c2e0ef4cc4e22b11630f0ca0ac8334760b52f10 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 8 Oct 2021 00:14:27 +0000 Subject: [PATCH 235/258] Allow updating bitasset options after GS ... ... except backing asset --- libraries/chain/asset_evaluator.cpp | 10 +++++++--- libraries/chain/hardfork.d/CORE_2282.hf | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_2282.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 6e9d337ac1..9af365b5c7 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -740,8 +740,9 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita const asset_bitasset_data_object& current_bitasset_data = asset_obj.bitasset_data(d); - FC_ASSERT( !current_bitasset_data.has_settlement(), - "Cannot update a bitasset after a global settlement has executed" ); + if( !HARDFORK_CORE_2282_PASSED( next_maint_time ) ) + FC_ASSERT( !current_bitasset_data.has_settlement(), + "Cannot update a bitasset after a global settlement has executed" ); if( current_bitasset_data.is_prediction_market ) FC_ASSERT( !op.new_options.extensions.value.black_swan_response_method.valid(), @@ -805,9 +806,12 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita // Are we changing the backing asset? if( op.new_options.short_backing_asset != current_bitasset_data.options.short_backing_asset ) { + FC_ASSERT( !current_bitasset_data.has_settlement(), + "Cannot change backing asset after a global settlement has executed" ); + const asset_dynamic_data_object& dyn = asset_obj.dynamic_asset_data_id(d); FC_ASSERT( dyn.current_supply == 0, - "Cannot update a bitasset if there is already a current supply." ); + "Cannot change backing asset if there is already a current supply." ); FC_ASSERT( dyn.accumulated_collateral_fees == 0, "Must claim collateral-denominated fees before changing backing asset." ); diff --git a/libraries/chain/hardfork.d/CORE_2282.hf b/libraries/chain/hardfork.d/CORE_2282.hf new file mode 100644 index 0000000000..fb17611923 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_2282.hf @@ -0,0 +1,6 @@ +// bitshares-core issue #2282 Certain bitasset options should still be updatable after GS +#ifndef HARDFORK_CORE_2282_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_CORE_2282_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2282_PASSED(now) (now > HARDFORK_CORE_2282_TIME) +#endif From f291bccaa330b4896487e67e94fbc98cd522f995 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 8 Oct 2021 00:34:57 +0000 Subject: [PATCH 236/258] Remove outdated comments --- libraries/chain/db_market.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 49cd4aad63..efad3c800e 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -47,15 +47,6 @@ namespace detail { } //detail -/** - * let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral) - * let SP = current median feed's Settlement Price - * let LC = the least collateralized call order's swan price (debt/collateral) - * - * If there is no valid price feed or no bids then there is no black swan. - * - * A black swan occurs if MAX(HB,SP) <= LC - */ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan, const asset_bitasset_data_object* bitasset_ptr ) { @@ -226,7 +217,6 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s * Collateral received goes into a force-settlement fund * No new margin positions can be created for this asset * Force settlement happens without delay at the swan price, deducting from force-settlement fund - * No more asset updates may be issued. */ void database::globally_settle_asset( const asset_object& mia, const price& settlement_price, bool check_margin_calls ) From 042b9cadcb094a01c7d6b7f2fae3aa4ed29f1d06 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 8 Oct 2021 16:03:42 +0000 Subject: [PATCH 237/258] Add tests about updating bitasset options after GS --- tests/tests/bitasset_tests.cpp | 2 +- tests/tests/swan_tests.cpp | 110 +++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 96ac4555f2..e0b7629d0b 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -772,7 +772,7 @@ BOOST_AUTO_TEST_CASE( bitasset_evaluator_test_before_922_931 ) } /****** - * @brief Test various bitasset asserts within the asset_evaluator before the HF 922 / 931 + * @brief Test various bitasset asserts within the asset_evaluator after the HF 922 / 931 */ BOOST_AUTO_TEST_CASE( bitasset_evaluator_test_after_922_931 ) { diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index 27573b1b06..7253f4134d 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -1382,4 +1382,114 @@ BOOST_AUTO_TEST_CASE( disable_collateral_bidding_cross_hardfork_test ) } FC_LOG_AND_RETHROW() } +/// Tests updating bitasset options after GS +BOOST_AUTO_TEST_CASE( update_bitasset_after_gs ) +{ try { + + init_standard_swan( 2000, true ); + + // Advance to a time before core-2282 hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_2282_TIME - mi); + set_expiration( db, trx ); + + // try to update bitasset options, before hf core-2282, it is not allowed + auto old_options = swan().bitasset_data(db).options; + + asset_update_bitasset_operation aubop; + aubop.issuer = swan().issuer; + aubop.asset_to_update = _swan; + aubop.new_options = old_options; + aubop.new_options.feed_lifetime_sec += 1; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec ); + + // Advance to core-2282 hard fork + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + // should succeed + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec + 1 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + generate_block(); + + BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec == old_options.feed_lifetime_sec + 1 ); + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + // unable to update backing asset + + asset_id_type uia_id = create_user_issued_asset( "MYUIA" ).id; + + aubop.new_options.short_backing_asset = uia_id; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK( swan().bitasset_data(db).options.short_backing_asset == old_options.short_backing_asset ); + + aubop.new_options.short_backing_asset = old_options.short_backing_asset; + + // Update other bitasset options + aubop.new_options.minimum_feeds += 2; + aubop.new_options.force_settlement_delay_sec += 3; + aubop.new_options.force_settlement_offset_percent += 4; + aubop.new_options.maximum_force_settlement_volume += 5; + aubop.new_options.extensions.value.initial_collateral_ratio = 1900; + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1800; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1005; + aubop.new_options.extensions.value.margin_call_fee_ratio = 10; + aubop.new_options.extensions.value.force_settle_fee_percent = 20; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + const auto& check_result = [&]() + { + BOOST_CHECK( swan().bitasset_data(db).has_settlement() ); + + BOOST_CHECK( swan().bitasset_data(db).options.feed_lifetime_sec + == old_options.feed_lifetime_sec + 1 ); + BOOST_CHECK( swan().bitasset_data(db).options.minimum_feeds + == old_options.minimum_feeds + 2 ); + BOOST_CHECK( swan().bitasset_data(db).options.force_settlement_delay_sec + == old_options.force_settlement_delay_sec + 3 ); + BOOST_CHECK( swan().bitasset_data(db).options.force_settlement_offset_percent + == old_options.force_settlement_offset_percent + 4 ); + BOOST_CHECK( swan().bitasset_data(db).options.maximum_force_settlement_volume + == old_options.maximum_force_settlement_volume + 5 ); + + BOOST_CHECK( swan().bitasset_data(db).options.short_backing_asset == old_options.short_backing_asset ); + + auto extv = swan().bitasset_data(db).options.extensions.value; + BOOST_REQUIRE( extv.initial_collateral_ratio.valid() ); + BOOST_CHECK_EQUAL( *extv.initial_collateral_ratio, 1900U ); + BOOST_REQUIRE( extv.maintenance_collateral_ratio.valid() ); + BOOST_CHECK_EQUAL( *extv.maintenance_collateral_ratio, 1800U ); + BOOST_REQUIRE( extv.maximum_short_squeeze_ratio.valid() ); + BOOST_CHECK_EQUAL( *extv.maximum_short_squeeze_ratio, 1005U ); + BOOST_REQUIRE( extv.margin_call_fee_ratio.valid() ); + BOOST_CHECK_EQUAL( *extv.margin_call_fee_ratio, 10U ); + BOOST_REQUIRE( extv.force_settle_fee_percent.valid() ); + BOOST_CHECK_EQUAL( *extv.force_settle_fee_percent, 20U ); + }; + + check_result(); + + generate_block(); + + check_result(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From ac8a431a18ffbe5953c7e0a45c398afebde085c8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 10 Oct 2021 22:10:50 +0000 Subject: [PATCH 238/258] Update comments --- libraries/chain/hardfork.d/CORE_2295.hf | 2 +- libraries/chain/hardfork.d/CORE_2350.hf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/hardfork.d/CORE_2295.hf b/libraries/chain/hardfork.d/CORE_2295.hf index 52faf36bd1..df13439a0f 100644 --- a/libraries/chain/hardfork.d/CORE_2295.hf +++ b/libraries/chain/hardfork.d/CORE_2295.hf @@ -1,4 +1,4 @@ -// bitshares-core issue #2295 Skip asset authorization check on from_account for override_transfer +// bitshares-core issue #2295 Skip asset authorization checks for from_account for override_transfer #ifndef HARDFORK_CORE_2295_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2295_TIME (fc::time_point_sec( 1893456000 )) diff --git a/libraries/chain/hardfork.d/CORE_2350.hf b/libraries/chain/hardfork.d/CORE_2350.hf index 6d9fd2d01a..5386b4a15e 100644 --- a/libraries/chain/hardfork.d/CORE_2350.hf +++ b/libraries/chain/hardfork.d/CORE_2350.hf @@ -1,4 +1,4 @@ -// bitshares-core issue #2350 Check whitelisted and blacklisted markets for liquidity pools +// bitshares-core issue #2350 Liquidity pool exchange operation to comply with whitelist and blacklist market settings #ifndef HARDFORK_CORE_2350_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2350_TIME (fc::time_point_sec( 1893456000 )) From 1a98d904d2ade6c3735b7f2634f85d9439f490c6 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 13 Oct 2021 15:04:26 +0000 Subject: [PATCH 239/258] Fix code smells --- libraries/chain/asset_object.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 0d843d98f6..32d4a06b75 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -28,7 +28,7 @@ #include #include -using namespace graphene::chain; +namespace graphene { namespace chain { share_type asset_bitasset_data_object::max_force_settlement_volume(share_type current_supply) const { @@ -44,8 +44,8 @@ share_type asset_bitasset_data_object::max_force_settlement_volume(share_type cu return static_cast(volume); } -void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_point_sec current_time, - time_point_sec next_maintenance_time ) +void asset_bitasset_data_object::update_median_feeds( time_point_sec current_time, + time_point_sec next_maintenance_time ) { bool after_core_hardfork_1270 = ( next_maintenance_time > HARDFORK_CORE_1270_TIME ); // call price caching issue current_feed_publication_time = current_time; @@ -65,7 +65,8 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin if( effective_feeds.size() < options.minimum_feeds ) { //... don't calculate a median, and set a null feed - feed_cer_updated = false; // new median cer is null, won't update asset_object anyway, set to false for better performance + feed_cer_updated = false; // new median cer is null, won't update asset_object anyway, + // set to false for better performance current_feed_publication_time = current_time; median_feed = price_feed_with_icr(); if( after_core_hardfork_1270 ) @@ -76,7 +77,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin return; } - if( 1u == effective_feeds.size() ) + if( 1U == effective_feeds.size() ) { if( median_feed.core_exchange_rate != effective_feeds.front().get().core_exchange_rate ) feed_cer_updated = true; @@ -218,6 +219,8 @@ string asset_object::amount_to_string(share_type amount) const return result; } +} } // namespace graphene::chain + FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_dynamic_data_object, (graphene::db::object), (current_supply)(confidential_supply)(accumulated_fees)(accumulated_collateral_fees)(fee_pool) ) From e0ad2b6032047e4871cbb76f9493204c600edd28 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 13 Oct 2021 16:06:45 +0000 Subject: [PATCH 240/258] Fix code smells --- .../graphene/chain/hardfork_visitor.hpp | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp index 00cae3d93c..6aa7b3164c 100644 --- a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp +++ b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp @@ -1,4 +1,3 @@ -#pragma once /* * Copyright (c) 2019 Contributors * @@ -22,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#pragma once #include @@ -33,8 +33,6 @@ #include namespace graphene { namespace chain { -using namespace protocol; -namespace TL { using namespace fc::typelist; } /** * @brief The hardfork_visitor struct checks whether a given operation type has been hardforked in or not @@ -45,66 +43,69 @@ namespace TL { using namespace fc::typelist; } */ struct hardfork_visitor { using result_type = bool; - using first_unforked_op = custom_authority_create_operation; - using BSIP_40_ops = TL::list; - using hf2103_ops = TL::list; - using liquidity_pool_ops = TL::list< liquidity_pool_create_operation, - liquidity_pool_delete_operation, - liquidity_pool_deposit_operation, - liquidity_pool_withdraw_operation, - liquidity_pool_exchange_operation >; - using samet_fund_ops = TL::list< samet_fund_create_operation, - samet_fund_delete_operation, - samet_fund_update_operation, - samet_fund_borrow_operation, - samet_fund_repay_operation >; - using credit_offer_ops = TL::list< credit_offer_create_operation, - credit_offer_delete_operation, - credit_offer_update_operation, - credit_offer_accept_operation, - credit_deal_repay_operation, - credit_deal_expired_operation >; + using first_unforked_op = protocol::custom_authority_create_operation; + using BSIP_40_ops = fc::typelist::list< protocol::custom_authority_create_operation, + protocol::custom_authority_update_operation, + protocol::custom_authority_delete_operation>; + using hf2103_ops = fc::typelist::list< protocol::ticket_create_operation, + protocol::ticket_update_operation>; + using liquidity_pool_ops = fc::typelist::list< protocol::liquidity_pool_create_operation, + protocol::liquidity_pool_delete_operation, + protocol::liquidity_pool_deposit_operation, + protocol::liquidity_pool_withdraw_operation, + protocol::liquidity_pool_exchange_operation >; + using samet_fund_ops = fc::typelist::list< protocol::samet_fund_create_operation, + protocol::samet_fund_delete_operation, + protocol::samet_fund_update_operation, + protocol::samet_fund_borrow_operation, + protocol::samet_fund_repay_operation >; + using credit_offer_ops = fc::typelist::list< protocol::credit_offer_create_operation, + protocol::credit_offer_delete_operation, + protocol::credit_offer_update_operation, + protocol::credit_offer_accept_operation, + protocol::credit_deal_repay_operation, + protocol::credit_deal_expired_operation >; fc::time_point_sec now; - hardfork_visitor(fc::time_point_sec now) : now(now) {} + /// @note using head block time for all operations + explicit hardfork_visitor(const fc::time_point_sec& head_block_time) : now(head_block_time) {} /// The real visitor implementations. Future operation types get added in here. /// @{ template - std::enable_if_t::value < operation::tag::value, bool> + std::enable_if_t::value < protocol::operation::tag::value, bool> visit() { return true; } template - std::enable_if_t(), bool> + std::enable_if_t(), bool> visit() { return HARDFORK_BSIP_40_PASSED(now); } template - std::enable_if_t(), bool> + std::enable_if_t(), bool> visit() { return HARDFORK_CORE_2103_PASSED(now); } template - std::enable_if_t(), bool> + std::enable_if_t(), bool> visit() { return HARDFORK_LIQUIDITY_POOL_PASSED(now); } template - std::enable_if_t(), bool> + std::enable_if_t(), bool> visit() { return HARDFORK_CORE_2351_PASSED(now); } template - std::enable_if_t(), bool> + std::enable_if_t(), bool> visit() { return HARDFORK_CORE_2362_PASSED(now); } /// @} /// typelist::runtime::dispatch adaptor template - std::enable_if_t(), bool> + std::enable_if_t(), bool> operator()(W) { return visit(); } /// static_variant::visit adaptor template - std::enable_if_t(), bool> + std::enable_if_t(), bool> operator()(const Op&) { return visit(); } /// Tag adaptor - bool visit(operation::tag_type tag) { - return TL::runtime::dispatch(operation::list(), (size_t)tag, *this); + bool visit(protocol::operation::tag_type tag) const { + return fc::typelist::runtime::dispatch(protocol::operation::list(), (size_t)tag, *this); } /// operation adaptor - bool visit(const operation& op) { + bool visit(const protocol::operation& op) { return visit(op.which()); } }; From e3a09ebcf6ff7783456e24aa696a5f4b9112bc03 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 13 Oct 2021 16:15:23 +0000 Subject: [PATCH 241/258] Update variable names for better readability --- libraries/chain/hardfork.d/CORE_2281.hf | 2 +- libraries/chain/hardfork.d/CORE_2282.hf | 2 +- libraries/chain/hardfork.d/CORE_2290.hf | 2 +- libraries/chain/hardfork.d/CORE_2467.hf | 2 +- libraries/chain/hardfork.d/CORE_2481.hf | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/chain/hardfork.d/CORE_2281.hf b/libraries/chain/hardfork.d/CORE_2281.hf index e4e47833fd..322464aa93 100644 --- a/libraries/chain/hardfork.d/CORE_2281.hf +++ b/libraries/chain/hardfork.d/CORE_2281.hf @@ -2,5 +2,5 @@ #ifndef HARDFORK_CORE_2281_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2281_TIME (fc::time_point_sec( 1893456000 )) -#define HARDFORK_CORE_2281_PASSED(now) (now > HARDFORK_CORE_2281_TIME) +#define HARDFORK_CORE_2281_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2281_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2282.hf b/libraries/chain/hardfork.d/CORE_2282.hf index fb17611923..948b8cfcd1 100644 --- a/libraries/chain/hardfork.d/CORE_2282.hf +++ b/libraries/chain/hardfork.d/CORE_2282.hf @@ -2,5 +2,5 @@ #ifndef HARDFORK_CORE_2282_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2282_TIME (fc::time_point_sec( 1893456000 )) -#define HARDFORK_CORE_2282_PASSED(now) (now > HARDFORK_CORE_2282_TIME) +#define HARDFORK_CORE_2282_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2282_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2290.hf b/libraries/chain/hardfork.d/CORE_2290.hf index 65d2a6347d..f460103542 100644 --- a/libraries/chain/hardfork.d/CORE_2290.hf +++ b/libraries/chain/hardfork.d/CORE_2290.hf @@ -2,5 +2,5 @@ #ifndef HARDFORK_CORE_2290_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2290_TIME (fc::time_point_sec( 1893456000 )) -#define HARDFORK_CORE_2290_PASSED(now) (now > HARDFORK_CORE_2290_TIME) +#define HARDFORK_CORE_2290_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2290_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2467.hf b/libraries/chain/hardfork.d/CORE_2467.hf index 43fc67ab14..e4289f39e2 100644 --- a/libraries/chain/hardfork.d/CORE_2467.hf +++ b/libraries/chain/hardfork.d/CORE_2467.hf @@ -2,5 +2,5 @@ #ifndef HARDFORK_CORE_2467_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2467_TIME (fc::time_point_sec( 1893456000 )) -#define HARDFORK_CORE_2467_PASSED(now) (now > HARDFORK_CORE_2467_TIME) +#define HARDFORK_CORE_2467_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2467_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2481.hf b/libraries/chain/hardfork.d/CORE_2481.hf index c42f1bccb5..560099bfbe 100644 --- a/libraries/chain/hardfork.d/CORE_2481.hf +++ b/libraries/chain/hardfork.d/CORE_2481.hf @@ -2,5 +2,5 @@ #ifndef HARDFORK_CORE_2481_TIME // Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled #define HARDFORK_CORE_2481_TIME (fc::time_point_sec( 1893456000 )) -#define HARDFORK_CORE_2481_PASSED(now) (now > HARDFORK_CORE_2481_TIME) +#define HARDFORK_CORE_2481_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2481_TIME) #endif From 88f0f4ae2915f8b1cb7a4b7b1ca0570b378d9df2 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 13 Oct 2021 17:35:22 +0000 Subject: [PATCH 242/258] Fix code smells --- libraries/chain/include/graphene/chain/hardfork_visitor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp index 6aa7b3164c..fb284f78db 100644 --- a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp +++ b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp @@ -105,7 +105,7 @@ struct hardfork_visitor { return fc::typelist::runtime::dispatch(protocol::operation::list(), (size_t)tag, *this); } /// operation adaptor - bool visit(const protocol::operation& op) { + bool visit(const protocol::operation& op) const { return visit(op.which()); } }; From 51042174f9b15d8f90f037bb51e5e22fa85fa8c7 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 14 Oct 2021 16:09:23 +0000 Subject: [PATCH 243/258] Add conditions when accepting a credit offer --- libraries/chain/credit_offer_evaluator.cpp | 6 ++++++ .../protocol/include/graphene/protocol/credit_offer.hpp | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/libraries/chain/credit_offer_evaluator.cpp b/libraries/chain/credit_offer_evaluator.cpp index 8452f8be97..d9413f547b 100644 --- a/libraries/chain/credit_offer_evaluator.cpp +++ b/libraries/chain/credit_offer_evaluator.cpp @@ -242,6 +242,12 @@ void_result credit_offer_accept_evaluator::do_evaluate(const credit_offer_accept FC_ASSERT( _offer->min_deal_amount <= op.borrow_amount.amount, "Borrowing amount should not be less than minimum deal amount" ); + FC_ASSERT( _offer->fee_rate <= op.max_fee_rate, + "The maximum accceptable fee rate is lower than offered" ); + + FC_ASSERT( _offer->max_duration_seconds >= op.min_duration_seconds, + "The minimum accceptable duration is longer than offered" ); + auto coll_itr = _offer->acceptable_collateral.find( op.collateral.asset_id ); FC_ASSERT( coll_itr != _offer->acceptable_collateral.end(), "Collateral asset type is not acceptable by the credit offer" ); diff --git a/libraries/protocol/include/graphene/protocol/credit_offer.hpp b/libraries/protocol/include/graphene/protocol/credit_offer.hpp index 5f436938d3..3e9fe72244 100644 --- a/libraries/protocol/include/graphene/protocol/credit_offer.hpp +++ b/libraries/protocol/include/graphene/protocol/credit_offer.hpp @@ -128,6 +128,8 @@ namespace graphene { namespace protocol { credit_offer_id_type offer_id; ///< ID of the credit offer asset borrow_amount; ///< The amount to borrow asset collateral; ///< The collateral + uint32_t max_fee_rate = 0; ///< The maximum acceptable fee rate + uint32_t min_duration_seconds = 0; ///< The minimum acceptable duration extensions_type extensions; ///< Unused. Reserved for future use. @@ -239,6 +241,8 @@ FC_REFLECT( graphene::protocol::credit_offer_accept_operation, (offer_id) (borrow_amount) (collateral) + (max_fee_rate) + (min_duration_seconds) (extensions) ) From 1e1ac0e4280c455780dd7f3d6b250d3ef8e48f09 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 14 Oct 2021 16:11:58 +0000 Subject: [PATCH 244/258] Add tests for conditions to accept credit offers --- tests/common/database_fixture.cpp | 11 ++++++++--- tests/common/database_fixture.hpp | 8 ++++++-- tests/tests/credit_offer_tests.cpp | 18 ++++++++++++++++-- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 9218a90a63..a46d273146 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1807,21 +1807,26 @@ void database_fixture_base::update_credit_offer( account_id_type account, credit credit_offer_accept_operation database_fixture_base::make_credit_offer_accept_op( account_id_type account, credit_offer_id_type offer_id, - const asset& borrow_amount, const asset& collateral )const + const asset& borrow_amount, const asset& collateral, + uint32_t max_fee_rate, uint32_t min_duration )const { credit_offer_accept_operation op; op.borrower = account; op.offer_id = offer_id; op.borrow_amount = borrow_amount; op.collateral = collateral; + op.max_fee_rate = max_fee_rate; + op.min_duration_seconds = min_duration; return op; } const credit_deal_object& database_fixture_base::borrow_from_credit_offer( account_id_type account, credit_offer_id_type offer_id, - const asset& borrow_amount, const asset& collateral ) + const asset& borrow_amount, const asset& collateral, + uint32_t max_fee_rate, uint32_t min_duration ) { - credit_offer_accept_operation op = make_credit_offer_accept_op( account, offer_id, borrow_amount, collateral ); + credit_offer_accept_operation op = make_credit_offer_accept_op( account, offer_id, borrow_amount, collateral, + max_fee_rate, min_duration ); trx.operations.clear(); trx.operations.push_back( op ); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 89f17df53d..9ab8ec757b 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -517,10 +517,14 @@ struct database_fixture_base { const optional>& acceptable_borrowers ); credit_offer_accept_operation make_credit_offer_accept_op( account_id_type account, credit_offer_id_type offer_id, - const asset& borrow_amount, const asset& collateral )const; + const asset& borrow_amount, const asset& collateral, + uint32_t max_fee_rate = GRAPHENE_FEE_RATE_DENOM, + uint32_t min_duration = 0 )const; const credit_deal_object& borrow_from_credit_offer( account_id_type account, credit_offer_id_type offer_id, - const asset& borrow_amount, const asset& collateral ); + const asset& borrow_amount, const asset& collateral, + uint32_t max_fee_rate = GRAPHENE_FEE_RATE_DENOM, + uint32_t min_duration = 0 ); credit_deal_repay_operation make_credit_deal_repay_op( account_id_type account, credit_deal_id_type deal_id, const asset& repay_amount, const asset& credit_fee )const; diff --git a/tests/tests/credit_offer_tests.cpp b/tests/tests/credit_offer_tests.cpp index 70d7476a3a..3df4e4fd10 100644 --- a/tests/tests/credit_offer_tests.cpp +++ b/tests/tests/credit_offer_tests.cpp @@ -806,7 +806,7 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, tmp_co_id, asset(100), asset(200, usd_id) ), fc::exception ); - // create a credit offers + // create credit offers auto disable_time1 = db.head_block_time() + fc::minutes(20); // 20 minutes after init flat_map collateral_map1; @@ -899,9 +899,23 @@ BOOST_AUTO_TEST_CASE( credit_offer_borrow_repay_test ) BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(init_amount, usd_id) ), fc::exception ); + // Unable to borrow : maximum acceptable fee rate too low + auto ok_fee_rate = co1_id(db).fee_rate; + auto low_fee_rate = co1_id(db).fee_rate - 1; + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id), low_fee_rate ), + fc::exception ); + + // Unable to borrow : minimum acceptable duration too long + auto ok_duration = co1_id(db).max_duration_seconds; + auto long_duration = co1_id(db).max_duration_seconds + 1; + BOOST_CHECK_THROW( borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id), ok_fee_rate, + long_duration ), + fc::exception ); + // Able to borrow the same amount with the same collateral BOOST_TEST_MESSAGE( "Ray borrows more" ); - const credit_deal_object& cdo12 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id) ); + const credit_deal_object& cdo12 = borrow_from_credit_offer( ray_id, co1_id, asset(100), asset(200, usd_id), + ok_fee_rate, ok_duration ); credit_deal_id_type cd12_id = cdo12.id; time_point_sec expected_repay_time12 = db.head_block_time() + fc::seconds(3600); // 60 minutes after init From 95aeabcca4c510b2000fd724f810564134070fc3 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 14 Oct 2021 17:33:11 +0000 Subject: [PATCH 245/258] Add dependabot.yml for GitHub Actions --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..f372768a6c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + target-branch: "develop" From caf0beb9e2349a249a0bcab43b3e3770752c61c0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 Oct 2021 11:53:15 +0000 Subject: [PATCH 246/258] Add Github Action to build and push to Docker Hub --- .github/workflows/build-docker.yml | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/build-docker.yml diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 0000000000..8a451332dd --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,46 @@ +name: Build and push to DockerHub +on: [ push, pull_request ] +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Inject slug/short environment variables + uses: rlespinasse/github-slug-action@v3.x + - name: Decide whether to push to DockerHub, and set tag + if: | + github.event_name == 'push' && + ( startsWith( github.ref, 'refs/tags/' ) || + contains( fromJSON('["master","develop","testnet","hardfork"]'), env.GITHUB_REF_NAME ) ) + run: | + if [[ "${GITHUB_REF_NAME}" == "master" ]] ; then + DOCKER_PUSH_TAG=latest + else + DOCKER_PUSH_TAG=${GITHUB_REF_NAME} + fi + echo "DOCKER_PUSH_TAG=${DOCKER_PUSH_TAG}" + echo "DOCKER_PUSH_TAG=${DOCKER_PUSH_TAG}" >> $GITHUB_ENV + - name: Test tag + if: env.DOCKER_PUSH_TAG != '' + run: echo "${DOCKER_PUSH_TAG}" + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Build only + uses: docker/build-push-action@v2 + with: + context: . + load: true + - name: Login to DockerHub + if: env.DOCKER_PUSH_TAG != '' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Push to DockerHub + if: env.DOCKER_PUSH_TAG != '' + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: bitshares/bitshares-core:${{ env.DOCKER_PUSH_TAG }} From a9ab268bd5de144d61d19febe21fba189ede183f Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 20 Oct 2021 00:40:48 +0000 Subject: [PATCH 247/258] Fix liquidity pool history APIs There were 2 issues: - stop should not be included in result according to documentation - unexpected data may be returned when stop is behind start --- libraries/app/api.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 5906ea63c2..51d08f6cdb 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -586,7 +586,7 @@ namespace graphene { namespace app { const auto& idx = hist_idx.indices().get(); auto itr = start.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *operation_type, *start ) ) : idx.lower_bound( boost::make_tuple( pool_id, *operation_type ) ); - auto itr_stop = stop.valid() ? idx.upper_bound( boost::make_tuple( pool_id, *operation_type, *stop ) ) + auto itr_stop = stop.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *operation_type, *stop ) ) : idx.upper_bound( boost::make_tuple( pool_id, *operation_type ) ); while( itr != itr_stop && result.size() < limit ) { @@ -599,7 +599,7 @@ namespace graphene { namespace app { const auto& idx = hist_idx.indices().get(); auto itr = start.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *start ) ) : idx.lower_bound( pool_id ); - auto itr_stop = stop.valid() ? idx.upper_bound( boost::make_tuple( pool_id, *stop ) ) + auto itr_stop = stop.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *stop ) ) : idx.upper_bound( pool_id ); while( itr != itr_stop && result.size() < limit ) { @@ -645,7 +645,11 @@ namespace graphene { namespace app { const auto& idx_t = hist_idx.indices().get(); auto itr = start.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *operation_type, *start ) ) : idx.lower_bound( boost::make_tuple( pool_id, *operation_type ) ); - auto itr_temp = stop.valid() ? idx_t.upper_bound( boost::make_tuple( pool_id, *operation_type, *stop ) ) + if( itr == idx.end() || itr->pool != pool_id || itr->op_type != *operation_type ) // empty result + return result; + if( stop.valid() && itr->time <= *stop ) // empty result + return result; + auto itr_temp = stop.valid() ? idx_t.lower_bound( boost::make_tuple( pool_id, *operation_type, *stop ) ) : idx_t.upper_bound( boost::make_tuple( pool_id, *operation_type ) ); auto itr_stop = ( itr_temp == idx_t.end() ? idx.end() : idx.iterator_to( *itr_temp ) ); while( itr != itr_stop && result.size() < limit ) @@ -660,7 +664,11 @@ namespace graphene { namespace app { const auto& idx_t = hist_idx.indices().get(); auto itr = start.valid() ? idx.lower_bound( boost::make_tuple( pool_id, *start ) ) : idx.lower_bound( pool_id ); - auto itr_temp = stop.valid() ? idx_t.upper_bound( boost::make_tuple( pool_id, *stop ) ) + if( itr == idx.end() || itr->pool != pool_id ) // empty result + return result; + if( stop.valid() && itr->time <= *stop ) // empty result + return result; + auto itr_temp = stop.valid() ? idx_t.lower_bound( boost::make_tuple( pool_id, *stop ) ) : idx_t.upper_bound( pool_id ); auto itr_stop = ( itr_temp == idx_t.end() ? idx.end() : idx.iterator_to( *itr_temp ) ); while( itr != itr_stop && result.size() < limit ) From 15cb7a2023e719c11cc25713feb0e2eff4bce925 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 20 Oct 2021 09:03:43 +0000 Subject: [PATCH 248/258] Add more tests for liquidity pool history APIs --- tests/tests/liquidity_pool_tests.cpp | 108 ++++++++++++++++++++++----- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/tests/tests/liquidity_pool_tests.cpp b/tests/tests/liquidity_pool_tests.cpp index a188341c3e..770851cee8 100644 --- a/tests/tests/liquidity_pool_tests.cpp +++ b/tests/tests/liquidity_pool_tests.cpp @@ -788,16 +788,20 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) expected_pool_balance_a = 100; expected_pool_balance_b = 120; expected_lp_supply = 120; - BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a); - BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b); - BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); - BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply ); + BOOST_CHECK_EQUAL( lp_id(db).balance_a.value, expected_pool_balance_a); + BOOST_CHECK_EQUAL( lp_id(db).balance_b.value, expected_pool_balance_b); + BOOST_CHECK( lp_id(db).virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); + BOOST_CHECK_EQUAL( lpa_id(db).dynamic_data(db).current_supply.value, expected_lp_supply ); expected_balance_sam_eur -= 100; expected_balance_sam_usd -= 120; expected_balance_sam_lpa += 120; check_balances(); + // Generates a block + generate_block(); + set_expiration( db, trx ); + // Deposit again with 900 EUR and 3000 USD, the pool only takes 900 EUR and 1080 USD result = deposit_to_liquidity_pool( sam_id, lp_id, asset( 900, eur_id ), asset( 3000, usd_id ) ); @@ -811,10 +815,10 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) expected_pool_balance_a += 900; expected_pool_balance_b += 1080; expected_lp_supply += 1080; - BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a); - BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b); - BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); - BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply ); + BOOST_CHECK_EQUAL( lp_id(db).balance_a.value, expected_pool_balance_a); + BOOST_CHECK_EQUAL( lp_id(db).balance_b.value, expected_pool_balance_b); + BOOST_CHECK( lp_id(db).virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); + BOOST_CHECK_EQUAL( lpa_id(db).dynamic_data(db).current_supply.value, expected_lp_supply ); expected_balance_sam_eur -= 900; expected_balance_sam_usd -= 1080; @@ -906,15 +910,15 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) expected_pool_balance_a += delta_a; // 1000 + 998 = 1998 expected_pool_balance_b += delta_b; // 1200 - 588 = 612 - BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a); - BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b); - BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); - BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply ); + BOOST_CHECK_EQUAL( lp_id(db).balance_a.value, expected_pool_balance_a); + BOOST_CHECK_EQUAL( lp_id(db).balance_b.value, expected_pool_balance_b); + BOOST_CHECK( lp_id(db).virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); + BOOST_CHECK_EQUAL( lpa_id(db).dynamic_data(db).current_supply.value, expected_lp_supply ); expected_accumulated_fees_eur += maker_fee; expected_accumulated_fees_usd += taker_fee; - BOOST_CHECK_EQUAL( eur.dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_eur ); - BOOST_CHECK_EQUAL( usd.dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_usd ); + BOOST_CHECK_EQUAL( eur_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_eur ); + BOOST_CHECK_EQUAL( usd_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_usd ); expected_balance_ted_eur -= 1000; expected_balance_ted_usd += ted_receives; @@ -948,15 +952,15 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) expected_pool_balance_a += delta_a; // 1998 - 1214 = 784 expected_pool_balance_b += delta_b; // 612 + 997 = 1609 - BOOST_CHECK_EQUAL( lpo.balance_a.value, expected_pool_balance_a); - BOOST_CHECK_EQUAL( lpo.balance_b.value, expected_pool_balance_b); - BOOST_CHECK( lpo.virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); - BOOST_CHECK_EQUAL( lpa.dynamic_data(db).current_supply.value, expected_lp_supply ); + BOOST_CHECK_EQUAL( lp_id(db).balance_a.value, expected_pool_balance_a); + BOOST_CHECK_EQUAL( lp_id(db).balance_b.value, expected_pool_balance_b); + BOOST_CHECK( lp_id(db).virtual_value == fc::uint128_t(expected_pool_balance_a) * expected_pool_balance_b ); + BOOST_CHECK_EQUAL( lpa_id(db).dynamic_data(db).current_supply.value, expected_lp_supply ); expected_accumulated_fees_eur += taker_fee; expected_accumulated_fees_usd += maker_fee; - BOOST_CHECK_EQUAL( eur.dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_eur ); - BOOST_CHECK_EQUAL( usd.dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_usd ); + BOOST_CHECK_EQUAL( eur_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_eur ); + BOOST_CHECK_EQUAL( usd_id(db).dynamic_data(db).accumulated_fees.value, expected_accumulated_fees_usd ); expected_balance_ted_eur += ted_receives; expected_balance_ted_usd -= 1000; @@ -1038,6 +1042,7 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) auto head_time = db.head_block_time(); // all histories : 1:create, 2:deposit, 3:deposit, 4:exchange, 5:exchange, 6:withdrawal + // The 1st block: {1, 2}, the 2nd block: {3, 4, 5, 6} auto histories = hist_api.get_liquidity_pool_history( lp_id ); BOOST_CHECK_EQUAL( histories.size(), 6u ); @@ -1047,7 +1052,39 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) // only deposits histories = hist_api.get_liquidity_pool_history( lp_id, {}, {}, {}, 61 ); + BOOST_REQUIRE_EQUAL( histories.size(), 2u ); + auto second_time = histories[0].time; + auto first_time = histories[1].time; + auto late_time = second_time + fc::seconds(1); + auto early_time = first_time - fc::seconds(1); + histories = hist_api.get_liquidity_pool_history( lp_id, second_time, {}, {}, 61 ); BOOST_CHECK_EQUAL( histories.size(), 2u ); + histories = hist_api.get_liquidity_pool_history( lp_id, second_time, {}, 1, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 1u ); + histories = hist_api.get_liquidity_pool_history( lp_id, second_time, first_time, {}, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 1u ); + histories = hist_api.get_liquidity_pool_history( lp_id, first_time, {}, {}, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 1u ); + histories = hist_api.get_liquidity_pool_history( lp_id, {}, first_time, {}, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 1u ); + histories = hist_api.get_liquidity_pool_history( lp_id, {}, early_time, {}, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 2u ); + histories = hist_api.get_liquidity_pool_history( lp_id, {}, early_time, 1, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 1u ); + histories = hist_api.get_liquidity_pool_history( lp_id, second_time, early_time, 5, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 2u ); + histories = hist_api.get_liquidity_pool_history( lp_id, second_time, early_time, 1, 61 ); + BOOST_CHECK_EQUAL( histories.size(), 1u ); + + // time is fine + histories = hist_api.get_liquidity_pool_history( lp_id, second_time, first_time ); + BOOST_CHECK_EQUAL( histories.size(), 4u ); + histories = hist_api.get_liquidity_pool_history( lp_id, {}, first_time ); + BOOST_CHECK_EQUAL( histories.size(), 4u ); + histories = hist_api.get_liquidity_pool_history( lp_id, first_time, early_time ); + BOOST_CHECK_EQUAL( histories.size(), 2u ); + histories = hist_api.get_liquidity_pool_history( lp_id, {}, early_time ); + BOOST_CHECK_EQUAL( histories.size(), 6u ); // time too early histories = hist_api.get_liquidity_pool_history( lp_id, head_time - fc::days(3) ); @@ -1057,10 +1094,21 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) histories = hist_api.get_liquidity_pool_history( lp_id, head_time, head_time - fc::days(1) ); BOOST_CHECK_EQUAL( histories.size(), 0u ); + // stop and start are the same, or stop is later than start + histories = hist_api.get_liquidity_pool_history( lp_id, second_time, second_time ); + BOOST_CHECK_EQUAL( histories.size(), 0u ); + histories = hist_api.get_liquidity_pool_history( lp_id, first_time, second_time, 10 ); + BOOST_CHECK_EQUAL( histories.size(), 0u ); + histories = hist_api.get_liquidity_pool_history( lp_id, first_time, late_time, 10 ); + BOOST_CHECK_EQUAL( histories.size(), 0u ); + // time is fine, only exchanges histories = hist_api.get_liquidity_pool_history( lp_id, {}, head_time - fc::days(3), {}, 63 ); BOOST_CHECK_EQUAL( histories.size(), 2u ); + // all histories : 1:create, 2:deposit, 3:deposit, 4:exchange, 5:exchange, 6:withdrawal + // The 1st block: {1, 2}, the 2nd block: {3, 4, 5, 6} + // start = 2, limit = 3, so result sequence == {2,1} // note: range is (stop, start] histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, {}, 3 ); @@ -1070,10 +1118,30 @@ BOOST_AUTO_TEST_CASE( liquidity_pool_exchange_test ) histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, {}, 1 ); BOOST_CHECK_EQUAL( histories.size(), 1u ); + // start = 2, limit = 50, stop = the 2nd block time or later, result sequence == {} + histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, second_time, 50 ); + BOOST_CHECK_EQUAL( histories.size(), 0u ); + histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 2, late_time, 50 ); + BOOST_CHECK_EQUAL( histories.size(), 0u ); + + // start = 1, limit = 10, stop = the 1st block time, result sequence == {} + histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 1, first_time, 10 ); + BOOST_CHECK_EQUAL( histories.size(), 0u ); + + // start = 4, limit is default, stop = the 1st block time, result sequence == {4,3} + histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, first_time ); + BOOST_CHECK_EQUAL( histories.size(), 2u ); + // start = 4, limit is default, but exchange only, so result sequence == {4} histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, head_time - fc::days(3), {}, 63 ); BOOST_CHECK_EQUAL( histories.size(), 1u ); + // start = 4, limit is default, stop = the 2nd block time or later, exchange only, result sequence == {} + histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, second_time, {}, 63 ); + BOOST_CHECK_EQUAL( histories.size(), 0u ); + histories = hist_api.get_liquidity_pool_history_by_sequence( lp_id, 4, late_time, {}, 63 ); + BOOST_CHECK_EQUAL( histories.size(), 0u ); + // Proceeds to the hard fork time that added white/blacklist checks for bitshares-core issue #2350 generate_blocks( HARDFORK_CORE_2350_TIME ); From 7f2fbfcd12c628a8becadb529002f049b9df28bf Mon Sep 17 00:00:00 2001 From: Abit Date: Mon, 25 Oct 2021 01:30:41 +0200 Subject: [PATCH 249/258] Update sonar.branch.target in release branch --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index aed59c14cb..83767bec8a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -26,4 +26,4 @@ sonar.cfamily.cache.path=sonar_cache # Decide which tree the current build belongs to in SonarCloud. # Managed by the `set_sonar_branch*` script(s) when building with CI. -sonar.branch.target=hardfork +sonar.branch.target=master From bc289a0b5f107332d6d3658fbc2a35cf31f5b5a4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 2 Nov 2021 19:54:54 +0000 Subject: [PATCH 250/258] Remove default value from an optional member in op If the type of a member variable is Optional, setting a non-standard default value will cause serialization / deserialization problems. --- libraries/protocol/include/graphene/protocol/samet_fund.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/protocol/include/graphene/protocol/samet_fund.hpp b/libraries/protocol/include/graphene/protocol/samet_fund.hpp index 6b19fc670f..e7c4bcfe1f 100644 --- a/libraries/protocol/include/graphene/protocol/samet_fund.hpp +++ b/libraries/protocol/include/graphene/protocol/samet_fund.hpp @@ -79,7 +79,7 @@ namespace graphene { namespace protocol { account_id_type owner_account; ///< Owner of the fund samet_fund_id_type fund_id; ///< ID of the SameT Fund object optional delta_amount; ///< Delta amount, optional - optional new_fee_rate = 0; ///< New fee rate, optional + optional new_fee_rate; ///< New fee rate, optional extensions_type extensions; ///< Unused. Reserved for future use. From ce9e480ef90859c2c6fbc6afc4a115ab5ffef8bc Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 5 Nov 2021 20:50:05 +0000 Subject: [PATCH 251/258] Add tests to reproduce operation_results issue The broadcast_transaction_with_callback API returns an empty operation_results --- tests/tests/network_broadcast_api_tests.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/tests/network_broadcast_api_tests.cpp b/tests/tests/network_broadcast_api_tests.cpp index 9a4a264492..17dd7e8857 100644 --- a/tests/tests/network_broadcast_api_tests.cpp +++ b/tests/tests/network_broadcast_api_tests.cpp @@ -43,6 +43,9 @@ BOOST_AUTO_TEST_CASE( broadcast_transaction_with_callback_test ) { auto callback = [&]( const variant& v ) { ++called; + idump((v)); + auto callback_obj = v.as(200); + BOOST_CHECK_EQUAL( callback_obj.trx.operations.size(), callback_obj.trx.operation_results.size() ); }; fc::ecc::private_key cid_key = fc::ecc::private_key::regenerate( fc::digest("key") ); From f812666bb3bb884f6e8e5e7e669dc0050d3f3a69 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 5 Nov 2021 20:58:22 +0000 Subject: [PATCH 252/258] Notify on_block listeners with processed block... ... so that the listeners know the operation results --- libraries/chain/db_block.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 0c54dbf0f9..7dab0b27d6 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -613,7 +613,8 @@ void database::_apply_block( const signed_block& next_block ) _issue_453_affected_assets.clear(); - for( const auto& trx : next_block.transactions ) + signed_block processed_block( next_block ); // make a copy + for( auto& trx : processed_block.transactions ) { /* We do not need to push the undo state for each transaction * because they either all apply and are valid or the @@ -621,7 +622,7 @@ void database::_apply_block( const signed_block& next_block ) * for transactions when validating broadcast transactions or * when building a block. */ - apply_transaction( trx, skip ); + trx.operation_results = apply_transaction( trx, skip ).operation_results; ++_current_trx_in_block; } @@ -661,7 +662,7 @@ void database::_apply_block( const signed_block& next_block ) apply_debug_updates(); // notify observers that the block has been applied - notify_applied_block( next_block ); //emit + notify_applied_block( processed_block ); //emit _applied_ops.clear(); notify_changed_objects(); From 1a842d883a6ee1c90b6814338ccbf3c8038f545b Mon Sep 17 00:00:00 2001 From: Abit Date: Tue, 16 Nov 2021 22:40:24 +0100 Subject: [PATCH 253/258] Update in-code docs --- .../protocol/include/graphene/protocol/types.hpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 94328130aa..f5cf9aab17 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -174,15 +174,18 @@ enum asset_issuer_permission_flags { /// @note These parameters are for issuer permission only. /// For each parameter, if it is set in issuer permission, /// it means the bitasset owner can not update the corresponding parameter. - /// In this case, if the value of the parameter was set by the bitasset owner, it can not be updated; - /// if no value was set by the owner, the value can still be updated by the feed producers. ///@{ - disable_mcr_update = 0x800, ///< the bitasset owner can not update MCR, permisison only - disable_icr_update = 0x1000, ///< the bitasset owner can not update ICR, permisison only - disable_mssr_update = 0x2000, ///< the bitasset owner can not update MSSR, permisison only + /// @note For each one of these parameters, if it is set in issuer permission, and + /// * if the value of the parameter was set by the bitasset owner, it can not be updated, + /// * if no value was set by the owner, the value can still be updated by the feed producers. + ///@{ + disable_mcr_update = 0x800, ///< the bitasset owner can not update MCR, permission only + disable_icr_update = 0x1000, ///< the bitasset owner can not update ICR, permission only + disable_mssr_update = 0x2000, ///< the bitasset owner can not update MSSR, permission only + ///@} disable_bsrm_update = 0x4000, ///< the bitasset owner can not update BSRM, permission only - disable_collateral_bidding = 0x8000 ///< Can not bid collateral after a global settlement ///@} + disable_collateral_bidding = 0x8000 ///< Can not bid collateral after a global settlement ///@} }; From a856f3e66ef23fb01eaf086960aca7ad17ec9b77 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 24 Nov 2021 16:07:02 +0000 Subject: [PATCH 254/258] Set Mainnet 6.0 HF time to 2022-01-11 14:02:00 UTC --- libraries/chain/hardfork.d/CORE_2281.hf | 3 +-- libraries/chain/hardfork.d/CORE_2282.hf | 3 +-- libraries/chain/hardfork.d/CORE_2290.hf | 3 +-- libraries/chain/hardfork.d/CORE_2295.hf | 3 +-- libraries/chain/hardfork.d/CORE_2350.hf | 3 +-- libraries/chain/hardfork.d/CORE_2351.hf | 3 +-- libraries/chain/hardfork.d/CORE_2362.hf | 3 +-- libraries/chain/hardfork.d/CORE_2467.hf | 3 +-- libraries/chain/hardfork.d/CORE_2481.hf | 3 +-- libraries/chain/hardfork.d/CORE_265.hf | 3 +-- libraries/chain/hardfork.d/CORE_973.hf | 3 +-- 11 files changed, 11 insertions(+), 22 deletions(-) diff --git a/libraries/chain/hardfork.d/CORE_2281.hf b/libraries/chain/hardfork.d/CORE_2281.hf index 322464aa93..d37f0dcc80 100644 --- a/libraries/chain/hardfork.d/CORE_2281.hf +++ b/libraries/chain/hardfork.d/CORE_2281.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2281 Add option for MPA owners to disable collateral bidding #ifndef HARDFORK_CORE_2281_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2281_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2281_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2281_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2281_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2282.hf b/libraries/chain/hardfork.d/CORE_2282.hf index 948b8cfcd1..f6a125bd09 100644 --- a/libraries/chain/hardfork.d/CORE_2282.hf +++ b/libraries/chain/hardfork.d/CORE_2282.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2282 Certain bitasset options should still be updatable after GS #ifndef HARDFORK_CORE_2282_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2282_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2282_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2282_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2282_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2290.hf b/libraries/chain/hardfork.d/CORE_2290.hf index f460103542..96b7c598a9 100644 --- a/libraries/chain/hardfork.d/CORE_2290.hf +++ b/libraries/chain/hardfork.d/CORE_2290.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2290 Revival of globally-settled assets should depend on ICR but not MCR #ifndef HARDFORK_CORE_2290_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2290_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2290_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2290_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2290_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2295.hf b/libraries/chain/hardfork.d/CORE_2295.hf index df13439a0f..0e882807f6 100644 --- a/libraries/chain/hardfork.d/CORE_2295.hf +++ b/libraries/chain/hardfork.d/CORE_2295.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2295 Skip asset authorization checks for from_account for override_transfer #ifndef HARDFORK_CORE_2295_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2295_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2295_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2295_PASSED(now) (now >= HARDFORK_CORE_2295_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2350.hf b/libraries/chain/hardfork.d/CORE_2350.hf index 5386b4a15e..c2e0924213 100644 --- a/libraries/chain/hardfork.d/CORE_2350.hf +++ b/libraries/chain/hardfork.d/CORE_2350.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2350 Liquidity pool exchange operation to comply with whitelist and blacklist market settings #ifndef HARDFORK_CORE_2350_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2350_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2350_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2350_PASSED(now) (now >= HARDFORK_CORE_2350_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2351.hf b/libraries/chain/hardfork.d/CORE_2351.hf index 4c0c59e9a5..483886786a 100644 --- a/libraries/chain/hardfork.d/CORE_2351.hf +++ b/libraries/chain/hardfork.d/CORE_2351.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2351 No-collateral Funding #ifndef HARDFORK_CORE_2351_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2351_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2351_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2351_PASSED(now) (now >= HARDFORK_CORE_2351_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2362.hf b/libraries/chain/hardfork.d/CORE_2362.hf index af8402bad4..d57c42061f 100644 --- a/libraries/chain/hardfork.d/CORE_2362.hf +++ b/libraries/chain/hardfork.d/CORE_2362.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2362 Simple collateralized P2P funding #ifndef HARDFORK_CORE_2362_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2362_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2362_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2362_PASSED(now) (now >= HARDFORK_CORE_2362_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2467.hf b/libraries/chain/hardfork.d/CORE_2467.hf index e4289f39e2..2eb2990f76 100644 --- a/libraries/chain/hardfork.d/CORE_2467.hf +++ b/libraries/chain/hardfork.d/CORE_2467.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2467 Alternative black swan response methods #ifndef HARDFORK_CORE_2467_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2467_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2467_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2467_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2467_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_2481.hf b/libraries/chain/hardfork.d/CORE_2481.hf index 560099bfbe..a70773444b 100644 --- a/libraries/chain/hardfork.d/CORE_2481.hf +++ b/libraries/chain/hardfork.d/CORE_2481.hf @@ -1,6 +1,5 @@ // bitshares-core issue #2481 Match force-settlements with margin calls at normal margin call fill price #ifndef HARDFORK_CORE_2481_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_2481_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_2481_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_2481_PASSED(next_maintenance_time) (next_maintenance_time > HARDFORK_CORE_2481_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_265.hf b/libraries/chain/hardfork.d/CORE_265.hf index 2c5c684a91..1f81211f37 100644 --- a/libraries/chain/hardfork.d/CORE_265.hf +++ b/libraries/chain/hardfork.d/CORE_265.hf @@ -1,6 +1,5 @@ // bitshares-core issue #265 Account_history plugin: notify all related accounts after a new account is created #ifndef HARDFORK_CORE_265_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_265_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_265_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_265_PASSED(now) (now >= HARDFORK_CORE_265_TIME) #endif diff --git a/libraries/chain/hardfork.d/CORE_973.hf b/libraries/chain/hardfork.d/CORE_973.hf index 548cf2bba4..3a37a6e733 100644 --- a/libraries/chain/hardfork.d/CORE_973.hf +++ b/libraries/chain/hardfork.d/CORE_973.hf @@ -1,6 +1,5 @@ // bitshares-core issue #973 check asset authorizations for operations #ifndef HARDFORK_CORE_973_TIME -// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled -#define HARDFORK_CORE_973_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_CORE_973_TIME (fc::time_point_sec( 1641909720 )) // Tuesday, January 11, 2022 14:02:00 UTC #define HARDFORK_CORE_973_PASSED(now) (now >= HARDFORK_CORE_973_TIME) #endif From 9015b02b44f23fb1c8f9e89b398c0325c8a6e078 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 25 Nov 2021 12:23:55 +0000 Subject: [PATCH 255/258] Add defensive check to mute SonarScanner warning SonarScanner (version 4.6.2.2472) reports a division by zero error in the '/=' operation below, which is a false positive. --- libraries/protocol/asset.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index 485a9d2ac6..188f8e1fd7 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -149,6 +149,7 @@ namespace graphene { namespace protocol { { uint128_t num = ocp.numerator(); uint128_t den = ocp.denominator(); + FC_ASSERT( num > 0 && den > 0, "Internal error" ); if( num > den ) { num /= den; From a8abdaad66aaac6336fa98da5d3dc64f406df60d Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 28 Nov 2021 12:49:12 +0000 Subject: [PATCH 256/258] Bump docs submodule --- docs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs b/docs index aa766179d5..96173301e1 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit aa766179d5a206581e7507c365646ec24d23b638 +Subproject commit 96173301e1d12353440a04a16ad4560e28f346b7 From a983613af0c9fd6cbbd317ac14afcae8fa4c6be1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 28 Nov 2021 12:50:57 +0000 Subject: [PATCH 257/258] Update version in Doxyfile to 6.0.0 --- Doxyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doxyfile b/Doxyfile index 92e9ffa1fa..2ac91967ce 100644 --- a/Doxyfile +++ b/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "BitShares-Core" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = "5.2.1" +PROJECT_NUMBER = "6.0.0" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a From 0eb0bedba746b1c0b2ddedf7f70c94fad0df9adc Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 28 Nov 2021 12:52:06 +0000 Subject: [PATCH 258/258] Update CONTRIBUTORS.txt --- CONTRIBUTORS.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index 14820ce96d..c86b7126f6 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -44,6 +44,7 @@ Anzhy Cherrnyavski Tengfei Niu Tiago Peralta ioBanker <37595908+ioBanker@users.noreply.github.com> +xiao93 <42384581+xiao93@users.noreply.github.com> Karl Semich <0xloem@gmail.com> SahkanDesertHawk Scott Howard @@ -55,7 +56,6 @@ d.yakovitsky ddylko iHashFury necklace -xiao93 <42384581+xiao93@users.noreply.github.com> xuquan316 Bartek Wrona BhuzOr