/*
 * Copyright (c) 2017 Peter Conrad, and other 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 <boost/test/unit_test.hpp>

#include <graphene/chain/hardfork.hpp>

#include <graphene/chain/market_object.hpp>

#include "../common/database_fixture.hpp"

using namespace graphene::chain;
using namespace graphene::chain::test;

BOOST_FIXTURE_TEST_SUITE(market_tests, database_fixture)

BOOST_AUTO_TEST_CASE(issue_338_etc)
{ try {
   generate_blocks(HARDFORK_615_TIME); // get around Graphene issue #615 feed expiration bug
   generate_block();

   set_expiration( db, trx );

   ACTORS((buyer)(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;
   asset_id_type core_id = core.id;

   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( 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 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7
   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
   call_order_id_type call2_id = call2.id;
   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7
   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));
   call_order_id_type call3_id = call3.id;
   transfer(borrower, seller, bitusd.amount(1000));

   BOOST_CHECK_EQUAL( 1000, call.debt.value );
   BOOST_CHECK_EQUAL( 15000, call.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 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 order slightly below the call price will not be matched #606
   limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(7), core.amount(59))->id;
   // This order above the MSSP will not be matched
   limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id;
   // This would match but is blocked by sell_low?! #606
   limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(7), core.amount(60))->id;

   cancel_limit_order( sell_med(db) );
   cancel_limit_order( sell_high(db) );
   cancel_limit_order( sell_low(db) );

   // current implementation: an incoming limit order will be filled at the
   // requested price #338
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(60)) );
   BOOST_CHECK_EQUAL( 993, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 60, get_balance(seller, core) );
   BOOST_CHECK_EQUAL( 993, call.debt.value );
   BOOST_CHECK_EQUAL( 14940, call.collateral.value );

   limit_order_id_type buy_low = create_sell_order(buyer, asset(90), bitusd.amount(10))->id;
   // margin call takes precedence
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(60)) );
   BOOST_CHECK_EQUAL( 986, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 120, get_balance(seller, core) );
   BOOST_CHECK_EQUAL( 986, call.debt.value );
   BOOST_CHECK_EQUAL( 14880, call.collateral.value );

   limit_order_id_type buy_med = create_sell_order(buyer, asset(105), bitusd.amount(10))->id;
   // margin call takes precedence
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(70)) );
   BOOST_CHECK_EQUAL( 979, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 190, get_balance(seller, core) );
   BOOST_CHECK_EQUAL( 979, call.debt.value );
   BOOST_CHECK_EQUAL( 14810, call.collateral.value );

   limit_order_id_type buy_high = create_sell_order(buyer, asset(115), bitusd.amount(10))->id;
   // margin call still has precedence (!) #625
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(77)) );
   BOOST_CHECK_EQUAL( 972, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 267, get_balance(seller, core) );
   BOOST_CHECK_EQUAL( 972, call.debt.value );
   BOOST_CHECK_EQUAL( 14733, call.collateral.value );

   cancel_limit_order( buy_high(db) );
   cancel_limit_order( buy_med(db) );
   cancel_limit_order( buy_low(db) );

   // call with more usd
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(7700)) );
   BOOST_CHECK_EQUAL( 272, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 7967, get_balance(seller, core) );
   BOOST_CHECK_EQUAL( 272, call.debt.value );
   BOOST_CHECK_EQUAL( 7033, call.collateral.value );

   // at this moment, collateralization of call is 7033 / 272 = 25.8
   // collateralization of call2 is 15500 / 1000 = 15.5
   // collateralization of call3 is 16000 / 1000 = 16

   // call more, still matches with the first call order #343
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(10), core.amount(110)) );
   BOOST_CHECK_EQUAL( 262, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 8077, get_balance(seller, core) );
   BOOST_CHECK_EQUAL( 262, call.debt.value );
   BOOST_CHECK_EQUAL( 6923, call.collateral.value );

   // at this moment, collateralization of call is 6923 / 262 = 26.4
   // collateralization of call2 is 15500 / 1000 = 15.5
   // collateralization of call3 is 16000 / 1000 = 16

   // force settle
   force_settle( seller, bitusd.amount(10) );
   BOOST_CHECK_EQUAL( 252, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 8077, get_balance(seller, core) );
   BOOST_CHECK_EQUAL( 262, call.debt.value );
   BOOST_CHECK_EQUAL( 6923, call.collateral.value );

   // generate blocks to let the settle order execute (price feed will expire after it)
   generate_blocks( HARDFORK_615_TIME + fc::hours(25) );
   // call2 get settled #343
   BOOST_CHECK_EQUAL( 252, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 8177, get_balance(seller_id, core_id) );
   BOOST_CHECK_EQUAL( 262, call_id(db).debt.value );
   BOOST_CHECK_EQUAL( 6923, call_id(db).collateral.value );
   BOOST_CHECK_EQUAL( 990, call2_id(db).debt.value );
   BOOST_CHECK_EQUAL( 15400, call2_id(db).collateral.value );

   set_expiration( db, trx );
   update_feed_producers( usd_id(db), {feedproducer_id} );

   // at this moment, collateralization of call is 8177 / 252 = 32.4
   // collateralization of call2 is 15400 / 990 = 15.5
   // collateralization of call3 is 16000 / 1000 = 16

   // adjust price feed to get call2 into black swan territory, but not the first call order
   current_feed.settlement_price = asset(1, usd_id) / asset(20, core_id);
   publish_feed( usd_id(db), feedproducer_id(db), current_feed );
   // settlement price = 1/20, mssp = 1/22

   // black swan event doesn't occur #649
   BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() );

   // generate a block
   generate_block();

   set_expiration( db, trx );
   update_feed_producers( usd_id(db), {feedproducer_id} );

   // adjust price feed back
   current_feed.settlement_price = asset(1, usd_id) / asset(10, core_id);
   publish_feed( usd_id(db), feedproducer_id(db), current_feed );
   // settlement price = 1/10, mssp = 1/11

   transfer(borrower2_id, seller_id, asset(1000, usd_id));
   transfer(borrower3_id, seller_id, asset(1000, usd_id));

   // Re-create sell_low, slightly below the call price, will not be matched, will expire soon
   sell_low = create_sell_order(seller_id(db), asset(7, usd_id), asset(59), db.head_block_time()+fc::seconds(300) )->id;
   // This would match but is blocked by sell_low, it has an amount same as call's debt which will be full filled later
   sell_med = create_sell_order(seller_id(db), asset(262, usd_id), asset(2620))->id; // 1/10
   // Another big order above sell_med, blocked
   limit_order_id_type sell_med2 = create_sell_order(seller_id(db), asset(1200, usd_id), asset(12120))->id; // 1/10.1
   // Another small order above sell_med2, blocked
   limit_order_id_type sell_med3 = create_sell_order(seller_id(db), asset(120, usd_id), asset(1224))->id; // 1/10.2

   // generate a block, sell_low will expire
   BOOST_TEST_MESSAGE( "Expire sell_low" );
   generate_blocks( HARDFORK_615_TIME + fc::hours(26) );
   BOOST_CHECK( db.find<limit_order_object>( sell_low ) == nullptr );

   // #453 multiple order matching issue occurs
   BOOST_CHECK( db.find<limit_order_object>( sell_med ) == nullptr ); // sell_med get filled
   BOOST_CHECK( db.find<limit_order_object>( sell_med2 ) != nullptr ); // sell_med2 is still there
   BOOST_CHECK( db.find<limit_order_object>( sell_med3 ) == nullptr ); // sell_med3 get filled
   BOOST_CHECK( db.find<call_order_object>( call_id ) == nullptr ); // the first call order get filled
   BOOST_CHECK( db.find<call_order_object>( call2_id ) == nullptr ); // the second call order get filled
   BOOST_CHECK( db.find<call_order_object>( call3_id ) != nullptr ); // the third call order is still there

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE(hardfork_core_338_test)
{ try {
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time
   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);

   set_expiration( db, trx );

   ACTORS((buyer)(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;
   asset_id_type core_id = core.id;

   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( 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 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7
   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
   call_order_id_type call2_id = call2.id;
   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7
   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));
   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( 15500, call2.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call3.debt.value );
   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );
   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );

   // adjust price feed to get call_order 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
   create_sell_order(seller, bitusd.amount(7), core.amount(78))->id;

   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(90), 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(buyer, asset(110), bitusd.amount(10))->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(buyer, asset(111), bitusd.amount(10))->id;

   BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) );
   BOOST_CHECK_EQUAL( init_balance - 90 - 110 - 111, get_balance(buyer, core) );

   // This order slightly below the call price will be matched: #606 fixed
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(5900) ) );

   // firstly it will match with buy_high, at buy_high's price: #625 fixed
   BOOST_CHECK( !db.find<limit_order_object>( buy_high ) );
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_med )->for_sale.value, 110 );
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_low )->for_sale.value, 90 );

   // buy_high pays 111 CORE, receives 10 USD goes to buyer's balance
   BOOST_CHECK_EQUAL( 10, get_balance(buyer, bitusd) );
   BOOST_CHECK_EQUAL( init_balance - 90 - 110 - 111, get_balance(buyer, core) );
   // sell order pays 10 USD, receives 111 CORE, remaining 690 USD for sale, still at price 7/59

   // then it will match with call, at mssp: 1/11 = 690/7590 : #338 fixed
   BOOST_CHECK_EQUAL( 2293, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 7701, get_balance(seller, core) );
   BOOST_CHECK_EQUAL( 310, call.debt.value );
   BOOST_CHECK_EQUAL( 7410, 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( 16000, call3.collateral.value );

   // 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
   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
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6000) ) );
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_med )->for_sale.value, 110 );
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_low )->for_sale.value, 90 );

   // fill price would be mssp: 1/11 = 700/7700 : #338 fixed
   BOOST_CHECK_EQUAL( 1593, get_balance(seller, bitusd) );
   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 );
   // call2's call_price will be updated after the match, to 78/3/1.75 CORE/USD = 312/21
   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

   // at this moment, collateralization of call is 7410 / 310 = 23.9
   // collateralization of call2 is 7800 / 300 = 26
   // collateralization of call3 is 16000 / 1000 = 16

   // force settle
   force_settle( seller, bitusd.amount(10) );

   BOOST_CHECK_EQUAL( 1583, get_balance(seller, bitusd) );
   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 );

   // generate blocks to let the settle order execute (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
   BOOST_CHECK_EQUAL( 1583, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 15501, get_balance(seller_id, core_id) );
   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 );

   set_expiration( db, trx );
   update_feed_producers( usd_id(db), {feedproducer_id} );

   // at this moment, collateralization of call is 7410 / 310 = 23.9
   // collateralization of call2 is 7800 / 300 = 26
   // collateralization of call3 is 15900 / 990 = 16.06

   // adjust price feed to get call3 into black swan territory, but not the other call orders
   // Note: after hard fork, black swan should occur when callateralization < mssp, but not at < feed
   current_feed.settlement_price = asset(1, usd_id) / asset(16, core_id);
   publish_feed( usd_id(db), feedproducer_id(db), current_feed );
   // settlement price = 1/16, mssp = 10/176

   // black swan event will occur: #649 fixed
   BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() );
   // short positions will be closed
   BOOST_CHECK( !db.find<call_order_object>( call_id ) );
   BOOST_CHECK( !db.find<call_order_object>( call2_id ) );
   BOOST_CHECK( !db.find<call_order_object>( call3_id ) );

   // generate a block
   generate_block();

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE(hardfork_core_453_test)
{ try {
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_453_TIME - mi); // assume all hard forks occur at same time
   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);

   set_expiration( db, trx );

   ACTORS((buyer)(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, 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( 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 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7
   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
   call_order_id_type call2_id = call2.id;
   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7
   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));
   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( 15500, call2.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call3.debt.value );
   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );
   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );

   // no margin call so far

   // This order would match call when it's margin called, it has an amount same as call's debt which will be full filled later
   limit_order_id_type sell_med = create_sell_order(seller_id(db), asset(1000, usd_id), asset(10000))->id; // 1/10
   // Another big order above sell_med, amount bigger than call2's debt
   limit_order_id_type sell_med2 = create_sell_order(seller_id(db), asset(1200, usd_id), asset(12120))->id; // 1/10.1
   // Another small order above sell_med2
   limit_order_id_type sell_med3 = create_sell_order(seller_id(db), asset(120, usd_id), asset(1224))->id; // 1/10.2

   // adjust price feed to get the call orders  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

   // Fixed #453 multiple order matching issue occurs
   BOOST_CHECK( !db.find<limit_order_object>( sell_med ) ); // sell_med get filled
   BOOST_CHECK( !db.find<limit_order_object>( sell_med2 ) ); // sell_med2 get filled
   BOOST_CHECK( !db.find<limit_order_object>( sell_med3 ) ); // sell_med3 get filled
   BOOST_CHECK( !db.find<call_order_object>( call_id ) ); // the first call order get filled
   BOOST_CHECK( !db.find<call_order_object>( call2_id ) ); // the second call order get filled
   BOOST_CHECK( db.find<call_order_object>( call3_id ) ); // the third call order is still there

   // generate a block
   generate_block();

} FC_LOG_AND_RETHROW() }

/***
 * Tests (big) limit order matching logic after #625 got fixed
 */
BOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test)
{ try {
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_625_TIME - mi); // assume all hard forks occur at same time
   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);

   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
   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));
   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
   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
   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
   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<limit_order_object>( 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(11000), bitusd.amount(1000))->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 - 11000, get_balance(buyer2, core) );
   BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) );

   // Create a big sell order slightly below the call price, will be matched with several orders
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700*4), core.amount(5900*4) ) );

   // firstly it will match with buy_high, at buy_high's price
   BOOST_CHECK( !db.find<limit_order_object>( buy_high ) );
   // buy_high pays 111 CORE, receives 10 USD goes to buyer3's balance
   BOOST_CHECK_EQUAL( 10, get_balance(buyer3, bitusd) );
   BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) );

   // then it will match with call, at mssp: 1/11 = 1000/11000
   BOOST_CHECK( !db.find<call_order_object>( call_id ) );
   // call pays 11000 CORE, receives 1000 USD to cover borrower's position, remaining CORE goes to borrower's balance
   BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(borrower, core) );
   BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) );

   // then it will match with call2, at mssp: 1/11 = 1000/11000
   BOOST_CHECK( !db.find<call_order_object>( call2_id ) );
   // call2 pays 11000 CORE, receives 1000 USD to cover borrower2's position, remaining CORE goes to borrower2's balance
   BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(borrower2, core) );
   BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) );

   // then it will match with buy_med, at buy_med's price. Since buy_med is too big, it's partially filled.
   // buy_med receives the remaining USD of sell order, minus market fees, goes to buyer2's balance
   BOOST_CHECK_EQUAL( 783, get_balance(buyer2, bitusd) ); // 700*4-10-1000-1000=790, minus 1% market fee 790*100/10000=7
   BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(buyer2, core) );
   // buy_med pays at 1/11 = 790/8690
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_med )->for_sale.value, 11000-8690 );

   // 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 );

   // buy_low's price is too low that won't be matched
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_low )->for_sale.value, 80 );

   // check seller balance
   BOOST_CHECK_EQUAL( 193, get_balance(seller, bitusd) ); // 3000 - 7 - 700*4
   BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) ); // 111 + 11000 + 11000 + 8690

   // Cancel buy_med
   cancel_limit_order( buy_med(db) );
   BOOST_CHECK( !db.find<limit_order_object>( buy_med ) );
   BOOST_CHECK_EQUAL( 783, get_balance(buyer2, bitusd) );
   BOOST_CHECK_EQUAL( init_balance - 8690, get_balance(buyer2, core) );

   // Create another sell order slightly below the call price, won't fill
   limit_order_id_type sell_med = create_sell_order( seller, bitusd.amount(7), core.amount(59) )->id;
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( sell_med )->for_sale.value, 7 );
   // check seller balance
   BOOST_CHECK_EQUAL( 193-7, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) );

   // 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 );

   // buy_low's price is too low that won't be matched
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_low )->for_sale.value, 80 );

   // generate a block
   generate_block();

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE(hard_fork_453_cross_test)
{ try { // create orders before hard fork, which will be matched on hard fork
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_453_TIME - mi); // assume all hard forks occur at same time
   generate_block();

   set_expiration( db, trx );

   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer));

   const auto& bitusd = create_bitasset("USDBIT", feedproducer_id);
   const auto& biteur = create_bitasset("EURBIT", feedproducer_id);
   const auto& bitcny = create_bitasset("CNYBIT", feedproducer_id);
   const auto& core   = asset_id_type()(db);
   asset_id_type usd_id = bitusd.id;
   asset_id_type eur_id = biteur.id;
   asset_id_type cny_id = bitcny.id;
   asset_id_type core_id = core.id;

   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} );
   update_feed_producers( biteur, {feedproducer.id} );
   update_feed_producers( bitcny, {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 );
   current_feed.settlement_price = biteur.amount( 1 ) / core.amount(5);
   publish_feed( biteur, feedproducer, current_feed );
   current_feed.settlement_price = bitcny.amount( 1 ) / core.amount(5);
   publish_feed( bitcny, feedproducer, current_feed );
   // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7
   const call_order_object& call_usd = *borrow( borrower, bitusd.amount(1000), asset(15000));
   call_order_id_type call_usd_id = call_usd.id;
   const call_order_object& call_eur = *borrow( borrower, biteur.amount(1000), asset(15000));
   call_order_id_type call_eur_id = call_eur.id;
   const call_order_object& call_cny = *borrow( borrower, bitcny.amount(1000), asset(15000));
   call_order_id_type call_cny_id = call_cny.id;
   // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7
   const call_order_object& call_usd2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
   call_order_id_type call_usd2_id = call_usd2.id;
   const call_order_object& call_eur2 = *borrow( borrower2, biteur.amount(1000), asset(15500));
   call_order_id_type call_eur2_id = call_eur2.id;
   const call_order_object& call_cny2 = *borrow( borrower2, bitcny.amount(1000), asset(15500));
   call_order_id_type call_cny2_id = call_cny2.id;
   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7
   const call_order_object& call_usd3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));
   call_order_id_type call_usd3_id = call_usd3.id;
   const call_order_object& call_eur3 = *borrow( borrower3, biteur.amount(1000), asset(16000));
   call_order_id_type call_eur3_id = call_eur3.id;
   const call_order_object& call_cny3 = *borrow( borrower3, bitcny.amount(1000), asset(16000));
   call_order_id_type call_cny3_id = call_cny3.id;
   transfer(borrower, seller, bitusd.amount(1000));
   transfer(borrower2, seller, bitusd.amount(1000));
   transfer(borrower3, seller, bitusd.amount(1000));
   transfer(borrower, seller, biteur.amount(1000));
   transfer(borrower2, seller, biteur.amount(1000));
   transfer(borrower3, seller, biteur.amount(1000));
   transfer(borrower, seller, bitcny.amount(1000));
   transfer(borrower2, seller, bitcny.amount(1000));
   transfer(borrower3, seller, bitcny.amount(1000));

   BOOST_CHECK_EQUAL( 1000, call_usd.debt.value );
   BOOST_CHECK_EQUAL( 15000, call_usd.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call_usd2.debt.value );
   BOOST_CHECK_EQUAL( 15500, call_usd2.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call_usd3.debt.value );
   BOOST_CHECK_EQUAL( 16000, call_usd3.collateral.value );
   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 1000, call_eur.debt.value );
   BOOST_CHECK_EQUAL( 15000, call_eur.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call_eur2.debt.value );
   BOOST_CHECK_EQUAL( 15500, call_eur2.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call_eur3.debt.value );
   BOOST_CHECK_EQUAL( 16000, call_eur3.collateral.value );
   BOOST_CHECK_EQUAL( 3000, get_balance(seller, biteur) );
   BOOST_CHECK_EQUAL( 1000, call_cny.debt.value );
   BOOST_CHECK_EQUAL( 15000, call_cny.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call_cny2.debt.value );
   BOOST_CHECK_EQUAL( 15500, call_cny2.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call_cny3.debt.value );
   BOOST_CHECK_EQUAL( 16000, call_cny3.collateral.value );
   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitcny) );
   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );

   // adjust price feed to get call_order into margin call territory
   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10);
   publish_feed( bitusd, feedproducer, current_feed );
   current_feed.settlement_price = biteur.amount( 1 ) / core.amount(10);
   publish_feed( biteur, feedproducer, current_feed );
   current_feed.settlement_price = bitcny.amount( 1 ) / core.amount(10);
   publish_feed( bitcny, feedproducer, current_feed );
   // settlement price = 1/10, mssp = 1/11

   // This order below the call price will not be matched before hard fork: 1/8 #606
   limit_order_id_type sell_usd_low = create_sell_order(seller, bitusd.amount(1000), core.amount(7000))->id;
   // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606
   limit_order_id_type sell_usd_low2 = create_sell_order(seller, bitusd.amount(1007), core.amount(8056))->id;
   // This order above the MSSP will not be matched before hard fork
   limit_order_id_type sell_usd_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id;
   // This would match but is blocked by sell_low?! #606
   limit_order_id_type sell_usd_med = create_sell_order(seller, bitusd.amount(700), core.amount(6400))->id;
   // This would match but is blocked by sell_low?! #606
   limit_order_id_type sell_usd_med2 = create_sell_order(seller, bitusd.amount(7), core.amount(65))->id;

   // This order below the call price will not be matched before hard fork: 1/8 #606
   limit_order_id_type sell_eur_low = create_sell_order(seller, biteur.amount(1000), core.amount(7000))->id;
   // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606
   limit_order_id_type sell_eur_low2 = create_sell_order(seller, biteur.amount(1007), core.amount(8056))->id;
   // This order above the MSSP will not be matched before hard fork
   limit_order_id_type sell_eur_high = create_sell_order(seller, biteur.amount(7), core.amount(78))->id;
   // This would match but is blocked by sell_low?! #606
   limit_order_id_type sell_eur_med = create_sell_order(seller, biteur.amount(700), core.amount(6400))->id;
   // This would match but is blocked by sell_low?! #606
   limit_order_id_type sell_eur_med2 = create_sell_order(seller, biteur.amount(7), core.amount(65))->id;

   // This order below the call price will not be matched before hard fork: 1/8 #606
   limit_order_id_type sell_cny_low = create_sell_order(seller, bitcny.amount(1000), core.amount(7000))->id;
   // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606
   limit_order_id_type sell_cny_low2 = create_sell_order(seller, bitcny.amount(1007), core.amount(8056))->id;
   // This order above the MSSP will not be matched before hard fork
   limit_order_id_type sell_cny_high = create_sell_order(seller, bitcny.amount(7), core.amount(78))->id;
   // This would match but is blocked by sell_low?! #606
   limit_order_id_type sell_cny_med = create_sell_order(seller, bitcny.amount(700), core.amount(6400))->id;
   // This would match but is blocked by sell_low?! #606
   limit_order_id_type sell_cny_med2 = create_sell_order(seller, bitcny.amount(7), core.amount(65))->id;

   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, eur_id) );
   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, cny_id) );
   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );

   // generate a block to include operations above
   generate_block();
   // go over the hard fork, make sure feed doesn't expire
   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);

   // sell_low and call should get matched first
   BOOST_CHECK( !db.find<limit_order_object>( sell_usd_low ) );
   BOOST_CHECK( !db.find<call_order_object>( call_usd_id ) );
   // sell_low2 and call2 should get matched
   BOOST_CHECK( !db.find<call_order_object>( call_usd2_id ) );
   // sell_low2 and call3 should get matched: fixed #453
   BOOST_CHECK( !db.find<limit_order_object>( sell_usd_low2 ) );
   // sell_med and call3 should get matched
   BOOST_CHECK( !db.find<limit_order_object>( sell_usd_med ) );
   // call3 now is not at margin call state, so sell_med2 won't get matched
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( sell_usd_med2 )->for_sale.value, 7 );
   // sell_high should still be there, didn't match anything
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( sell_usd_high )->for_sale.value, 7 );

   // sell_low and call should get matched first
   BOOST_CHECK( !db.find<limit_order_object>( sell_eur_low ) );
   BOOST_CHECK( !db.find<call_order_object>( call_eur_id ) );
   // sell_low2 and call2 should get matched
   BOOST_CHECK( !db.find<call_order_object>( call_eur2_id ) );
   // sell_low2 and call3 should get matched: fixed #453
   BOOST_CHECK( !db.find<limit_order_object>( sell_eur_low2 ) );
   // sell_med and call3 should get matched
   BOOST_CHECK( !db.find<limit_order_object>( sell_eur_med ) );
   // call3 now is not at margin call state, so sell_med2 won't get matched
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( sell_eur_med2 )->for_sale.value, 7 );
   // sell_high should still be there, didn't match anything
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( sell_eur_high )->for_sale.value, 7 );

   // sell_low and call should get matched first
   BOOST_CHECK( !db.find<limit_order_object>( sell_cny_low ) );
   BOOST_CHECK( !db.find<call_order_object>( call_cny_id ) );
   // sell_low2 and call2 should get matched
   BOOST_CHECK( !db.find<call_order_object>( call_cny2_id ) );
   // sell_low2 and call3 should get matched: fixed #453
   BOOST_CHECK( !db.find<limit_order_object>( sell_cny_low2 ) );
   // sell_med and call3 should get matched
   BOOST_CHECK( !db.find<limit_order_object>( sell_cny_med ) );
   // call3 now is not at margin call state, so sell_med2 won't get matched
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( sell_cny_med2 )->for_sale.value, 7 );
   // sell_high should still be there, didn't match anything
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( sell_cny_high )->for_sale.value, 7 );

   // all match price would be limit order price
   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, eur_id) );
   BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, cny_id) );
   BOOST_CHECK_EQUAL( (7000+8056+6400)*3, get_balance(seller_id, core_id) );
   BOOST_CHECK_EQUAL( 1000-7-700, call_usd3_id(db).debt.value );
   BOOST_CHECK_EQUAL( 16000-56-6400, call_usd3_id(db).collateral.value );
   BOOST_CHECK_EQUAL( 1000-7-700, call_eur3_id(db).debt.value );
   BOOST_CHECK_EQUAL( 16000-56-6400, call_eur3_id(db).collateral.value );
   BOOST_CHECK_EQUAL( 1000-7-700, call_cny3_id(db).debt.value );
   BOOST_CHECK_EQUAL( 16000-56-6400, call_cny3_id(db).collateral.value );
   // call3's call_price should be updated: 9544/293/1.75 = 9544*4 / 293*7 = 38176/2051 CORE/USD
   BOOST_CHECK( price(asset(38176),asset(2051,usd_id)) == call_usd3_id(db).call_price );
   BOOST_CHECK( price(asset(38176),asset(2051,eur_id)) == call_eur3_id(db).call_price );
   BOOST_CHECK( price(asset(38176),asset(2051,cny_id)) == call_cny3_id(db).call_price );

   generate_block();

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE(hard_fork_338_cross_test)
{ try { // create orders before hard fork, which will be matched on hard fork
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_338_TIME - mi); // assume all hard forks occur at same time
   generate_block();

   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;
   asset_id_type core_id = core.id;

   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
   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));
   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
   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
   call_order_id_type call2_id = call2.id;
   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7
   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));
   call_order_id_type call3_id = call3.id;
   // create yet another position with 400% collateral, call price is 20/1.75 CORE/USD = 80/7
   const call_order_object& call4 = *borrow( borrower4, bitusd.amount(1000), asset(20000));
   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( 16000, call3.collateral.value );
   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );

   // adjust price feed to get call_order 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 order below the call price will not be matched before hard fork: 1/8 #606
   limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(1000), core.amount(7000))->id;
   // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606
   limit_order_id_type sell_low2 = create_sell_order(seller, bitusd.amount(1007), core.amount(8056))->id;
   // This would match but is blocked by sell_low?! #606
   limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(7), core.amount(64))->id;

   // adjust price feed to get call_order into black swan territory
   current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(16);
   publish_feed( bitusd, feedproducer, current_feed );
   // settlement price = 1/16, mssp = 10/176

   // due to sell_low, black swan won't occur
   BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() );

   BOOST_CHECK_EQUAL( 3000-1000-1007-7, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );

   // generate a block to include operations above
   generate_block();
   // go over the hard fork, make sure feed doesn't expire
   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);

   // sell_low and call should get matched first
   BOOST_CHECK( !db.find<limit_order_object>( sell_low ) );
   BOOST_CHECK( !db.find<call_order_object>( call_id ) );
   // sell_low2 and call2 should get matched
   BOOST_CHECK( !db.find<call_order_object>( call2_id ) );
   // sell_low2 and call3 should get matched: fixed #453
   BOOST_CHECK( !db.find<limit_order_object>( sell_low2 ) );
   // sell_med and call3 should get matched
   BOOST_CHECK( !db.find<limit_order_object>( sell_med ) );

   // at this moment,
   // collateralization of call3 is (16000-56-64) / (1000-7-7) = 15880/986 = 16.1, it's > 16 but < 17.6
   // although there is no sell order, it should trigger a black swan event right away,
   // because after hard fork new limit order won't trigger black swan event
   BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() );
   BOOST_CHECK( !db.find<call_order_object>( call3_id ) );
   BOOST_CHECK( !db.find<call_order_object>( call4_id ) );

   // since 16.1 > 16, global settlement should at feed price 16/1
   // so settlement fund should be 986*16 + 1000*16
   BOOST_CHECK_EQUAL( 1986*16, usd_id(db).bitasset_data(db).settlement_fund.value );
   // global settlement price should be 16/1, since no rounding here
   BOOST_CHECK( price(asset(1,usd_id),asset(16) ) == usd_id(db).bitasset_data(db).settlement_price );

   BOOST_CHECK_EQUAL( 3000-1000-1007-7, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 7000+8056+64, get_balance(seller_id, core_id) );
   BOOST_CHECK_EQUAL( 0, get_balance(borrower3_id, usd_id) );
   BOOST_CHECK_EQUAL( init_balance-16000+15880-986*16, get_balance(borrower3_id, core_id) );
   BOOST_CHECK_EQUAL( 1000, get_balance(borrower4_id, usd_id) );
   BOOST_CHECK_EQUAL( init_balance-1000*16, get_balance(borrower4_id, core_id) );

   generate_block();

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE(hard_fork_649_cross_test)
{ try { // create orders before hard fork, which will be matched on hard fork
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time
   generate_block();

   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;
   asset_id_type core_id = core.id;

   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
   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));
   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
   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
   call_order_id_type call2_id = call2.id;
   // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7
   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000));
   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( 15500, call2.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call3.debt.value );
   BOOST_CHECK_EQUAL( 16000, call3.collateral.value );
   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );

   // adjust price feed to get call_order 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 would match with call at price 707/6464
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(707), core.amount(6464)) );
   BOOST_CHECK_EQUAL( 3000-707, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 6464, get_balance(seller_id, core_id) );
   BOOST_CHECK_EQUAL( 293, call.debt.value );
   BOOST_CHECK_EQUAL( 8536, call.collateral.value );

   // at this moment,
   // collateralization of call is 8536 / 293 = 29.1
   // collateralization of call2 is 15500 / 1000 = 15.5
   // collateralization of call3 is 16000 / 1000 = 16

   generate_block();
   set_expiration( db, trx );
   update_feed_producers( usd_id(db), {feedproducer_id} );

   // adjust price feed to get call_order into black swan territory
   current_feed.settlement_price = price(asset(1,usd_id) / asset(20));
   publish_feed( usd_id(db), feedproducer_id(db), current_feed );
   // settlement price = 1/20, mssp = 1/22

   // due to #649, black swan won't occur
   BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() );

   // generate a block to include operations above
   generate_block();
   BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() );
   // go over the hard fork, make sure feed doesn't expire
   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);

   // a black swan event should occur
   BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() );
   BOOST_CHECK( !db.find<call_order_object>( call_id ) );
   BOOST_CHECK( !db.find<call_order_object>( call2_id ) );
   BOOST_CHECK( !db.find<call_order_object>( call3_id ) );

   // since least collateral ratio 15.5 < 20, global settlement should execute at price = least collateral ratio 15.5/1
   // so settlement fund should be 15500 + 15500 + round_up(15.5 * 293)
   BOOST_CHECK_EQUAL( 15500*2 + (293 * 155 + 9) / 10, usd_id(db).bitasset_data(db).settlement_fund.value );
   // global settlement price should be settlement_fund/(2000+293), but not 15.5/1 due to rounding
   BOOST_CHECK( price(asset(2293,usd_id),asset(15500*2+(293*155+9)/10) ) == usd_id(db).bitasset_data(db).settlement_price );

   BOOST_CHECK_EQUAL( 3000-707, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 6464, get_balance(seller_id, core_id) );
   BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, usd_id) );
   BOOST_CHECK_EQUAL( init_balance-6464-(293*155+9)/10, get_balance(borrower_id, core_id) );
   BOOST_CHECK_EQUAL( 0, get_balance(borrower2_id, usd_id) );
   BOOST_CHECK_EQUAL( init_balance-15500, get_balance(borrower2_id, core_id) );
   BOOST_CHECK_EQUAL( 0, get_balance(borrower3_id, usd_id) );
   BOOST_CHECK_EQUAL( init_balance-15500, get_balance(borrower3_id, core_id) );

   generate_block();

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test)
{ try { // create orders before hard fork, which will be matched on hard fork
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time
   generate_block();

   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;
   asset_id_type core_id = core.id;

   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
   const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000));
   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
   const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500));
   call_order_id_type call2_id = call2.id;
   // create yet another position with 350% collateral, call price is 17.5/1.75 CORE/USD = 77/7
   const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(17500));
   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( 15500, call2.collateral.value );
   BOOST_CHECK_EQUAL( 1000, call3.debt.value );
   BOOST_CHECK_EQUAL( 17500, call3.collateral.value );
   BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 0, get_balance(seller, core) );

   // adjust price feed to get call_order 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 would match with call at price 700/6400
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6400)) );
   BOOST_CHECK_EQUAL( 3000-700, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 6400, get_balance(seller_id, core_id) );
   BOOST_CHECK_EQUAL( 300, call.debt.value );
   BOOST_CHECK_EQUAL( 8600, call.collateral.value );

   // at this moment,
   // collateralization of call is 8600 / 300 = 28.67
   // collateralization of call2 is 15500 / 1000 = 15.5
   // collateralization of call3 is 17500 / 1000 = 17.5

   // generate a block to include operations above
   generate_block();
   // go over the hard fork, make sure feed doesn't expire
   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);

   set_expiration( db, trx );

   // This will match with call2 at price 7/77 (#343 fixed)
   BOOST_CHECK( !create_sell_order(seller_id(db), asset(7*50,usd_id), asset(65*50)) );
   BOOST_CHECK_EQUAL( 3000-700-7*50, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 6400+77*50, get_balance(seller_id, core_id) );
   BOOST_CHECK_EQUAL( 300, call_id(db).debt.value );
   BOOST_CHECK_EQUAL( 8600, call_id(db).collateral.value );
   BOOST_CHECK_EQUAL( 1000-7*50, call2_id(db).debt.value );
   BOOST_CHECK_EQUAL( 15500-77*50, call2_id(db).collateral.value );
   BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value );
   BOOST_CHECK_EQUAL( 17500, call3_id(db).collateral.value );

   // at this moment,
   // collateralization of call is 8600 / 300 = 28.67
   // collateralization of call2 is 11650 / 650 = 17.9
   // collateralization of call3 is 17500 / 1000 = 17.5

   // This will match with call3 at price 7/77 (#343 fixed)
   BOOST_CHECK( !create_sell_order(seller_id(db), asset(7,usd_id), asset(65)) );
   BOOST_CHECK_EQUAL( 3000-700-7*50-7, get_balance(seller_id, usd_id) );
   BOOST_CHECK_EQUAL( 6400+77*50+77, get_balance(seller_id, core_id) );
   BOOST_CHECK_EQUAL( 300, call_id(db).debt.value );
   BOOST_CHECK_EQUAL( 8600, call_id(db).collateral.value );
   BOOST_CHECK_EQUAL( 1000-7*50, call2_id(db).debt.value );
   BOOST_CHECK_EQUAL( 15500-77*50, call2_id(db).collateral.value );
   BOOST_CHECK_EQUAL( 1000-7, call3_id(db).debt.value );
   BOOST_CHECK_EQUAL( 17500-77, call3_id(db).collateral.value );

   // at this moment,
   // collateralization of call is 8600 / 300 = 28.67
   // collateralization of call2 is 11650 / 650 = 17.9
   // collateralization of call3 is 17423 / 993 = 17.55

   // no more margin call now
   BOOST_CHECK( create_sell_order(seller_id(db), asset(7,usd_id), asset(65)) );

   generate_block();

} FC_LOG_AND_RETHROW() }

/***
 * BSIP38 "target_collateral_ratio" test: matching a taker limit order with multiple maker call orders
 */
BOOST_AUTO_TEST_CASE(target_cr_test_limit_call)
{ try {
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_834_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);

   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% is higher than 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<limit_order_object>( 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 big sell order slightly below the call price, will be matched with several orders
   BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700*4), core.amount(5900*4) ) );

   // firstly it will match with buy_high, at buy_high's price
   BOOST_CHECK( !db.find<limit_order_object>( buy_high ) );
   // buy_high pays 111 CORE, receives 10 USD goes to buyer3's balance
   BOOST_CHECK_EQUAL( 10, get_balance(buyer3, bitusd) );
   BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) );

   // then it will match with call, at mssp: 1/11 = 1000/11000
   const call_order_object* tmp_call = db.find<call_order_object>( 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;
   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 limit order then will match with call2, at mssp: 1/11 = 1000/11000
   const call_order_object* tmp_call2 = db.find<call_order_object>( 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;
   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) );

   // then it will match with buy_med, at buy_med's price. Since buy_med is too big, it's partially filled.
   // buy_med receives the remaining USD of sell order, minus market fees, goes to buyer2's balance
   share_type buy_med_get = 700*4 - 10 - call_to_cover - call2_to_cover;
   share_type buy_med_pay = buy_med_get * 11; // buy_med pays at 1/11
   buy_med_get -= (buy_med_get/100); // minus 1% market fee
   BOOST_CHECK_EQUAL( buy_med_get.value, get_balance(buyer2, bitusd) );
   BOOST_CHECK_EQUAL( init_balance - 33000, get_balance(buyer2, core) );
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_med )->for_sale.value, 33000-buy_med_pay.value );

   // 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 );

   // buy_low's price is too low that won't be matched
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_low )->for_sale.value, 80 );

   // check seller balance
   BOOST_CHECK_EQUAL( 193, get_balance(seller, bitusd) ); // 3000 - 7 - 700*4
   BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) ); // 111 + (700*4-10)*11

   // Cancel buy_med
   cancel_limit_order( buy_med(db) );
   BOOST_CHECK( !db.find<limit_order_object>( buy_med ) );
   BOOST_CHECK_EQUAL( buy_med_get.value, get_balance(buyer2, bitusd) );
   BOOST_CHECK_EQUAL( init_balance - buy_med_pay.value, get_balance(buyer2, core) );

   // Create another sell order slightly below the call price, won't fill
   limit_order_id_type sell_med = create_sell_order( seller, bitusd.amount(7), core.amount(59) )->id;
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( sell_med )->for_sale.value, 7 );
   // check seller balance
   BOOST_CHECK_EQUAL( 193-7, get_balance(seller, bitusd) );
   BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) );

   // 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 );

   // buy_low's price is too low that won't be matched
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_low )->for_sale.value, 80 );

   // generate a block
   generate_block();

} FC_LOG_AND_RETHROW() }

/***
 * BSIP38 "target_collateral_ratio" test: matching a maker limit order with multiple taker call orders
 */
BOOST_AUTO_TEST_CASE(target_cr_test_call_limit)
{ try {
   auto mi = db.get_global_properties().parameters.maintenance_interval;
   generate_blocks(HARDFORK_CORE_834_TIME - mi);
   generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);

   set_expiration( db, trx );

   ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer));

   const auto& bitusd = create_bitasset("USDBIT", feedproducer_id);
   const auto& core   = asset_id_type()(db);

   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( 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% is higher than 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) );

   // 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<limit_order_object>( 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<limit_order_object>( sell_id )->for_sale.value, 500 );

   // 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;
   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 );

   // 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

   // firstly the limit order will match with call, at limit order's price: 1/9
   const call_order_object* tmp_call = db.find<call_order_object>( 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;
   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 limit order then will match with call2, at limit order's price: 1/9
   const call_order_object* tmp_call2 = db.find<call_order_object>( call2_id );
   BOOST_CHECK( tmp_call2 != nullptr );

   // if the limit is big enough, call2 will receive call2_to_cover, pay 11*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;
   BOOST_CHECK_EQUAL( 1000 - call2_to_cover.value, call2.debt.value );
   BOOST_CHECK_EQUAL( 15500 - call2_to_pay.value, call2.collateral.value );
   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 );

   // sell_id is completely filled
   BOOST_CHECK( !db.find<limit_order_object>( sell_id ) );

   // check seller balance
   BOOST_CHECK_EQUAL( 2493, get_balance(seller, bitusd) ); // 3000 - 7 - 500
   BOOST_CHECK_EQUAL( 4500, get_balance(seller, core) ); // 500*9

   // buy_low's price is too low that won't be matched
   BOOST_CHECK_EQUAL( db.find<limit_order_object>( buy_low )->for_sale.value, 80 );

   // generate a block
   generate_block();

} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_SUITE_END()


// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "Framework/TestingFramework.hpp"

#include <array>
#include <boost/functional/hash.hpp>
#include <cstddef>
#include <memory>
#include <unordered_set>
#include <utility>
#include <vector>

#include "DataStructures/Tensor/Tensor.hpp"
#include "Domain/BoundaryConditions/BoundaryCondition.hpp"
#include "Domain/CoordinateMaps/Affine.hpp"
#include "Domain/CoordinateMaps/CoordinateMap.hpp"
#include "Domain/CoordinateMaps/CoordinateMap.tpp"
#include "Domain/CoordinateMaps/ProductMaps.hpp"
#include "Domain/CoordinateMaps/ProductMaps.tpp"
#include "Domain/Creators/AlignedLattice.hpp"
#include "Domain/Creators/DomainCreator.hpp"
#include "Domain/Domain.hpp"
#include "Domain/OptionTags.hpp"
#include "Framework/TestCreation.hpp"
#include "Framework/TestHelpers.hpp"
#include "Helpers/Domain/BoundaryConditions/BoundaryCondition.hpp"
#include "Helpers/Domain/DomainTestHelpers.hpp"
#include "Parallel/RegisterDerivedClassesWithCharm.hpp"

namespace Frame {
struct Inertial;
}  // namespace Frame

namespace domain {
namespace {
template <size_t VolumeDim>
std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>
create_boundary_condition() {
  return std::make_unique<TestHelpers::domain::BoundaryConditions::
                              TestBoundaryCondition<VolumeDim>>(
      Direction<VolumeDim>::upper_xi(), 100);
}

template <size_t VolumeDim>
void test_aligned_blocks(
    const creators::AlignedLattice<VolumeDim>& aligned_lattice,
    const std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>&
        expected_boundary_condition) {
  Parallel::register_classes_with_charm(
      typename creators::AlignedLattice<VolumeDim>::maps_list{});

  const auto test_impl = [&expected_boundary_condition,
                          &aligned_lattice](const auto& domain) {
    test_initial_domain(domain, aligned_lattice.initial_refinement_levels());

    if (expected_boundary_condition != nullptr) {
      const auto& blocks = domain.blocks();
      for (size_t block_id = 0; block_id < blocks.size(); ++block_id) {
        CAPTURE(block_id);
        const auto& block = domain.blocks()[block_id];
        REQUIRE(block.external_boundaries().size() ==
                block.external_boundary_conditions().size());
        for (const auto& direction : block.external_boundaries()) {
          CAPTURE(direction);
          REQUIRE(block.external_boundary_conditions().count(direction) == 1);
          REQUIRE(block.external_boundary_conditions().at(direction) !=
                  nullptr);
          const auto& bc =
              dynamic_cast<const TestHelpers::domain::BoundaryConditions::
                               TestBoundaryCondition<VolumeDim>&>(
                  *block.external_boundary_conditions().at(direction));
          const auto& expected_bc =
              dynamic_cast<const TestHelpers::domain::BoundaryConditions::
                               TestBoundaryCondition<VolumeDim>&>(
                  *expected_boundary_condition);
          CHECK(bc.direction() == expected_bc.direction());
          CHECK(bc.block_id() == expected_bc.block_id());
        }
      }
    }
  };

  test_impl(aligned_lattice.create_domain());
  test_impl(serialize_and_deserialize(aligned_lattice.create_domain()));
}

template <size_t VolumeDim>
auto make_domain_creator(const std::string& opt_string,
                         const bool use_boundary_condition) {
  if (use_boundary_condition) {
    return TestHelpers::test_option_tag<
        domain::OptionTags::DomainCreator<VolumeDim>,
        TestHelpers::domain::BoundaryConditions::
            MetavariablesWithBoundaryConditions<
                VolumeDim, domain::creators::AlignedLattice<VolumeDim>>>(
        opt_string + std::string{"  BoundaryCondition:\n"
                                 "    TestBoundaryCondition:\n"
                                 "      Direction: upper-xi\n"
                                 "      BlockId: 100\n"});
  } else {
    return TestHelpers::test_option_tag<
        domain::OptionTags::DomainCreator<VolumeDim>,
        TestHelpers::domain::BoundaryConditions::
            MetavariablesWithoutBoundaryConditions<
                VolumeDim, domain::creators::AlignedLattice<VolumeDim>>>(
        opt_string);
  }
}
}  // namespace

SPECTRE_TEST_CASE("Unit.Domain.Creators.AlignedLattice", "[Domain][Unit]") {
  TestHelpers::domain::BoundaryConditions::register_derived_with_charm();

  for (const bool use_boundary_condition : {true, false}) {
    const std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>
        expected_boundary_condition_1d =
            use_boundary_condition ? create_boundary_condition<1>() : nullptr;
    const std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>
        expected_boundary_condition_2d =
            use_boundary_condition ? create_boundary_condition<2>() : nullptr;
    const std::unique_ptr<domain::BoundaryConditions::BoundaryCondition>
        expected_boundary_condition_3d =
            use_boundary_condition ? create_boundary_condition<3>() : nullptr;

    const auto domain_creator_1d = make_domain_creator<1>(
        "AlignedLattice:\n"
        "  BlockBounds: [[0.1, 2.6, 5.1, 5.2, 7.2]]\n" +
            std::string{use_boundary_condition ? ""
                                               : "  IsPeriodicIn: [false]\n"} +
            "  InitialGridPoints: [3]\n"
            "  InitialLevels: [2]\n"
            "  RefinedLevels: []\n"
            "  RefinedGridPoints: []\n"
            "  BlocksToExclude: []\n",
        use_boundary_condition);
    const auto* aligned_blocks_creator_1d =
        dynamic_cast<const creators::AlignedLattice<1>*>(
            domain_creator_1d.get());
    test_aligned_blocks(*aligned_blocks_creator_1d,
                        expected_boundary_condition_1d);

    const auto domain_creator_2d = make_domain_creator<2>(
        "AlignedLattice:\n"
        "  BlockBounds: [[0.1, 2.6, 5.1], [-0.4, 3.2, 6.2, 8.9]]\n" +
            std::string{use_boundary_condition
                            ? ""
                            : "  IsPeriodicIn: [false, false]\n"} +
            "  InitialGridPoints: [3, 4]\n"
            "  InitialLevels: [2, 1]\n"
            "  RefinedLevels: []\n"
            "  RefinedGridPoints: []\n"
            "  BlocksToExclude: []\n",
        use_boundary_condition);
    const auto* aligned_blocks_creator_2d =
        dynamic_cast<const creators::AlignedLattice<2>*>(
            domain_creator_2d.get());
    test_aligned_blocks(*aligned_blocks_creator_2d,
                        expected_boundary_condition_2d);

    const auto domain_creator_3d = make_domain_creator<3>(
        "AlignedLattice:\n"
        "  BlockBounds: [[0.1, 2.6, 5.1], [-0.4, 3.2, 6.2], [-0.2, 3.2]]\n" +
            std::string{use_boundary_condition
                            ? ""
                            : "  IsPeriodicIn: [false, false, false]\n"} +
            "  InitialGridPoints: [3, 4, 5]\n"
            "  InitialLevels: [2, 1, 0]\n"
            "  RefinedLevels: []\n"
            "  RefinedGridPoints: []\n"
            "  BlocksToExclude: []\n",
        use_boundary_condition);
    const auto* aligned_blocks_creator_3d =
        dynamic_cast<const creators::AlignedLattice<3>*>(
            domain_creator_3d.get());
    test_aligned_blocks(*aligned_blocks_creator_3d,
                        expected_boundary_condition_3d);

    const auto cubical_shell_domain = make_domain_creator<3>(
        "AlignedLattice:\n"
        "  BlockBounds: [[0.1, 2.6, 5.1, 6.0], [-0.4, 3.2, 6.2, 7.0], "
        "[-0.2, 3.2, 4.0, 5.2]]\n" +
            std::string{use_boundary_condition
                            ? ""
                            : "  IsPeriodicIn: [false, false, false]\n"} +
            "  InitialGridPoints: [3, 4, 5]\n"
            "  InitialLevels: [2, 1, 0]\n"
            "  RefinedLevels: []\n"
            "  RefinedGridPoints: []\n"
            "  BlocksToExclude: [[1, 1, 1]]\n",
        use_boundary_condition);
    const auto* cubical_shell_creator_3d =
        dynamic_cast<const creators::AlignedLattice<3>*>(
            cubical_shell_domain.get());
    test_aligned_blocks(*cubical_shell_creator_3d,
                        expected_boundary_condition_3d);

    const auto unit_cubical_shell_domain = make_domain_creator<3>(
        "AlignedLattice:\n"
        "  BlockBounds: [[-1.5, -0.5, 0.5, 1.5], [-1.5, -0.5, 0.5, 1.5], "
        "[-1.5, -0.5, 0.5, 1.5]]\n" +
            std::string{use_boundary_condition
                            ? ""
                            : "  IsPeriodicIn: [false, false, false]\n"} +
            "  InitialGridPoints: [5, 5, 5]\n"
            "  InitialLevels: [1, 1, 1]\n"
            "  RefinedLevels: []\n"
            "  RefinedGridPoints: []\n"
            "  BlocksToExclude: [[1, 1, 1]]\n",
        use_boundary_condition);
    const auto* unit_cubical_shell_creator_3d =
        dynamic_cast<const creators::AlignedLattice<3>*>(
            unit_cubical_shell_domain.get());
    test_aligned_blocks(*unit_cubical_shell_creator_3d,
                        expected_boundary_condition_3d);
  }

  const auto domain_creator_2d_periodic = TestHelpers::test_option_tag<
      domain::OptionTags::DomainCreator<2>,
      TestHelpers::domain::BoundaryConditions::
          MetavariablesWithoutBoundaryConditions<
              2, domain::creators::AlignedLattice<2>>>(
      "AlignedLattice:\n"
      "  BlockBounds: [[0.1, 2.6, 5.1], [-0.4, 3.2, 6.2, 8.9]]\n"
      "  IsPeriodicIn: [false, true]\n"
      "  InitialGridPoints: [3, 4]\n"
      "  InitialLevels: [2, 1]\n"
      "  RefinedLevels: []\n"
      "  RefinedGridPoints: []\n"
      "  BlocksToExclude: []\n");
  const auto* aligned_blocks_creator_2d_periodic =
      dynamic_cast<const creators::AlignedLattice<2>*>(
          domain_creator_2d_periodic.get());
  test_aligned_blocks(*aligned_blocks_creator_2d_periodic, nullptr);

  const auto domain_creator_3d_periodic = TestHelpers::test_option_tag<
      domain::OptionTags::DomainCreator<3>,
      TestHelpers::domain::BoundaryConditions::
          MetavariablesWithoutBoundaryConditions<
              3, domain::creators::AlignedLattice<3>>>(
      "AlignedLattice:\n"
      "  BlockBounds: [[0.1, 2.6, 5.1], [-0.4, 3.2, 6.2], [-0.2, 3.2]]\n"
      "  IsPeriodicIn: [false, true, false]\n"
      "  InitialGridPoints: [3, 4, 5]\n"
      "  InitialLevels: [2, 1, 0]\n"
      "  RefinedLevels: []\n"
      "  RefinedGridPoints: []\n"
      "  BlocksToExclude: []\n");
  const auto* aligned_blocks_creator_3d_periodic =
      dynamic_cast<const creators::AlignedLattice<3>*>(
          domain_creator_3d_periodic.get());
  test_aligned_blocks(*aligned_blocks_creator_3d_periodic, nullptr);

  {
    // Expected domain refinement:
    // 23 23 67
    // 23 45 67
    // 23 XX 45
    const auto refined_domain = TestHelpers::test_option_tag<
        domain::OptionTags::DomainCreator<2>,
        TestHelpers::domain::BoundaryConditions::
            MetavariablesWithoutBoundaryConditions<
                2, domain::creators::AlignedLattice<2>>>(
        "AlignedLattice:\n"
        "  BlockBounds: [[70, 71, 72, 73], [90, 91, 92, 93]]\n"
        "  IsPeriodicIn: [false, false]\n"
        "  InitialGridPoints: [2, 3]\n"
        "  InitialLevels: [0, 0]\n"
        "  BlocksToExclude: [[1, 0]]\n"
        "  RefinedLevels: []\n"
        "  RefinedGridPoints:\n"
        "  - LowerCornerIndex: [1, 0]\n"
        "    UpperCornerIndex: [3, 2]\n"
        "    Refinement: [4, 5]\n"
        "  - LowerCornerIndex: [2, 1]\n"
        "    UpperCornerIndex: [3, 3]\n"
        "    Refinement: [6, 7]");
    std::unordered_set<
        std::pair<std::vector<double>, std::array<size_t, 2>>,
        boost::hash<std::pair<std::vector<double>, std::array<size_t, 2>>>>
        expected_blocks{{{70.0, 90.0}, {{2, 3}}}, {{72.0, 90.0}, {{4, 5}}},
                        {{70.0, 91.0}, {{2, 3}}}, {{71.0, 91.0}, {{4, 5}}},
                        {{72.0, 91.0}, {{6, 7}}}, {{70.0, 92.0}, {{2, 3}}},
                        {{71.0, 92.0}, {{2, 3}}}, {{72.0, 92.0}, {{6, 7}}}};
    const auto domain = refined_domain->create_domain();
    test_initial_domain(domain, refined_domain->initial_refinement_levels());

    const auto& blocks = domain.blocks();
    const auto extents = refined_domain->initial_extents();
    REQUIRE(blocks.size() == extents.size());
    for (size_t i = 0; i < blocks.size(); ++i) {
      const auto location =
          blocks[i]
              .stationary_map()(
                  tnsr::I<double, 2, Frame::BlockLogical>{{{-1.0, -1.0}}})
              .get_vector_of_data()
              .second;
      INFO("Unexpected block");
      CAPTURE(location);
      CAPTURE(extents[i]);
      CHECK(expected_blocks.erase({location, extents[i]}) == 1);
    }
    CAPTURE(expected_blocks);
    CHECK(expected_blocks.empty());
  }

  {
    // Expected domain refinement:
    // 25 25 46
    // 25 35 46
    // 25 XX 35
    const auto refined_domain = TestHelpers::test_option_tag<
        domain::OptionTags::DomainCreator<2>,
        TestHelpers::domain::BoundaryConditions::
            MetavariablesWithoutBoundaryConditions<
                2, domain::creators::AlignedLattice<2>>>(
        "AlignedLattice:\n"
        "  BlockBounds: [[70, 71, 72, 73], [90, 91, 92, 93]]\n"
        "  IsPeriodicIn: [false, false]\n"
        "  InitialGridPoints: [10, 10]\n"
        "  InitialLevels: [2, 5]\n"
        "  BlocksToExclude: [[1, 0]]\n"
        "  RefinedGridPoints: []\n"
        "  RefinedLevels:\n"
        "  - LowerCornerIndex: [1, 0]\n"
        "    UpperCornerIndex: [3, 2]\n"
        "    Refinement: [3, 5]\n"
        "  - LowerCornerIndex: [2, 1]\n"
        "    UpperCornerIndex: [3, 3]\n"
        "    Refinement: [4, 6]");
    std::unordered_set<
        std::pair<std::vector<double>, std::array<size_t, 2>>,
        boost::hash<std::pair<std::vector<double>, std::array<size_t, 2>>>>
        expected_blocks{{{70.0, 90.0}, {{2, 5}}}, {{72.0, 90.0}, {{3, 5}}},
                        {{70.0, 91.0}, {{2, 5}}}, {{71.0, 91.0}, {{3, 5}}},
                        {{72.0, 91.0}, {{4, 6}}}, {{70.0, 92.0}, {{2, 5}}},
                        {{71.0, 92.0}, {{2, 5}}}, {{72.0, 92.0}, {{4, 6}}}};
    const auto domain = refined_domain->create_domain();
    const auto refinement_levels = refined_domain->initial_refinement_levels();
    test_initial_domain(domain, refinement_levels);

    const auto& blocks = domain.blocks();
    REQUIRE(blocks.size() == refinement_levels.size());
    for (size_t i = 0; i < blocks.size(); ++i) {
      const auto location =
          blocks[i]
              .stationary_map()(
                  tnsr::I<double, 2, Frame::BlockLogical>{{{-1.0, -1.0}}})
              .get_vector_of_data()
              .second;
      INFO("Unexpected block");
      CAPTURE(location);
      CAPTURE(refinement_levels[i]);
      CHECK(expected_blocks.erase({location, refinement_levels[i]}) == 1);
    }
    CAPTURE(expected_blocks);
    CHECK(expected_blocks.empty());
  }

  CHECK_THROWS_WITH(
      creators::AlignedLattice<3>({{{{-1.5, -0.5, 0.5, 1.5}},
                                    {{1.5, -0.5, 0.5, 1.5}},
                                    {{-1.5, -0.5, 0.5, 1.5}}}},
                                  {{1, 1, 1}}, {{5, 5, 5}}, {}, {},
                                  {{{{1, 1, 1}}}}, {{true, false, false}},
                                  Options::Context{false, {}, 1, 1}),
      Catch::Matchers::Contains(
          "Cannot exclude blocks as well as have periodic boundary"));
  CHECK_THROWS_WITH(
      creators::AlignedLattice<3>({{{{-1.5, -0.5, 0.5, 1.5}},
                                    {{1.5, -0.5, 0.5, 1.5}},
                                    {{-1.5, -0.5, 0.5, 1.5}}}},
                                  {{1, 1, 1}}, {{5, 5, 5}}, {}, {},
                                  {{{{1, 1, 1}}}}, {{false, true, false}},
                                  Options::Context{false, {}, 1, 1}),
      Catch::Matchers::Contains(
          "Cannot exclude blocks as well as have periodic boundary"));
  CHECK_THROWS_WITH(
      creators::AlignedLattice<3>({{{{-1.5, -0.5, 0.5, 1.5}},
                                    {{1.5, -0.5, 0.5, 1.5}},
                                    {{-1.5, -0.5, 0.5, 1.5}}}},
                                  {{1, 1, 1}}, {{5, 5, 5}}, {}, {},
                                  {{{{1, 1, 1}}}}, {{true, false, true}},
                                  Options::Context{false, {}, 1, 1}),
      Catch::Matchers::Contains(
          "Cannot exclude blocks as well as have periodic boundary"));
  CHECK_THROWS_WITH(
      creators::AlignedLattice<3>(
          {{{{-1.5, -0.5, 0.5, 1.5}},
            {{1.5, -0.5, 0.5, 1.5}},
            {{-1.5, -0.5, 0.5, 1.5}}}},
          {{1, 1, 1}}, {{5, 5, 5}}, {}, {}, {{{{1, 1, 1}}}},
          std::make_unique<TestHelpers::domain::BoundaryConditions::
                               TestPeriodicBoundaryCondition<3>>(),
          Options::Context{false, {}, 1, 1}),
      Catch::Matchers::Contains(
          "Cannot exclude blocks as well as have periodic boundary"));
  CHECK_THROWS_WITH(
      creators::AlignedLattice<3>(
          {{{{-1.5, -0.5, 0.5, 1.5}},
            {{1.5, -0.5, 0.5, 1.5}},
            {{-1.5, -0.5, 0.5, 1.5}}}},
          {{1, 1, 1}}, {{5, 5, 5}}, {}, {}, {{{{1, 1, 1}}}},
          std::make_unique<TestHelpers::domain::BoundaryConditions::
                               TestNoneBoundaryCondition<3>>(),
          Options::Context{false, {}, 1, 1}),
      Catch::Matchers::Contains(
          "None boundary condition is not supported. If you would like an "
          "outflow boundary condition, you must use that."));
}
}  // namespace domain


/*
 Copyright (C) 2017 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <boost/lexical_cast.hpp>
#include <ored/portfolio/creditdefaultswapdata.hpp>
#include <ored/portfolio/legdata.hpp>
#include <ored/utilities/log.hpp>
#include <ored/utilities/parsers.hpp>

using namespace QuantLib;
using namespace QuantExt;

namespace ore {
namespace data {

void CreditDefaultSwapData::fromXML(XMLNode* node) {
    XMLUtils::checkNode(node, "CreditDefaultSwapData");
    issuerId_ = XMLUtils::getChildValue(node, "IssuerId");
    creditCurveId_ = XMLUtils::getChildValue(node, "CreditCurveId", true);
    settlesAccrual_ = XMLUtils::getChildValueAsBool(node, "SettlesAccrual", false);       // default = Y
    paysAtDefaultTime_ = XMLUtils::getChildValueAsBool(node, "PaysAtDefaultTime", false); // default = Y
    XMLNode* tmp = XMLUtils::getChildNode(node, "ProtectionStart");
    if (tmp)
        protectionStart_ = parseDate(XMLUtils::getNodeValue(tmp)); // null date if empty or missing
    else
        protectionStart_ = Date();
    tmp = XMLUtils::getChildNode(node, "UpfrontDate");
    if (tmp)
        upfrontDate_ = parseDate(XMLUtils::getNodeValue(tmp)); // null date if empty or mssing
    else
        upfrontDate_ = Date();
    upfrontFee_ = parseReal(XMLUtils::getChildValue(node, "UpfrontFee", false)); // zero if empty or missing
    if (upfrontDate_ == Date()) {
        QL_REQUIRE(close_enough(upfrontFee_, 0.0), "CreditDefaultSwapData::fromXML(): UpfronFee not zero ("
                                                       << upfrontFee_ << "), but no upfront data given");
        upfrontFee_ = Null<Real>();
    }
    leg_.fromXML(XMLUtils::getChildNode(node, "LegData"));
}

XMLNode* CreditDefaultSwapData::toXML(XMLDocument& doc) {
    XMLNode* node = doc.allocNode("CreditDefaultSwapData");
    XMLUtils::addChild(doc, node, "IssuerId", issuerId_);
    XMLUtils::addChild(doc, node, "CreditCurveId", creditCurveId_);
    XMLUtils::addChild(doc, node, "SettlesAccrual", settlesAccrual_);
    XMLUtils::addChild(doc, node, "PaysAtDefaultTime", paysAtDefaultTime_);
    if (protectionStart_ != Date()) {
        std::ostringstream tmp;
        tmp << QuantLib::io::iso_date(protectionStart_);
        XMLUtils::addChild(doc, node, "ProtectionStart", tmp.str());
    }
    if (upfrontDate_ != Date()) {
        std::ostringstream tmp;
        tmp << QuantLib::io::iso_date(upfrontDate_);
        XMLUtils::addChild(doc, node, "UpfrontDate", tmp.str());
    }
    if (upfrontFee_ != Null<Real>())
        XMLUtils::addChild(doc, node, "UpfrontFee", upfrontFee_);
    XMLUtils::appendNode(node, leg_.toXML(doc));
    return node;
}
} // namespace data
} // namespace ore