diff --git a/docker/default_config.ini b/docker/default_config.ini index 0bbd64733c..53aa03bcfc 100644 --- a/docker/default_config.ini +++ b/docker/default_config.ini @@ -82,6 +82,9 @@ rpc-endpoint = 0.0.0.0:8090 # For database_api_impl::get_limit_orders to set max limit value # api-limit-get-limit-orders = 300 +# For database_api_impl::get_limit_orders_by_account to set max limit value +# api-limit-get-limit-orders-by-account = 101 + # For database_api_impl::get_order_book to set max limit value # api-limit-get-order-book = 50 diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index ef03a4181c..1f22020adb 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -342,8 +342,12 @@ namespace graphene { namespace app { { FC_ASSERT( _app.chain_database() ); const auto& db = *_app.chain_database(); - uint64_t api_limit_get_account_history=_app.get_options().api_limit_get_account_history; - FC_ASSERT( limit <= api_limit_get_account_history ); + + const auto configured_limit = _app.get_options().api_limit_get_account_history; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + vector result; account_id_type account; try { @@ -387,12 +391,16 @@ namespace graphene { namespace app { int operation_type, operation_history_id_type start, operation_history_id_type stop, - unsigned limit) const + unsigned limit ) const { FC_ASSERT( _app.chain_database() ); const auto& db = *_app.chain_database(); - uint64_t api_limit_get_account_history_operations=_app.get_options().api_limit_get_account_history_operations; - FC_ASSERT(limit <= api_limit_get_account_history_operations); + + const auto configured_limit = _app.get_options().api_limit_get_account_history_operations; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + vector result; account_id_type account; try { @@ -427,12 +435,16 @@ namespace graphene { namespace app { vector history_api::get_relative_account_history( const std::string account_id_or_name, uint64_t stop, unsigned limit, - uint64_t start) const + uint64_t start ) const { FC_ASSERT( _app.chain_database() ); const auto& db = *_app.chain_database(); - uint64_t api_limit_get_relative_account_history=_app.get_options().api_limit_get_relative_account_history; - FC_ASSERT(limit <= api_limit_get_relative_account_history); + + const auto configured_limit = _app.get_options().api_limit_get_relative_account_history; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + vector result; account_id_type account; try { @@ -469,24 +481,38 @@ namespace graphene { namespace app { return hist->tracked_buckets(); } - history_operation_detail history_api::get_account_history_by_operations(const std::string account_id_or_name, vector operation_types, uint32_t start, unsigned limit) + history_operation_detail history_api::get_account_history_by_operations( const std::string account_id_or_name, + flat_set operation_types, + uint32_t start, unsigned limit )const { - uint64_t api_limit_get_account_history_by_operations=_app.get_options().api_limit_get_account_history_by_operations; - FC_ASSERT(limit <= api_limit_get_account_history_by_operations); + const auto configured_limit = _app.get_options().api_limit_get_account_history_by_operations; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + history_operation_detail result; - vector objs = get_relative_account_history(account_id_or_name, start, limit, limit + start - 1); - std::for_each(objs.begin(), objs.end(), [&](const operation_history_object &o) { - if (operation_types.empty() || find(operation_types.begin(), operation_types.end(), o.op.which()) != operation_types.end()) { - result.operation_history_objs.push_back(o); - } - }); + vector objs = get_relative_account_history( account_id_or_name, start, limit, + limit + start - 1 ); + result.total_count = objs.size(); + + if( operation_types.empty() ) + result.operation_history_objs = std::move(objs); + else + { + for( const operation_history_object &o : objs ) + { + if( operation_types.find(o.op.which()) != operation_types.end() ) { + result.operation_history_objs.push_back(o); + } + } + } - result.total_count = objs.size(); - return result; + return result; } vector history_api::get_market_history( std::string asset_a, std::string asset_b, - uint32_t bucket_seconds, fc::time_point_sec start, fc::time_point_sec end )const + uint32_t bucket_seconds, + fc::time_point_sec start, fc::time_point_sec end )const { try { FC_ASSERT(_app.chain_database()); const auto& db = *_app.chain_database(); @@ -577,9 +603,13 @@ namespace graphene { namespace app { ) { } asset_api::~asset_api() { } - vector asset_api::get_asset_holders( std::string asset, uint32_t start, uint32_t limit ) const { - uint64_t api_limit_get_asset_holders=_app.get_options().api_limit_get_asset_holders; - FC_ASSERT(limit <= api_limit_get_asset_holders); + vector asset_api::get_asset_holders( std::string asset, uint32_t start, uint32_t limit ) const + { + const auto configured_limit = _app.get_options().api_limit_get_asset_holders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + asset_id_type asset_id = database_api.get_asset_id_from_string( asset ); const auto& bal_idx = _db.get_index_type< account_balance_index >().indices().get< by_asset_balance >(); auto range = bal_idx.equal_range( boost::make_tuple( asset_id ) ); @@ -660,8 +690,11 @@ namespace graphene { namespace app { optional start, uint32_t limit )const { - uint64_t api_limit_get_grouped_limit_orders=_app.get_options().api_limit_get_grouped_limit_orders; - FC_ASSERT( limit <= api_limit_get_grouped_limit_orders ); + const auto configured_limit = _app.get_options().api_limit_get_grouped_limit_orders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + auto plugin = _app.get_plugin( "grouped_orders" ); FC_ASSERT( plugin ); const auto& limit_groups = plugin->limit_order_groups(); diff --git a/libraries/app/api_objects.cpp b/libraries/app/api_objects.cpp index e002f061ae..a999ad99ca 100644 --- a/libraries/app/api_objects.cpp +++ b/libraries/app/api_objects.cpp @@ -38,8 +38,11 @@ market_ticker::market_ticker(const market_ticker_object& mto, quote = asset_quote.symbol; percent_change = "0"; lowest_ask = "0"; + lowest_ask_base_size = "0"; + lowest_ask_quote_size = "0"; highest_bid = "0"; - + highest_bid_base_size = "0"; + highest_bid_quote_size = "0"; fc::uint128_t bv; fc::uint128_t qv; price latest_price = asset( mto.latest_base, mto.base ) / asset( mto.latest_quote, mto.quote ); @@ -68,9 +71,19 @@ market_ticker::market_ticker(const market_ticker_object& mto, quote_volume = uint128_amount_to_string( qv, asset_quote.precision ); if(!orders.asks.empty()) - lowest_ask = orders.asks[0].price; + { + lowest_ask = orders.asks[0].price; + lowest_ask_base_size = orders.asks[0].base; + lowest_ask_quote_size = orders.asks[0].quote; + } + if(!orders.bids.empty()) - highest_bid = orders.bids[0].price; + { + highest_bid = orders.bids[0].price; + highest_bid_base_size = orders.bids[0].base; + highest_bid_quote_size = orders.bids[0].quote; + } + } market_ticker::market_ticker(const fc::time_point_sec& now, @@ -82,7 +95,11 @@ market_ticker::market_ticker(const fc::time_point_sec& now, quote = asset_quote.symbol; latest = "0"; lowest_ask = "0"; + lowest_ask_base_size = "0"; + lowest_ask_quote_size = "0"; highest_bid = "0"; + highest_bid_base_size = "0"; + highest_bid_quote_size = "0"; percent_change = "0"; base_volume = "0"; quote_volume = "0"; diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 672bed4dc8..6263ab1ec8 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include #include @@ -125,41 +124,14 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) if( _options->count("seed-node") ) { auto seeds = _options->at("seed-node").as>(); - for( const string& endpoint_string : seeds ) - { - try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); - for (const fc::ip::endpoint& endpoint : endpoints) - { - ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); - _p2p_network->add_node(endpoint); - _p2p_network->connect_to_endpoint(endpoint); - } - } catch( const fc::exception& e ) { - wlog( "caught exception ${e} while adding seed node ${endpoint}", - ("e", e.to_detail_string())("endpoint", endpoint_string) ); - } - } + _p2p_network->add_seed_nodes(seeds); } if( _options->count("seed-nodes") ) { auto seeds_str = _options->at("seed-nodes").as(); auto seeds = fc::json::from_string(seeds_str).as>(2); - for( const string& endpoint_string : seeds ) - { - try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); - for (const fc::ip::endpoint& endpoint : endpoints) - { - ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); - _p2p_network->add_node(endpoint); - } - } catch( const fc::exception& e ) { - wlog( "caught exception ${e} while adding seed node ${endpoint}", - ("e", e.to_detail_string())("endpoint", endpoint_string) ); - } - } + _p2p_network->add_seed_nodes(seeds); } else { @@ -167,20 +139,7 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) vector seeds = { #include "../egenesis/seed-nodes.txt" }; - for( const string& endpoint_string : seeds ) - { - try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); - for (const fc::ip::endpoint& endpoint : endpoints) - { - ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); - _p2p_network->add_node(endpoint); - } - } catch( const fc::exception& e ) { - wlog( "caught exception ${e} while adding seed node ${endpoint}", - ("e", e.to_detail_string())("endpoint", endpoint_string) ); - } - } + _p2p_network->add_seed_nodes(seeds); } if( _options->count("p2p-endpoint") ) @@ -196,36 +155,6 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) std::vector()); } FC_CAPTURE_AND_RETHROW() } -std::vector application_impl::resolve_string_to_ip_endpoints(const std::string& endpoint_string) -{ - try - { - string::size_type colon_pos = endpoint_string.find(':'); - if (colon_pos == std::string::npos) - FC_THROW("Missing required port number in endpoint string \"${endpoint_string}\"", - ("endpoint_string", endpoint_string)); - std::string port_string = endpoint_string.substr(colon_pos + 1); - try - { - uint16_t port = boost::lexical_cast(port_string); - - std::string hostname = endpoint_string.substr(0, colon_pos); - std::vector endpoints = fc::resolve(hostname, port); - if (endpoints.empty()) - FC_THROW_EXCEPTION( fc::unknown_host_exception, - "The host name can not be resolved: ${hostname}", - ("hostname", hostname) ); - return endpoints; - } - catch (const boost::bad_lexical_cast&) - { - FC_THROW("Bad port: ${port}", ("port", port_string)); - } - } - FC_CAPTURE_AND_RETHROW((endpoint_string)) -} - - void application_impl::new_connection( const fc::http::websocket_connection_ptr& c ) { auto wsc = std::make_shared(c, GRAPHENE_NET_MAX_NESTED_OBJECTS); @@ -343,6 +272,9 @@ void application_impl::set_api_limit() { if(_options->count("api-limit-get-limit-orders")){ _app_options.api_limit_get_limit_orders = _options->at("api-limit-get-limit-orders").as(); } + if(_options->count("api-limit-get-limit-orders-by-account")){ + _app_options.api_limit_get_limit_orders_by_account = _options->at("api-limit-get-limit-orders-by-account").as(); + } if(_options->count("api-limit-get-order-book")){ _app_options.api_limit_get_order_book = _options->at("api-limit-get-order-book").as(); } @@ -1079,6 +1011,8 @@ void application::set_program_options(boost::program_options::options_descriptio "For database_api_impl::list_assets and get_assets_by_issuer to set max limit value") ("api-limit-get-limit-orders",boost::program_options::value()->default_value(300), "For database_api_impl::get_limit_orders to set max limit value") + ("api-limit-get-limit-orders-by-account",boost::program_options::value()->default_value(101), + "For database_api_impl::get_limit_orders_by_account to set max limit value") ("api-limit-get-order-book",boost::program_options::value()->default_value(50), "For database_api_impl::get_order_book to set max limit value") ("api-limit-lookup-accounts",boost::program_options::value()->default_value(1000), diff --git a/libraries/app/application_impl.hxx b/libraries/app/application_impl.hxx index 175648e10f..accc8fe4f1 100644 --- a/libraries/app/application_impl.hxx +++ b/libraries/app/application_impl.hxx @@ -22,8 +22,6 @@ class application_impl : public net::node_delegate void reset_p2p_node(const fc::path& data_dir); - std::vector resolve_string_to_ip_endpoints(const std::string& endpoint_string); - void new_connection( const fc::http::websocket_connection_ptr& c ); void reset_websocket_server(); diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 5cc9dcede7..8ebaeaa309 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -339,8 +339,11 @@ vector> database_api_impl::get_key_references( vector< FC_ASSERT( _app_options && _app_options->has_api_helper_indexes_plugin, "api_helper_indexes plugin is not enabled on this server." ); - uint64_t api_limit_get_key_references=_app_options->api_limit_get_key_references; - FC_ASSERT(keys.size() <= api_limit_get_key_references); + const auto configured_limit = _app_options->api_limit_get_key_references; + FC_ASSERT( keys.size() <= configured_limit, + "Number of querying keys can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + const auto& idx = _db.get_index_type(); const auto& aidx = dynamic_cast(idx); const auto& refs = aidx.get_secondary_index(); @@ -461,7 +464,11 @@ std::map database_api::get_full_accounts( const vector database_api_impl::get_full_accounts( const vector& names_or_ids, optional subscribe ) { - FC_ASSERT( names_or_ids.size() <= _app_options->api_limit_get_full_accounts ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_full_accounts; + FC_ASSERT( names_or_ids.size() <= configured_limit, + "Number of querying accounts can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); bool to_subscribe = get_whether_to_subscribe( subscribe ); @@ -702,7 +709,12 @@ map database_api_impl::lookup_accounts( const string& lo uint32_t limit, optional subscribe )const { - FC_ASSERT( limit <= _app_options->api_limit_lookup_accounts ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_lookup_accounts; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + const auto& accounts_by_name = _db.get_index_type().indices().get(); map result; @@ -888,8 +900,11 @@ vector database_api::list_assets(const string& lower_boun vector database_api_impl::list_assets(const string& lower_bound_symbol, uint32_t limit)const { - uint64_t api_limit_get_assets = _app_options->api_limit_get_assets; - FC_ASSERT( limit <= api_limit_get_assets ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_assets; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); const auto& assets_by_symbol = _db.get_index_type().indices().get(); vector result; @@ -925,8 +940,11 @@ vector database_api::get_assets_by_issuer(const std::stri vector database_api_impl::get_assets_by_issuer(const std::string& issuer_name_or_id, asset_id_type start, uint32_t limit)const { - uint64_t api_limit_get_assets = _app_options->api_limit_get_assets; - FC_ASSERT( limit <= api_limit_get_assets ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_assets; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); vector result; const account_id_type account = get_account_from_string(issuer_name_or_id)->id; @@ -980,8 +998,11 @@ vector database_api::get_limit_orders(std::string a, std::st vector database_api_impl::get_limit_orders( const std::string& a, const std::string& b, uint32_t limit )const { - uint64_t api_limit_get_limit_orders=_app_options->api_limit_get_limit_orders; - FC_ASSERT( limit <= api_limit_get_limit_orders ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_limit_orders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); const asset_id_type asset_a_id = get_asset_from_string(a)->id; const asset_id_type asset_b_id = get_asset_from_string(b)->id; @@ -989,6 +1010,46 @@ vector database_api_impl::get_limit_orders( const std::strin return get_limit_orders(asset_a_id, asset_b_id, limit); } +vector database_api::get_limit_orders_by_account( const string& account_name_or_id, + optional limit, optional start_id ) +{ + return my->get_limit_orders_by_account( account_name_or_id, limit, start_id ); +} + +vector database_api_impl::get_limit_orders_by_account( const string& account_name_or_id, + optional olimit, optional ostart_id ) +{ + uint32_t limit = olimit.valid() ? *olimit : 101; + + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_limit_orders_by_account; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + + vector results; + + const account_object* account = get_account_from_string(account_name_or_id); + if (account == nullptr) + return results; + + limit_order_id_type start_id = ostart_id.valid() ? *ostart_id : limit_order_id_type(); + + const auto& index_by_account = _db.get_index_type().indices().get(); + auto lower_itr = index_by_account.lower_bound( std::make_tuple( account->id, start_id ) ); + auto upper_itr = index_by_account.upper_bound( account->id ); + + results.reserve( limit ); + uint32_t count = 0; + for ( ; lower_itr != upper_itr && count < limit; ++lower_itr, ++count) + { + const limit_order_object &order = *lower_itr; + results.emplace_back(order); + } + + return results; +} + vector database_api::get_account_limit_orders( const string& account_name_or_id, const string &base, const string "e, uint32_t limit, optional ostart_id, optional ostart_price ) @@ -1000,7 +1061,11 @@ vector database_api_impl::get_account_limit_orders( const string& account_name_or_id, const string &base, const string "e, uint32_t limit, optional ostart_id, optional ostart_price ) { - FC_ASSERT( limit <= _app_options->api_limit_get_account_limit_orders ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_account_limit_orders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); vector results; uint32_t count = 0; @@ -1021,9 +1086,9 @@ vector database_api_impl::get_account_limit_orders( FC_ASSERT(ostart_price->quote.asset_id == quote_id, "Quote asset inconsistent with start price"); } - const auto& index_by_account = _db.get_index_type().indices().get(); - limit_order_multi_index_type::index::type::const_iterator lower_itr; - limit_order_multi_index_type::index::type::const_iterator upper_itr; + const auto& index_by_account = _db.get_index_type().indices().get(); + limit_order_multi_index_type::index::type::const_iterator lower_itr; + limit_order_multi_index_type::index::type::const_iterator upper_itr; // if both order_id and price are invalid, query the first page if ( !ostart_id.valid() && !ostart_price.valid() ) @@ -1084,8 +1149,11 @@ vector database_api::get_call_orders(const std::string& a, ui vector database_api_impl::get_call_orders(const std::string& a, uint32_t limit)const { - uint64_t api_limit_get_call_orders = _app_options->api_limit_get_call_orders; - FC_ASSERT( limit <= api_limit_get_call_orders ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_call_orders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); const asset_object* mia = get_asset_from_string(a); const auto& call_index = _db.get_index_type().indices().get(); @@ -1111,8 +1179,11 @@ vector database_api::get_call_orders_by_account(const std::st vector database_api_impl::get_call_orders_by_account(const std::string& account_name_or_id, asset_id_type start, uint32_t limit)const { - uint64_t api_limit_get_call_orders = _app_options->api_limit_get_call_orders; - FC_ASSERT( limit <= api_limit_get_call_orders ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_call_orders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); vector result; const account_id_type account = get_account_from_string(account_name_or_id)->id; @@ -1134,8 +1205,11 @@ vector database_api::get_settle_orders(const std::strin vector database_api_impl::get_settle_orders(const std::string& a, uint32_t limit)const { - uint64_t api_limit_get_settle_orders = _app_options->api_limit_get_settle_orders; - FC_ASSERT( limit <= api_limit_get_settle_orders ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_settle_orders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); const asset_id_type asset_a_id = get_asset_from_string(a)->id; const auto& settle_index = _db.get_index_type().indices().get(); @@ -1165,8 +1239,11 @@ vector database_api_impl::get_settle_orders_by_account( force_settlement_id_type start, uint32_t limit )const { - uint64_t api_limit_get_settle_orders = _app_options->api_limit_get_settle_orders; - FC_ASSERT( limit <= api_limit_get_settle_orders ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_settle_orders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); vector result; const account_id_type account = get_account_from_string(account_name_or_id)->id; @@ -1215,7 +1292,12 @@ vector database_api::get_collateral_bids( const std::stri vector database_api_impl::get_collateral_bids( const std::string& asset, uint32_t limit, uint32_t skip )const { try { - FC_ASSERT( limit <= _app_options->api_limit_get_collateral_bids ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_collateral_bids; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + const asset_id_type asset_id = get_asset_from_string(asset)->id; const asset_object& swan = asset_id(_db); FC_ASSERT( swan.is_market_issued() ); @@ -1331,8 +1413,12 @@ order_book database_api::get_order_book( const string& base, const string& quote order_book database_api_impl::get_order_book( const string& base, const string& quote, unsigned limit )const { - FC_ASSERT( limit <= _app_options->api_limit_get_order_book ); - + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_order_book; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + order_book result; result.base = base; result.quote = quote; @@ -1381,7 +1467,10 @@ vector database_api_impl::get_top_markets(uint32_t limit)const { FC_ASSERT( _app_options && _app_options->has_market_history_plugin, "Market history plugin is not enabled." ); - FC_ASSERT( limit <= _app_options->api_limit_get_top_markets ); + const auto configured_limit = _app_options->api_limit_get_top_markets; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); const auto& volume_idx = _db.get_index_type().indices().get(); auto itr = volume_idx.rbegin(); @@ -1419,7 +1508,10 @@ vector database_api_impl::get_trade_history( const string& base, { FC_ASSERT( _app_options && _app_options->has_market_history_plugin, "Market history plugin is not enabled." ); - FC_ASSERT( limit <= _app_options->api_limit_get_trade_history ); + const auto configured_limit = _app_options->api_limit_get_trade_history; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); auto assets = lookup_asset_symbols( {base, quote} ); FC_ASSERT( assets[0], "Invalid base asset symbol: ${s}", ("s",base) ); @@ -1511,7 +1603,11 @@ vector database_api_impl::get_trade_history_by_sequence( { FC_ASSERT( _app_options && _app_options->has_market_history_plugin, "Market history plugin is not enabled." ); - FC_ASSERT( limit <= _app_options->api_limit_get_trade_history_by_sequence ); + const auto configured_limit = _app_options->api_limit_get_trade_history_by_sequence; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + FC_ASSERT( start >= 0 ); int64_t start_seq = -start; @@ -1645,7 +1741,12 @@ map database_api::lookup_witness_accounts( const string map database_api_impl::lookup_witness_accounts( const string& lower_bound_name, uint32_t limit )const { - FC_ASSERT( limit <= _app_options->api_limit_lookup_witness_accounts ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_lookup_witness_accounts; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + const auto& witnesses_by_id = _db.get_index_type().indices().get(); // we want to order witnesses by account name, but that name is in the account object @@ -1727,7 +1828,12 @@ map database_api::lookup_committee_member_acco map database_api_impl::lookup_committee_member_accounts( const string& lower_bound_name, uint32_t limit )const { - FC_ASSERT( limit <= _app_options->api_limit_lookup_committee_member_accounts ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_lookup_committee_member_accounts; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + const auto& committee_members_by_id = _db.get_index_type().indices().get(); // we want to order committee_members by account name, but that name is in the account object @@ -1765,39 +1871,56 @@ uint64_t database_api_impl::get_committee_count()const // // ////////////////////////////////////////////////////////////////////// -vector database_api::get_all_workers()const +vector database_api::get_all_workers( const optional is_expired )const { - return my->get_all_workers(); + return my->get_all_workers( is_expired ); } -vector database_api_impl::get_all_workers()const +vector database_api_impl::get_all_workers( const optional is_expired )const { - vector result; - const auto& workers_idx = _db.get_index_type().indices().get(); - for( const auto& w : workers_idx ) - { - result.push_back( w ); - } - return result; + vector result; + + if( !is_expired.valid() ) // query for all workers + { + const auto& workers_idx = _db.get_index_type().indices().get(); + result.reserve( workers_idx.size() ); + for( const auto& w : workers_idx ) + { + result.push_back( w ); + } + } + else // query for workers that are expired only or not expired only + { + const time_point_sec now = _db.head_block_time(); + const auto& workers_idx = _db.get_index_type().indices().get(); + auto itr = *is_expired ? workers_idx.begin() : workers_idx.lower_bound( now ); + auto end = *is_expired ? workers_idx.upper_bound( now ) : workers_idx.end(); + for( ; itr != end; ++itr ) + { + result.push_back( *itr ); + } + } + + return result; } -vector> database_api::get_workers_by_account(const std::string account_id_or_name)const +vector database_api::get_workers_by_account(const std::string account_id_or_name)const { - return my->get_workers_by_account( account_id_or_name ); + return my->get_workers_by_account( account_id_or_name ); } -vector> database_api_impl::get_workers_by_account(const std::string account_id_or_name)const +vector database_api_impl::get_workers_by_account(const std::string account_id_or_name)const { - vector> result; + vector result; const auto& workers_idx = _db.get_index_type().indices().get(); const account_id_type account = get_account_from_string(account_id_or_name)->id; - for( const auto& w : workers_idx ) - { - if( w.worker_account == account ) - result.push_back( w ); - } - return result; + auto range = workers_idx.equal_range(account); + for(auto itr = range.first; itr != range.second; ++itr) + { + result.push_back( *itr ); + } + return result; } uint64_t database_api::get_worker_count()const @@ -1825,7 +1948,11 @@ vector database_api::lookup_vote_ids( const vector& votes vector database_api_impl::lookup_vote_ids( const vector& votes )const { - FC_ASSERT( votes.size() < _app_options->api_limit_lookup_vote_ids ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_lookup_vote_ids; + FC_ASSERT( votes.size() <= configured_limit, + "Number of querying votes can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); const auto& witness_idx = _db.get_index_type().indices().get(); const auto& committee_idx = _db.get_index_type().indices().get(); @@ -2053,7 +2180,7 @@ bool database_api_impl::verify_account_authority( const string& account_name_or_ // Use a no-op lookup for custom authorities; we don't want it even if one does apply for our dummy op [](auto, auto, auto*) { return vector(); }, true, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(_db.head_block_time()) ); - } + } catch (fc::exception& ex) { return false; @@ -2238,7 +2365,12 @@ vector database_api_impl::get_withdraw_permissions_b withdraw_permission_id_type start, uint32_t limit)const { - FC_ASSERT( limit <= _app_options->api_limit_get_withdraw_permissions_by_giver ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_withdraw_permissions_by_giver; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + vector result; const auto& withdraw_idx = _db.get_index_type().indices().get(); @@ -2267,7 +2399,12 @@ vector database_api_impl::get_withdraw_permissions_b withdraw_permission_id_type start, uint32_t limit)const { - FC_ASSERT( limit <= _app_options->api_limit_get_withdraw_permissions_by_recipient ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_withdraw_permissions_by_recipient; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + vector result; const auto& withdraw_idx = _db.get_index_type().indices().get(); @@ -2312,7 +2449,12 @@ vector database_api::get_htlc_by_from( const std::string account_id vector database_api_impl::get_htlc_by_from( const std::string account_id_or_name, htlc_id_type start, uint32_t limit ) const { - FC_ASSERT( limit <= _app_options->api_limit_get_htlc_by ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_htlc_by; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); + vector result; const auto& htlc_idx = _db.get_index_type< htlc_index >().indices().get< by_from_id >(); @@ -2337,8 +2479,12 @@ vector database_api::get_htlc_by_to( const std::string account_id_o vector database_api_impl::get_htlc_by_to( const std::string account_id_or_name, htlc_id_type start, uint32_t limit ) const { + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_htlc_by; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); - FC_ASSERT( limit <= _app_options->api_limit_get_htlc_by ); vector result; const auto& htlc_idx = _db.get_index_type< htlc_index >().indices().get< by_to_id >(); @@ -2361,7 +2507,11 @@ vector database_api::list_htlcs(const htlc_id_type start, uint32_t vector database_api_impl::list_htlcs(const htlc_id_type start, uint32_t limit) const { - FC_ASSERT( limit <= _app_options->api_limit_list_htlcs ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_list_htlcs; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); vector result; const auto& htlc_idx = _db.get_index_type().indices().get(); @@ -2443,8 +2593,11 @@ vector> database_api_impl::get_assets( const vec vector database_api_impl::get_limit_orders( const asset_id_type a, const asset_id_type b, const uint32_t limit )const { - uint64_t api_limit_get_limit_orders=_app_options->api_limit_get_limit_orders; - FC_ASSERT( limit <= api_limit_get_limit_orders ); + FC_ASSERT( _app_options, "Internal error" ); + const auto configured_limit = _app_options->api_limit_get_limit_orders; + FC_ASSERT( limit <= configured_limit, + "limit can not be greater than ${configured_limit}", + ("configured_limit", configured_limit) ); const auto& limit_order_idx = _db.get_index_type(); const auto& limit_price_idx = limit_order_idx.indices().get(); diff --git a/libraries/app/database_api_impl.hxx b/libraries/app/database_api_impl.hxx index e2e7c376a4..4f544da427 100644 --- a/libraries/app/database_api_impl.hxx +++ b/libraries/app/database_api_impl.hxx @@ -101,6 +101,9 @@ class database_api_impl : public std::enable_shared_from_this // Markets / feeds vector get_limit_orders( const std::string& a, const std::string& b, uint32_t limit)const; + vector get_limit_orders_by_account( const string& account_name_or_id, + optional limit, + optional start_id ); vector get_account_limit_orders( const string& account_name_or_id, const string &base, const string "e, uint32_t limit, @@ -150,8 +153,8 @@ class database_api_impl : public std::enable_shared_from_this uint64_t get_committee_count()const; // Workers - vector get_all_workers()const; - vector> get_workers_by_account(const std::string account_id_or_name)const; + vector get_all_workers( const optional is_expired = optional() )const; + vector get_workers_by_account(const std::string account_id_or_name)const; uint64_t get_worker_count()const; // Votes diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index e861ce2a98..92c08c266a 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -151,10 +151,10 @@ namespace graphene { namespace app { */ history_operation_detail get_account_history_by_operations( const std::string account_id_or_name, - vector operation_types, + flat_set operation_types, uint32_t start, unsigned limit - ); + )const; /** * @brief Get only asked operations relevant to the specified account diff --git a/libraries/app/include/graphene/app/api_objects.hpp b/libraries/app/include/graphene/app/api_objects.hpp index af7a96e1ab..ff7f027f09 100644 --- a/libraries/app/include/graphene/app/api_objects.hpp +++ b/libraries/app/include/graphene/app/api_objects.hpp @@ -100,7 +100,11 @@ namespace graphene { namespace app { string quote; string latest; string lowest_ask; + string lowest_ask_base_size; + string lowest_ask_quote_size; string highest_bid; + string highest_bid_base_size; + string highest_bid_quote_size; string percent_change; string base_volume; string quote_volume; @@ -178,7 +182,8 @@ FC_REFLECT( graphene::app::full_account, FC_REFLECT( graphene::app::order, (price)(quote)(base) ); FC_REFLECT( graphene::app::order_book, (base)(quote)(bids)(asks) ); FC_REFLECT( graphene::app::market_ticker, - (time)(base)(quote)(latest)(lowest_ask)(highest_bid)(percent_change)(base_volume)(quote_volume) ); + (time)(base)(quote)(latest)(lowest_ask)(lowest_ask_base_size)(lowest_ask_quote_size) + (highest_bid)(highest_bid_base_size)(highest_bid_quote_size)(percent_change)(base_volume)(quote_volume) ); FC_REFLECT( graphene::app::market_volume, (time)(base)(quote)(base_volume)(quote_volume) ); FC_REFLECT( graphene::app::market_trade, (sequence)(date)(price)(amount)(value)(side1_account_id)(side2_account_id) ); diff --git a/libraries/app/include/graphene/app/application.hpp b/libraries/app/include/graphene/app/application.hpp index d0f81960db..96e2d431ec 100644 --- a/libraries/app/include/graphene/app/application.hpp +++ b/libraries/app/include/graphene/app/application.hpp @@ -57,6 +57,7 @@ namespace graphene { namespace app { uint64_t api_limit_get_settle_orders = 300; uint64_t api_limit_get_assets = 101; uint64_t api_limit_get_limit_orders = 300; + uint64_t api_limit_get_limit_orders_by_account = 101; uint64_t api_limit_get_order_book = 50; uint64_t api_limit_list_htlcs = 100; uint64_t api_limit_lookup_accounts = 1000; diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 1960e64824..4934f9ebc9 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -431,6 +431,25 @@ class database_api */ vector get_limit_orders(std::string a, std::string b, uint32_t limit)const; + /** + * @brief Fetch open limit orders in all markets relevant to the specified account, ordered by ID + * + * @param account_name_or_id The name or ID of an account to retrieve + * @param limit The limitation of items each query can fetch, not greater than a configured value + * @param start_id Start order id, fetch orders whose IDs are greater than or equal to this order + * + * @return List of limit orders of the specified account + * + * @note + * 1. if @p account_name_or_id cannot be tied to an account, an error will be returned + * 2. @p limit can be omitted or be null, if so the default value 101 will be used + * 3. @p start_id can be omitted or be null, if so the api will return the "first page" of orders + * 4. can only omit one or more arguments in the end of the list, but not one or more in the middle + */ + vector get_limit_orders_by_account( const string& account_name_or_id, + optional limit = 101, + optional start_id = optional() ); + /** * @brief Fetch all orders relevant to the specified account and specified market, result orders * are sorted descendingly by price @@ -446,7 +465,7 @@ class database_api * @return List of orders from @p account_name_or_id to the corresponding account * * @note - * 1. if @p account_name_or_id cannot be tied to an account, empty result will be returned + * 1. if @p account_name_or_id cannot be tied to an account, an error will be returned * 2. @p ostart_id and @p ostart_price can be empty, if so the api will return the "first page" of orders; * if @p ostart_id is specified, its price will be used to do page query preferentially, * otherwise the @p ostart_price will be used; @@ -674,18 +693,19 @@ class database_api /////////////////////// /** - * @brief Get all workers - * @return All the workers + * @brief Get workers + * @param is_expired null for all workers, true for expired workers only, false for non-expired workers only + * @return A list of worker objects * */ - vector get_all_workers()const; + vector get_all_workers( const optional is_expired = optional() )const; /** * @brief Get the workers owned by a given account * @param account_name_or_id The name or ID of the account whose worker should be retrieved * @return A list of worker objects owned by the account */ - vector> get_workers_by_account(const std::string account_name_or_id)const; + vector get_workers_by_account(const std::string account_name_or_id)const; /** * @brief Get the total number of workers registered with the blockchain @@ -952,6 +972,7 @@ FC_API(graphene::app::database_api, // Markets / feeds (get_order_book) (get_limit_orders) + (get_limit_orders_by_account) (get_account_limit_orders) (get_call_orders) (get_call_orders_by_account) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 3f8963441a..6fbb8a3685 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -45,12 +45,31 @@ namespace detail { "Asset extension reward percent must be less than 100% till HARDFORK_1774_TIME!"); } } -} + + // TODO review and remove code below and links to it after HARDFORK_BSIP_81_TIME + void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options) + { + if (block_time < HARDFORK_BSIP_81_TIME) { + // Taker fees should not be set until activation of BSIP81 + FC_ASSERT(!options.extensions.value.taker_fee_percent.valid(), + "Taker fee percent should not be defined before HARDFORK_BSIP_81_TIME"); + } + } + + void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op) + { + // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: + FC_ASSERT( !op.extensions.value.claim_from_asset_id.valid() || + block_time >= HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME, + "Collateral-denominated fees are not yet active and therefore cannot be claimed." ); + } + +} // graphene::chain::detail void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) { try { - database& d = db(); + const database& d = db(); const auto& chain_parameters = d.get_global_properties().parameters; FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); @@ -68,8 +87,10 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o auto asset_symbol_itr = asset_indx.find( op.symbol ); FC_ASSERT( asset_symbol_itr == asset_indx.end() ); + // Define now from the current block time + const time_point_sec now = d.head_block_time(); // This must remain due to "BOND.CNY" being allowed before this HF - if( d.head_block_time() > HARDFORK_385_TIME ) + if( now > HARDFORK_385_TIME ) { auto dotpos = op.symbol.rfind( '.' ); if( dotpos != std::string::npos ) @@ -112,6 +133,9 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o FC_ASSERT( op.precision == op.bitasset_opts->short_backing_asset(d).precision ); } + // Check the taker fee percent + detail::check_asset_options_hf_bsip81(now, op.common_options); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -272,7 +296,8 @@ static void validate_new_issuer( const database& d, const asset_object& a, accou void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) { try { - database& d = db(); + const database& d = db(); + const time_point_sec now = d.head_block_time(); const asset_object& a = o.asset_to_update(d); auto a_copy = a; @@ -281,7 +306,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( o.new_issuer ) { - FC_ASSERT( d.head_block_time() < HARDFORK_CORE_199_TIME, + FC_ASSERT( now < HARDFORK_CORE_199_TIME, "Since Hardfork #199, updating issuer requires the use of asset_update_issuer_operation."); validate_new_issuer( d, a, *o.new_issuer ); } @@ -317,6 +342,9 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) for( auto id : o.new_options.blacklist_authorities ) d.get_object(id); + // Check the taker fee percent + detail::check_asset_options_hf_bsip81(now, o.new_options); + return void_result(); } FC_CAPTURE_AND_RETHROW((o)) } @@ -445,6 +473,9 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( asset_obj.dynamic_asset_data_id(d).current_supply == 0, "Cannot update a bitasset if there is already a current supply." ); + FC_ASSERT( asset_obj.dynamic_asset_data_id(d).accumulated_collateral_fees == 0, + "Must claim collateral-denominated fees before changing backing asset." ); + const asset_object& new_backing_asset = op.new_options.short_backing_asset(d); // check if the asset exists if( after_hf_core_922_931 ) @@ -770,7 +801,8 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: // performance loss. Needs testing. if( d.head_block_time() >= HARDFORK_CORE_1780_TIME ) { - auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount ); + const bool is_maker = false; // Settlement orders are takers + auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount , is_maker ); settled_amount -= issuer_fees; } @@ -909,25 +941,58 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope } FC_CAPTURE_AND_RETHROW((o)) } - +/*** + * @brief evaluator for asset_claim_fees operation + * + * Checks that we are able to claim fees denominated in asset Y (the amount_to_claim asset), + * from some container asset X which is presumed to have accumulated the fees we wish to claim. + * The container asset is either explicitly named in the extensions, or else assumed as the same + * asset as the amount_to_claim asset. Evaluation fails if either (a) operation issuer is not + * the same as the container_asset issuer, or (b) container_asset has no fee bucket for + * amount_to_claim asset, or (c) accumulated fees are insufficient to cover amount claimed. + */ void_result asset_claim_fees_evaluator::do_evaluate( const asset_claim_fees_operation& o ) { try { - FC_ASSERT( o.amount_to_claim.asset_id(db()).issuer == o.issuer, "Asset fees may only be claimed by the issuer" ); + const database& d = db(); + + detail::check_asset_claim_fees_hardfork_87_74_collatfee(d.head_block_time(), o); // HF_REMOVABLE + + container_asset = o.extensions.value.claim_from_asset_id.valid() ? + &(*o.extensions.value.claim_from_asset_id)(d) : &o.amount_to_claim.asset_id(d); + + FC_ASSERT( container_asset->issuer == o.issuer, "Asset fees may only be claimed by the issuer" ); + FC_ASSERT( container_asset->can_accumulate_fee(d,o.amount_to_claim), + "Asset ${a} (${id}) is not backed by asset (${fid}) and does not hold it as fees.", + ("a",container_asset->symbol)("id",container_asset->id)("fid",o.amount_to_claim.asset_id) ); + + container_ddo = &container_asset->dynamic_asset_data_id(d); + + FC_ASSERT( o.amount_to_claim.amount <= ((container_asset->get_id() == o.amount_to_claim.asset_id) ? + container_ddo->accumulated_fees : + container_ddo->accumulated_collateral_fees), + "Attempt to claim more fees than have accumulated within asset ${a} (${id})", + ("a",container_asset->symbol)("id",container_asset->id)("ddo",*container_ddo) ); + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } +/*** + * @brief apply asset_claim_fees operation + */ void_result asset_claim_fees_evaluator::do_apply( const asset_claim_fees_operation& o ) { try { database& d = db(); - const asset_object& a = o.amount_to_claim.asset_id(d); - const asset_dynamic_data_object& addo = a.dynamic_asset_data_id(d); - FC_ASSERT( o.amount_to_claim.amount <= addo.accumulated_fees, "Attempt to claim more fees than have accumulated", ("addo",addo) ); - - d.modify( addo, [&]( asset_dynamic_data_object& _addo ) { - _addo.accumulated_fees -= o.amount_to_claim.amount; - }); + if ( container_asset->get_id() == o.amount_to_claim.asset_id ) { + d.modify( *container_ddo, [&o]( asset_dynamic_data_object& _addo ) { + _addo.accumulated_fees -= o.amount_to_claim.amount; + }); + } else { + d.modify( *container_ddo, [&o]( asset_dynamic_data_object& _addo ) { + _addo.accumulated_collateral_fees -= o.amount_to_claim.amount; + }); + } d.adjust_balance( o.issuer, o.amount_to_claim ); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 50e874f858..ce74e934b4 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -178,7 +178,7 @@ string asset_object::amount_to_string(share_type amount) const } FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_dynamic_data_object, (graphene::db::object), - (current_supply)(confidential_supply)(accumulated_fees)(fee_pool) ) + (current_supply)(confidential_supply)(accumulated_fees)(accumulated_collateral_fees)(fee_pool) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (graphene::db::object), (asset_id) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 1bdcc67d1a..5aa2eb6e1a 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -1079,6 +1079,7 @@ void process_hf_868_890( database& db, bool skip_check_call_orders ) } // for each market issued asset } + /** * @brief Remove any custom active authorities whose expiration dates are in the past * @param db A mutable database reference diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index e43697471c..86ba4c7408 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -805,30 +805,82 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p const account_object& seller = order.seller(*this); const asset_object& recv_asset = receives.asset_id(*this); - auto issuer_fees = pay_market_fees(&seller, recv_asset, receives); + auto issuer_fees = pay_market_fees(&seller, recv_asset, receives, is_maker); pay_order( seller, receives - issuer_fees, pays ); assert( pays.asset_id != receives.asset_id ); push_applied_operation( fill_order_operation( order.id, order.seller, pays, receives, issuer_fees, fill_price, is_maker ) ); + // BSIP85: Maker order creation fee discount, https://github.com/bitshares/bsips/blob/master/bsip-0085.md + // if the order creation fee was paid in BTS, + // return round_down(deferred_fee * maker_fee_discount_percent) to the owner, + // then process the remaining deferred fee as before; + // if the order creation fee was paid in another asset, + // return round_down(deferred_paid_fee * maker_fee_discount_percent) to the owner, + // return round_down(deferred_fee * maker_fee_discount_percent) to the fee pool of the asset, + // then process the remaining deferred fee and deferred paid fee as before. + const uint16_t maker_discount_percent = get_global_properties().parameters.get_maker_fee_discount_percent(); + + // Save local copies for calculation + share_type deferred_fee = order.deferred_fee; + share_type deferred_paid_fee = order.deferred_paid_fee.amount; + // conditional because cheap integer comparison may allow us to avoid two expensive modify() and object lookups - if( order.deferred_fee > 0 ) + if( order.deferred_paid_fee.amount > 0 ) // implies head_block_time() > HARDFORK_CORE_604_TIME { - modify( seller.statistics(*this), [&]( account_statistics_object& statistics ) + share_type fee_pool_refund = 0; + if( is_maker && maker_discount_percent > 0 ) { - statistics.pay_fee( order.deferred_fee, get_global_properties().parameters.cashback_vesting_threshold ); - } ); - } + share_type refund = detail::calculate_percent( deferred_paid_fee, maker_discount_percent ); + // Note: it's possible that the deferred_paid_fee is very small, + // which can result in a zero refund due to rounding issue, + // in this case, no refund to the fee pool + if( refund > 0 ) + { + FC_ASSERT( refund <= deferred_paid_fee, "Internal error" ); + adjust_balance( order.seller, asset(refund, order.deferred_paid_fee.asset_id) ); + deferred_paid_fee -= refund; + + // deferred_fee might be positive too + FC_ASSERT( deferred_fee > 0, "Internal error" ); + fee_pool_refund = detail::calculate_percent( deferred_fee, maker_discount_percent ); + FC_ASSERT( fee_pool_refund <= deferred_fee, "Internal error" ); + deferred_fee -= fee_pool_refund; + } + } - if( order.deferred_paid_fee.amount > 0 ) // implies head_block_time() > HARDFORK_CORE_604_TIME - { const auto& fee_asset_dyn_data = order.deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); - modify( fee_asset_dyn_data, [&](asset_dynamic_data_object& addo) { - addo.accumulated_fees += order.deferred_paid_fee.amount; + modify( fee_asset_dyn_data, [deferred_paid_fee,fee_pool_refund](asset_dynamic_data_object& addo) { + addo.accumulated_fees += deferred_paid_fee; + addo.fee_pool += fee_pool_refund; }); } + if( order.deferred_fee > 0 ) + { + if( order.deferred_paid_fee.amount <= 0 // paid in CORE, or before HF 604 + && is_maker && maker_discount_percent > 0 ) + { + share_type refund = detail::calculate_percent( deferred_fee, maker_discount_percent ); + if( refund > 0 ) + { + FC_ASSERT( refund <= deferred_fee, "Internal error" ); + adjust_balance( order.seller, asset(refund, asset_id_type()) ); + deferred_fee -= refund; + } + } + // else do nothing here, because we have already processed it above, or no need to process + + if( deferred_fee > 0 ) + { + modify( seller.statistics(*this), [deferred_fee,this]( account_statistics_object& statistics ) + { + statistics.pay_fee( deferred_fee, get_global_properties().parameters.cashback_vesting_threshold ); + } ); + } + } + if( pays == order.amount_for_sale() ) { remove( order ); @@ -836,7 +888,7 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p } else { - modify( order, [&]( limit_order_object& b ) { + modify( order, [&pays]( limit_order_object& b ) { b.for_sale -= pays.amount; b.deferred_fee = 0; b.deferred_paid_fee.amount = 0; @@ -948,7 +1000,7 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a if( head_block_time() >= HARDFORK_CORE_1780_TIME ) settle_owner_ptr = &settle.owner(*this); // Compute and pay the market fees: - asset market_fees = pay_market_fees( settle_owner_ptr, get(receives.asset_id), receives ); + asset market_fees = pay_market_fees( settle_owner_ptr, get(receives.asset_id), receives, is_maker ); // Issuer of the settled smartcoin asset lays claim to a force-settlement fee (BSIP87), but // note that fee is denominated in collateral asset, not the debt asset. Asset object of @@ -1212,16 +1264,31 @@ void database::pay_order( const account_object& receiver, const asset& receives, adjust_balance(receiver.get_id(), receives); } -asset database::calculate_market_fee( const asset_object& trade_asset, const asset& trade_amount ) +asset database::calculate_market_fee( const asset_object& trade_asset, const asset& trade_amount, const bool& is_maker) { assert( trade_asset.id == trade_amount.asset_id ); if( !trade_asset.charges_market_fees() ) return trade_asset.amount(0); - if( trade_asset.options.market_fee_percent == 0 ) + // Optimization: The fee is zero if the order is a maker, and the maker fee percent is 0% + if( is_maker && trade_asset.options.market_fee_percent == 0 ) + return trade_asset.amount(0); + + // Optimization: The fee is zero if the order is a taker, and the taker fee percent is 0% + const optional& taker_fee_percent = trade_asset.options.extensions.value.taker_fee_percent; + if(!is_maker && taker_fee_percent.valid() && *taker_fee_percent == 0) return trade_asset.amount(0); - auto value = detail::calculate_percent(trade_amount.amount, trade_asset.options.market_fee_percent); + uint16_t fee_percent; + if (is_maker) { + // Maker orders are charged the maker fee percent + fee_percent = trade_asset.options.market_fee_percent; + } else { + // Taker orders are charged the taker fee percent if they are valid. Otherwise, the maker fee percent. + fee_percent = taker_fee_percent.valid() ? *taker_fee_percent : trade_asset.options.market_fee_percent; + } + + auto value = detail::calculate_percent(trade_amount.amount, fee_percent); asset percent_fee = trade_asset.amount(value); if( percent_fee.amount > trade_asset.options.max_market_fee ) @@ -1230,9 +1297,10 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass return percent_fee; } -asset database::pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives ) +asset database::pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives, + const bool& is_maker) { - const auto market_fees = calculate_market_fee( recv_asset, receives ); + const auto market_fees = calculate_market_fee( recv_asset, receives, is_maker ); auto issuer_fees = market_fees; FC_ASSERT( issuer_fees <= receives, "Market fee shouldn't be greater than receives"); //Don't dirty undo state if not actually collecting any fees diff --git a/libraries/chain/hardfork.d/BSIP_81.hf b/libraries/chain/hardfork.d/BSIP_81.hf new file mode 100644 index 0000000000..5f4851bdc9 --- /dev/null +++ b/libraries/chain/hardfork.d/BSIP_81.hf @@ -0,0 +1,5 @@ +// BSIP 81 (Simple Maker-Taker Market Fees) hardfork check +#ifndef HARDFORK_BSIP_81_TIME +// Jan 1 2030, midnight this is a dummy date until a hardfork date is scheduled +#define HARDFORK_BSIP_81_TIME (fc::time_point_sec( 1893456000 )) +#endif diff --git a/libraries/chain/hardfork.d/BSIP_85.hf b/libraries/chain/hardfork.d/BSIP_85.hf new file mode 100644 index 0000000000..7fb612a385 --- /dev/null +++ b/libraries/chain/hardfork.d/BSIP_85.hf @@ -0,0 +1,6 @@ +// BSIP 85 (Maker order creation fee discount) hardfork check +#ifndef HARDFORK_BSIP_85_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_BSIP_85_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_BSIP_85_PASSED(now) (now >= HARDFORK_BSIP_85_TIME) +#endif diff --git a/libraries/chain/hardfork.d/CORE_BSIP64.hf b/libraries/chain/hardfork.d/CORE_BSIP64.hf new file mode 100644 index 0000000000..a035996482 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_BSIP64.hf @@ -0,0 +1,4 @@ +// bitshares BSIP 64 HTLC modifications +#ifndef HARDFORK_CORE_BSIP64_TIME +#define HARDFORK_CORE_BSIP64_TIME (fc::time_point_sec( 1600000000 ) ) // Sep 2020 +#endif diff --git a/libraries/chain/hardfork.d/CORE_BSIP_87_74_COLLATFEE.hf b/libraries/chain/hardfork.d/CORE_BSIP_87_74_COLLATFEE.hf new file mode 100644 index 0000000000..3f1add317a --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_BSIP_87_74_COLLATFEE.hf @@ -0,0 +1,7 @@ +// This hardfork enables the extension to asset_claim_fees_operation to claim collateral-denominated fees. +// These fees are collected by BSIPs 87 and 74. This should be set to match the earlier of either +// HARDFORK_CORE_BSIP87_TIME or HARDFORK_CORE_BSIP74_TIME. +// This hardfork check should be removable after the hardfork date passes. +#ifndef HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME +#define HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME (fc::time_point_sec( 1679955066 ) ) // Temporary date until actual hardfork date is set +#endif diff --git a/libraries/chain/htlc_evaluator.cpp b/libraries/chain/htlc_evaluator.cpp index 008980b9b3..339c2fc4bb 100644 --- a/libraries/chain/htlc_evaluator.cpp +++ b/libraries/chain/htlc_evaluator.cpp @@ -29,6 +29,42 @@ namespace graphene { namespace chain { + namespace detail + { + void check_htlc_create_hf_bsip64(const fc::time_point_sec& block_time, + const htlc_create_operation& op, const asset_object& asset_to_transfer) + { + if (block_time < HARDFORK_CORE_BSIP64_TIME) + { + // memo field added at harfork BSIP64 + // NOTE: both of these checks can be removed after hardfork time + FC_ASSERT( !op.extensions.value.memo.valid(), + "Memo unavailable until after HARDFORK BSIP64"); + // HASH160 added at hardfork BSIP64 + FC_ASSERT( !op.preimage_hash.is_type(), + "HASH160 unavailable until after HARDFORK BSIP64" ); + } + else + { + // this can be moved to the normal non-hf checks after HF_BSIP64 + // IF there were no restricted transfers before HF_BSIP64 + FC_ASSERT( !asset_to_transfer.is_transfer_restricted() + || op.from == asset_to_transfer.issuer || op.to == asset_to_transfer.issuer, + "Asset ${asset} cannot be transfered.", ("asset", asset_to_transfer.id) ); + } + } + + void check_htlc_redeem_hf_bsip64(const fc::time_point_sec& block_time, + const htlc_redeem_operation& op, const htlc_object* htlc_obj) + { + // TODO: The hardfork portion of this check can be removed if no HTLC redemptions are + // attempted on an HTLC with a 0 preimage size before the hardfork date. + if ( htlc_obj->conditions.hash_lock.preimage_size > 0U || + block_time < HARDFORK_CORE_BSIP64_TIME ) + FC_ASSERT(op.preimage.size() == htlc_obj->conditions.hash_lock.preimage_size, + "Preimage size mismatch."); + } + } // end of graphene::chain::details optional get_committee_htlc_options(graphene::chain::database& db) { @@ -43,14 +79,17 @@ namespace graphene { FC_ASSERT(htlc_options, "HTLC Committee options are not set."); // make sure the expiration is reasonable - FC_ASSERT( o.claim_period_seconds <= htlc_options->max_timeout_secs, "HTLC Timeout exceeds allowed length" ); + FC_ASSERT( o.claim_period_seconds <= htlc_options->max_timeout_secs, + "HTLC Timeout exceeds allowed length" ); // make sure the preimage length is reasonable - FC_ASSERT( o.preimage_size <= htlc_options->max_preimage_size, "HTLC preimage length exceeds allowed length" ); + FC_ASSERT( o.preimage_size <= htlc_options->max_preimage_size, + "HTLC preimage length exceeds allowed length" ); // make sure the sender has the funds for the HTLC FC_ASSERT( d.get_balance( o.from, o.amount.asset_id) >= (o.amount), "Insufficient funds") ; const auto& asset_to_transfer = o.amount.asset_id( d ); const auto& from_account = o.from( d ); const auto& to_account = o.to( d ); + detail::check_htlc_create_hf_bsip64(d.head_block_time(), o, asset_to_transfer); FC_ASSERT( is_authorized_asset( d, from_account, asset_to_transfer ), "Asset ${asset} is not authorized for account ${acct}.", ( "asset", asset_to_transfer.id )( "acct", from_account.id ) ); @@ -73,6 +112,8 @@ namespace graphene { esc.transfer.asset_id = o.amount.asset_id; esc.conditions.hash_lock.preimage_hash = o.preimage_hash; esc.conditions.hash_lock.preimage_size = o.preimage_size; + if ( o.extensions.value.memo.valid() ) + esc.memo = o.extensions.value.memo; esc.conditions.time_lock.expiration = dbase.head_block_time() + o.claim_period_seconds; }); return esc.id; @@ -99,9 +140,10 @@ namespace graphene { void_result htlc_redeem_evaluator::do_evaluate(const htlc_redeem_operation& o) { - htlc_obj = &db().get(o.htlc_id); + auto& d = db(); + htlc_obj = &d.get(o.htlc_id); + detail::check_htlc_redeem_hf_bsip64(d.head_block_time(), o, htlc_obj); - FC_ASSERT(o.preimage.size() == htlc_obj->conditions.hash_lock.preimage_size, "Preimage size mismatch."); const htlc_redeem_visitor vtor( o.preimage ); FC_ASSERT( htlc_obj->conditions.hash_lock.preimage_hash.visit( vtor ), "Provided preimage does not generate correct hash."); @@ -115,7 +157,8 @@ namespace graphene { db().adjust_balance(htlc_obj->transfer.to, amount); // notify related parties htlc_redeemed_operation virt_op( htlc_obj->id, htlc_obj->transfer.from, htlc_obj->transfer.to, o.redeemer, - amount, htlc_obj->conditions.hash_lock.preimage_hash, htlc_obj->conditions.hash_lock.preimage_size ); + amount, htlc_obj->conditions.hash_lock.preimage_hash, htlc_obj->conditions.hash_lock.preimage_size, + o.preimage ); db().push_applied_operation( virt_op ); db().remove(*htlc_obj); return void_result(); diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 544b4b8b21..068f2cf93e 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -166,6 +166,9 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_claim_fees_operation& o ); void_result do_apply( const asset_claim_fees_operation& o ); + + const asset_object* container_asset = nullptr; + const asset_dynamic_data_object* container_ddo = nullptr; }; class asset_claim_pool_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 3541f87f5b..4e6fccee83 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -65,6 +65,7 @@ namespace graphene { namespace chain { share_type current_supply; share_type confidential_supply; ///< total asset held in confidential balances share_type accumulated_fees; ///< fees accumulate to be paid out over time + share_type accumulated_collateral_fees; ///< accumulated collateral-denominated fees (for bitassets) share_type fee_pool; ///< in core asset }; @@ -164,6 +165,46 @@ namespace graphene { namespace chain { template share_type reserved( const DB& db )const { return options.max_supply - dynamic_data(db).current_supply; } + + /// @return true if asset can accumulate fees in the given denomination + template + bool can_accumulate_fee(const DB& db, const asset& fee) const { + return (( fee.asset_id == get_id() ) || + ( is_market_issued() && fee.asset_id == bitasset_data(db).options.short_backing_asset )); + } + + /*** + * @brief receive a fee asset to accrue in dynamic_data object + * + * Asset owners define various fees (market fees, force-settle fees, etc.) to be + * collected for the asset owners. These fees are typically denominated in the asset + * itself, but for bitassets some of the fees are denominated in the collateral + * asset. This will place the fee in the right container. + */ + template + void accumulate_fee(DB& db, const asset& fee) const + { + if (fee.amount == 0) return; + FC_ASSERT( fee.amount >= 0, "Fee amount must be non-negative." ); + const auto& dyn_data = dynamic_asset_data_id(db); + if (fee.asset_id == get_id()) { // fee same as asset + db.modify( dyn_data, [&fee]( asset_dynamic_data_object& obj ){ + obj.accumulated_fees += fee.amount; + }); + } else { // fee different asset; perhaps collateral-denominated fee + FC_ASSERT( is_market_issued(), + "Asset ${a} (${id}) cannot accept fee of asset (${fid}).", + ("a",this->symbol)("id",this->id)("fid",fee.asset_id) ); + const auto & bad = bitasset_data(db); + FC_ASSERT( fee.asset_id == bad.options.short_backing_asset, + "Asset ${a} (${id}) cannot accept fee of asset (${fid}).", + ("a",this->symbol)("id",this->id)("fid",fee.asset_id) ); + db.modify( dyn_data, [&fee]( asset_dynamic_data_object& obj ){ + obj.accumulated_collateral_fees += fee.amount; + }); + } + } + }; /** diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 81d0bd9287..b11cb2e983 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -439,8 +439,9 @@ namespace graphene { namespace chain { // helpers to fill_order void pay_order( const account_object& receiver, const asset& receives, const asset& pays ); - asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount); - asset pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives ); + asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount, const bool& is_maker); + asset pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives, + const bool& is_maker); asset pay_force_settle_fees(const asset_object& collecting_asset, const asset& collat_receives); ///@} diff --git a/libraries/chain/include/graphene/chain/htlc_object.hpp b/libraries/chain/include/graphene/chain/htlc_object.hpp index 6679c6b636..a9f8cf5f43 100644 --- a/libraries/chain/include/graphene/chain/htlc_object.hpp +++ b/libraries/chain/include/graphene/chain/htlc_object.hpp @@ -59,6 +59,8 @@ namespace graphene { namespace chain { } time_lock; } conditions; + fc::optional memo; + /**** * Index helper for timelock */ diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 60a37e5bd3..ca27ff5f9b 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -42,8 +42,6 @@ namespace graphene { namespace chain { void_result do_evaluate( const limit_order_create_operation& o ); object_id_type do_apply( const limit_order_create_operation& o ); - asset calculate_market_fee( const asset_object* aobj, const asset& trade_amount ); - /** override the default behavior defined by generic_evalautor */ virtual void convert_fee() override; diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 0861f0eccc..365e2c281c 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -70,6 +70,7 @@ class limit_order_object : public abstract_object struct by_price; struct by_expiration; struct by_account; +struct by_account_price; typedef multi_index_container< limit_order_object, indexed_by< @@ -87,7 +88,15 @@ typedef multi_index_container< >, composite_key_compare< std::greater, std::less > >, + // index used by APIs ordered_unique< tag, + composite_key< limit_order_object, + member, + member + > + >, + // index used by APIs + ordered_unique< tag, composite_key< limit_order_object, member, member, diff --git a/libraries/chain/include/graphene/chain/worker_object.hpp b/libraries/chain/include/graphene/chain/worker_object.hpp index da5c170ca1..34528a79ef 100644 --- a/libraries/chain/include/graphene/chain/worker_object.hpp +++ b/libraries/chain/include/graphene/chain/worker_object.hpp @@ -145,13 +145,15 @@ class worker_object : public abstract_object struct by_account; struct by_vote_for; struct by_vote_against; +struct by_end_date; typedef multi_index_container< worker_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, ordered_non_unique< tag, member< worker_object, account_id_type, &worker_object::worker_account > >, ordered_unique< tag, member< worker_object, vote_id_type, &worker_object::vote_for > >, - ordered_unique< tag, member< worker_object, vote_id_type, &worker_object::vote_against > > + ordered_unique< tag, member< worker_object, vote_id_type, &worker_object::vote_against > >, + ordered_non_unique< tag, member< worker_object, time_point_sec, &worker_object::work_end_date> > > > worker_object_multi_index_type; diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 5a4a2b69bc..d5a244787b 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -29,7 +29,9 @@ namespace graphene { namespace chain { namespace detail { - void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options); + void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options); + void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options); + void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op); } struct proposal_operation_hardfork_visitor @@ -45,18 +47,29 @@ struct proposal_operation_hardfork_visitor template void operator()(const T &v) const {} - // hf_1774 void operator()(const graphene::chain::asset_create_operation &v) const { + // hf_1774 detail::check_asset_options_hf_1774(block_time, v.common_options); + + // HARDFORK_BSIP_81 + detail::check_asset_options_hf_bsip81(block_time, v.common_options); } - // hf_1774 void operator()(const graphene::chain::asset_update_operation &v) const { + // hf_1774 detail::check_asset_options_hf_1774(block_time, v.new_options); + + // HARDFORK_BSIP_81 + detail::check_asset_options_hf_bsip81(block_time, v.new_options); + } + + void operator()(const graphene::chain::asset_claim_fees_operation &v) const { + detail::check_asset_claim_fees_hardfork_87_74_collatfee(block_time, v); // HF_REMOVABLE } void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const { if (block_time < HARDFORK_CORE_1468_TIME) { - FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), "Unable to set HTLC options before hardfork 1468"); + FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), + "Unable to set HTLC options before hardfork 1468"); FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); @@ -71,6 +84,10 @@ struct proposal_operation_hardfork_visitor FC_ASSERT(!op.new_parameters.current_fees->exists(), "Unable to define fees for custom authority operations prior to hardfork BSIP 40"); } + if (!HARDFORK_BSIP_85_PASSED(block_time)) { + FC_ASSERT(!op.new_parameters.extensions.value.maker_fee_discount_percent.valid(), + "Unable to set maker_fee_discount_percent before hardfork BSIP 85"); + } if (!HARDFORK_BSIP_86_PASSED(block_time)) { FC_ASSERT(!op.new_parameters.extensions.value.market_fee_network_percent.valid(), "Unable to set market_fee_network_percent before hardfork BSIP 86"); @@ -78,6 +95,16 @@ struct proposal_operation_hardfork_visitor } void operator()(const graphene::chain::htlc_create_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); + if (block_time < HARDFORK_CORE_BSIP64_TIME) + { + // memo field added at harfork BSIP64 + // NOTE: both of these checks can be removed after hardfork time + FC_ASSERT( !op.extensions.value.memo.valid(), + "Memo unavailable until after HARDFORK BSIP64"); + // HASH160 added at hardfork BSIP64 + FC_ASSERT( !op.preimage_hash.is_type(), + "HASH160 unavailable until after HARDFORK BSIP64" ); + } } void operator()(const graphene::chain::htlc_redeem_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); @@ -94,6 +121,7 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::custom_authority_delete_operation&) const { FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" ); } + // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { bool already_contains_proposal_update = false; @@ -104,7 +132,8 @@ struct proposal_operation_hardfork_visitor // Do not allow more than 1 proposal_update in a proposal if ( op.op.is_type() ) { - FC_ASSERT( !already_contains_proposal_update, "At most one proposal update can be nested in a proposal!" ); + FC_ASSERT( !already_contains_proposal_update, + "At most one proposal update can be nested in a proposal!" ); already_contains_proposal_update = true; } } @@ -166,8 +195,9 @@ void_result proposal_create_evaluator::do_evaluate( const proposal_create_operat FC_ASSERT( o.expiration_time > block_time, "Proposal has already expired on creation." ); FC_ASSERT( o.expiration_time <= block_time + global_parameters.maximum_proposal_lifetime, "Proposal expiration time is too far in the future." ); - FC_ASSERT( !o.review_period_seconds || fc::seconds( *o.review_period_seconds ) < ( o.expiration_time - block_time ), - "Proposal review period must be less than its overall lifetime." ); + FC_ASSERT( !o.review_period_seconds || + fc::seconds( *o.review_period_seconds ) < ( o.expiration_time - block_time ), + "Proposal review period must be less than its overall lifetime." ); // Find all authorities required by the proposed operations flat_set tmp_required_active_auths; @@ -177,8 +207,8 @@ void_result proposal_create_evaluator::do_evaluate( const proposal_create_operat operation_get_required_authorities( op.op, tmp_required_active_auths, _required_owner_auths, other, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( block_time ) ); } - // All accounts which must provide both owner and active authority should be omitted from the active authority set; - // owner authority approval implies active authority approval. + // All accounts which must provide both owner and active authority should be omitted from the + // active authority set; owner authority approval implies active authority approval. std::set_difference( tmp_required_active_auths.begin(), tmp_required_active_auths.end(), _required_owner_auths.begin(), _required_owner_auths.end(), std::inserter( _required_active_auths, _required_active_auths.begin() ) ); @@ -273,9 +303,9 @@ void_result proposal_update_evaluator::do_apply(const proposal_update_operation& { try { database& d = db(); - // Potential optimization: if _executed_proposal is true, we can skip the modify step and make push_proposal skip - // signature checks. This isn't done now because I just wrote all the proposals code, and I'm not yet 100% sure the - // required approvals are sufficient to authorize the transaction. + // Potential optimization: if _executed_proposal is true, we can skip the modify step and make push_proposal + // skip signature checks. This isn't done now because I just wrote all the proposals code, and I'm not yet + // 100% sure the required approvals are sufficient to authorize the transaction. d.modify(*_proposal, [&o](proposal_object& p) { p.available_active_approvals.insert(o.active_approvals_to_add.begin(), o.active_approvals_to_add.end()); p.available_owner_approvals.insert(o.owner_approvals_to_add.begin(), o.owner_approvals_to_add.end()); diff --git a/libraries/chain/small_objects.cpp b/libraries/chain/small_objects.cpp index 0233edc54a..c308519e4e 100644 --- a/libraries/chain/small_objects.cpp +++ b/libraries/chain/small_objects.cpp @@ -128,7 +128,7 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object::condition_info::ti FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object::condition_info, BOOST_PP_SEQ_NIL, (hash_lock)(time_lock) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object, (graphene::db::object), - (transfer) (conditions) ) + (transfer) (conditions) (memo) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::operation_history_object, (graphene::chain::object), (op)(result)(block_num)(trx_in_block)(op_in_trx)(virtual_op) ) diff --git a/libraries/fc b/libraries/fc index 23f0c6ab8e..73a7f08f00 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 23f0c6ab8e9516c8d0bc62b64f3ebedfe2f698b6 +Subproject commit 73a7f08f00456b0984cd431dd8f55bd901282e15 diff --git a/libraries/net/include/graphene/net/node.hpp b/libraries/net/include/graphene/net/node.hpp index fe03ac0cb6..43aa94a6e9 100644 --- a/libraries/net/include/graphene/net/node.hpp +++ b/libraries/net/include/graphene/net/node.hpp @@ -211,11 +211,34 @@ namespace graphene { namespace net { */ void add_node( const fc::ip::endpoint& ep ); + /***** + * @brief add a list of nodes to seed the p2p network + * @param seeds a vector of url strings + */ + void add_seed_nodes( std::vector seeds ); + + /**** + * @brief add a node to seed the p2p network + * @param in the url as a string + */ + void add_seed_node( const std::string& in); + /** * Attempt to connect to the specified endpoint immediately. */ virtual void connect_to_endpoint( const fc::ip::endpoint& ep ); + /** + * @brief Helper to convert a string to a collection of endpoints + * + * This converts a string (i.e. "bitshares.eu:665535" to a collection of endpoints. + * NOTE: Throws an exception if not in correct format or was unable to resolve URL. + * + * @param in the incoming string + * @returns a vector of endpoints + */ + static std::vector resolve_string_to_ip_endpoints( const std::string& in ); + /** * Specifies the network interface and port upon which incoming * connections should be accepted. diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index ec2f6a1f80..5f5227ba98 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -70,6 +71,7 @@ #include #include #include +#include #include #include @@ -481,6 +483,43 @@ namespace graphene { namespace net { namespace detail { // _retrigger_connect_loop_promise->set_value(); } + void node_impl::update_seed_nodes_task() + { + VERIFY_CORRECT_THREAD(); + + try + { + dlog("Starting an iteration of update_seed_nodes loop."); + for( const std::string& endpoint_string : _seed_nodes ) + { + resolve_seed_node_and_add( endpoint_string ); + } + dlog("Done an iteration of update_seed_nodes loop."); + } + catch (const fc::canceled_exception&) + { + throw; + } + FC_CAPTURE_AND_LOG( (_seed_nodes) ) + + schedule_next_update_seed_nodes_task(); + } + + void node_impl::schedule_next_update_seed_nodes_task() + { + VERIFY_CORRECT_THREAD(); + + if( _node_is_shutting_down ) + return; + + if( _update_seed_nodes_loop_done.valid() && _update_seed_nodes_loop_done.canceled() ) + return; + + _update_seed_nodes_loop_done = fc::schedule( [this]() { update_seed_nodes_task(); }, + fc::time_point::now() + fc::hours(3), + "update_seed_nodes_loop" ); + } + bool node_impl::have_already_received_sync_item( const item_hash_t& item_hash ) { VERIFY_CORRECT_THREAD(); @@ -3760,6 +3799,20 @@ namespace graphene { namespace net { namespace detail { wlog( "Exception thrown while terminating Fetch updated peer lists loop, ignoring" ); } + try + { + _update_seed_nodes_loop_done.cancel_and_wait("node_impl::close()"); + dlog("Update seed nodes loop terminated"); + } + catch ( const fc::exception& e ) + { + wlog( "Exception thrown while terminating Update seed nodes loop, ignoring: ${e}", ("e", e) ); + } + catch (...) + { + wlog( "Exception thrown while terminating Update seed nodes loop, ignoring" ); + } + try { _bandwidth_monitor_loop_done.cancel_and_wait("node_impl::close()"); @@ -4164,6 +4217,7 @@ namespace graphene { namespace net { namespace detail { assert(!_accept_loop_complete.valid() && !_p2p_network_connect_loop_done.valid() && + !_update_seed_nodes_loop_done.valid() && !_fetch_sync_items_loop_done.valid() && !_fetch_item_loop_done.valid() && !_advertise_inventory_loop_done.valid() && @@ -4181,6 +4235,7 @@ namespace graphene { namespace net { namespace detail { _fetch_updated_peer_lists_loop_done = fc::async([=](){ fetch_updated_peer_lists_loop(); }, "fetch_updated_peer_lists_loop"); _bandwidth_monitor_loop_done = fc::async([=](){ bandwidth_monitor_loop(); }, "bandwidth_monitor_loop"); _dump_node_status_task_done = fc::async([=](){ dump_node_status_task(); }, "dump_node_status_task"); + schedule_next_update_seed_nodes_task(); } void node_impl::add_node(const fc::ip::endpoint& ep) @@ -4198,6 +4253,33 @@ namespace graphene { namespace net { namespace detail { trigger_p2p_network_connect_loop(); } + void node_impl::add_seed_node(const std::string& endpoint_string) + { + VERIFY_CORRECT_THREAD(); + _seed_nodes.insert( endpoint_string ); + resolve_seed_node_and_add( endpoint_string ); + } + + void node_impl::resolve_seed_node_and_add(const std::string& endpoint_string) + { + VERIFY_CORRECT_THREAD(); + std::vector endpoints; + ilog("Resolving seed node ${endpoint}", ("endpoint", endpoint_string)); + try + { + endpoints = graphene::net::node::resolve_string_to_ip_endpoints(endpoint_string); + } + catch(...) + { + wlog( "Unable to resolve endpoint during attempt to add seed node ${ep}", ("ep", endpoint_string) ); + } + for (const fc::ip::endpoint& endpoint : endpoints) + { + ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); + add_node(endpoint); + } + } + void node_impl::initiate_connect_to(const peer_connection_ptr& new_peer) { new_peer->get_socket().open(); @@ -5104,4 +5186,52 @@ namespace graphene { namespace net { namespace detail { } // end namespace detail + // TODO move this function to impl class + std::vector node::resolve_string_to_ip_endpoints(const std::string& in) + { + try + { + std::string::size_type colon_pos = in.find(':'); + if (colon_pos == std::string::npos) + FC_THROW("Missing required port number in endpoint string \"${endpoint_string}\"", + ("endpoint_string", in)); + std::string port_string = in.substr(colon_pos + 1); + try + { + uint16_t port = boost::lexical_cast(port_string); + + std::string hostname = in.substr(0, colon_pos); + std::vector endpoints = fc::resolve(hostname, port); + if (endpoints.empty()) + FC_THROW_EXCEPTION( fc::unknown_host_exception, + "The host name can not be resolved: ${hostname}", + ("hostname", hostname) ); + return endpoints; + } + catch (const boost::bad_lexical_cast&) + { + FC_THROW("Bad port: ${port}", ("port", port_string)); + } + } + FC_CAPTURE_AND_RETHROW((in)) + } + + void node::add_seed_nodes(std::vector seeds) + { + for(const std::string& endpoint_string : seeds ) + { + try { + add_seed_node(endpoint_string); + } catch( const fc::exception& e ) { + wlog( "caught exception ${e} while adding seed node ${endpoint}", + ("e", e.to_detail_string())("endpoint", endpoint_string) ); + } + } + } + + void node::add_seed_node(const std::string& in) + { + INVOKE_IN_IMPL(add_seed_node, in); + } + } } // end namespace graphene::net diff --git a/libraries/net/node_impl.hxx b/libraries/net/node_impl.hxx index 7d31d16eea..da12718b98 100644 --- a/libraries/net/node_impl.hxx +++ b/libraries/net/node_impl.hxx @@ -337,6 +337,14 @@ class node_impl : public peer_connection_delegate std::list > _handle_message_calls_in_progress; + /// used by the task that checks whether addresses of seed nodes have been updated + // @{ + boost::container::flat_set _seed_nodes; + fc::future _update_seed_nodes_loop_done; + void update_seed_nodes_task(); + void schedule_next_update_seed_nodes_task(); + // @} + node_impl(const std::string& user_agent); virtual ~node_impl(); @@ -482,6 +490,8 @@ class node_impl : public peer_connection_delegate void listen_to_p2p_network(); void connect_to_p2p_network(); void add_node( const fc::ip::endpoint& ep ); + void add_seed_node( const std::string& seed_string ); + void resolve_seed_node_and_add( const std::string& seed_string ); void initiate_connect_to(const peer_connection_ptr& peer); void connect_to_endpoint(const fc::ip::endpoint& ep); void listen_on_endpoint(const fc::ip::endpoint& ep , bool wait_if_not_available); diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 3dc47cb654..d02c62d454 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -216,7 +216,13 @@ void asset_options::validate()const { FC_ASSERT( max_supply > 0 ); FC_ASSERT( max_supply <= GRAPHENE_MAX_SHARE_SUPPLY ); + // The non-negative maker fee must be less than or equal to 100% FC_ASSERT( market_fee_percent <= GRAPHENE_100_PERCENT ); + + // The non-negative taker fee must be less than or equal to 100% + if( extensions.value.taker_fee_percent.valid() ) + FC_ASSERT( *extensions.value.taker_fee_percent <= GRAPHENE_100_PERCENT ); + FC_ASSERT( max_market_fee >= 0 && max_market_fee <= GRAPHENE_MAX_SHARE_SUPPLY ); // There must be no high bits in permissions whose meaning is not known. FC_ASSERT( !(issuer_permissions & ~ASSET_ISSUER_PERMISSION_MASK) ); @@ -247,6 +253,8 @@ void asset_options::validate()const void asset_claim_fees_operation::validate()const { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( amount_to_claim.amount > 0 ); + if( extensions.value.claim_from_asset_id.valid() ) + FC_ASSERT( *extensions.value.claim_from_asset_id != amount_to_claim.asset_id ); } void asset_claim_pool_operation::validate()const { @@ -267,6 +275,7 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_oper GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::additional_options_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_parameters_type ) diff --git a/libraries/protocol/chain_parameters.cpp b/libraries/protocol/chain_parameters.cpp index 00bf0a3e55..1169224f41 100644 --- a/libraries/protocol/chain_parameters.cpp +++ b/libraries/protocol/chain_parameters.cpp @@ -103,8 +103,13 @@ namespace graphene { namespace protocol { "Committee proposal review period must be less than the maximum proposal lifetime" ); if( extensions.value.market_fee_network_percent.valid() ) { - FC_ASSERT( *extensions.value.market_fee_network_percent <= GRAPHENE_100_PERCENT, - "The market_fee_network_percent parameter can not exceed 100%" ); + FC_ASSERT( *extensions.value.market_fee_network_percent <= 3000, // GRAPHENE_100_PERCENT is 10000 + "The market_fee_network_percent parameter can not exceed 30%" ); + } + if( extensions.value.maker_fee_discount_percent.valid() ) + { + FC_ASSERT( *extensions.value.maker_fee_discount_percent <= GRAPHENE_100_PERCENT, + "The maker_fee_discount_percent parameter can not exceed 100%" ); } } @@ -114,6 +119,12 @@ namespace graphene { namespace protocol { *extensions.value.market_fee_network_percent : 0; } + uint16_t chain_parameters::get_maker_fee_discount_percent() const + { + return extensions.value.maker_fee_discount_percent.valid() ? + *extensions.value.maker_fee_discount_percent : 0; + } + }} GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::chain_parameters ) diff --git a/libraries/protocol/fee_schedule_calc.cpp b/libraries/protocol/fee_schedule_calc.cpp index 8c0900d75f..6b21890d1d 100644 --- a/libraries/protocol/fee_schedule_calc.cpp +++ b/libraries/protocol/fee_schedule_calc.cpp @@ -53,6 +53,16 @@ namespace graphene { namespace protocol { } }; + template<> + uint64_t calc_fee_visitor::operator()(const htlc_create_operation& op)const + { + //TODO: refactor for performance (see https://github.com/bitshares/bitshares-core/issues/2150) + transfer_operation::fee_parameters_type t; + if (param.exists()) + t = param.get(); + return op.calculate_fee( param.get(), t.price_per_kbyte).value; + } + asset fee_schedule::calculate_fee( const operation& op )const { uint64_t required_fee = op.visit( calc_fee_visitor( *this, op ) ); diff --git a/libraries/protocol/htlc.cpp b/libraries/protocol/htlc.cpp index b7fe9ef4a0..5694ec5a26 100644 --- a/libraries/protocol/htlc.cpp +++ b/libraries/protocol/htlc.cpp @@ -34,13 +34,16 @@ namespace graphene { namespace protocol { FC_ASSERT( amount.amount > 0, "HTLC amount should be greater than zero" ); } - share_type htlc_create_operation::calculate_fee( const fee_parameters_type& fee_params )const + share_type htlc_create_operation::calculate_fee( const fee_parameters_type& fee_params, + uint32_t fee_per_kb )const { uint64_t days = ( claim_period_seconds + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY; // multiply with overflow check - uint64_t per_day_fee = fee_params.fee_per_day * days; - FC_ASSERT( days == 0 || per_day_fee / days == fee_params.fee_per_day, "Fee calculation overflow" ); - return fee_params.fee + per_day_fee; + share_type total_fee = fee_params.fee; + total_fee += share_type(fee_params.fee_per_day) * days; + if (extensions.value.memo.valid()) + total_fee += calculate_data_fee( fc::raw::pack_size(extensions.value.memo), fee_per_kb); + return total_fee; } void htlc_redeem_operation::validate()const { @@ -69,6 +72,7 @@ namespace graphene { namespace protocol { } } GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::additional_options_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation ) diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index d8c29cbc8d..34c6aea006 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -32,7 +32,9 @@ namespace graphene { namespace protocol { { fc::optional reward_percent; fc::optional> whitelist_market_fee_sharing; - // MERGE NOTE: BSIP-74 and/or BSIP-81 additions here + // After BSIP81 activation, taker_fee_percent is the taker fee + fc::optional taker_fee_percent; + // MERGE NOTE: BSIP-74 additions here fc::optional force_settle_fee_percent; // BSIP-87 }; typedef extension additional_asset_options_t; @@ -51,6 +53,8 @@ namespace graphene { namespace protocol { /// When this asset is traded on the markets, this percentage of the total traded will be exacted and paid /// to the issuer. This is a fixed point value, representing hundredths of a percent, i.e. a value of 100 /// in this field means a 1% fee is charged on market trades of this asset. + // BSIP81: Asset owners may specify different market fee rate for maker orders and taker orders + // After BSIP81 activation, market_fee_percent is the maker fee uint16_t market_fee_percent = 0; /// Market fees calculated as @ref market_fee_percent of the traded volume are capped to this value share_type max_market_fee = GRAPHENE_MAX_SHARE_SUPPLY; @@ -444,10 +448,21 @@ namespace graphene { namespace protocol { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct additional_options_type + { + /// Which asset to claim fees from. This is needed, e.g., to claim collateral- + /// denominated fees from a collateral-backed smart asset. If unset, assumed to be same + /// asset as amount_to_claim is denominated in, such as would be the case when claiming + /// market fees. If set, validation requires it to be a different asset_id than + /// amount_to_claim (else there would exist two ways to form the same request). + fc::optional claim_from_asset_id; + }; + asset fee; - account_id_type issuer; - asset amount_to_claim; /// amount_to_claim.asset_id->issuer must == issuer - extensions_type extensions; + account_id_type issuer; ///< must match issuer of asset from which we claim fees + asset amount_to_claim; + + extension extensions; account_id_type fee_payer()const { return issuer; } void validate()const; @@ -519,6 +534,8 @@ namespace graphene { namespace protocol { FC_REFLECT( graphene::protocol::asset_claim_fees_operation, (fee)(issuer)(amount_to_claim)(extensions) ) FC_REFLECT( graphene::protocol::asset_claim_fees_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::asset_claim_fees_operation::additional_options_type, (claim_from_asset_id) ) + FC_REFLECT( graphene::protocol::asset_claim_pool_operation, (fee)(issuer)(asset_id)(amount_to_claim)(extensions) ) FC_REFLECT( graphene::protocol::asset_claim_pool_operation::fee_parameters_type, (fee) ) @@ -546,7 +563,7 @@ FC_REFLECT( graphene::protocol::bitasset_options, (extensions) ) -FC_REFLECT( graphene::protocol::additional_asset_options, (reward_percent)(whitelist_market_fee_sharing)(force_settle_fee_percent) ) +FC_REFLECT( graphene::protocol::additional_asset_options, (reward_percent)(whitelist_market_fee_sharing)(taker_fee_percent)(force_settle_fee_percent) ) FC_REFLECT( graphene::protocol::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) FC_REFLECT( graphene::protocol::asset_global_settle_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::protocol::asset_settle_operation::fee_parameters_type, (fee) ) @@ -617,6 +634,7 @@ GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operat GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::additional_options_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_parameters_type ) diff --git a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp index 249c99ae6e..7bde1f6b07 100644 --- a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp +++ b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp @@ -84,6 +84,7 @@ namespace graphene { namespace protocol { optional< htlc_options > updatable_htlc_options; optional< custom_authority_options_type > custom_authority_options; optional< uint16_t > market_fee_network_percent; + optional< uint16_t > maker_fee_discount_percent; }; extension extensions; @@ -99,6 +100,9 @@ namespace graphene { namespace protocol { /// If @ref market_fee_network_percent is valid, return the value it contains, otherwise return 0 uint16_t get_market_fee_network_percent() const; + /// If @ref maker_fee_discount_percent is valid, return the value it contains, otherwise return 0 + uint16_t get_maker_fee_discount_percent() const; + private: static void safe_copy(chain_parameters& to, const chain_parameters& from); }; @@ -121,6 +125,7 @@ FC_REFLECT( graphene::protocol::chain_parameters::ext, (updatable_htlc_options) (custom_authority_options) (market_fee_network_percent) + (maker_fee_discount_percent) ) FC_REFLECT( graphene::protocol::chain_parameters, diff --git a/libraries/protocol/include/graphene/protocol/htlc.hpp b/libraries/protocol/include/graphene/protocol/htlc.hpp index 9c8058be16..606b2f383f 100644 --- a/libraries/protocol/include/graphene/protocol/htlc.hpp +++ b/libraries/protocol/include/graphene/protocol/htlc.hpp @@ -26,17 +26,20 @@ #include #include #include +#include #include // std::max namespace graphene { namespace protocol { typedef fc::ripemd160 htlc_algo_ripemd160; typedef fc::sha1 htlc_algo_sha1; typedef fc::sha256 htlc_algo_sha256; + typedef fc::hash160 htlc_algo_hash160; typedef fc::static_variant< htlc_algo_ripemd160, htlc_algo_sha1, - htlc_algo_sha256 + htlc_algo_sha256, + htlc_algo_hash160 > htlc_hash; struct htlc_create_operation : public base_operation @@ -45,6 +48,7 @@ namespace graphene { namespace protocol { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + // paid to network asset fee; // where the held monies are to come from @@ -59,8 +63,13 @@ namespace graphene { namespace protocol { uint16_t preimage_size; // The time the funds will be returned to the source if not claimed uint32_t claim_period_seconds; - // for future expansion - extensions_type extensions; + + // additional extensions + struct additional_options_type + { + fc::optional memo; + }; + extension extensions; /*** * @brief Does simple validation of this object @@ -75,7 +84,7 @@ namespace graphene { namespace protocol { /**** * @brief calculates the fee to be paid for this operation */ - share_type calculate_fee(const fee_parameters_type& fee_params)const; + share_type calculate_fee(const fee_parameters_type& fee_params, uint32_t fee_per_kb)const; }; struct htlc_redeem_operation : public base_operation @@ -121,9 +130,10 @@ namespace graphene { namespace protocol { htlc_redeemed_operation() {} htlc_redeemed_operation( htlc_id_type htlc_id, account_id_type from, account_id_type to, - account_id_type redeemer, asset amount, const htlc_hash& preimage_hash, uint16_t preimage_size ) : + account_id_type redeemer, asset amount, const htlc_hash& preimage_hash, uint16_t preimage_size, + const std::vector& preimage ) : htlc_id(htlc_id), from(from), to(to), redeemer(redeemer), amount(amount), - htlc_preimage_hash(preimage_hash), htlc_preimage_size(preimage_size) {} + htlc_preimage_hash(preimage_hash), htlc_preimage_size(preimage_size), preimage(preimage) {} account_id_type fee_payer()const { return to; } void validate()const { FC_ASSERT( !"virtual operation" ); } @@ -137,6 +147,7 @@ namespace graphene { namespace protocol { uint16_t htlc_preimage_size; asset fee; + std::vector preimage; }; struct htlc_extend_operation : public base_operation @@ -206,6 +217,7 @@ namespace graphene { namespace protocol { FC_REFLECT_TYPENAME( graphene::protocol::htlc_hash ) FC_REFLECT( graphene::protocol::htlc_create_operation::fee_parameters_type, (fee) (fee_per_day) ) +FC_REFLECT( graphene::protocol::htlc_create_operation::additional_options_type, (memo)) FC_REFLECT( graphene::protocol::htlc_redeem_operation::fee_parameters_type, (fee) (fee_per_kb) ) FC_REFLECT( graphene::protocol::htlc_redeemed_operation::fee_parameters_type, ) // VIRTUAL FC_REFLECT( graphene::protocol::htlc_extend_operation::fee_parameters_type, (fee) (fee_per_day)) @@ -215,12 +227,13 @@ FC_REFLECT( graphene::protocol::htlc_create_operation, (fee)(from)(to)(amount)(preimage_hash)(preimage_size)(claim_period_seconds)(extensions)) FC_REFLECT( graphene::protocol::htlc_redeem_operation, (fee)(htlc_id)(redeemer)(preimage)(extensions)) FC_REFLECT( graphene::protocol::htlc_redeemed_operation, - (fee)(htlc_id)(from)(to)(redeemer)(amount)(htlc_preimage_hash)(htlc_preimage_size)) + (fee)(htlc_id)(from)(to)(redeemer)(amount)(htlc_preimage_hash)(htlc_preimage_size)(preimage)) FC_REFLECT( graphene::protocol::htlc_extend_operation, (fee)(htlc_id)(update_issuer)(seconds_to_add)(extensions)) FC_REFLECT( graphene::protocol::htlc_refund_operation, (fee)(htlc_id)(to)(original_htlc_recipient)(htlc_amount)(htlc_preimage_hash)(htlc_preimage_size)) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::additional_options_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation ) diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 2134f3b772..567a1230bb 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 3f4d6bb10f..90cc6fd2c2 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -241,7 +241,7 @@ class wallet_api * @returns account_history_operation_detail */ account_history_operation_detail get_account_history_by_operations( string name, - vector operation_types, + flat_set operation_types, uint32_t start, int limit); /** Returns the block chain's rapidly-changing properties. @@ -1446,12 +1446,13 @@ class wallet_api * @param preimage_hash the hash of the preimage * @param preimage_size the size of the preimage in bytes * @param claim_period_seconds how long after creation until the lock expires + * @param memo the memo * @param broadcast true if you wish to broadcast the transaction * @return the signed transaction */ signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, - const uint32_t claim_period_seconds, bool broadcast = false ); + const uint32_t claim_period_seconds, const std::string& memo, bool broadcast = false ); /**** * Update a hashed time lock contract diff --git a/libraries/wallet/operation_printer.cpp b/libraries/wallet/operation_printer.cpp index 0d948e023c..d3f950c4e9 100644 --- a/libraries/wallet/operation_printer.cpp +++ b/libraries/wallet/operation_printer.cpp @@ -47,6 +47,10 @@ class htlc_hash_to_string_visitor { return "SHA256 " + hash.str(); } + result_type operator()( const fc::hash160& hash )const + { + return "HASH160 " + hash.str(); + } }; std::string operation_printer::fee(const graphene::protocol::asset& a)const { @@ -54,6 +58,64 @@ std::string operation_printer::fee(const graphene::protocol::asset& a)const { return ""; } +string operation_printer::print_memo( const fc::optional& memo )const +{ + std::string outstr; + if( memo ) + { + if( wallet.is_locked() ) + { + out << " -- Unlock wallet to see memo."; + } else { + try { + FC_ASSERT( wallet._keys.count(memo->to) || wallet._keys.count(memo->from), + "Memo is encrypted to a key ${to} or ${from} not in this wallet.", + ("to", memo->to)("from",memo->from) ); + if( wallet._keys.count(memo->to) ) { + auto my_key = wif_to_key(wallet._keys.at(memo->to)); + FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted."); + outstr = memo->get_message(*my_key, memo->from); + out << " -- Memo: " << outstr; + } else { + auto my_key = wif_to_key(wallet._keys.at(memo->from)); + FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted."); + outstr = memo->get_message(*my_key, memo->to); + out << " -- Memo: " << outstr; + } + } catch (const fc::exception& e) { + out << " -- could not decrypt memo"; + } + } + } + return outstr; +} + +void operation_printer::print_preimage(const std::vector& preimage)const +{ + if (preimage.size() == 0) + return; + out << " with preimage \""; + // cut it at 300 bytes max + auto flags = out.flags(); + out << std::hex << setw(2) << setfill('0'); + for (size_t i = 0; i < std::min(300, preimage.size()); i++) + out << +preimage[i]; + out.flags(flags); + if (preimage.size() > 300) + out << "...(truncated due to size)"; + out << "\""; +} + +string operation_printer::print_redeem(const graphene::protocol::htlc_id_type& id, + const std::string& redeemer, const std::vector& preimage, + const graphene::protocol::asset& op_fee)const +{ + out << redeemer << " redeemed HTLC with id " + << std::string( static_cast(id)); + print_preimage( preimage ); + return fee(op_fee); +} + std::string operation_printer::operator()(const transfer_from_blind_operation& op)const { auto a = wallet.get_asset( op.fee.asset_id ); @@ -75,37 +137,12 @@ std::string operation_printer::operator()(const transfer_to_blind_operation& op) << " fee: " << fa.amount_to_pretty_string( op.fee ); return ""; } + string operation_printer::operator()(const transfer_operation& op) const { out << "Transfer " << wallet.get_asset(op.amount.asset_id).amount_to_pretty_string(op.amount) << " from " << wallet.get_account(op.from).name << " to " << wallet.get_account(op.to).name; - std::string memo; - if( op.memo ) - { - if( wallet.is_locked() ) - { - out << " -- Unlock wallet to see memo."; - } else { - try { - FC_ASSERT( wallet._keys.count(op.memo->to) || wallet._keys.count(op.memo->from), - "Memo is encrypted to a key ${to} or ${from} not in this wallet.", - ("to", op.memo->to)("from",op.memo->from) ); - if( wallet._keys.count(op.memo->to) ) { - auto my_key = wif_to_key(wallet._keys.at(op.memo->to)); - FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted."); - memo = op.memo->get_message(*my_key, op.memo->from); - out << " -- Memo: " << memo; - } else { - auto my_key = wif_to_key(wallet._keys.at(op.memo->from)); - FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted."); - memo = op.memo->get_message(*my_key, op.memo->to); - out << " -- Memo: " << memo; - } - } catch (const fc::exception& e) { - out << " -- could not decrypt memo"; - } - } - } + std::string memo = print_memo( op.memo ); fee(op.fee); return memo; } @@ -135,15 +172,12 @@ std::string operation_printer::operator()(const asset_create_operation& op) cons std::string operation_printer::operator()(const htlc_redeem_operation& op) const { - out << "Redeem HTLC with database id " - << std::to_string(op.htlc_id.space_id) - << "." << std::to_string(op.htlc_id.type_id) - << "." << std::to_string((uint64_t)op.htlc_id.instance) - << " with preimage \""; - for (unsigned char c : op.preimage) - out << c; - out << "\""; - return fee(op.fee); + return print_redeem(op.htlc_id, wallet.get_account(op.redeemer).name, op.preimage, op.fee); +} + +std::string operation_printer::operator()(const htlc_redeemed_operation& op) const +{ + return print_redeem(op.htlc_id, wallet.get_account(op.redeemer).name, op.preimage, op.fee); } std::string operation_printer::operator()(const htlc_create_operation& op) const @@ -152,20 +186,19 @@ std::string operation_printer::operator()(const htlc_create_operation& op) const auto fee_asset = wallet.get_asset( op.fee.asset_id ); auto to = wallet.get_account( op.to ); + auto from = wallet.get_account( op.from ); operation_result_printer rprinter(wallet); std::string database_id = result.visit(rprinter); - out << "Create HTLC to " << to.name + out << "Create HTLC from " << from.name << " to " << to.name << " with id " << database_id - << " preimage hash: [" - << op.preimage_hash.visit( vtor ) - << "] (Fee: " << fee_asset.amount_to_pretty_string( op.fee ) << ")"; + << " preimage hash: [" << op.preimage_hash.visit( vtor ) << "] "; + print_memo( op.extensions.value.memo ); // determine if the block that the HTLC is in is before or after LIB int32_t pending_blocks = hist.block_num - wallet.get_dynamic_global_properties().last_irreversible_block_num; if (pending_blocks > 0) out << " (pending " << std::to_string(pending_blocks) << " blocks)"; - - return ""; + return fee(op.fee); } std::string operation_result_printer::operator()(const void_result& x) const diff --git a/libraries/wallet/operation_printer.hpp b/libraries/wallet/operation_printer.hpp index 9ff09586cf..428ca469d1 100644 --- a/libraries/wallet/operation_printer.hpp +++ b/libraries/wallet/operation_printer.hpp @@ -98,6 +98,13 @@ struct operation_printer std::string operator()(const graphene::protocol::asset_create_operation& op)const; std::string operator()(const graphene::protocol::htlc_create_operation& op)const; std::string operator()(const graphene::protocol::htlc_redeem_operation& op)const; + std::string operator()(const graphene::protocol::htlc_redeemed_operation& op)const; + protected: + std::string print_memo( const fc::optional& memo)const; + void print_preimage( const std::vector& preimage)const; + std::string print_redeem(const graphene::protocol::htlc_id_type& id, + const std::string& redeemer, const std::vector& preimage, + const graphene::protocol::asset& op_fee)const; }; }}} // namespace graphene::wallet::detail diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 3c73d7530f..8e1ca9f840 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -202,10 +202,10 @@ uint64_t wallet_api::get_asset_count()const signed_transaction wallet_api::htlc_create( string source, string destination, string amount, string asset_symbol, string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, - const uint32_t claim_period_seconds, bool broadcast) + const uint32_t claim_period_seconds, const std::string& memo, bool broadcast) { return my->htlc_create(source, destination, amount, asset_symbol, hash_algorithm, preimage_hash, preimage_size, - claim_period_seconds, broadcast); + claim_period_seconds, memo, broadcast); } fc::optional wallet_api::get_htlc(std::string htlc_id) const @@ -223,6 +223,8 @@ fc::optional wallet_api::get_htlc(std::string htlc_id) const const auto& asset = my->get_asset( obj.transfer.asset_id ); transfer["asset"] = asset.symbol; transfer["amount"] = graphene::app::uint128_amount_to_string( obj.transfer.amount.value, asset.precision ); + if (obj.memo.valid()) + transfer["memo"] = my->read_memo( *obj.memo ); class htlc_hash_to_variant_visitor { public: @@ -234,6 +236,8 @@ fc::optional wallet_api::get_htlc(std::string htlc_id) const { return convert("SHA1", obj.str()); } result_type operator()(const fc::sha256& obj)const { return convert("SHA256", obj.str()); } + result_type operator()(const fc::hash160& obj)const + { return convert("HASH160", obj.str()); } private: result_type convert(const std::string& type, const std::string& hash)const { @@ -369,7 +373,7 @@ vector wallet_api::get_relative_account_history( account_history_operation_detail wallet_api::get_account_history_by_operations( string name, - vector operation_types, + flat_set operation_types, uint32_t start, int limit) { diff --git a/libraries/wallet/wallet_api_impl.hpp b/libraries/wallet/wallet_api_impl.hpp index 465c071f0b..2ee1de89d8 100644 --- a/libraries/wallet/wallet_api_impl.hpp +++ b/libraries/wallet/wallet_api_impl.hpp @@ -220,8 +220,7 @@ class wallet_api_impl transaction preview_builder_transaction(transaction_handle_type handle); signed_transaction sign_builder_transaction(transaction_handle_type transaction_handle, bool broadcast = true); signed_transaction sign_builder_transaction2(transaction_handle_type transaction_handle, - const vector& signing_keys = vector(), - bool broadcast = true); + const vector& signing_keys = vector(), bool broadcast = true); pair broadcast_transaction(signed_transaction tx); @@ -300,9 +299,10 @@ class wallet_api_impl signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, - const uint32_t claim_period_seconds, bool broadcast = false ); + const uint32_t claim_period_seconds, const std::string& memo, bool broadcast = false ); - signed_transaction htlc_redeem( string htlc_id, string issuer, const std::vector& preimage, bool broadcast ); + signed_transaction htlc_redeem( string htlc_id, string issuer, const std::vector& preimage, + bool broadcast ); signed_transaction htlc_extend ( string htlc_id, string issuer, const uint32_t seconds_to_add, bool broadcast); diff --git a/libraries/wallet/wallet_transfer.cpp b/libraries/wallet/wallet_transfer.cpp index 7228cec5dc..57a8ae4960 100644 --- a/libraries/wallet/wallet_transfer.cpp +++ b/libraries/wallet/wallet_transfer.cpp @@ -43,6 +43,8 @@ namespace graphene { namespace wallet { namespace detail { return fc::sha256( hash ); if( name_upper == "SHA1" ) return fc::sha1( hash ); + if( name_upper == "HASH160" ) + return fc::hash160( hash ); FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unknown algorithm '${a}'", ("a",algorithm) ); } @@ -81,9 +83,9 @@ namespace graphene { namespace wallet { namespace detail { return sign_transaction(tx, broadcast); } FC_CAPTURE_AND_RETHROW( (from)(to)(amount)(asset_symbol)(memo)(broadcast) ) } - signed_transaction wallet_api_impl::htlc_create( string source, string destination, string amount, string asset_symbol, - string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, - const uint32_t claim_period_seconds, bool broadcast ) + signed_transaction wallet_api_impl::htlc_create( string source, string destination, string amount, + string asset_symbol, string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, + const uint32_t claim_period_seconds, const std::string& memo, bool broadcast ) { try { @@ -91,6 +93,8 @@ namespace graphene { namespace wallet { namespace detail { fc::optional asset_obj = get_asset(asset_symbol); FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol)); + const account_object& from_acct = get_account(source); + const account_object& to_acct = get_account(destination); htlc_create_operation create_op; create_op.from = get_account(source).id; create_op.to = get_account(destination).id; @@ -98,6 +102,15 @@ namespace graphene { namespace wallet { namespace detail { create_op.claim_period_seconds = claim_period_seconds; create_op.preimage_hash = do_hash( hash_algorithm, preimage_hash ); create_op.preimage_size = preimage_size; + if (!memo.empty()) + { + memo_data data; + data.from = from_acct.options.memo_key; + data.to = to_acct.options.memo_key; + data.set_message( + get_private_key(from_acct.options.memo_key), to_acct.options.memo_key, memo); + create_op.extensions.value.memo = data; + } signed_transaction tx; tx.operations.push_back(create_op); @@ -109,8 +122,8 @@ namespace graphene { namespace wallet { namespace detail { (preimage_hash)(preimage_size)(claim_period_seconds)(broadcast) ) } - signed_transaction wallet_api_impl::htlc_redeem( string htlc_id, string issuer, const std::vector& preimage, - bool broadcast ) + signed_transaction wallet_api_impl::htlc_redeem( string htlc_id, string issuer, + const std::vector& preimage, bool broadcast ) { try { @@ -134,7 +147,7 @@ namespace graphene { namespace wallet { namespace detail { } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(preimage)(broadcast) ) } - signed_transaction wallet_api_impl::htlc_extend ( string htlc_id, string issuer, const uint32_t seconds_to_add, + signed_transaction wallet_api_impl::htlc_extend ( string htlc_id, string issuer, const uint32_t seconds_to_add, bool broadcast) { try diff --git a/programs/build_helpers/member_enumerator.cpp b/programs/build_helpers/member_enumerator.cpp index 6a292298ee..830878e314 100644 --- a/programs/build_helpers/member_enumerator.cpp +++ b/programs/build_helpers/member_enumerator.cpp @@ -103,7 +103,7 @@ void class_processor::process_class( const static_variant< T... >* dummy ) static_variant dummy2; static_variant_visitor vtor( this ); - for( int w=0; w, false > { init = true; fc::static_variant var; - for( int i = 0; i < var.count(); ++i ) + for( size_t i = 0; i < var.count(); ++i ) { var.set_which(i); var.visit( register_type_visitor() ); @@ -372,7 +372,7 @@ int main( int argc, char** argv ) operation op; std::cout << "ChainTypes.operations=\n"; - for( int i = 0; i < op.count(); ++i ) + for( size_t i = 0; i < op.count(); ++i ) { op.set_which(i); op.visit( detail_ns::serialize_type_visitor(i) ); diff --git a/programs/size_checker/main.cpp b/programs/size_checker/main.cpp index a7c09308fc..e283e2d6e6 100644 --- a/programs/size_checker/main.cpp +++ b/programs/size_checker/main.cpp @@ -85,7 +85,7 @@ int main( int argc, char** argv ) idump( (witnesses) ); - for( int32_t i = 0; i < op.count(); ++i ) + for( size_t i = 0; i < op.count(); ++i ) { op.set_which(i); op.visit( size_check_type_visitor(i) ); diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index 58959f7c88..0615f8dff6 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -423,7 +423,7 @@ BOOST_FIXTURE_TEST_CASE( create_new_account, cli_fixture ) BOOST_CHECK(con.wallet_api_ptr->import_key("jmjatlanta", bki.wif_priv_key)); con.wallet_api_ptr->save_wallet_file(con.wallet_filename); - // attempt to give jmjatlanta some bitsahres + // attempt to give jmjatlanta some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to jmjatlanta"); signed_transaction transfer_tx = con.wallet_api_ptr->transfer( "nathan", "jmjatlanta", "10000", "1.3.0", "Here are some CORE token for your new account", true @@ -848,7 +848,7 @@ BOOST_FIXTURE_TEST_CASE( account_history_pagination, cli_fixture ) { INVOKE(create_new_account); - // attempt to give jmjatlanta some bitsahres + // attempt to give jmjatlanta some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to jmjatlanta"); for(int i = 1; i <= 199; i++) { @@ -945,7 +945,7 @@ BOOST_AUTO_TEST_CASE( cli_multisig_transaction ) create_multisig_acct_tx.operations.push_back(account_create_op); con.wallet_api_ptr->sign_transaction(create_multisig_acct_tx, true); - // attempt to give cifer.test some bitsahres + // attempt to give cifer.test some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to cifer.test"); signed_transaction transfer_tx1 = con.wallet_api_ptr->transfer("nathan", "cifer.test", "10000", "1.3.0", "Here are some BTS for your new account", true); @@ -997,7 +997,7 @@ BOOST_AUTO_TEST_CASE( cli_multisig_transaction ) auto balances = con.wallet_api_ptr->list_account_balances( "cifer.test" ); for (auto b : balances) { if (b.asset_id == asset_id_type()) { - BOOST_ASSERT(b == asset(900000000 - 3000000)); + BOOST_CHECK(b == asset(900000000 - 3000000)); } } @@ -1126,10 +1126,8 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) BOOST_CHECK(!bki.brain_priv_key.empty()); signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, "alice", "nathan", "nathan", true); - // save the private key for this new account in the wallet file - BOOST_CHECK(con.wallet_api_ptr->import_key("alice", bki.wif_priv_key)); con.wallet_api_ptr->save_wallet_file(con.wallet_filename); - // attempt to give alice some bitsahres + // attempt to give alice some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to alice"); signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "alice", "10000", "1.3.0", "Here are some CORE token for your new account", true); @@ -1141,17 +1139,15 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) BOOST_CHECK(!bki.brain_priv_key.empty()); signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, "bob", "nathan", "nathan", true); - // save the private key for this new account in the wallet file - BOOST_CHECK(con.wallet_api_ptr->import_key("bob", bki.wif_priv_key)); - con.wallet_api_ptr->save_wallet_file(con.wallet_filename); - // attempt to give bob some bitsahres + // this should cause resync which will import the keys of alice and bob + generate_block(app1); + // attempt to give bob some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to Bob"); signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "bob", "10000", "1.3.0", "Here are some CORE token for your new account", true); con.wallet_api_ptr->issue_asset("bob", "5", "BOBCOIN", "Here are your BOBCOINs", true); } - BOOST_TEST_MESSAGE("Alice has agreed to buy 3 BOBCOIN from Bob for 3 BTS. Alice creates an HTLC"); // create an HTLC std::string preimage_string = "My Secret"; @@ -1168,7 +1164,7 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) uint32_t timelock = fc::days(1).to_seconds(); graphene::chain::signed_transaction result_tx = con.wallet_api_ptr->htlc_create("alice", "bob", - "3", "1.3.0", "SHA256", hash_str, preimage_string.size(), timelock, true); + "3", "1.3.0", "SHA256", hash_str, preimage_string.size(), timelock, "", true); // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: std::string alice_htlc_id_as_string; @@ -1190,7 +1186,7 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) // Bob likes what he sees, so he creates an HTLC, using the info he retrieved from Alice's HTLC con.wallet_api_ptr->htlc_create("bob", "alice", - "3", "BOBCOIN", "SHA256", hash_str, preimage_string.size(), timelock, true); + "3", "BOBCOIN", "SHA256", hash_str, preimage_string.size(), timelock, "", true); // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: std::string bob_htlc_id_as_string; @@ -1572,7 +1568,7 @@ BOOST_FIXTURE_TEST_CASE(cli_use_authorized_transfer, cli_fixture) { BOOST_CHECK_EQUAL(charlie_balances.size(), 1); asset charlie_core_balance = charlie_balances.front(); asset expected_charlie_core_balance = transfer_amount; - BOOST_ASSERT(charlie_core_balance == expected_charlie_core_balance); + BOOST_CHECK(charlie_core_balance == expected_charlie_core_balance); // Check Bob's balances vector bob_balances = con.wallet_api_ptr->list_account_balances("bob"); @@ -1593,3 +1589,233 @@ BOOST_FIXTURE_TEST_CASE(cli_use_authorized_transfer, cli_fixture) { throw; } } + +BOOST_AUTO_TEST_CASE( cli_create_htlc_bsip64 ) +{ + using namespace graphene::chain; + using namespace graphene::app; + std::shared_ptr app1; + try { + fc::temp_directory app_dir( graphene::utilities::temp_directory_path() ); + + int server_port_number = 0; + app1 = start_application(app_dir, server_port_number); + // set committee parameters + app1->chain_database()->modify(app1->chain_database()->get_global_properties(), [](global_property_object& p) + { + graphene::chain::htlc_options params; + params.max_preimage_size = 1024; + params.max_timeout_secs = 60 * 60 * 24 * 28; + p.parameters.extensions.value.updatable_htlc_options = params; + }); + + // connect to the server + client_connection con(app1, app_dir, server_port_number); + + // get past hardforks + generate_blocks( app1, HARDFORK_CORE_BSIP64_TIME + 10); + + BOOST_TEST_MESSAGE("Setting wallet password"); + con.wallet_api_ptr->set_password("supersecret"); + con.wallet_api_ptr->unlock("supersecret"); + + // import Nathan account + BOOST_TEST_MESSAGE("Importing nathan key"); + std::vector nathan_keys{"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"}; + BOOST_CHECK_EQUAL(nathan_keys[0], "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"); + BOOST_CHECK(con.wallet_api_ptr->import_key("nathan", nathan_keys[0])); + + BOOST_TEST_MESSAGE("Importing nathan's balance"); + std::vector import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); + account_object nathan_acct_before_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // upgrade nathan + BOOST_TEST_MESSAGE("Upgrading Nathan to LTM"); + signed_transaction upgrade_tx = con.wallet_api_ptr->upgrade_account("nathan", true); + account_object nathan_acct_after_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // verify that the upgrade was successful + BOOST_CHECK_PREDICATE( std::not_equal_to(), + (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch()) + (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) ); + BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member()); + + // Create new asset called BOBCOIN + try + { + graphene::chain::asset_options asset_ops; + asset_ops.max_supply = 1000000; + asset_ops.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); + fc::optional bit_opts; + con.wallet_api_ptr->create_asset("nathan", "BOBCOIN", 5, asset_ops, bit_opts, true); + } + catch(exception& e) + { + BOOST_FAIL(e.what()); + } + catch(...) + { + BOOST_FAIL("Unknown exception creating BOBCOIN"); + } + + // create a new account for Alice + { + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, + "alice", "nathan", "nathan", true); + con.wallet_api_ptr->save_wallet_file(con.wallet_filename); + // attempt to give alice some bitshares + BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to alice"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "alice", "10000", "1.3.0", + "Here are some CORE token for your new account", true); + } + + // create a new account for Bob + { + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, + "bob", "nathan", "nathan", true); + // this should cause resync which will import the keys of alice and bob + generate_block(app1); + // attempt to give bob some bitshares + BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to Bob"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "bob", "10000", "1.3.0", + "Here are some CORE token for your new account", true); + con.wallet_api_ptr->issue_asset("bob", "5", "BOBCOIN", "Here are your BOBCOINs", true); + } + + BOOST_TEST_MESSAGE("Alice has agreed to buy 3 BOBCOIN from Bob for 3 BTS. Alice creates an HTLC"); + // create an HTLC + std::string preimage_string = "My Super Long Secret that is larger than 50 charaters. How do I look?\n"; + fc::hash160 preimage_md = fc::hash160::hash(preimage_string); + std::stringstream ss; + for(size_t i = 0; i < preimage_md.data_size(); i++) + { + char d = preimage_md.data()[i]; + unsigned char uc = static_cast(d); + ss << std::setfill('0') << std::setw(2) << std::hex << (int)uc; + } + std::string hash_str = ss.str(); + BOOST_TEST_MESSAGE("Secret is " + preimage_string + " and hash is " + hash_str); + uint32_t timelock = fc::days(1).to_seconds(); + graphene::chain::signed_transaction result_tx + = con.wallet_api_ptr->htlc_create("alice", "bob", + "3", "1.3.0", "HASH160", hash_str, preimage_string.size(), timelock, "Alice to Bob", true); + + // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: + std::string alice_htlc_id_as_string; + { + BOOST_TEST_MESSAGE("The system is generating a block"); + graphene::chain::signed_block result_block; + BOOST_CHECK(generate_block(app1, result_block)); + + // get the ID: + htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1] + .operation_results[0].get(); + alice_htlc_id_as_string = (std::string)(object_id_type)htlc_id; + BOOST_TEST_MESSAGE("Alice shares the HTLC ID with Bob. The HTLC ID is: " + alice_htlc_id_as_string); + } + + // Bob can now look over Alice's HTLC, to see if it is what was agreed to. + BOOST_TEST_MESSAGE("Bob retrieves the HTLC Object by ID to examine it."); + auto alice_htlc = con.wallet_api_ptr->get_htlc(alice_htlc_id_as_string); + BOOST_TEST_MESSAGE("The HTLC Object is: " + fc::json::to_pretty_string(alice_htlc)); + + // Bob likes what he sees, so he creates an HTLC, using the info he retrieved from Alice's HTLC + con.wallet_api_ptr->htlc_create("bob", "alice", + "3", "BOBCOIN", "HASH160", hash_str, preimage_string.size(), fc::hours(12).to_seconds(), + "Bob to Alice", true); + + // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: + std::string bob_htlc_id_as_string; + { + BOOST_TEST_MESSAGE("The system is generating a block"); + graphene::chain::signed_block result_block; + BOOST_CHECK(generate_block(app1, result_block)); + + // get the ID: + htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1] + .operation_results[0].get(); + bob_htlc_id_as_string = (std::string)(object_id_type)htlc_id; + BOOST_TEST_MESSAGE("Bob shares the HTLC ID with Alice. The HTLC ID is: " + bob_htlc_id_as_string); + } + + // Alice can now look over Bob's HTLC, to see if it is what was agreed to: + BOOST_TEST_MESSAGE("Alice retrieves the HTLC Object by ID to examine it."); + auto bob_htlc = con.wallet_api_ptr->get_htlc(bob_htlc_id_as_string); + BOOST_TEST_MESSAGE("The HTLC Object is: " + fc::json::to_pretty_string(bob_htlc)); + + // Alice likes what she sees, so uses her preimage to get her BOBCOIN + { + BOOST_TEST_MESSAGE("Alice uses her preimage to retrieve the BOBCOIN"); + con.wallet_api_ptr->htlc_redeem(bob_htlc_id_as_string, "alice", preimage_string, true); + BOOST_TEST_MESSAGE("The system is generating a block"); + BOOST_CHECK(generate_block(app1)); + } + + // Bob can look at Alice's history to see her preimage + { + BOOST_TEST_MESSAGE("Bob can look at the history of Alice to see the preimage"); + std::vector hist = con.wallet_api_ptr->get_account_history("alice", 1); + BOOST_CHECK( hist[0].description.find("with preimage \"4d792") != hist[0].description.npos); + } + + // Bob can also look at his own history to see Alice's preimage + { + BOOST_TEST_MESSAGE("Bob can look at his own history to see the preimage"); + std::vector hist = con.wallet_api_ptr->get_account_history("bob", 1); + BOOST_CHECK( hist[0].description.find("with preimage \"4d792") != hist[0].description.npos); + } + + // Bob can use the preimage to retrieve his BTS + { + BOOST_TEST_MESSAGE("Bob uses Alice's preimage to retrieve the BOBCOIN"); + con.wallet_api_ptr->htlc_redeem(alice_htlc_id_as_string, "bob", preimage_string, true); + BOOST_TEST_MESSAGE("The system is generating a block"); + BOOST_CHECK(generate_block(app1)); + } + + // test operation_printer + auto hist = con.wallet_api_ptr->get_account_history("alice", 10); + for(size_t i = 0; i < hist.size(); ++i) + { + auto obj = hist[i]; + std::stringstream ss; + ss << "Description: " << obj.description << "\n"; + auto str = ss.str(); + BOOST_TEST_MESSAGE( str ); + if (i == 3 || i == 4) + { + BOOST_CHECK( str.find("HASH160 620e4d5ba") != std::string::npos ); + } + } + con.wallet_api_ptr->lock(); + hist = con.wallet_api_ptr->get_account_history("alice", 10); + for(size_t i = 0; i < hist.size(); ++i) + { + auto obj = hist[i]; + std::stringstream ss; + ss << "Description: " << obj.description << "\n"; + auto str = ss.str(); + BOOST_TEST_MESSAGE( str ); + if (i == 3 || i == 4) + { + BOOST_CHECK( str.find("HASH160 620e4d5ba") != std::string::npos ); + } + } + con.wallet_api_ptr->unlock("supersecret"); + + // wait for everything to finish up + fc::usleep(fc::seconds(1)); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } + app1->shutdown(); + app1.reset(); + // Intentional delay after app1->shutdown + std::cout << "cli_create_htlc conclusion: Intentional delay" << std::endl; + fc::usleep(fc::seconds(1)); +} diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 64e4f0ba7a..9c1354b06c 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -175,6 +175,11 @@ database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) options.insert(std::make_pair("api-limit-get-limit-orders", boost::program_options::variable_value( (uint64_t)350, false))); } + if(current_test_name =="api_limit_get_limit_orders_by_account") + { + options.insert(std::make_pair("api-limit-get-limit-orders-by-account", boost::program_options::variable_value( + (uint64_t)150, false))); + } if(current_test_name =="api_limit_get_call_orders") { options.insert(std::make_pair("api-limit-get-call-orders", boost::program_options::variable_value( @@ -218,7 +223,7 @@ database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) if(current_test_name =="api_limit_lookup_vote_ids") { options.insert(std::make_pair("api-limit-lookup-vote-ids", boost::program_options::variable_value - ((uint64_t)3, false))); + ((uint64_t)2, false))); } if(current_test_name =="api_limit_get_account_limit_orders") { @@ -332,7 +337,8 @@ database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) } else if( current_test_name == "asset_in_collateral" || current_test_name == "htlc_database_api" - || current_suite_name == "database_api_tests" ) + || current_suite_name == "database_api_tests" + || current_suite_name == "api_limit_tests" ) { auto ahiplugin = app.register_plugin(); ahiplugin->plugin_set_app(&app); @@ -454,7 +460,7 @@ bool database_fixture::validation_current_test_name_for_setting_api_limit( const vector valid_testcase {"api_limit_get_account_history_operations","api_limit_get_account_history" ,"api_limit_get_grouped_limit_orders","api_limit_get_relative_account_history" ,"api_limit_get_account_history_by_operations","api_limit_get_asset_holders" - ,"api_limit_get_key_references","api_limit_get_limit_orders" + ,"api_limit_get_key_references","api_limit_get_limit_orders","api_limit_get_limit_orders_by_account" ,"api_limit_get_call_orders","api_limit_get_settle_orders" ,"api_limit_get_order_book","api_limit_lookup_accounts" ,"api_limit_lookup_witness_accounts","api_limit_lookup_committee_member_accounts" @@ -523,6 +529,7 @@ void database_fixture::verify_asset_supplies( const database& db ) { const auto& bad = asset_obj.bitasset_data(db); total_balances[bad.options.short_backing_asset] += bad.settlement_fund; + total_balances[bad.options.short_backing_asset] += dasset_obj.accumulated_collateral_fees; } total_balances[asset_obj.id] += dasset_obj.confidential_supply.value; } @@ -1457,6 +1464,12 @@ flat_map< uint64_t, graphene::chain::fee_parameters > database_fixture::get_htlc extend_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; ret_val[((operation)htlc_extend_operation()).which()] = extend_param; + // set the transfer kb fee to something other than default, to verify we're looking + // at the correct fee + transfer_operation::fee_parameters_type transfer_param; + transfer_param.price_per_kbyte *= 2; + ret_val[ ((operation)transfer_operation()).which() ] = transfer_param; + return ret_val; } diff --git a/tests/tests/api_limit_tests.cpp b/tests/tests/api_limit_tests.cpp new file mode 100644 index 0000000000..5c332322a8 --- /dev/null +++ b/tests/tests/api_limit_tests.cpp @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2018 contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(api_limit_tests, database_fixture) + +BOOST_AUTO_TEST_CASE( api_limit_get_key_references ){ + try{ + const int num_keys = 210; + const int num_keys1 = 2; + vector< private_key_type > numbered_private_keys; + vector< public_key_type > numbered_key_id; + numbered_private_keys.reserve( num_keys ); + + graphene::app::database_api db_api1( db, &( app.get_options() )); + BOOST_CHECK_THROW( db_api1.get_key_references(numbered_key_id), fc::exception ); + + graphene::app::application_options opt = app.get_options(); + opt.has_api_helper_indexes_plugin = true; + graphene::app::database_api db_api( db, &opt ); + + for( int i=0; i > final_result=db_api.get_key_references(numbered_key_id); + BOOST_REQUIRE_EQUAL( final_result.size(), 2u ); + numbered_private_keys.reserve( num_keys ); + for( int i=num_keys1; iapp.get_options())); + + const account_object& alice = create_account("alice"); + const account_object& bob = create_account("bob"); + const account_object& carl = create_account("carl"); + const account_object& dan = create_account("dan"); + const account_object& fred = create_account("fred"); + const account_object& henry = create_account("henry"); + const account_object& kevin = create_account("kevin"); + const account_object& laura = create_account("laura"); + const account_object& lucy = create_account("lucy"); + const account_object& martin = create_account("martin"); + const account_object& patty = create_account("patty"); + + vector accounts; + accounts.push_back(alice.name); + accounts.push_back(bob.name); + accounts.push_back(carl.name); + accounts.push_back(dan.name); + accounts.push_back(fred.name); + accounts.push_back(henry.name); + accounts.push_back(kevin.name); + accounts.push_back(laura.name); + accounts.push_back(lucy.name); + accounts.push_back(martin.name); + accounts.push_back(patty.name); + + GRAPHENE_CHECK_THROW(db_api.get_full_accounts(accounts, false), fc::exception); + + accounts.erase(accounts.begin()); + auto full_accounts = db_api.get_full_accounts(accounts, false); + BOOST_CHECK(full_accounts.size() == 10); + + // not an account + accounts.erase(accounts.begin()); + accounts.push_back("nosuchaccount"); + + // non existing accounts will be ignored in the results + full_accounts = db_api.get_full_accounts(accounts, false); + BOOST_CHECK(full_accounts.size() == 9); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( api_limit_get_limit_orders ){ + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + //account_id_type() do 3 ops + create_bitasset("USD", account_id_type()); + create_account("dan"); + create_account("bob"); + asset_id_type bit_jmj_id = create_bitasset("JMJBIT").id; + generate_block(); + fc::usleep(fc::milliseconds(100)); + GRAPHENE_CHECK_THROW(db_api.get_limit_orders(std::string(static_cast(asset_id_type())), + std::string(static_cast(bit_jmj_id)), 370), fc::exception); + vector limit_orders =db_api.get_limit_orders(std::string( + static_cast(asset_id_type())), + std::string(static_cast(bit_jmj_id)), 340); + BOOST_REQUIRE_EQUAL( limit_orders.size(), 0u); + + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( api_limit_get_limit_orders_by_account ){ + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + const auto& test = create_user_issued_asset("TESTASSET"); + create_sell_order( account_id_type(), asset(1,asset_id_type()), test.amount(1) ); + GRAPHENE_CHECK_THROW(db_api.get_limit_orders_by_account( + std::string(static_cast(account_id_type())), 160), fc::exception); + vector limit_orders =db_api.get_limit_orders_by_account( + std::string(static_cast(account_id_type())), 145); + BOOST_REQUIRE_EQUAL( limit_orders.size(), 1u); + + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( api_limit_get_call_orders ){ + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + //account_id_type() do 3 ops + auto nathan_private_key = generate_private_key("nathan"); + account_id_type nathan_id = create_account("nathan", nathan_private_key.get_public_key()).id; + transfer(account_id_type(), nathan_id, asset(100)); + asset_id_type bitusd_id = create_bitasset( + "USDBIT", nathan_id, 100, disable_force_settle).id; + generate_block(); + fc::usleep(fc::milliseconds(100)); + BOOST_CHECK( bitusd_id(db).is_market_issued() ); + GRAPHENE_CHECK_THROW(db_api.get_call_orders(std::string(static_cast(bitusd_id)), + 370), fc::exception); + vector< call_order_object> call_order =db_api.get_call_orders(std::string( + static_cast(bitusd_id)), 340); + BOOST_REQUIRE_EQUAL( call_order.size(), 0u); + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE( api_limit_get_settle_orders ){ + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + //account_id_type() do 3 ops + auto nathan_private_key = generate_private_key("nathan"); + account_id_type nathan_id = create_account("nathan", nathan_private_key.get_public_key()).id; + transfer(account_id_type(), nathan_id, asset(100)); + asset_id_type bitusd_id = create_bitasset( + "USDBIT", nathan_id, 100, disable_force_settle).id; + generate_block(); + fc::usleep(fc::milliseconds(100)); + GRAPHENE_CHECK_THROW(db_api.get_settle_orders( + std::string(static_cast(bitusd_id)), 370), fc::exception); + vector result =db_api.get_settle_orders( + std::string(static_cast(bitusd_id)), 340); + BOOST_REQUIRE_EQUAL( result.size(), 0u); + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE( api_limit_get_order_book ){ + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + auto nathan_private_key = generate_private_key("nathan"); + auto dan_private_key = generate_private_key("dan"); + account_id_type nathan_id = create_account("nathan", nathan_private_key.get_public_key()).id; + account_id_type dan_id = create_account("dan", dan_private_key.get_public_key()).id; + transfer(account_id_type(), nathan_id, asset(100)); + transfer(account_id_type(), dan_id, asset(100)); + asset_id_type bitusd_id = create_bitasset( + "USDBIT", nathan_id, 100, disable_force_settle).id; + asset_id_type bitdan_id = create_bitasset( + "DANBIT", dan_id, 100, disable_force_settle).id; + generate_block(); + fc::usleep(fc::milliseconds(100)); + GRAPHENE_CHECK_THROW(db_api.get_order_book(std::string(static_cast(bitusd_id)), + std::string(static_cast(bitdan_id)),89), fc::exception); + graphene::app::order_book result =db_api.get_order_book(std::string( + static_cast(bitusd_id)), std::string(static_cast(bitdan_id)),78); + BOOST_REQUIRE_EQUAL( result.bids.size(), 0u); + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( api_limit_lookup_accounts ) { + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + ACTOR(bob); + GRAPHENE_CHECK_THROW(db_api.lookup_accounts("bob",220), fc::exception); + map result =db_api.lookup_accounts("bob",190); + BOOST_REQUIRE_EQUAL( result.size(), 17u); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( api_limit_lookup_witness_accounts ) { + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + ACTORS((bob)) ; + GRAPHENE_CHECK_THROW(db_api.lookup_witness_accounts("bob",220), fc::exception); + map result =db_api.lookup_witness_accounts("bob",190); + BOOST_REQUIRE_EQUAL( result.size(), 10u); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE( api_limit_get_full_accounts2 ) { + + try { + graphene::app::database_api db_api(db, &(this->app.get_options())); + vector accounts; + for (int i=0; i<201; i++) { + std::string acct_name = "mytempacct" + std::to_string(i); + const account_object& account_name=create_account(acct_name); + accounts.push_back(account_name.name); + } + GRAPHENE_CHECK_THROW(db_api.get_full_accounts(accounts, false), fc::exception); + accounts.erase(accounts.begin()); + auto full_accounts = db_api.get_full_accounts(accounts, false); + BOOST_REQUIRE_EQUAL(full_accounts.size(), 200u); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE(api_limit_get_withdraw_permissions_by_recipient){ + try{ + graphene::app::database_api db_api( db, &app.get_options()); + ACTORS((bob)) ; + withdraw_permission_id_type withdraw_permission; + GRAPHENE_CHECK_THROW(db_api.get_withdraw_permissions_by_recipient( + "bob",withdraw_permission, 251), fc::exception); + vector result =db_api.get_withdraw_permissions_by_recipient( + "bob",withdraw_permission,250); + BOOST_REQUIRE_EQUAL( result.size(), 0u); + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE(api_limit_get_withdraw_permissions_by_giver){ + try{ + graphene::app::database_api db_api( db, &app.get_options()); + ACTORS((bob)) ; + withdraw_permission_id_type withdraw_permission; + GRAPHENE_CHECK_THROW(db_api.get_withdraw_permissions_by_giver( + "bob",withdraw_permission, 251), fc::exception); + vector result =db_api.get_withdraw_permissions_by_giver( + "bob",withdraw_permission,250); + BOOST_REQUIRE_EQUAL( result.size(), 0u); + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE(api_limit_get_trade_history_by_sequence){ + try{ + app.enable_plugin("market_history"); + graphene::app::application_options opt=app.get_options(); + opt.has_market_history_plugin = true; + graphene::app::database_api db_api( db, &opt); + const auto& bitusd = create_bitasset("USDBIT"); + asset_id_type asset_1, asset_2; + asset_1 = bitusd.id; + asset_2 = asset_id_type(); + GRAPHENE_CHECK_THROW(db_api.get_trade_history_by_sequence( + std::string( static_cast(asset_1)), + std::string( static_cast(asset_2)), + 0,fc::time_point_sec(), 251), fc::exception); + vector result =db_api.get_trade_history_by_sequence( + std::string( static_cast(asset_1)), + std::string( static_cast(asset_2)), + 0,fc::time_point_sec(),250); + BOOST_REQUIRE_EQUAL( result.size(), 0u); + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(api_limit_get_trade_history){ + try{ + app.enable_plugin("market_history"); + graphene::app::application_options opt=app.get_options(); + opt.has_market_history_plugin = true; + graphene::app::database_api db_api( db, &opt); + const auto& bitusd = create_bitasset("USDBIT"); + asset_id_type asset_1, asset_2; + asset_1 = bitusd.id; + asset_2 = asset_id_type(); + GRAPHENE_CHECK_THROW(db_api.get_trade_history( + std::string( static_cast(asset_1)), + std::string( static_cast(asset_2)), + fc::time_point_sec(),fc::time_point_sec(), + 251), fc::exception); + vector result =db_api.get_trade_history( + std::string( static_cast(asset_1)), + std::string( static_cast(asset_2)), + fc::time_point_sec(),fc::time_point_sec(),250); + BOOST_REQUIRE_EQUAL( result.size(), 0u); + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE(api_limit_get_top_markets){ + try{ + app.enable_plugin("market_history"); + graphene::app::application_options opt=app.get_options(); + opt.has_market_history_plugin = true; + graphene::app::database_api db_api( db, &opt); + const auto& bitusd = create_bitasset("USDBIT"); + asset_id_type asset_1, asset_2; + asset_1 = bitusd.id; + asset_2 = asset_id_type(); + GRAPHENE_CHECK_THROW(db_api.get_top_markets(251), fc::exception); + vector result =db_api.get_top_markets(250); + BOOST_REQUIRE_EQUAL( result.size(), 0u); + }catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE(api_limit_get_collateral_bids) { + try { + graphene::app::database_api db_api( db, &( app.get_options() )); + + int64_t init_balance = 10000; + ///account_id_type borrower, borrower2, feedproducer; + asset_id_type swan, back; + ACTORS((borrower) (borrower2) (feedproducer)) ; + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + swan = bitusd.id; + back = asset_id_type(); + update_feed_producers(swan(db), {feedproducer_id}); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + + generate_blocks( HARDFORK_CORE_216_TIME ); + generate_block(); + + price_feed feed; + feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default + feed.settlement_price = swan(db).amount(1) / back(db).amount(1); + publish_feed(swan(db), feedproducer_id(db), feed); + // start out with 2:1 collateral + borrow(borrower_id(db), swan(db).amount(10), back(db).amount(2*10)); + borrow(borrower2_id(db), swan(db).amount(10), back(db).amount(4*10)); + //feed 1: 2 + feed.settlement_price = swan(db).amount(1) / back(db).amount(2); + publish_feed(swan(db), feedproducer_id(db), feed); + + // this sell order is designed to trigger a black swan + + create_sell_order( borrower2_id(db), swan(db).amount(1), back(db).amount(3) ); + BOOST_CHECK( swan(db).bitasset_data(db).has_settlement() ); + //making 3 collateral bids + for (int i=0; i<3; i++) { + + std::string acct_name = "mytempacct" + std::to_string(i); + account_id_type account_id=create_account(acct_name).id; + transfer(committee_account, account_id, asset(init_balance)); + bid_collateral(account_id(db), back(db).amount(10), swan(db).amount(1)); + } + auto swan_symbol = swan(db).symbol; + + + //validating normal case; total_bids =3 ; result_bids=3 + vector result_bids = db_api.get_collateral_bids(swan_symbol, 250, 0); + BOOST_CHECK_EQUAL( 3u, result_bids.size() ); + + //verify skip /// inefficient code test + //skip < total_bids; skip=1; total_bids =3 ; result_bids=2 + result_bids = db_api.get_collateral_bids(swan_symbol, 250, 1); + BOOST_CHECK_EQUAL( 2u, result_bids.size() ); + //skip= total_bids; skip=3; total_bids =3 ; result_bids=0 + result_bids = db_api.get_collateral_bids(swan_symbol, 250, 3); + BOOST_CHECK_EQUAL( 0u, result_bids.size() ); + //skip> total_bids; skip=4; total_bids =3 ; result_bids=0 + result_bids = db_api.get_collateral_bids(swan_symbol, 250, 4); + BOOST_CHECK_EQUAL( 0u, result_bids.size() ); + + //verify limit // inefficient code test + //limit= api_limit + for (int i=3; i<255; i++) { + std::string acct_name = "mytempacct" + std::to_string(i); + account_id_type account_id=create_account(acct_name).id; + transfer(committee_account, account_id, asset(init_balance)); + bid_collateral(account_id(db), back(db).amount(10), swan(db).amount(1)); + } + result_bids=db_api.get_collateral_bids(swan_symbol, 250, 0); + BOOST_CHECK_EQUAL( 250u, result_bids.size() ); + //limit> api_limit throw error + GRAPHENE_CHECK_THROW(db_api.get_collateral_bids(swan_symbol, 253, 3), fc::exception); + + } + catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE(api_limit_get_account_limit_orders) { + try { + graphene::app::database_api db_api( db, &( app.get_options() )); + ACTORS((seller)); + const auto &bitcny = create_bitasset("CNY"); + const auto &core = asset_id_type()(db); + + int64_t init_balance(10000000); + transfer(committee_account, seller_id, asset(init_balance)); + + /// Create versatile orders + for (size_t i = 0; i < 250; ++i) { + BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250+i))); + } + + + std::vector results=db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY",250); + BOOST_REQUIRE_EQUAL( results.size(), 250u); + GRAPHENE_CHECK_THROW( db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY",251), fc::exception); + + } + catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_CASE( api_limit_lookup_vote_ids ) { + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + ACTORS( (connie)(whitney)(wolverine) ); + fund(connie); + upgrade_to_lifetime_member(connie); + fund(whitney); + upgrade_to_lifetime_member(whitney); + fund(wolverine); + upgrade_to_lifetime_member(wolverine); + const auto& committee = create_committee_member( connie ); + const auto& witness = create_witness( whitney ); + const auto& worker = create_worker( wolverine_id ); + std::vector votes; + votes.push_back( committee.vote_id ); + votes.push_back( witness.vote_id ); + const auto results = db_api.lookup_vote_ids( votes ); + BOOST_REQUIRE_EQUAL( results.size(), 2u); + votes.push_back( worker.vote_for ); + GRAPHENE_CHECK_THROW(db_api.lookup_vote_ids(votes), fc::exception); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( api_limit_lookup_committee_member_accounts ) { + try{ + graphene::app::database_api db_api( db, &( app.get_options() )); + ACTORS((bob)); + GRAPHENE_CHECK_THROW(db_api.lookup_committee_member_accounts("bob",220), fc::exception); + std::map result =db_api.lookup_committee_member_accounts("bob",190); + BOOST_REQUIRE_EQUAL( result.size(), 10u); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/bsip85_tests.cpp b/tests/tests/bsip85_tests.cpp new file mode 100644 index 0000000000..88eaab71e0 --- /dev/null +++ b/tests/tests/bsip85_tests.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2020 contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( bsip85_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( hardfork_time_test ) +{ try { + + { + // The maker fee discount percent is 0 by default + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 ); + + // Try to set new committee parameter before hardfork + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db.get_global_properties().parameters, db.head_block_time() ); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation cmuop; + cmuop.new_parameters.extensions.value.maker_fee_discount_percent = 1; + cop.proposed_ops.emplace_back( cmuop ); + trx.operations.push_back( cop ); + + // It should fail + GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + trx.clear(); + + // The percent should still be 0 + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 ); + } + + // Pass the hardfork + generate_blocks( HARDFORK_BSIP_85_TIME ); + set_expiration( db, trx ); + + { + // The maker fee discount percent is still 0 + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 ); + + // Try to set new committee parameter after hardfork + proposal_create_operation cop = proposal_create_operation::committee_proposal( + db.get_global_properties().parameters, db.head_block_time() ); + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; + committee_member_update_global_parameters_operation cmuop; + cmuop.new_parameters.extensions.value.maker_fee_discount_percent = 10001; // 100.01% + cop.proposed_ops.emplace_back(cmuop); + trx.operations.push_back(cop); + + // Should fail since the value is too big + GRAPHENE_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // The maker fee discount percent is still 0 + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 ); + + trx.operations.clear(); + cop.proposed_ops.clear(); + cmuop.new_parameters.extensions.value.maker_fee_discount_percent = 1123; // 11.23% + cop.proposed_ops.emplace_back(cmuop); + trx.operations.push_back(cop); + + // Should succeed + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + proposal_id_type prop_id = ptx.operation_results[0].get(); + + // The maker fee discount percent is still 0 + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 ); + + // Approve the proposal + proposal_update_operation uop; + uop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + uop.active_approvals_to_add = { get_account("init0").get_id(), get_account("init1").get_id(), + get_account("init2").get_id(), get_account("init3").get_id(), + get_account("init4").get_id(), get_account("init5").get_id(), + get_account("init6").get_id(), get_account("init7").get_id() }; + trx.operations.push_back(uop); + PUSH_TX(db, trx, ~0); + + // The maker fee discount percent is still 0 + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 0 ); + + generate_blocks( prop_id( db ).expiration_time + 5 ); + generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); + generate_block(); + + // The maker fee discount percent should have changed + BOOST_CHECK_EQUAL( db.get_global_properties().parameters.get_maker_fee_discount_percent(), 1123 ); + + } +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( bsip85_maker_fee_discount_test ) +{ + try + { + ACTORS((alice)(bob)(izzy)); + + int64_t alice_b0 = 1000000, bob_b0 = 1000000; + int64_t pool_0 = 1000000, accum_0 = 0; + + transfer( account_id_type(), alice_id, asset(alice_b0) ); + transfer( account_id_type(), bob_id, asset(bob_b0) ); + + asset_id_type core_id = asset_id_type(); + int64_t cer_core_amount = 1801; + int64_t cer_usd_amount = 31; + price tmp_cer( asset( cer_core_amount ), asset( cer_usd_amount, asset_id_type(1) ) ); + const auto& usd_obj = create_user_issued_asset( "IZZYUSD", izzy_id(db), charge_market_fee, tmp_cer ); + asset_id_type usd_id = usd_obj.id; + issue_uia( alice_id, asset( alice_b0, usd_id ) ); + issue_uia( bob_id, asset( bob_b0, usd_id ) ); + + fund_fee_pool( committee_account( db ), usd_obj, pool_0 ); + + // If pay fee in CORE + int64_t order_create_fee = 547; + int64_t order_maker_refund = 61; // 547 * 11.23% = 61.4281 + + // If pay fee in USD + int64_t usd_create_fee = order_create_fee * cer_usd_amount / cer_core_amount; + if( usd_create_fee * cer_core_amount != order_create_fee * cer_usd_amount ) usd_create_fee += 1; + int64_t usd_maker_refund = usd_create_fee * 1123 / 10000; + // amount paid by fee pool + int64_t core_create_fee = usd_create_fee * cer_core_amount / cer_usd_amount; + int64_t core_maker_refund = usd_maker_refund == 0 ? 0 : core_create_fee * 1123 / 10000; + + fee_parameters::flat_set_type new_fees; + limit_order_create_operation::fee_parameters_type create_fee_params; + create_fee_params.fee = order_create_fee; + new_fees.insert( create_fee_params ); + + // Pass BSIP 85 HF time + // Note: no test case for the behavior before the HF since it's covered by other test cases + INVOKE( hardfork_time_test ); + set_expiration( db, trx ); + + // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation + // so we have to do it every time we stop generating/popping blocks and start doing tx's + enable_fees(); + change_fees( new_fees ); + + { + // prepare params + time_point_sec max_exp = time_point_sec::maximum(); + price cer = usd_id( db ).options.core_exchange_rate; + const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db ); + + // balance data + int64_t alice_bc = alice_b0, bob_bc = bob_b0; // core balance + int64_t alice_bu = alice_b0, bob_bu = bob_b0; // usd balance + int64_t pool_b = pool_0, accum_b = accum_0; + + // Check order fill + BOOST_TEST_MESSAGE( "Creating ao1, then be filled by bo1" ); + // pays fee in core + const limit_order_object* ao1 = create_sell_order( alice_id, asset(1000), asset(200, usd_id) ); + const limit_order_id_type ao1id = ao1->id; + // pays fee in usd + const limit_order_object* bo1 = create_sell_order( bob_id, asset(200, usd_id), asset(1000), max_exp, cer ); + + BOOST_CHECK( db.find( ao1id ) == nullptr ); + BOOST_CHECK( bo1 == nullptr ); + + // data after order created + alice_bc -= 1000; // amount for sale + alice_bc -= order_create_fee; // fee + bob_bu -= 200; // amount for sale + bob_bu -= usd_create_fee; // fee + pool_b -= core_create_fee; // fee pool + accum_b += 0; + + // data after order filled + alice_bu += 200; // bob pays + alice_bc += order_maker_refund; // maker fee refund + bob_bc += 1000; // alice pays + accum_b += usd_create_fee; // bo1 paid fee, was taker, no refund + pool_b += 0; // no change + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + + // Check partial fill + BOOST_TEST_MESSAGE( "Creating ao2, then be partially filled by bo2" ); + // pays fee in usd + const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); + const limit_order_id_type ao2id = ao2->id; + // pays fee in core + const limit_order_object* bo2 = create_sell_order( bob_id, asset(100, usd_id), asset(500) ); + + BOOST_CHECK( db.find( ao2id ) != nullptr ); + BOOST_CHECK( bo2 == nullptr ); + + // data after order created + alice_bc -= 1000; // amount to sell + alice_bu -= usd_create_fee; // fee + pool_b -= core_create_fee; // fee pool + accum_b += 0; + bob_bc -= order_create_fee; // fee + bob_bu -= 100; // amount to sell + + // data after order filled + alice_bu += 100; // bob pays + alice_bu += usd_maker_refund; // maker fee refund + bob_bc += 500; + accum_b += usd_create_fee - usd_maker_refund; // ao2 paid fee deduct maker refund + pool_b += core_maker_refund; // ao2 maker refund + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/tests/tests/bsip86_tests.cpp b/tests/tests/bsip86_tests.cpp index bb90e6de37..739720e1ba 100644 --- a/tests/tests/bsip86_tests.cpp +++ b/tests/tests/bsip86_tests.cpp @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE( hardfork_time_test ) cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; committee_member_update_global_parameters_operation cmuop; - cmuop.new_parameters.extensions.value.market_fee_network_percent = 10001; // 100.01% + cmuop.new_parameters.extensions.value.market_fee_network_percent = 3001; // 30.01% cop.proposed_ops.emplace_back(cmuop); trx.operations.push_back(cop); diff --git a/tests/tests/database_api_tests.cpp b/tests/tests/database_api_tests.cpp index 98febcf2af..36c160c271 100644 --- a/tests/tests/database_api_tests.cpp +++ b/tests/tests/database_api_tests.cpp @@ -1090,6 +1090,122 @@ BOOST_AUTO_TEST_CASE( subscription_notification_test ) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( get_all_workers ) +{ try { + graphene::app::database_api db_api( db, &( app.get_options() )); + ACTORS( (connie)(whitney)(wolverine) ); + + fund(connie); + upgrade_to_lifetime_member(connie); + fund(whitney); + upgrade_to_lifetime_member(whitney); + fund(wolverine); + upgrade_to_lifetime_member(wolverine); + + vector results; + + const auto& worker1 = create_worker( connie_id, 1000, fc::days(10) ); + worker_id_type worker1_id = worker1.id; + + BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 1 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 0 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 1 ); + BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers(false).front().id == worker1_id ); + + generate_blocks( db.head_block_time() + fc::days(11) ); + set_expiration( db, trx ); + + BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 1 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 1 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 0 ); + BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id ); + + const auto& worker2 = create_worker( whitney_id, 1000, fc::days(50) ); + worker_id_type worker2_id = worker2.id; + + BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 2 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 1 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 1 ); + BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers().back().id == worker2_id ); + BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers(false).front().id == worker2_id ); + + const auto& worker3 = create_worker( wolverine_id, 1000, fc::days(100) ); + worker_id_type worker3_id = worker3.id; + + BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 3 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 1 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 2 ); + BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers().back().id == worker3_id ); + BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers(false).front().id == worker2_id ); + BOOST_CHECK( db_api.get_all_workers(false).back().id == worker3_id ); + + generate_blocks( db.head_block_time() + fc::days(55) ); + set_expiration( db, trx ); + + BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 3 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 2 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 1 ); + BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers().back().id == worker3_id ); + BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers(true).back().id == worker2_id ); + BOOST_CHECK( db_api.get_all_workers(false).front().id == worker3_id ); + + generate_blocks( db.head_block_time() + fc::days(55) ); + set_expiration( db, trx ); + + BOOST_REQUIRE_EQUAL( db_api.get_all_workers().size(), 3 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(true).size(), 3 ); + BOOST_REQUIRE_EQUAL( db_api.get_all_workers(false).size(), 0 ); + BOOST_CHECK( db_api.get_all_workers().front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers().back().id == worker3_id ); + BOOST_CHECK( db_api.get_all_workers(true).front().id == worker1_id ); + BOOST_CHECK( db_api.get_all_workers(true).back().id == worker3_id ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( get_workers_by_account ) +{ try { + graphene::app::database_api db_api( db, &( app.get_options() )); + ACTORS( (connie)(whitney)(wolverine) ); + + fund(connie); + upgrade_to_lifetime_member(connie); + fund(whitney); + upgrade_to_lifetime_member(whitney); + fund(wolverine); + upgrade_to_lifetime_member(wolverine); + + vector results; + + const auto& worker1 = create_worker( connie_id ); + worker_id_type worker1_id = worker1.id; + + const auto& worker2 = create_worker( whitney_id, 1000, fc::days(50) ); + worker_id_type worker2_id = worker2.id; + + const auto& worker3 = create_worker( whitney_id, 1000, fc::days(100) ); + worker_id_type worker3_id = worker3.id; + + BOOST_REQUIRE_EQUAL( db_api.get_workers_by_account("connie").size(), 1 ); + BOOST_CHECK( db_api.get_workers_by_account("connie").front().id == worker1_id ); + + BOOST_REQUIRE_EQUAL( db_api.get_workers_by_account(string(whitney.id)).size(), 2 ); + BOOST_CHECK( db_api.get_workers_by_account(string(whitney.id)).front().id == worker2_id ); + BOOST_CHECK( db_api.get_workers_by_account(string(whitney.id)).back().id == worker3_id ); + + BOOST_REQUIRE_EQUAL( db_api.get_workers_by_account("wolverine").size(), 0 ); + + BOOST_REQUIRE_THROW( db_api.get_workers_by_account("not-a-user"), fc::exception ); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE( lookup_vote_ids ) { try { graphene::app::database_api db_api( db, &( app.get_options() )); @@ -1115,6 +1231,115 @@ BOOST_AUTO_TEST_CASE( lookup_vote_ids ) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(get_limit_orders_by_account) +{ try { + graphene::app::database_api db_api( db, &( app.get_options() )); + ACTORS((seller)(buyer)(watcher)); + + const auto& bitcny = create_user_issued_asset("CNY"); + const auto& core = asset_id_type()(db); + + int64_t init_balance(10000000); + transfer( committee_account, seller_id, asset(init_balance) ); + issue_uia( buyer_id, bitcny.amount(init_balance) ); + BOOST_CHECK_EQUAL( 10000000, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 10000000, get_balance(buyer, bitcny) ); + + std::vector results, results2; + limit_order_object o; + + // limit too large + BOOST_CHECK_THROW( db_api.get_limit_orders_by_account( seller.name, 102 ), fc::exception ); + + // The order book is empty + results = db_api.get_limit_orders_by_account( seller.name ); + BOOST_CHECK_EQUAL( results.size(), 0 ); + + // Seller create 50 orders + for (size_t i = 0 ; i < 50 ; ++i) + { + BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250))); + } + + // Get all orders + results = db_api.get_limit_orders_by_account( seller.name ); + BOOST_CHECK_EQUAL( results.size(), 50 ); + + // Seller create 200 orders + for (size_t i = 1 ; i < 101 ; ++i) + { + BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250 + i))); + BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250 - i))); + } + + // Buyer create 20 orders + for (size_t i = 0 ; i < 70 ; ++i) + { + BOOST_CHECK(create_sell_order(buyer, bitcny.amount(100), core.amount(5000 + i))); + } + + // Get the first 101 orders + results = db_api.get_limit_orders_by_account( seller.name ); + BOOST_CHECK_EQUAL( results.size(), 101 ); + for (size_t i = 0 ; i < results.size() - 1 ; ++i) + { + BOOST_CHECK(results[i].id < results[i+1].id); + } + BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(250))); + BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(276))); + o = results.back(); + + // Get the No. 101-201 orders + results = db_api.get_limit_orders_by_account( seller.name, {}, o.id ); + BOOST_CHECK_EQUAL( results.size(), 101 ); + for (size_t i = 0 ; i < results.size() - 1 ; ++i) + { + BOOST_CHECK(results[i].id < results[i+1].id); + } + BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(276))); + BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(326))); + o = results.back(); + + // Get the No. 201- orders + results = db_api.get_limit_orders_by_account( seller.name, {}, o.id ); + BOOST_CHECK_EQUAL( results.size(), 50 ); + for (size_t i = 0 ; i < results.size() - 1 ; ++i) + { + BOOST_CHECK(results[i].id < results[i+1].id); + } + BOOST_CHECK(results.front().sell_price == price(core.amount(100), bitcny.amount(326))); + BOOST_CHECK(results.back().sell_price == price(core.amount(100), bitcny.amount(150))); + + // Get the No. 201-210 orders + results2 = db_api.get_limit_orders_by_account( seller.name, 10, o.id ); + BOOST_CHECK_EQUAL( results2.size(), 10 ); + for (size_t i = 0 ; i < results2.size() - 1 ; ++i) + { + BOOST_CHECK(results2[i].id < results2[i+1].id); + BOOST_CHECK(results[i].id == results2[i].id); + } + BOOST_CHECK(results2.front().sell_price == price(core.amount(100), bitcny.amount(326))); + BOOST_CHECK(results2.back().sell_price == price(core.amount(100), bitcny.amount(170))); + + // Buyer has 70 orders, all IDs are greater than sellers + results = db_api.get_limit_orders_by_account( buyer.name, 90, o.id ); + BOOST_CHECK_EQUAL( results.size(), 70 ); + o = results.back(); + + // All seller's order IDs are smaller, so querying with a buyer's ID will get nothing + results = db_api.get_limit_orders_by_account( seller.name, 90, o.id ); + BOOST_CHECK_EQUAL( results.size(), 0 ); + + // Watcher has no order + results = db_api.get_limit_orders_by_account( watcher.name ); + BOOST_CHECK_EQUAL( results.size(), 0 ); + + // unregistered account, throws exception + BOOST_CHECK_THROW( db_api.get_limit_orders_by_account( "not-a-user", 10, limit_order_id_type() ), + fc::exception ); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(get_account_limit_orders) { try { graphene::app::database_api db_api( db, &( app.get_options() )); @@ -1363,94 +1588,6 @@ BOOST_AUTO_TEST_CASE( verify_authority_multiple_accounts ) throw; } } -BOOST_AUTO_TEST_CASE( api_limit_get_key_references ){ - try{ - const int num_keys = 210; - const int num_keys1 = 2; - vector< private_key_type > numbered_private_keys; - vector< public_key_type > numbered_key_id; - numbered_private_keys.reserve( num_keys ); - - graphene::app::database_api db_api1( db, &( app.get_options() )); - BOOST_CHECK_THROW( db_api1.get_key_references(numbered_key_id), fc::exception ); - - graphene::app::application_options opt = app.get_options(); - opt.has_api_helper_indexes_plugin = true; - graphene::app::database_api db_api( db, &opt ); - - for( int i=0; i > final_result=db_api.get_key_references(numbered_key_id); - BOOST_REQUIRE_EQUAL( final_result.size(), 2u ); - numbered_private_keys.reserve( num_keys ); - for( int i=num_keys1; iapp.get_options())); - - const account_object& alice = create_account("alice"); - const account_object& bob = create_account("bob"); - const account_object& carl = create_account("carl"); - const account_object& dan = create_account("dan"); - const account_object& fred = create_account("fred"); - const account_object& henry = create_account("henry"); - const account_object& kevin = create_account("kevin"); - const account_object& laura = create_account("laura"); - const account_object& lucy = create_account("lucy"); - const account_object& martin = create_account("martin"); - const account_object& patty = create_account("patty"); - - vector accounts; - accounts.push_back(alice.name); - accounts.push_back(bob.name); - accounts.push_back(carl.name); - accounts.push_back(dan.name); - accounts.push_back(fred.name); - accounts.push_back(henry.name); - accounts.push_back(kevin.name); - accounts.push_back(laura.name); - accounts.push_back(lucy.name); - accounts.push_back(martin.name); - accounts.push_back(patty.name); - - GRAPHENE_CHECK_THROW(db_api.get_full_accounts(accounts, false), fc::exception); - - accounts.erase(accounts.begin()); - auto full_accounts = db_api.get_full_accounts(accounts, false); - BOOST_CHECK(full_accounts.size() == 10); - - // not an account - accounts.erase(accounts.begin()); - accounts.push_back("nosuchaccount"); - - // non existing accounts will be ignored in the results - full_accounts = db_api.get_full_accounts(accounts, false); - BOOST_CHECK(full_accounts.size() == 9); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} BOOST_AUTO_TEST_CASE( get_assets_by_issuer ) { try { @@ -1568,97 +1705,6 @@ BOOST_AUTO_TEST_CASE( get_settle_orders_by_account ) { } } -BOOST_AUTO_TEST_CASE( api_limit_get_limit_orders ){ - try{ - graphene::app::database_api db_api( db, &( app.get_options() )); - //account_id_type() do 3 ops - create_bitasset("USD", account_id_type()); - create_account("dan"); - create_account("bob"); - asset_id_type bit_jmj_id = create_bitasset("JMJBIT").id; - generate_block(); - fc::usleep(fc::milliseconds(100)); - GRAPHENE_CHECK_THROW(db_api.get_limit_orders(std::string(static_cast(asset_id_type())), - std::string(static_cast(bit_jmj_id)), 370), fc::exception); - vector limit_orders =db_api.get_limit_orders(std::string( - static_cast(asset_id_type())), - std::string(static_cast(bit_jmj_id)), 340); - BOOST_REQUIRE_EQUAL( limit_orders.size(), 0u); - - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE( api_limit_get_call_orders ){ - try{ - graphene::app::database_api db_api( db, &( app.get_options() )); - //account_id_type() do 3 ops - auto nathan_private_key = generate_private_key("nathan"); - account_id_type nathan_id = create_account("nathan", nathan_private_key.get_public_key()).id; - transfer(account_id_type(), nathan_id, asset(100)); - asset_id_type bitusd_id = create_bitasset( - "USDBIT", nathan_id, 100, disable_force_settle).id; - generate_block(); - fc::usleep(fc::milliseconds(100)); - BOOST_CHECK( bitusd_id(db).is_market_issued() ); - GRAPHENE_CHECK_THROW(db_api.get_call_orders(std::string(static_cast(bitusd_id)), - 370), fc::exception); - vector< call_order_object> call_order =db_api.get_call_orders(std::string( - static_cast(bitusd_id)), 340); - BOOST_REQUIRE_EQUAL( call_order.size(), 0u); - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE( api_limit_get_settle_orders ){ - try{ - graphene::app::database_api db_api( db, &( app.get_options() )); - //account_id_type() do 3 ops - auto nathan_private_key = generate_private_key("nathan"); - account_id_type nathan_id = create_account("nathan", nathan_private_key.get_public_key()).id; - transfer(account_id_type(), nathan_id, asset(100)); - asset_id_type bitusd_id = create_bitasset( - "USDBIT", nathan_id, 100, disable_force_settle).id; - generate_block(); - fc::usleep(fc::milliseconds(100)); - GRAPHENE_CHECK_THROW(db_api.get_settle_orders( - std::string(static_cast(bitusd_id)), 370), fc::exception); - vector result =db_api.get_settle_orders( - std::string(static_cast(bitusd_id)), 340); - BOOST_REQUIRE_EQUAL( result.size(), 0u); - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE( api_limit_get_order_book ){ - try{ - graphene::app::database_api db_api( db, &( app.get_options() )); - auto nathan_private_key = generate_private_key("nathan"); - auto dan_private_key = generate_private_key("dan"); - account_id_type nathan_id = create_account("nathan", nathan_private_key.get_public_key()).id; - account_id_type dan_id = create_account("dan", dan_private_key.get_public_key()).id; - transfer(account_id_type(), nathan_id, asset(100)); - transfer(account_id_type(), dan_id, asset(100)); - asset_id_type bitusd_id = create_bitasset( - "USDBIT", nathan_id, 100, disable_force_settle).id; - asset_id_type bitdan_id = create_bitasset( - "DANBIT", dan_id, 100, disable_force_settle).id; - generate_block(); - fc::usleep(fc::milliseconds(100)); - GRAPHENE_CHECK_THROW(db_api.get_order_book(std::string(static_cast(bitusd_id)), - std::string(static_cast(bitdan_id)),89), fc::exception); - graphene::app::order_book result =db_api.get_order_book(std::string( - static_cast(bitusd_id)), std::string(static_cast(bitdan_id)),78); - BOOST_REQUIRE_EQUAL( result.bids.size(), 0u); - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - BOOST_AUTO_TEST_CASE( asset_in_collateral ) { try { ACTORS( (dan)(nathan) ); @@ -1795,292 +1841,5 @@ BOOST_AUTO_TEST_CASE( asset_in_collateral ) BOOST_CHECK_EQUAL( 1000, assets[1].total_backing_collateral->value ); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( api_limit_lookup_accounts ) { - try{ - graphene::app::database_api db_api( db, &( app.get_options() )); - ACTOR(bob); - GRAPHENE_CHECK_THROW(db_api.lookup_accounts("bob",220), fc::exception); - map result =db_api.lookup_accounts("bob",190); - BOOST_REQUIRE_EQUAL( result.size(), 17u); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( api_limit_lookup_witness_accounts ) { - try{ - graphene::app::database_api db_api( db, &( app.get_options() )); - ACTORS((bob)) ; - GRAPHENE_CHECK_THROW(db_api.lookup_witness_accounts("bob",220), fc::exception); - map result =db_api.lookup_witness_accounts("bob",190); - BOOST_REQUIRE_EQUAL( result.size(), 10u); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE( api_limit_get_full_accounts2 ) { - - try { - graphene::app::database_api db_api(db, &(this->app.get_options())); - vector accounts; - for (int i=0; i<201; i++) { - std::string acct_name = "mytempacct" + std::to_string(i); - const account_object& account_name=create_account(acct_name); - accounts.push_back(account_name.name); - } - GRAPHENE_CHECK_THROW(db_api.get_full_accounts(accounts, false), fc::exception); - accounts.erase(accounts.begin()); - auto full_accounts = db_api.get_full_accounts(accounts, false); - BOOST_REQUIRE_EQUAL(full_accounts.size(), 200u); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE(api_limit_get_withdraw_permissions_by_recipient){ - try{ - graphene::app::database_api db_api( db, &app.get_options()); - ACTORS((bob)) ; - withdraw_permission_id_type withdraw_permission; - GRAPHENE_CHECK_THROW(db_api.get_withdraw_permissions_by_recipient( - "bob",withdraw_permission, 251), fc::exception); - vector result =db_api.get_withdraw_permissions_by_recipient( - "bob",withdraw_permission,250); - BOOST_REQUIRE_EQUAL( result.size(), 0u); - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE(api_limit_get_withdraw_permissions_by_giver){ - try{ - graphene::app::database_api db_api( db, &app.get_options()); - ACTORS((bob)) ; - withdraw_permission_id_type withdraw_permission; - GRAPHENE_CHECK_THROW(db_api.get_withdraw_permissions_by_giver( - "bob",withdraw_permission, 251), fc::exception); - vector result =db_api.get_withdraw_permissions_by_giver( - "bob",withdraw_permission,250); - BOOST_REQUIRE_EQUAL( result.size(), 0u); - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE(api_limit_get_trade_history_by_sequence){ - try{ - app.enable_plugin("market_history"); - graphene::app::application_options opt=app.get_options(); - opt.has_market_history_plugin = true; - graphene::app::database_api db_api( db, &opt); - const auto& bitusd = create_bitasset("USDBIT"); - asset_id_type asset_1, asset_2; - asset_1 = bitusd.id; - asset_2 = asset_id_type(); - GRAPHENE_CHECK_THROW(db_api.get_trade_history_by_sequence( - std::string( static_cast(asset_1)), - std::string( static_cast(asset_2)), - 0,fc::time_point_sec(), 251), fc::exception); - vector result =db_api.get_trade_history_by_sequence( - std::string( static_cast(asset_1)), - std::string( static_cast(asset_2)), - 0,fc::time_point_sec(),250); - BOOST_REQUIRE_EQUAL( result.size(), 0u); - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE(api_limit_get_trade_history){ - try{ - app.enable_plugin("market_history"); - graphene::app::application_options opt=app.get_options(); - opt.has_market_history_plugin = true; - graphene::app::database_api db_api( db, &opt); - const auto& bitusd = create_bitasset("USDBIT"); - asset_id_type asset_1, asset_2; - asset_1 = bitusd.id; - asset_2 = asset_id_type(); - GRAPHENE_CHECK_THROW(db_api.get_trade_history( - std::string( static_cast(asset_1)), - std::string( static_cast(asset_2)), - fc::time_point_sec(),fc::time_point_sec(), - 251), fc::exception); - vector result =db_api.get_trade_history( - std::string( static_cast(asset_1)), - std::string( static_cast(asset_2)), - fc::time_point_sec(),fc::time_point_sec(),250); - BOOST_REQUIRE_EQUAL( result.size(), 0u); - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE(api_limit_get_top_markets){ - try{ - app.enable_plugin("market_history"); - graphene::app::application_options opt=app.get_options(); - opt.has_market_history_plugin = true; - graphene::app::database_api db_api( db, &opt); - const auto& bitusd = create_bitasset("USDBIT"); - asset_id_type asset_1, asset_2; - asset_1 = bitusd.id; - asset_2 = asset_id_type(); - GRAPHENE_CHECK_THROW(db_api.get_top_markets(251), fc::exception); - vector result =db_api.get_top_markets(250); - BOOST_REQUIRE_EQUAL( result.size(), 0u); - }catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE(api_limit_get_collateral_bids) { - try { - graphene::app::database_api db_api( db, &( app.get_options() )); - - int64_t init_balance = 10000; - ///account_id_type borrower, borrower2, feedproducer; - asset_id_type swan, back; - ACTORS((borrower) (borrower2) (feedproducer)) ; - const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); - swan = bitusd.id; - back = asset_id_type(); - update_feed_producers(swan(db), {feedproducer_id}); - transfer(committee_account, borrower_id, asset(init_balance)); - transfer(committee_account, borrower2_id, asset(init_balance)); - - generate_blocks( HARDFORK_CORE_216_TIME ); - generate_block(); - - price_feed feed; - feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default - feed.settlement_price = swan(db).amount(1) / back(db).amount(1); - publish_feed(swan(db), feedproducer_id(db), feed); - // start out with 2:1 collateral - borrow(borrower_id(db), swan(db).amount(10), back(db).amount(2*10)); - borrow(borrower2_id(db), swan(db).amount(10), back(db).amount(4*10)); - //feed 1: 2 - feed.settlement_price = swan(db).amount(1) / back(db).amount(2); - publish_feed(swan(db), feedproducer_id(db), feed); - - // this sell order is designed to trigger a black swan - - create_sell_order( borrower2_id(db), swan(db).amount(1), back(db).amount(3) ); - BOOST_CHECK( swan(db).bitasset_data(db).has_settlement() ); - //making 3 collateral bids - for (int i=0; i<3; i++) { - - std::string acct_name = "mytempacct" + std::to_string(i); - account_id_type account_id=create_account(acct_name).id; - transfer(committee_account, account_id, asset(init_balance)); - bid_collateral(account_id(db), back(db).amount(10), swan(db).amount(1)); - } - auto swan_symbol = swan(db).symbol; - - - //validating normal case; total_bids =3 ; result_bids=3 - vector result_bids = db_api.get_collateral_bids(swan_symbol, 250, 0); - BOOST_CHECK_EQUAL( 3u, result_bids.size() ); - - //verify skip /// inefficient code test - //skip < total_bids; skip=1; total_bids =3 ; result_bids=2 - result_bids = db_api.get_collateral_bids(swan_symbol, 250, 1); - BOOST_CHECK_EQUAL( 2u, result_bids.size() ); - //skip= total_bids; skip=3; total_bids =3 ; result_bids=0 - result_bids = db_api.get_collateral_bids(swan_symbol, 250, 3); - BOOST_CHECK_EQUAL( 0u, result_bids.size() ); - //skip> total_bids; skip=4; total_bids =3 ; result_bids=0 - result_bids = db_api.get_collateral_bids(swan_symbol, 250, 4); - BOOST_CHECK_EQUAL( 0u, result_bids.size() ); - - //verify limit // inefficient code test - //limit= api_limit - for (int i=3; i<255; i++) { - std::string acct_name = "mytempacct" + std::to_string(i); - account_id_type account_id=create_account(acct_name).id; - transfer(committee_account, account_id, asset(init_balance)); - bid_collateral(account_id(db), back(db).amount(10), swan(db).amount(1)); - } - result_bids=db_api.get_collateral_bids(swan_symbol, 250, 0); - BOOST_CHECK_EQUAL( 250u, result_bids.size() ); - //limit> api_limit throw error - GRAPHENE_CHECK_THROW(db_api.get_collateral_bids(swan_symbol, 253, 3), fc::exception); - } - catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE(api_limit_get_account_limit_orders) { - try { - graphene::app::database_api db_api( db, &( app.get_options() )); - ACTORS((seller)); - const auto &bitcny = create_bitasset("CNY"); - const auto &core = asset_id_type()(db); - - int64_t init_balance(10000000); - transfer(committee_account, seller_id, asset(init_balance)); - - /// Create versatile orders - for (size_t i = 0; i < 250; ++i) { - BOOST_CHECK(create_sell_order(seller, core.amount(100), bitcny.amount(250+i))); - } - - - std::vector results=db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY",250); - BOOST_REQUIRE_EQUAL( results.size(), 250u); - GRAPHENE_CHECK_THROW( db_api.get_account_limit_orders(seller.name, GRAPHENE_SYMBOL, "CNY",251), fc::exception); - - } - catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_CASE( api_limit_lookup_vote_ids ) { - try{ - graphene::app::database_api db_api( db, &( app.get_options() )); - ACTORS( (connie)(whitney)(wolverine) ); - fund(connie); - upgrade_to_lifetime_member(connie); - fund(whitney); - upgrade_to_lifetime_member(whitney); - fund(wolverine); - upgrade_to_lifetime_member(wolverine); - const auto& committee = create_committee_member( connie ); - const auto& witness = create_witness( whitney ); - const auto& worker = create_worker( wolverine_id ); - std::vector votes; - votes.push_back( committee.vote_id ); - votes.push_back( witness.vote_id ); - const auto results = db_api.lookup_vote_ids( votes ); - BOOST_REQUIRE_EQUAL( results.size(), 2u); - votes.push_back( worker.vote_for ); - GRAPHENE_CHECK_THROW(db_api.lookup_vote_ids(votes), fc::exception); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( api_limit_lookup_committee_member_accounts ) { - try{ - graphene::app::database_api db_api( db, &( app.get_options() )); - ACTORS((bob)); - GRAPHENE_CHECK_THROW(db_api.lookup_committee_member_accounts("bob",220), fc::exception); - std::map result =db_api.lookup_committee_member_accounts("bob",190); - BOOST_REQUIRE_EQUAL( result.size(), 10u); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/history_api_tests.cpp b/tests/tests/history_api_tests.cpp index e8856fda28..877762bc3b 100644 --- a/tests/tests/history_api_tests.cpp +++ b/tests/tests/history_api_tests.cpp @@ -744,7 +744,7 @@ BOOST_AUTO_TEST_CASE(api_limit_get_relative_account_history) { BOOST_AUTO_TEST_CASE(api_limit_get_account_history_by_operations) { try { graphene::app::history_api hist_api(app); - vector operation_types; + flat_set operation_types; //account_id_type() do 3 ops create_bitasset("USD", account_id_type()); create_account("dan"); diff --git a/tests/tests/htlc_tests.cpp b/tests/tests/htlc_tests.cpp index fda3ab2fb9..1e27e051d1 100644 --- a/tests/tests/htlc_tests.cpp +++ b/tests/tests/htlc_tests.cpp @@ -63,7 +63,7 @@ void generate_random_preimage(uint16_t key_size, std::vector& vec) return; } -void advance_past_hardfork(database_fixture* db_fixture) +void advance_past_htlc_first_hardfork(database_fixture* db_fixture) { db_fixture->generate_blocks(HARDFORK_CORE_1468_TIME); set_expiration(db_fixture->db, db_fixture->trx); @@ -80,7 +80,7 @@ try { transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); uint16_t preimage_size = 256; std::vector pre_image(256); @@ -147,7 +147,8 @@ try { { graphene::chain::htlc_extend_operation big_extend; big_extend.htlc_id = alice_htlc_id; - big_extend.seconds_to_add = db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_timeout_secs + 10; + big_extend.seconds_to_add = db.get_global_properties().parameters.extensions.value + .updatable_htlc_options->max_timeout_secs + 10; big_extend.fee = db.get_global_properties().parameters.current_fees->calculate_fee(big_extend); big_extend.update_issuer = alice_id; trx.operations.push_back(big_extend); @@ -177,6 +178,347 @@ try { } FC_LOG_AND_RETHROW() } +/**** + * @brief helper to create htlc_create_operation + */ +htlc_create_operation create_htlc(const database& db, const account_id_type& from, const account_id_type& to, + const asset& amount, const graphene::protocol::htlc_hash& preimage_hash, uint16_t preimage_size, + uint64_t seconds, const fc::optional& memo = fc::optional()) +{ + htlc_create_operation ret_val; + ret_val.from = from; + ret_val.to = to; + ret_val.amount = amount; + ret_val.preimage_hash = preimage_hash; + ret_val.preimage_size = preimage_size; + ret_val.claim_period_seconds = seconds; + ret_val.extensions.value.memo = memo; + ret_val.fee = db.get_global_properties().parameters.current_fees->calculate_fee(ret_val); + return ret_val; +} + +/**** + * @brief helper to create a proposal + */ +proposal_create_operation create_proposal(const database& db, const account_id_type& from, + const htlc_create_operation& op, const fc::time_point_sec& expiration) +{ + proposal_create_operation ret_val; + ret_val.fee_paying_account = from; + ret_val.expiration_time = expiration; + ret_val.proposed_ops.emplace_back(op); + ret_val.fee = db.get_global_properties().parameters.current_fees->calculate_fee(ret_val); + return ret_val; +} + +BOOST_AUTO_TEST_CASE( htlc_hf_bsip64 ) +{ +try { + ACTORS((alice)(bob)(joker)); + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + transfer( committee_account, joker_id, graphene::chain::asset(init_balance) ); + transfer( committee_account, bob_id, graphene::chain::asset(init_balance) ); + + advance_past_htlc_first_hardfork(this); + + uint16_t preimage_size = 256; + std::vector pre_image(256); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // cler everything out + generate_block(); + trx.clear(); + + /*** + * Proposals before the hardfork + */ + { + BOOST_TEST_MESSAGE( + "Alice is creating a proposal with an HTLC that contains a memo before the hardfork (should fail)"); + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60, data); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "memo"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with HASH160 (should fail)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "HASH160"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with a preimage size of 0 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + /*** + * HTLC Contracts (non-proposals) before hardfork + */ + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC that contains a memo before the hardfork (should fail)"); + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60, data); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "memo"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC with HASH160 (should fail)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "HASH160"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC with a preimage size of 0 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + htlc_id_type htlc_id = PUSH_TX(db, trx, ~0).operation_results[0].get(); + trx.clear(); + BOOST_TEST_MESSAGE("Bob attempts to redeem, but can't because preimage size is 0 (should fail)"); + graphene::chain::htlc_redeem_operation redeem; + redeem.htlc_id = htlc_id; + redeem.preimage = pre_image; + redeem.redeemer = bob_id; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "Preimage size mismatch"); + trx.clear(); + } + + // Alice creates an asset + BOOST_TEST_MESSAGE("Create ALICECOIN so transfer_restricted can be controlled"); + const asset_id_type uia_id = create_user_issued_asset( "ALICECOIN", alice, transfer_restricted).id; + BOOST_TEST_MESSAGE("Issuing ALICECOIN to Bob"); + issue_uia(bob, asset(10000, uia_id) ); + // verify transfer restrictions are in place + REQUIRE_EXCEPTION_WITH_TEXT(transfer(bob, joker, asset(1, uia_id)), "transfer"); + trx.operations.clear(); + + { + BOOST_TEST_MESSAGE("Bob wants to transfer ALICECOIN, which is transfer_restricted (allowed before HF)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, + asset(3, uia_id), hash_it(pre_image), preimage_size, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE( + "Bob wants to transfer ALICECOIN within a proposal, always allowed, although will fail later"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, asset(3,uia_id), + hash_it(pre_image), preimage_size, 60); + graphene::chain::proposal_create_operation prop_create = create_proposal( + db, bob_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop_create); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + BOOST_TEST_MESSAGE("Fast Forwarding beyond HF BSIP64"); + generate_blocks( HARDFORK_CORE_BSIP64_TIME + 60 ); + set_expiration( db, trx ); + + /*** + * Proposals after the hardfork + */ + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with an HTLC that contains a memo (should pass)"); + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60, data); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with HASH160 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with a preimage size of 0 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + /*** + * HTLC Contracts (non-proposals) after hardfork + */ + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC that contains a memo after the hardfork (should pass)"); + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60, data); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC with HASH160 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Bob wants to transfer ALICECOIN, which is transfer_restricted (no longer allowed)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, + asset(3, uia_id), hash_it(pre_image), preimage_size, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "transfer_restricted"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE( + "Bob wants to transfer ALICECOIN within a proposal, always allowed, although will fail later"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, asset(3,uia_id), + hash_it(pre_image), preimage_size, 60); + graphene::chain::proposal_create_operation prop_create = create_proposal( + db, bob_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop_create); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("A memo field should include a charge per kb (uses fee from transfer_operation)"); + htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3), hash_it(pre_image), + preimage_size, 60); + asset no_memo_fee = op.fee; + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + op.extensions.value.memo = data; + asset with_memo_fee = db.current_fee_schedule().calculate_fee(op); + BOOST_CHECK_GT( with_memo_fee.amount.value, no_memo_fee.amount.value ); + } + // After HF 64, a zero in the preimage_size means 2 things, no preimage (can happen, but who would do such a + // thing), or simply skip the size validation. To test, we must attempt to redeem both cases + { + // case 1: 0 preimage with 0 preimage_size + BOOST_TEST_MESSAGE("Attempt to create an HTLC with no preimage (should pass)"); + htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), + hash_it(std::vector()), 0, 60); + trx.operations.push_back(op); + sign(trx, alice_private_key); + graphene::protocol::processed_transaction results = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + htlc_id_type htlc_id = results.operation_results[0].get(); + BOOST_TEST_MESSAGE("Attempt to redeem HTLC that has no preimage, but include one anyway (should fail)"); + htlc_redeem_operation redeem; + redeem.htlc_id = htlc_id; + redeem.preimage = pre_image; + redeem.redeemer = bob_id; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "Provided preimage does not generate"); + trx.operations.clear(); + BOOST_TEST_MESSAGE("Attempt to redeem HTLC that has no preimage (should pass)"); + redeem.preimage = std::vector(); + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + } + { + // case 2: a real preimage with 0 preimage size + htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), + hash_it(pre_image), 0, 60); + trx.operations.push_back(op); + sign(trx, alice_private_key); + graphene::protocol::processed_transaction results = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + htlc_id_type htlc_id = results.operation_results[0].get(); + BOOST_TEST_MESSAGE("Attempt to redeem with no preimage (should fail)"); + htlc_redeem_operation redeem; + redeem.htlc_id = htlc_id; + redeem.preimage = std::vector(); + redeem.redeemer = bob_id; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "Provided preimage does not generate"); + trx.operations.clear(); + BOOST_TEST_MESSAGE("Attempt to redeem with no preimage size, but incorrect preimage"); + redeem.preimage = std::vector{'H','e','l','l','o'}; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "Provided preimage does not generate"); + trx.operations.clear(); + BOOST_TEST_MESSAGE("Attempt to redeem with no preimage size (should pass)"); + redeem.preimage = pre_image; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + } +} FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_CASE( htlc_fulfilled ) { try { @@ -188,7 +530,7 @@ try { transfer( committee_account, bob_id, graphene::chain::asset(init_balance) ); transfer( committee_account, joker_id, graphene::chain::asset(init_balance) ); - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); uint16_t preimage_size = 256; std::vector pre_image(preimage_size); @@ -271,7 +613,7 @@ try { BOOST_AUTO_TEST_CASE( other_peoples_money ) { try { - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); ACTORS((alice)(bob)); @@ -323,6 +665,9 @@ try { BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) { try { + ACTORS( (alice) ); + int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION); + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); { // try to set committee parameters before hardfork proposal_create_operation cop = proposal_create_operation::committee_proposal( @@ -388,7 +733,7 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) // now things should start working... BOOST_TEST_MESSAGE("Advancing to HTLC hardfork time."); - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); proposal_id_type good_proposal_id; BOOST_TEST_MESSAGE( "Creating a proposal to change the max_preimage_size to 2048 and set higher fees" ); @@ -411,7 +756,8 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) new_fee_schedule->parameters.insert( (*itr).second); } } - proposal_create_operation cop = proposal_create_operation::committee_proposal(db.get_global_properties().parameters, db.head_block_time()); + proposal_create_operation cop = proposal_create_operation::committee_proposal(db.get_global_properties() + .parameters, db.head_block_time()); cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; committee_member_update_global_parameters_operation uop; @@ -442,12 +788,14 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) } BOOST_TEST_MESSAGE( "Verifying that the parameters didn't change immediately" ); - BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); + BOOST_CHECK_EQUAL( + db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); BOOST_TEST_MESSAGE( "Generating blocks until proposal expires" ); generate_blocks(good_proposal_id(db).expiration_time + 5); BOOST_TEST_MESSAGE( "Verify that the parameters still have not changed" ); - BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); + BOOST_CHECK_EQUAL(db.get_global_properties() + .parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); @@ -455,13 +803,17 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) BOOST_TEST_MESSAGE( "Verify that the change has been implemented" ); - BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 2048u); - const graphene::chain::fee_schedule& current_fee_schedule = *(db.get_global_properties().parameters.current_fees); + BOOST_CHECK_EQUAL(db.get_global_properties() + .parameters.extensions.value.updatable_htlc_options->max_preimage_size, 2048u); + const graphene::chain::fee_schedule& current_fee_schedule = + *(db.get_global_properties().parameters.current_fees); const htlc_create_operation::fee_parameters_type& htlc_fee = current_fee_schedule.get(); BOOST_CHECK_EQUAL(htlc_fee.fee, 2 * GRAPHENE_BLOCKCHAIN_PRECISION); - -} FC_LOG_AND_RETHROW() } + + } + FC_LOG_AND_RETHROW() +} BOOST_AUTO_TEST_CASE( htlc_before_hardfork ) { try { @@ -562,13 +914,13 @@ BOOST_AUTO_TEST_CASE( fee_calculations ) htlc_create_operation create; // no days create.claim_period_seconds = 0; - BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 2 ); + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 2 ); // exactly 1 day create.claim_period_seconds = 60 * 60 * 24; - BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 4 ); + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 4 ); // tad over a day create.claim_period_seconds++; - BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 6 ); + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 6 ); } // redeem { @@ -633,7 +985,7 @@ try { fund( alice, graphene::chain::asset(init_balance) ); fund( bob, graphene::chain::asset(init_balance) ); - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); // blacklist bob { @@ -924,6 +1276,4 @@ try { } } - - BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/simple_maker_taker_fee_tests.cpp b/tests/tests/simple_maker_taker_fee_tests.cpp new file mode 100644 index 0000000000..97a609b634 --- /dev/null +++ b/tests/tests/simple_maker_taker_fee_tests.cpp @@ -0,0 +1,1952 @@ +/* + * Copyright (c) 2020 Michel Santos, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include +#include + +#include "../common/database_fixture.hpp" + + +using namespace graphene::chain; +using namespace graphene::chain::test; + +struct simple_maker_taker_database_fixture : database_fixture { + simple_maker_taker_database_fixture() + : database_fixture() { + } + + const limit_order_create_operation + create_sell_operation(account_id_type user, const asset &amount, const asset &recv) { + const time_point_sec order_expiration = time_point_sec::maximum(); + const price &fee_core_exchange_rate = price::unit_price(); + limit_order_create_operation op = create_sell_operation(user, amount, recv, order_expiration, + fee_core_exchange_rate); + return op; + } + + const limit_order_create_operation + create_sell_operation(account_id_type user, const asset &amount, const asset &recv, + const time_point_sec order_expiration, + const price &fee_core_exchange_rate) { + limit_order_create_operation op = create_sell_operation(user(db), amount, recv, order_expiration, + fee_core_exchange_rate); + return op; + } + + const limit_order_create_operation + create_sell_operation(const account_object &user, const asset &amount, const asset &recv, + const time_point_sec order_expiration, + const price &fee_core_exchange_rate) { + limit_order_create_operation sell_order; + sell_order.seller = user.id; + sell_order.amount_to_sell = amount; + sell_order.min_to_receive = recv; + sell_order.expiration = order_expiration; + + return sell_order; + } + + const asset_create_operation create_user_issued_asset_operation(const string &name, const account_object &issuer, + uint16_t flags, const price &core_exchange_rate, + uint8_t precision, uint16_t maker_fee_percent, + uint16_t taker_fee_percent) { + asset_create_operation creator; + creator.issuer = issuer.id; + creator.fee = asset(); + creator.symbol = name; + creator.common_options.max_supply = 0; + creator.precision = precision; + + creator.common_options.core_exchange_rate = core_exchange_rate; + creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + creator.common_options.flags = flags; + creator.common_options.issuer_permissions = flags; + creator.common_options.market_fee_percent = maker_fee_percent; + creator.common_options.extensions.value.taker_fee_percent = taker_fee_percent; + + return creator; + + } +}; + + +/** + * BSIP81: Asset owners may specify different market fee rate for maker orders and taker orders + */ +BOOST_FIXTURE_TEST_SUITE(simple_maker_taker_fee_tests, simple_maker_taker_database_fixture) + + /** + * Test of setting taker fee before HF and after HF for a UIA + */ + BOOST_AUTO_TEST_CASE(setting_taker_fees_uia) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((jill)(izzy)); + account_id_type issuer_id = jill.id; + fc::ecc::private_key issuer_private_key = jill_private_key; + + // Initialize tokens + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + const asset_object jillcoin = create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, + market_fee_percent); + + ////// + // Before HF, test inability to set taker fees + ////// + asset_update_operation uop; + uop.issuer = issuer_id; + uop.asset_to_update = jillcoin.get_id(); + uop.new_options = jillcoin.options; + uint16_t new_taker_fee_percent = uop.new_options.market_fee_percent / 2; + uop.new_options.extensions.value.taker_fee_percent = new_taker_fee_percent; + + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, issuer_private_key); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + // TODO: Check the specific exception? + + // Check the taker fee + asset_object updated_asset = jillcoin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + + ////// + // Before HF, test inability to set taker fees with an asset update operation inside of a proposal + ////// + { + trx.clear(); + set_expiration(db, trx); + + uint64_t alternate_taker_fee_percent = new_taker_fee_percent * 2; + uop.new_options.extensions.value.taker_fee_percent = alternate_taker_fee_percent; + + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(uop); + + trx.operations.push_back(cop); + // sign(trx, issuer_private_key); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + + // Check the taker fee is not changed because the proposal has not been approved + updated_asset = jillcoin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + } + + + ////// + // Before HF, test inability to set taker fees with an asset create operation inside of a proposal + ////// + { + trx.clear(); + set_expiration(db, trx); + + uint64_t maker_fee_percent = 10 * GRAPHENE_1_PERCENT; + uint64_t taker_fee_percent = 2 * GRAPHENE_1_PERCENT; + asset_create_operation ac_op = create_user_issued_asset_operation("JCOIN2", jill, charge_market_fee, price, + 2, + maker_fee_percent, taker_fee_percent); + + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(ac_op); + + trx.operations.push_back(cop); + // sign(trx, issuer_private_key); + + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); // The proposal should be rejected + + } + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test default values of taker fee after HF + // After the HF its default value should still not be set + ////// + updated_asset = jillcoin.get_id()(db); + uint16_t expected_taker_fee_percent = updated_asset.options.market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + + ////// + // After HF, test invalid taker fees + ////// + uop.new_options.extensions.value.taker_fee_percent = GRAPHENE_100_PERCENT + 1; + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, issuer_private_key); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); // An exception should be thrown indicating the reason + // TODO: Check the specific exception? + + + ////// + // After HF, test that new values can be set + ////// + uop.new_options.extensions.value.taker_fee_percent = new_taker_fee_percent; + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, issuer_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee + updated_asset = jillcoin.get_id()(db); + expected_taker_fee_percent = new_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + + ////// + // After HF, test ability to set taker fees with an asset update operation inside of a proposal + ////// + { + trx.clear(); + set_expiration(db, trx); + + uint64_t alternate_taker_fee_percent = new_taker_fee_percent * 2; + uop.new_options.extensions.value.taker_fee_percent = alternate_taker_fee_percent; + + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(uop); + + trx.operations.push_back(cop); + // sign(trx, issuer_private_key); + processed_transaction processed = PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee is not changed because the proposal has not been approved + updated_asset = jillcoin.get_id()(db); + expected_taker_fee_percent = new_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + + // Approve the proposal + trx.clear(); + proposal_id_type pid = processed.operation_results[0].get(); + + proposal_update_operation pup; + pup.fee_paying_account = jill.id; + pup.proposal = pid; + pup.active_approvals_to_add.insert(jill.id); + trx.operations.push_back(pup); + set_expiration(db, trx); + sign(trx, jill_private_key); + + PUSH_TX(db, trx); // No exception should be thrown + + // Advance to after proposal expires + generate_blocks(cop.expiration_time); + + // Check the taker fee is not changed because the proposal has not been approved + updated_asset = jillcoin.get_id()(db); + expected_taker_fee_percent = alternate_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + } + + + ////// + // After HF, test ability to set taker fees with an asset create operation inside of a proposal + ////// + { + trx.clear(); + set_expiration(db, trx); + + uint64_t maker_fee_percent = 10 * GRAPHENE_1_PERCENT; + uint64_t taker_fee_percent = 2 * GRAPHENE_1_PERCENT; + asset_create_operation ac_op = create_user_issued_asset_operation("JCOIN2", jill, charge_market_fee, price, + 2, + maker_fee_percent, taker_fee_percent); + + proposal_create_operation cop; + cop.review_period_seconds = 86400; + uint32_t buffer_seconds = 60 * 60; + cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + buffer_seconds; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.proposed_ops.emplace_back(ac_op); + + trx.operations.push_back(cop); + // sign(trx, issuer_private_key); + + processed_transaction processed = PUSH_TX(db, trx); // No exception should be thrown + + // Check the asset does not exist because the proposal has not been approved + const auto& asset_idx = db.get_index_type().indices().get(); + const auto itr = asset_idx.find("JCOIN2"); + BOOST_CHECK(itr == asset_idx.end()); + + // Approve the proposal + trx.clear(); + proposal_id_type pid = processed.operation_results[0].get(); + + proposal_update_operation pup; + pup.fee_paying_account = jill.id; + pup.proposal = pid; + pup.active_approvals_to_add.insert(jill.id); + trx.operations.push_back(pup); + set_expiration(db, trx); + sign(trx, jill_private_key); + + PUSH_TX(db, trx); // No exception should be thrown + + // Advance to after proposal expires + generate_blocks(cop.expiration_time); + + // Check the taker fee is not changed because the proposal has not been approved + BOOST_CHECK(asset_idx.find("JCOIN2") != asset_idx.end()); + updated_asset = *asset_idx.find("JCOIN2"); + expected_taker_fee_percent = taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + uint16_t expected_maker_fee_percent = maker_fee_percent; + BOOST_CHECK_EQUAL(expected_maker_fee_percent, updated_asset.options.market_fee_percent); + + } + + } FC_LOG_AND_RETHROW() + } + + + /** + * Test of setting taker fee before HF and after HF for a smart asset + */ + BOOST_AUTO_TEST_CASE(setting_taker_fees_smart_asset) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((smartissuer)(feedproducer)); + + // Initialize tokens + create_bitasset("SMARTBIT", smartissuer.id); + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object &bitsmart = get_asset("SMARTBIT"); + + generate_blocks(HARDFORK_615_TIME); // get around Graphene issue #615 feed expiration bug + generate_block(); + + + ////// + // Before HF, test inability to set taker fees + ////// + asset_update_operation uop; + uop.issuer = smartissuer.id; + uop.asset_to_update = bitsmart.get_id(); + uop.new_options = bitsmart.options; + uint16_t new_taker_fee_percent = uop.new_options.market_fee_percent / 2; + uop.new_options.extensions.value.taker_fee_percent = new_taker_fee_percent; + + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, smartissuer_private_key); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); // An exception should be thrown indicating the reason + // TODO: Check the specific exception? + + // Check the taker fee + asset_object updated_asset = bitsmart.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test default values of taker fee after HF + // After the HF its default value should still not be set + ////// + updated_asset = bitsmart.get_id()(db); + uint16_t expected_taker_fee_percent = updated_asset.options.market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + + ////// + // After HF, test invalid taker fees + ////// + uop.new_options.extensions.value.taker_fee_percent = GRAPHENE_100_PERCENT + 1; + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, smartissuer_private_key); + GRAPHENE_CHECK_THROW(PUSH_TX(db, trx), fc::exception); // An exception should be thrown indicating the reason + // TODO: Check the specific exception? + + + ////// + // After HF, test that new values can be set + ////// + uop.new_options.extensions.value.taker_fee_percent = new_taker_fee_percent; + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, smartissuer_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee + updated_asset = bitsmart.get_id()(db); + expected_taker_fee_percent = new_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + } FC_LOG_AND_RETHROW() + } + + + /** + * Test the default taker fee values of multiple different assets after HF + */ + BOOST_AUTO_TEST_CASE(default_taker_fees) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((alice)(bob)(charlie)(smartissuer)); + + // Initialize tokens with custom market fees + price price(asset(1, asset_id_type(1)), asset(1)); + + const uint16_t alice1coin_market_fee_percent = 1 * GRAPHENE_1_PERCENT; + create_user_issued_asset("ALICE1COIN", alice, charge_market_fee, price, 2, + alice1coin_market_fee_percent); + + const uint16_t alice2coin_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + create_user_issued_asset("ALICE2COIN", alice, charge_market_fee, price, 2, + alice2coin_market_fee_percent); + + const uint16_t bob1coin_market_fee_percent = 3 * GRAPHENE_1_PERCENT; + create_user_issued_asset("BOB1COIN", alice, charge_market_fee, price, 2, + bob1coin_market_fee_percent); + + const uint16_t bob2coin_market_fee_percent = 4 * GRAPHENE_1_PERCENT; + create_user_issued_asset("BOB2COIN", alice, charge_market_fee, price, 2, + bob2coin_market_fee_percent); + + const uint16_t charlie1coin_market_fee_percent = 4 * GRAPHENE_1_PERCENT; + create_user_issued_asset("CHARLIE1COIN", alice, charge_market_fee, price, 2, + charlie1coin_market_fee_percent); + + const uint16_t charlie2coin_market_fee_percent = 5 * GRAPHENE_1_PERCENT; + create_user_issued_asset("CHARLIE2COIN", alice, charge_market_fee, price, 2, + charlie2coin_market_fee_percent); + + const uint16_t bitsmart1coin_market_fee_percent = 7 * GRAPHENE_1_PERCENT; + create_bitasset("SMARTBIT1", smartissuer.id, bitsmart1coin_market_fee_percent); + + + const uint16_t bitsmart2coin_market_fee_percent = 8 * GRAPHENE_1_PERCENT; + create_bitasset("SMARTBIT2", smartissuer.id, bitsmart2coin_market_fee_percent); + + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object& alice1coin = get_asset("ALICE1COIN"); + const asset_object& alice2coin = get_asset("ALICE2COIN"); + const asset_object& bob1coin = get_asset("BOB1COIN"); + const asset_object& bob2coin = get_asset("BOB2COIN"); + const asset_object& charlie1coin = get_asset("CHARLIE1COIN"); + const asset_object& charlie2coin = get_asset("CHARLIE2COIN"); + const asset_object& bitsmart1 = get_asset("SMARTBIT1"); + const asset_object& bitsmart2 = get_asset("SMARTBIT2"); + + + ////// + // Before HF, test the market/maker fees for each asset + ////// + asset_object updated_asset; + uint16_t expected_fee_percent; + + updated_asset = alice1coin.get_id()(db); + expected_fee_percent = alice1coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = alice2coin.get_id()(db); + expected_fee_percent = alice2coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = bob1coin.get_id()(db); + expected_fee_percent = bob1coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = bob2coin.get_id()(db); + expected_fee_percent = bob2coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = charlie1coin.get_id()(db); + expected_fee_percent = charlie1coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = charlie2coin.get_id()(db); + expected_fee_percent = charlie2coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = bitsmart1.get_id()(db); + expected_fee_percent = bitsmart1coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = bitsmart2.get_id()(db); + expected_fee_percent = bitsmart2coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + + ////// + // Before HF, test that taker fees are not set + ////// + // Check the taker fee + updated_asset = alice1coin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = alice2coin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = bob1coin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = bob2coin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = charlie1coin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = charlie2coin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = bitsmart1.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = bitsmart2.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test the maker fees for each asset are unchanged + ////// + updated_asset = alice1coin.get_id()(db); + expected_fee_percent = alice1coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = alice2coin.get_id()(db); + expected_fee_percent = alice2coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = bob1coin.get_id()(db); + expected_fee_percent = bob1coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = bob2coin.get_id()(db); + expected_fee_percent = bob2coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = charlie1coin.get_id()(db); + expected_fee_percent = charlie1coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = charlie2coin.get_id()(db); + expected_fee_percent = charlie2coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = bitsmart1.get_id()(db); + expected_fee_percent = bitsmart1coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + updated_asset = bitsmart2.get_id()(db); + expected_fee_percent = bitsmart2coin_market_fee_percent; + BOOST_CHECK_EQUAL(expected_fee_percent, updated_asset.options.market_fee_percent); + + + ////// + // After HF, test the taker fees for each asset are not set + ////// + updated_asset = alice1coin.get_id()(db); + expected_fee_percent = alice1coin_market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = alice2coin.get_id()(db); + expected_fee_percent = alice2coin_market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = bob1coin.get_id()(db); + expected_fee_percent = bob1coin_market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = bob2coin.get_id()(db); + expected_fee_percent = bob2coin_market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = charlie1coin.get_id()(db); + expected_fee_percent = charlie1coin_market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = charlie2coin.get_id()(db); + expected_fee_percent = charlie2coin_market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = bitsmart1.get_id()(db); + expected_fee_percent = bitsmart1coin_market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + updated_asset = bitsmart2.get_id()(db); + expected_fee_percent = bitsmart2coin_market_fee_percent; + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + } FC_LOG_AND_RETHROW() + } + + + /** + * Test of different maker and taker fees charged when filling limit orders after HF for a UIA + */ + BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_uia_1) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((jill)(izzy)(alice)(bob)); + + // Initialize tokens + price price(asset(1, asset_id_type(1)), asset(1)); + + const uint16_t JILL_PRECISION = 100; + const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, + jill_market_fee_percent); + + const uint16_t IZZY_PRECISION = 1000; + const uint16_t izzy_market_fee_percent = 5 * GRAPHENE_1_PERCENT; + create_user_issued_asset("ICOIN", izzy, charge_market_fee, price, 3, + izzy_market_fee_percent); + + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object& jillcoin = get_asset("JCOIN"); + const asset_object& izzycoin = get_asset("ICOIN"); + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test that new values can be set + ////// + // Define the new taker fees + uint16_t jill_maker_fee_percent = jill_market_fee_percent; + uint16_t jill_taker_fee_percent = jill_maker_fee_percent / 2; + + uint16_t izzy_maker_fee_percent = izzy_market_fee_percent; + uint16_t izzy_taker_fee_percent = izzy_maker_fee_percent / 2; + + // Set the new taker fee for JILLCOIN + asset_update_operation uop; + uop.issuer = jill.id; + uop.asset_to_update = jillcoin.get_id(); + uop.new_options = jillcoin.options; + uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, jill_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for JILLCOIN + asset_object updated_asset = jillcoin.get_id()(db); + uint16_t expected_taker_fee_percent = jill_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + // Set the new taker fee for IZZYCOIN + uop.issuer = izzy.id; + uop.asset_to_update = izzycoin.get_id(); + uop.new_options = izzycoin.options; + uop.new_options.extensions.value.taker_fee_percent = izzy_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, izzy_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for IZZYCOIN + updated_asset = izzycoin.get_id()(db); + expected_taker_fee_percent = izzy_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + + ////// + // After HF, create limit orders that will perfectly match + ////// + BOOST_TEST_MESSAGE("Issuing 10 jillcoin to alice"); + issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking alice's balance"); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION); + + BOOST_TEST_MESSAGE("Issuing 300 izzycoin to bob"); + issue_uia(bob, izzycoin.amount(300 * IZZY_PRECISION)); + BOOST_TEST_MESSAGE("Checking bob's balance"); + BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 300 * IZZY_PRECISION); + + // Alice and Bob place orders which match, and are completely filled by each other + // Alice is willing to sell 10 JILLCOIN for at least 300 IZZYCOIN + limit_order_create_operation alice_sell_op = create_sell_operation(alice.id, + jillcoin.amount(10 * JILL_PRECISION), + izzycoin.amount(300 * + IZZY_PRECISION)); + trx.clear(); + trx.operations.push_back(alice_sell_op); + asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type alice_order_id = ptx.operation_results[0].get(); + + const limit_order_object* alice_order_before = db.find(alice_order_id); + BOOST_CHECK(alice_order_before != nullptr); + + // Bob is willing to sell 300 IZZYCOIN for at least 10 JILLCOIN + limit_order_create_operation bob_sell_op = create_sell_operation(bob.id, izzycoin.amount(300 * IZZY_PRECISION), + jillcoin.amount( + 10 * + JILL_PRECISION)); + trx.clear(); + trx.operations.push_back(bob_sell_op); + asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, bob_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type bob_order_id = ptx.operation_results[0].get(); + + // Check that the orders were filled by ensuring that they are no longer on the order books + const limit_order_object* alice_order = db.find(alice_order_id); + BOOST_CHECK(alice_order == nullptr); + const limit_order_object* bob_order = db.find(bob_order_id); + BOOST_CHECK(bob_order == nullptr); + + + // Check the new balances of the maker + // Alice was the maker; she is receiving IZZYCOIN + asset expected_izzy_fee = izzycoin.amount( + 300 * IZZY_PRECISION * izzy_maker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(alice, izzycoin), + (300 * IZZY_PRECISION) - alice_sell_fee.amount.value - expected_izzy_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0); + + // Check the new balance of the taker + // Bob was the taker; he is receiving JILLCOIN + asset expected_jill_fee = jillcoin.amount( + 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), + (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 0); + + // Check the asset issuer's accumulated fees + BOOST_CHECK(izzycoin.dynamic_asset_data_id(db).accumulated_fees == expected_izzy_fee.amount); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount); + + } FC_LOG_AND_RETHROW() + } + + + /** + * Test of different maker and taker fees charged when filling limit orders after HF for a UIA + * + * Test the filling of a taker fee when the **maker** fee percent is set to 0. This tests some optimizations + * in database::calculate_market_fee(). + */ + BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_uia_2) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((jill)(izzy)(alice)(bob)); + + // Initialize tokens + price price(asset(1, asset_id_type(1)), asset(1)); + + const uint16_t JILL_PRECISION = 100; + const uint16_t jill_market_fee_percent = 0 * GRAPHENE_1_PERCENT; + create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, + jill_market_fee_percent); + + const uint16_t IZZY_PRECISION = 1000; + const uint16_t izzy_market_fee_percent = 0 * GRAPHENE_1_PERCENT; + create_user_issued_asset("ICOIN", izzy, charge_market_fee, price, 3, + izzy_market_fee_percent); + + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object& jillcoin = get_asset("JCOIN"); + const asset_object& izzycoin = get_asset("ICOIN"); + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test that new values can be set + ////// + // Define the new taker fees + uint16_t jill_maker_fee_percent = jill_market_fee_percent; + uint16_t jill_taker_fee_percent = 1 * GRAPHENE_1_PERCENT; + + uint16_t izzy_maker_fee_percent = izzy_market_fee_percent; + uint16_t izzy_taker_fee_percent = 3 * GRAPHENE_1_PERCENT; + + // Set the new taker fee for JILLCOIN + asset_update_operation uop; + uop.issuer = jill.id; + uop.asset_to_update = jillcoin.get_id(); + uop.new_options.market_fee_percent = jill_maker_fee_percent; + uop.new_options = jillcoin.options; + uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, jill_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for JILLCOIN + asset_object updated_asset = jillcoin.get_id()(db); + uint16_t expected_taker_fee_percent = jill_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + // Set the new taker fee for IZZYCOIN + uop.issuer = izzy.id; + uop.asset_to_update = izzycoin.get_id(); + uop.new_options.market_fee_percent = izzy_maker_fee_percent; + uop.new_options = izzycoin.options; + uop.new_options.extensions.value.taker_fee_percent = izzy_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, izzy_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for IZZYCOIN + updated_asset = izzycoin.get_id()(db); + expected_taker_fee_percent = izzy_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + + ////// + // After HF, create limit orders that will perfectly match + ////// + BOOST_TEST_MESSAGE("Issuing 10 jillcoin to alice"); + issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking alice's balance"); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION); + + BOOST_TEST_MESSAGE("Issuing 300 izzycoin to bob"); + issue_uia(bob, izzycoin.amount(300 * IZZY_PRECISION)); + BOOST_TEST_MESSAGE("Checking bob's balance"); + BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 300 * IZZY_PRECISION); + + // Alice and Bob place orders which match, and are completely filled by each other + // Alice is willing to sell 10 JILLCOIN for at least 300 IZZYCOIN + limit_order_create_operation alice_sell_op = create_sell_operation(alice.id, + jillcoin.amount(10 * JILL_PRECISION), + izzycoin.amount(300 * + IZZY_PRECISION)); + trx.clear(); + trx.operations.push_back(alice_sell_op); + asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type alice_order_id = ptx.operation_results[0].get(); + + const limit_order_object* alice_order_before = db.find(alice_order_id); + BOOST_CHECK(alice_order_before != nullptr); + + // Bob is willing to sell 300 IZZYCOIN for at least 10 JILLCOIN + limit_order_create_operation bob_sell_op = create_sell_operation(bob.id, izzycoin.amount(300 * IZZY_PRECISION), + jillcoin.amount( + 10 * + JILL_PRECISION)); + trx.clear(); + trx.operations.push_back(bob_sell_op); + asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, bob_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type bob_order_id = ptx.operation_results[0].get(); + + // Check that the orders were filled by ensuring that they are no longer on the order books + const limit_order_object* alice_order = db.find(alice_order_id); + BOOST_CHECK(alice_order == nullptr); + const limit_order_object* bob_order = db.find(bob_order_id); + BOOST_CHECK(bob_order == nullptr); + + + // Check the new balances of the maker + // Alice was the maker; she is receiving IZZYCOIN + asset expected_izzy_fee = izzycoin.amount( + 300 * IZZY_PRECISION * izzy_maker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(alice, izzycoin), + (300 * IZZY_PRECISION) - alice_sell_fee.amount.value - expected_izzy_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0); + + // Check the new balance of the taker + // Bob was the taker; he is receiving JILLCOIN + asset expected_jill_fee = jillcoin.amount( + 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), + (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 0); + + // Check the asset issuer's accumulated fees + BOOST_CHECK(izzycoin.dynamic_asset_data_id(db).accumulated_fees == expected_izzy_fee.amount); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount); + + } FC_LOG_AND_RETHROW() + } + + + /** + * Test of different maker and taker fees charged when filling limit orders after HF for a UIA + * + * Test the filling of a taker fee when the **taker** fee percent is set to 0. This tests some optimizations + * in database::calculate_market_fee(). + */ + BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_uia_3) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((jill)(izzy)(alice)(bob)); + + // Initialize tokens + price price(asset(1, asset_id_type(1)), asset(1)); + + const uint16_t JILL_PRECISION = 100; + const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, + jill_market_fee_percent); + + const uint16_t IZZY_PRECISION = 1000; + const uint16_t izzy_market_fee_percent = 5 * GRAPHENE_1_PERCENT; + create_user_issued_asset("ICOIN", izzy, charge_market_fee, price, 3, + izzy_market_fee_percent); + + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object& jillcoin = get_asset("JCOIN"); + const asset_object& izzycoin = get_asset("ICOIN"); + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test that new values can be set + ////// + // Define the new taker fees + uint16_t jill_maker_fee_percent = jill_market_fee_percent; + uint16_t jill_taker_fee_percent = 0 * GRAPHENE_1_PERCENT; + + uint16_t izzy_maker_fee_percent = izzy_market_fee_percent; + uint16_t izzy_taker_fee_percent = 0 * GRAPHENE_1_PERCENT; + + // Set the new taker fee for JILLCOIN + asset_update_operation uop; + uop.issuer = jill.id; + uop.asset_to_update = jillcoin.get_id(); + uop.new_options.market_fee_percent = jill_maker_fee_percent; + uop.new_options = jillcoin.options; + uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, jill_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for JILLCOIN + asset_object updated_asset = jillcoin.get_id()(db); + uint16_t expected_taker_fee_percent = jill_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + // Set the new taker fee for IZZYCOIN + uop.issuer = izzy.id; + uop.asset_to_update = izzycoin.get_id(); + uop.new_options.market_fee_percent = izzy_maker_fee_percent; + uop.new_options = izzycoin.options; + uop.new_options.extensions.value.taker_fee_percent = izzy_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, izzy_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for IZZYCOIN + updated_asset = izzycoin.get_id()(db); + expected_taker_fee_percent = izzy_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + + ////// + // After HF, create limit orders that will perfectly match + ////// + BOOST_TEST_MESSAGE("Issuing 10 jillcoin to alice"); + issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking alice's balance"); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION); + + BOOST_TEST_MESSAGE("Issuing 300 izzycoin to bob"); + issue_uia(bob, izzycoin.amount(300 * IZZY_PRECISION)); + BOOST_TEST_MESSAGE("Checking bob's balance"); + BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 300 * IZZY_PRECISION); + + // Alice and Bob place orders which match, and are completely filled by each other + // Alice is willing to sell 10 JILLCOIN for at least 300 IZZYCOIN + limit_order_create_operation alice_sell_op = create_sell_operation(alice.id, + jillcoin.amount(10 * JILL_PRECISION), + izzycoin.amount(300 * + IZZY_PRECISION)); + trx.clear(); + trx.operations.push_back(alice_sell_op); + asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type alice_order_id = ptx.operation_results[0].get(); + + const limit_order_object* alice_order_before = db.find(alice_order_id); + BOOST_CHECK(alice_order_before != nullptr); + + // Bob is willing to sell 300 IZZYCOIN for at least 10 JILLCOIN + limit_order_create_operation bob_sell_op = create_sell_operation(bob.id, izzycoin.amount(300 * IZZY_PRECISION), + jillcoin.amount( + 10 * + JILL_PRECISION)); + trx.clear(); + trx.operations.push_back(bob_sell_op); + asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, bob_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type bob_order_id = ptx.operation_results[0].get(); + + // Check that the orders were filled by ensuring that they are no longer on the order books + const limit_order_object* alice_order = db.find(alice_order_id); + BOOST_CHECK(alice_order == nullptr); + const limit_order_object* bob_order = db.find(bob_order_id); + BOOST_CHECK(bob_order == nullptr); + + + // Check the new balances of the maker + // Alice was the maker; she is receiving IZZYCOIN + asset expected_izzy_fee = izzycoin.amount( + 300 * IZZY_PRECISION * izzy_maker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(alice, izzycoin), + (300 * IZZY_PRECISION) - alice_sell_fee.amount.value - expected_izzy_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0); + + // Check the new balance of the taker + // Bob was the taker; he is receiving JILLCOIN + asset expected_jill_fee = jillcoin.amount( + 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), + (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 0); + + // Check the asset issuer's accumulated fees + BOOST_CHECK(izzycoin.dynamic_asset_data_id(db).accumulated_fees == expected_izzy_fee.amount); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount); + + } FC_LOG_AND_RETHROW() + } + + + /** + * Test of **default** taker fees charged when filling limit orders after HF for a UIA. + * + * This test is similar to simple_match_and_fill_with_different_fees_uia_1 + * except that the taker fee is not explicitly set and instead defaults to the maker fee. + */ + BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_uia_4) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((jill)(izzy)(alice)(bob)); + + // Initialize tokens + price price(asset(1, asset_id_type(1)), asset(1)); + + const uint16_t JILL_PRECISION = 100; + const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, + jill_market_fee_percent); + + const uint16_t IZZY_PRECISION = 1000; + const uint16_t izzy_market_fee_percent = 5 * GRAPHENE_1_PERCENT; + create_user_issued_asset("ICOIN", izzy, charge_market_fee, price, 3, + izzy_market_fee_percent); + + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object& jillcoin = get_asset("JCOIN"); + const asset_object& izzycoin = get_asset("ICOIN"); + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test that default taker values has not been set + ////// + // The taker fees should automatically default to maker fees if the taker fee is not explicitly set + // UNUSED: uint16_t jill_maker_fee_percent = jill_market_fee_percent; + uint16_t jill_taker_fee_percent = jill_market_fee_percent; + + uint16_t izzy_maker_fee_percent = izzy_market_fee_percent; + // UNUSED: uint16_t izzy_taker_fee_percent = izzy_market_fee_percent; + + // Check the taker fee for JCOIN: it should still not be set + asset_object updated_asset = jillcoin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + // Check the taker fee for ICOIN: it should still not be set + updated_asset = izzycoin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + + ////// + // After HF, create limit orders that will perfectly match + ////// + BOOST_TEST_MESSAGE("Issuing 10 jillcoin to alice"); + issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking alice's balance"); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION); + + BOOST_TEST_MESSAGE("Issuing 300 izzycoin to bob"); + issue_uia(bob, izzycoin.amount(300 * IZZY_PRECISION)); + BOOST_TEST_MESSAGE("Checking bob's balance"); + BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 300 * IZZY_PRECISION); + + // Alice and Bob place orders which match, and are completely filled by each other + // Alice is willing to sell 10 JILLCOIN for at least 300 IZZYCOIN + limit_order_create_operation alice_sell_op = create_sell_operation(alice.id, + jillcoin.amount(10 * JILL_PRECISION), + izzycoin.amount(300 * + IZZY_PRECISION)); + trx.clear(); + trx.operations.push_back(alice_sell_op); + asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type alice_order_id = ptx.operation_results[0].get(); + + const limit_order_object* alice_order_before = db.find(alice_order_id); + BOOST_CHECK(alice_order_before != nullptr); + + // Bob is willing to sell 300 IZZYCOIN for at least 10 JILLCOIN + limit_order_create_operation bob_sell_op = create_sell_operation(bob.id, izzycoin.amount(300 * IZZY_PRECISION), + jillcoin.amount( + 10 * + JILL_PRECISION)); + trx.clear(); + trx.operations.push_back(bob_sell_op); + asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, bob_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type bob_order_id = ptx.operation_results[0].get(); + + // Check that the orders were filled by ensuring that they are no longer on the order books + const limit_order_object* alice_order = db.find(alice_order_id); + BOOST_CHECK(alice_order == nullptr); + const limit_order_object* bob_order = db.find(bob_order_id); + BOOST_CHECK(bob_order == nullptr); + + + // Check the new balances of the maker + // Alice was the maker; she is receiving IZZYCOIN + asset expected_izzy_fee = izzycoin.amount( + 300 * IZZY_PRECISION * izzy_maker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(alice, izzycoin), + (300 * IZZY_PRECISION) - alice_sell_fee.amount.value - expected_izzy_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0); + + // Check the new balance of the taker + // Bob was the taker; he is receiving JILLCOIN + asset expected_jill_fee = jillcoin.amount( + 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), + (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(bob, izzycoin), 0); + + // Check the asset issuer's accumulated fees + BOOST_CHECK(izzycoin.dynamic_asset_data_id(db).accumulated_fees == expected_izzy_fee.amount); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount); + + } FC_LOG_AND_RETHROW() + } + + + + /** + * Test of different maker and taker fees charged when filling limit orders after HF for a smart asset + */ + BOOST_AUTO_TEST_CASE(simple_match_and_fill_with_different_fees_smart_asset) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((jill)(izzy)(alice)(bob)); + ACTORS((smartissuer)(feedproducer)); + + // Initialize tokens + price price(asset(1, asset_id_type(1)), asset(1)); + const uint16_t JILL_PRECISION = 100; + const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + const asset_object jillcoin = create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, + jill_market_fee_percent); + + const uint16_t SMARTBIT_PRECISION = 10000; + const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + create_bitasset("SMARTBIT", smartissuer.id, smartbit_market_fee_percent, + charge_market_fee, 4); + + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object &smartbit = get_asset("SMARTBIT"); + + const auto &core = asset_id_type()(db); + + update_feed_producers(smartbit, {feedproducer.id}); + + price_feed current_feed; + current_feed.settlement_price = smartbit.amount(100) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default + publish_feed(smartbit, feedproducer, current_feed); + + FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test that new values can be set + ////// + // Define the new taker fees + uint16_t jill_maker_fee_percent = jill_market_fee_percent; + uint16_t jill_taker_fee_percent = jill_maker_fee_percent / 2; + + uint16_t smartbit_maker_fee_percent = 1 * GRAPHENE_1_PERCENT; + uint16_t smartbit_taker_fee_percent = 3 * GRAPHENE_1_PERCENT; + + // Set the new taker fee for JILLCOIN + asset_update_operation uop; + uop.issuer = jill.id; + uop.asset_to_update = jillcoin.get_id(); + uop.new_options = jillcoin.options; + uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, jill_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for JILLCOIN + asset_object updated_asset = jillcoin.get_id()(db); + uint16_t expected_taker_fee_percent = jill_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + + // Set the new taker fee for SMARTBIT + uop = asset_update_operation(); + uop.issuer = smartissuer.id; + uop.asset_to_update = smartbit.get_id(); + uop.new_options = smartbit.options; + uop.new_options.market_fee_percent = smartbit_maker_fee_percent; + uop.new_options.extensions.value.taker_fee_percent = smartbit_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, smartissuer_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for SMARTBIT + updated_asset = smartbit.get_id()(db); + expected_taker_fee_percent = smartbit_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + // Check the maker fee for SMARTBIT + updated_asset = smartbit.get_id()(db); + expected_taker_fee_percent = smartbit_maker_fee_percent; + BOOST_CHECK_EQUAL(expected_taker_fee_percent, updated_asset.options.market_fee_percent); + + + ////// + // After HF, create limit orders that will perfectly match + ////// + BOOST_TEST_MESSAGE("Issuing 10 jillcoin to alice"); + issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking alice's balance"); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION); + + BOOST_TEST_MESSAGE("Issuing 300 SMARTBIT to bob"); + transfer(committee_account, bob.id, asset(10000000)); + publish_feed(smartbit, feedproducer, current_feed); // Publish a recent feed + borrow(bob, smartbit.amount(300 * SMARTBIT_PRECISION), asset(2 * 300 * SMARTBIT_PRECISION)); + BOOST_TEST_MESSAGE("Checking bob's balance"); + BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 300 * SMARTBIT_PRECISION); + + // Alice and Bob place orders which match, and are completely filled by each other + // Alice is willing to sell 10 JILLCOIN for at least 300 SMARTBIT + limit_order_create_operation alice_sell_op = create_sell_operation(alice.id, + jillcoin.amount(10 * JILL_PRECISION), + smartbit.amount(300 * SMARTBIT_PRECISION)); + trx.clear(); + trx.operations.push_back(alice_sell_op); + asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type alice_order_id = ptx.operation_results[0].get(); + + const limit_order_object *alice_order_before = db.find(alice_order_id); + BOOST_CHECK(alice_order_before != nullptr); + + + // Bob is willing to sell 300 SMARTBIT for at least 10 JILLCOIN + limit_order_create_operation bob_sell_op + = create_sell_operation(bob.id, smartbit.amount(300 * SMARTBIT_PRECISION), + jillcoin.amount(10 * JILL_PRECISION)); + trx.clear(); + trx.operations.push_back(bob_sell_op); + asset bob_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, bob_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type bob_order_id = ptx.operation_results[0].get(); + + // Check that the orders were filled by ensuring that they are no longer on the order books + const limit_order_object *alice_order = db.find(alice_order_id); + BOOST_CHECK(alice_order == nullptr); + const limit_order_object *bob_order = db.find(bob_order_id); + BOOST_CHECK(bob_order == nullptr); + + + // Check the new balances of the maker + // Alice was the maker; she is receiving SMARTBIT + asset expected_smartbit_fee = smartbit.amount( + 300 * SMARTBIT_PRECISION * smartbit_maker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit), + (300 * SMARTBIT_PRECISION) - alice_sell_fee.amount.value - + expected_smartbit_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0); + + // Check the new balance of the taker + // Bob was the taker; he is receiving JILLCOIN + asset expected_jill_fee = jillcoin.amount( + 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT); + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), + (10 * JILL_PRECISION) - bob_sell_fee.amount.value - expected_jill_fee.amount.value); + BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0); + + // Check the asset issuer's accumulated fees + BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee.amount); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee.amount); + + } FC_LOG_AND_RETHROW() + } + + + /** + * Test of different maker and taker fees charged when filling limit orders after HF for a smart asset + * and a user-issued asset + * + * 1. (Order 1) An order will be placed to offer JCOIN + * + * 2. (Order 2) A matching-order will be placed to offer SMARTBIT. + * Order 2 is large enough that it should be partially filled, and Order 1 will be completely filled. + * Order 1 should be charged a maker fee, and Order 2 should be charged a taker fee. + * Order 2 should remain on the book. + * + * 3. (Order 3) A matching order will be placed to offer JCOIN. + * Order 3 should be charged a taker fee, and Order 2 should be charged a maker fee. + * + * Summary: Order 2 should be charged a taker fee when matching Order 1, + * and Order 2 should be charged a maker fee when matching Order 3. + */ + BOOST_AUTO_TEST_CASE(partial_maker_partial_taker_fills_1) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((jill)(izzy)(alice)(bob)(charlie)); + ACTORS((smartissuer)(feedproducer)); + + // Initialize tokens + price price(asset(1, asset_id_type(1)), asset(1)); + const uint16_t JILL_PRECISION = 100; + const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + const asset_object jillcoin = create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, + jill_market_fee_percent); + + const uint16_t SMARTBIT_PRECISION = 10000; + const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + create_bitasset("SMARTBIT", smartissuer.id, smartbit_market_fee_percent, + charge_market_fee, 4); + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object &smartbit = get_asset("SMARTBIT"); + + const auto &core = asset_id_type()(db); + + update_feed_producers(smartbit, {feedproducer.id}); + + price_feed current_feed; + current_feed.settlement_price = smartbit.amount(100) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default + publish_feed(smartbit, feedproducer, current_feed); + + FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); + + + ////// + // Advance to activate hardfork + ////// + generate_blocks(HARDFORK_BSIP_81_TIME); + generate_block(); + trx.clear(); + set_expiration(db, trx); + + + ////// + // After HF, test that new values can be set + ////// + // Define the new taker fees + uint16_t jill_maker_fee_percent = jill_market_fee_percent; + uint16_t jill_taker_fee_percent = jill_maker_fee_percent / 2; + + uint16_t smartbit_maker_fee_percent = 1 * GRAPHENE_1_PERCENT; + uint16_t smartbit_taker_fee_percent = 3 * GRAPHENE_1_PERCENT; + + // Set the new taker fee for JILLCOIN + asset_update_operation uop; + uop.issuer = jill.id; + uop.asset_to_update = jillcoin.get_id(); + uop.new_options = jillcoin.options; + uop.new_options.extensions.value.taker_fee_percent = jill_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, jill_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for JILLCOIN + asset_object updated_asset = jillcoin.get_id()(db); + uint16_t expected_taker_fee_percent = jill_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + // Check the maker fee for JILLCOIN + uint16_t expected_maker_fee_percent = jill_maker_fee_percent; + BOOST_CHECK_EQUAL(expected_maker_fee_percent, updated_asset.options.market_fee_percent); + + + // Set the new taker fee for SMARTBIT + uop = asset_update_operation(); + uop.issuer = smartissuer.id; + uop.asset_to_update = smartbit.get_id(); + uop.new_options = smartbit.options; + uop.new_options.market_fee_percent = smartbit_maker_fee_percent; + uop.new_options.extensions.value.taker_fee_percent = smartbit_taker_fee_percent; + + trx.clear(); + trx.operations.push_back(uop); + db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, smartissuer_private_key); + PUSH_TX(db, trx); // No exception should be thrown + + // Check the taker fee for SMARTBIT + updated_asset = smartbit.get_id()(db); + expected_taker_fee_percent = smartbit_taker_fee_percent; + BOOST_CHECK(updated_asset.options.extensions.value.taker_fee_percent.valid()); + BOOST_CHECK_EQUAL(expected_taker_fee_percent, *updated_asset.options.extensions.value.taker_fee_percent); + + // Check the maker fee for SMARTBIT + updated_asset = smartbit.get_id()(db); + expected_taker_fee_percent = smartbit_maker_fee_percent; + BOOST_CHECK_EQUAL(expected_taker_fee_percent, updated_asset.options.market_fee_percent); + + + ////// + // Create Orders 1 and 2 to match. + // Order 1 will be completely filled, and Order 2 will be partially filled. + ////// + // Initialize token balance of actors + BOOST_TEST_MESSAGE("Issuing 10 JCOIN to alice"); + issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking alice's balance"); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION); + + BOOST_TEST_MESSAGE("Issuing 600 SMARTBIT to bob"); + transfer(committee_account, bob.id, asset(2 * 1000 * SMARTBIT_PRECISION)); + publish_feed(smartbit, feedproducer, current_feed); // Publish a recent feed + borrow(bob, smartbit.amount(600 * SMARTBIT_PRECISION), asset(2 * 600 * SMARTBIT_PRECISION)); + BOOST_TEST_MESSAGE("Checking bob's balance"); + BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 600 * SMARTBIT_PRECISION); + + // Alice and Bob place orders which match, and are completely filled by each other + // Alice is willing to sell 10 JILLCOIN for at least 300 SMARTBIT + limit_order_create_operation order_1_op = create_sell_operation(alice.id, + jillcoin.amount(10 * JILL_PRECISION), + smartbit.amount(300 * SMARTBIT_PRECISION)); + trx.clear(); + trx.operations.push_back(order_1_op); + asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type order_1_id = ptx.operation_results[0].get(); + + const limit_order_object *order_1_before = db.find(order_1_id); + BOOST_CHECK(order_1_before != nullptr); + + + // Bob is willing to sell 600 SMARTBIT for at least 20 JILLCOIN + limit_order_create_operation order_2_op + = create_sell_operation(bob.id, smartbit.amount(600 * SMARTBIT_PRECISION), + jillcoin.amount(20 * JILL_PRECISION)); + trx.clear(); + trx.operations.push_back(order_2_op); + asset order_2_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, bob_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type order_2_id = ptx.operation_results[0].get(); + + // Check that order 1 was completely filled by ensuring that they it is no longer on the order book + const limit_order_object *order_1 = db.find(order_1_id); + BOOST_CHECK(order_1 == nullptr); + // Check that order 2 was partially filled by ensuring that they it is still on the order book + const limit_order_object *order_2 = db.find(order_2_id); + BOOST_CHECK(order_2 != nullptr); + + + // Check the new balances of the maker + // Alice was the maker; she is receiving SMARTBIT + asset expected_smartbit_fee = smartbit.amount( + 300 * SMARTBIT_PRECISION * smartbit_maker_fee_percent / GRAPHENE_100_PERCENT); + int64_t expected_alice_balance_after_order_2 = + (300 * SMARTBIT_PRECISION) - alice_sell_fee.amount.value - expected_smartbit_fee.amount.value; + BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit), expected_alice_balance_after_order_2); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0); + + // Check the new balance of the taker + // Bob was the taker; he is receiving JILLCOIN + asset expected_jill_fee = jillcoin.amount( + 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT); + int64_t expected_bob_balance_after_order_2 = + (10 * JILL_PRECISION) - order_2_sell_fee.amount.value - expected_jill_fee.amount.value; + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), expected_bob_balance_after_order_2); + BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0); + + // Check the asset issuer's accumulated fees + share_type expected_smartbit_fee_after_order_2 = expected_smartbit_fee.amount; + share_type expected_jill_fee_after_order_2 = expected_jill_fee.amount; + BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee_after_order_2); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee_after_order_2); + + + ////// + // Create Order 3 to match the remainder of match Order 2 + ////// + // Initialize token balance of actors + BOOST_TEST_MESSAGE("Issuing 5 JCOIN to charlie"); + trx.clear(); + issue_uia(charlie, jillcoin.amount(5 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking charlie's balance"); + BOOST_REQUIRE_EQUAL(get_balance(charlie, jillcoin), 5 * JILL_PRECISION); + + // Charlie is is willing to sell 5 JILLCOIN for at least 150 SMARTBIT + limit_order_create_operation order_3_op = create_sell_operation(charlie.id, + jillcoin.amount(5 * JILL_PRECISION), + smartbit.amount(150 * SMARTBIT_PRECISION)); + trx.clear(); + trx.operations.push_back(order_3_op); + asset charlie_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, charlie_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type order_3_id = ptx.operation_results[0].get(); + + // Order 3 should be completely filled + const limit_order_object *order_3 = db.find(order_3_id); + BOOST_CHECK(order_3 == nullptr); + + // Order 2 should be partially filled and still present on the order books + const limit_order_object *order_2_after = db.find(order_2_id); + BOOST_CHECK(order_2_after != nullptr); + + // Check the new balance of the taker + // Charlie was the taker; he is receiving SMARTBIT + expected_smartbit_fee = smartbit.amount( + 150 * SMARTBIT_PRECISION * smartbit_taker_fee_percent / GRAPHENE_100_PERCENT); + int64_t expected_charlie_balance_after_order_3 = + (150 * SMARTBIT_PRECISION) - charlie_sell_fee.amount.value - expected_smartbit_fee.amount.value; + BOOST_REQUIRE_EQUAL(get_balance(charlie, smartbit), expected_charlie_balance_after_order_3); + BOOST_REQUIRE_EQUAL(get_balance(charlie, jillcoin), 0); + + // Check the new balance of the maker + // Bob was the maker; he is receiving JILLCOIN + asset expected_jill_order_3_fee = jillcoin.amount( + 5 * JILL_PRECISION * jill_maker_fee_percent / GRAPHENE_100_PERCENT); + int64_t expected_bob_balance_after_order_3 = + expected_bob_balance_after_order_2 + + (5 * JILL_PRECISION) - expected_jill_order_3_fee.amount.value; + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), expected_bob_balance_after_order_3); + BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0); + + // Check the asset issuer's accumulated fees + share_type expected_smartbit_fee_after_order_3 = + expected_smartbit_fee_after_order_2 + expected_smartbit_fee.amount; + share_type expected_jill_fee_after_order_3 = expected_jill_fee_after_order_2 + expected_jill_order_3_fee.amount; + BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee_after_order_3); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee_after_order_3); + + } FC_LOG_AND_RETHROW() + } + + + /** + * Test of **default** taker fees charged when filling limit orders after HF for a smart asset + * and a user-issued asset + * + * This test is similar to partial_maker_partial_taker_fills_1 except that + * (a) the taker fee is not explicitly set and instead defaults to the maker fee, and + * (b) Orders 1 and 2 are placed before the HF and Order 3 is placed after the HF. + * + * 1. (Order 1) An order will be placed to offer JCOIN + * + * 2. (Order 2) A matching-order will be placed to offer SMARTBIT. + * Order 2 is large enough that it should be partially filled, and Order 1 will be completely filled. + * Order 1 should be charged a maker fee, and Order 2 should be charged a taker fee. + * Order 2 should remain on the book. + * + * 3. (Order 3) A matching order will be placed to offer JCOIN. + * Order 3 should be charged a taker fee, and Order 2 should be charged a maker fee. + * + * Summary: Order 2 should be charged a taker fee when matching Order 1, + * and Order 2 should be charged a maker fee when matching Order 3. + */ + BOOST_AUTO_TEST_CASE(partial_maker_partial_taker_fills_2) { + try { + // Initialize for the current time + trx.clear(); + set_expiration(db, trx); + + // Initialize actors + ACTORS((jill)(izzy)(alice)(bob)(charlie)); + ACTORS((smartissuer)(feedproducer)); + + // Initialize tokens + price price(asset(1, asset_id_type(1)), asset(1)); + const uint16_t JILL_PRECISION = 100; + const uint16_t jill_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + const asset_object jillcoin = create_user_issued_asset("JCOIN", jill, charge_market_fee, price, 2, + jill_market_fee_percent); + + const uint16_t SMARTBIT_PRECISION = 10000; + const uint16_t smartbit_market_fee_percent = 2 * GRAPHENE_1_PERCENT; + create_bitasset("SMARTBIT", smartissuer.id, smartbit_market_fee_percent, + charge_market_fee, 4); + + uint16_t jill_maker_fee_percent = jill_market_fee_percent; + uint16_t jill_taker_fee_percent = jill_market_fee_percent; + + uint16_t smartbit_maker_fee_percent = smartbit_market_fee_percent; + uint16_t smartbit_taker_fee_percent = smartbit_market_fee_percent; + + + // Obtain asset object after a block is generated to obtain the final object that is commited to the database + generate_block(); + const asset_object &smartbit = get_asset("SMARTBIT"); + + const auto &core = asset_id_type()(db); + + update_feed_producers(smartbit, {feedproducer.id}); + + price_feed current_feed; + current_feed.settlement_price = smartbit.amount(100) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default + publish_feed(smartbit, feedproducer, current_feed); + + FC_ASSERT(smartbit.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price); + + + ////// + // Create Orders 1 and 2 to match. + // Order 1 will be completely filled, and Order 2 will be partially filled. + ////// + // Initialize token balance of actors + BOOST_TEST_MESSAGE("Issuing 10 JCOIN to alice"); + issue_uia(alice, jillcoin.amount(10 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking alice's balance"); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 10 * JILL_PRECISION); + + BOOST_TEST_MESSAGE("Issuing 600 SMARTBIT to bob"); + transfer(committee_account, bob.id, asset(2 * 1000 * SMARTBIT_PRECISION)); + publish_feed(smartbit, feedproducer, current_feed); // Publish a recent feed + borrow(bob, smartbit.amount(600 * SMARTBIT_PRECISION), asset(2 * 600 * SMARTBIT_PRECISION)); + BOOST_TEST_MESSAGE("Checking bob's balance"); + BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 600 * SMARTBIT_PRECISION); + + // Alice and Bob place orders which match, and are completely filled by each other + // Alice is willing to sell 10 JILLCOIN for at least 300 SMARTBIT + limit_order_create_operation order_1_op = create_sell_operation(alice.id, + jillcoin.amount(10 * JILL_PRECISION), + smartbit.amount(300 * SMARTBIT_PRECISION)); + trx.clear(); + trx.operations.push_back(order_1_op); + asset alice_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type order_1_id = ptx.operation_results[0].get(); + + const limit_order_object *order_1_before = db.find(order_1_id); + BOOST_CHECK(order_1_before != nullptr); + + + // Bob is willing to sell 600 SMARTBIT for at least 20 JILLCOIN + limit_order_create_operation order_2_op + = create_sell_operation(bob.id, smartbit.amount(600 * SMARTBIT_PRECISION), + jillcoin.amount(20 * JILL_PRECISION)); + trx.clear(); + trx.operations.push_back(order_2_op); + asset order_2_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, bob_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type order_2_id = ptx.operation_results[0].get(); + + // Check that order 1 was completely filled by ensuring that they it is no longer on the order book + const limit_order_object *order_1 = db.find(order_1_id); + BOOST_CHECK(order_1 == nullptr); + // Check that order 2 was partially filled by ensuring that they it is still on the order book + const limit_order_object *order_2 = db.find(order_2_id); + BOOST_CHECK(order_2 != nullptr); + + + // Check the new balances of the maker + // Alice was the maker; she is receiving SMARTBIT + asset expected_smartbit_fee = smartbit.amount( + 300 * SMARTBIT_PRECISION * smartbit_maker_fee_percent / GRAPHENE_100_PERCENT); + int64_t expected_alice_balance_after_order_2 = + (300 * SMARTBIT_PRECISION) - alice_sell_fee.amount.value - expected_smartbit_fee.amount.value; + BOOST_REQUIRE_EQUAL(get_balance(alice, smartbit), expected_alice_balance_after_order_2); + BOOST_REQUIRE_EQUAL(get_balance(alice, jillcoin), 0); + + // Check the new balance of the taker + // Bob was the taker; he is receiving JILLCOIN + asset expected_jill_fee = jillcoin.amount( + 10 * JILL_PRECISION * jill_taker_fee_percent / GRAPHENE_100_PERCENT); + int64_t expected_bob_balance_after_order_2 = + (10 * JILL_PRECISION) - order_2_sell_fee.amount.value - expected_jill_fee.amount.value; + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), expected_bob_balance_after_order_2); + BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0); + + // Check the asset issuer's accumulated fees + share_type expected_smartbit_fee_after_order_2 = expected_smartbit_fee.amount; + share_type expected_jill_fee_after_order_2 = expected_jill_fee.amount; + BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee_after_order_2); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee_after_order_2); + + + ////// + // After HF, The taker fees should automatically default to maker fees when the taker fee is not explicitly set + ////// + // Check the taker fee for JILLCOIN + asset_object updated_asset = jillcoin.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + // Check the maker fee for JILLCOIN + uint16_t expected_maker_fee_percent = jill_maker_fee_percent; + BOOST_CHECK_EQUAL(expected_maker_fee_percent, updated_asset.options.market_fee_percent); + + // Check the taker fee for SMARTBIT + updated_asset = smartbit.get_id()(db); + BOOST_CHECK(!updated_asset.options.extensions.value.taker_fee_percent.valid()); + + // Check the maker fee for SMARTBIT + expected_maker_fee_percent = smartbit_maker_fee_percent; + BOOST_CHECK_EQUAL(expected_maker_fee_percent, updated_asset.options.market_fee_percent); + + + ////// + // Create Order 3 to match the remainder of match Order 2 + ////// + // Initialize token balance of actors + BOOST_TEST_MESSAGE("Issuing 5 JCOIN to charlie"); + trx.clear(); + issue_uia(charlie, jillcoin.amount(5 * JILL_PRECISION)); + BOOST_TEST_MESSAGE("Checking charlie's balance"); + BOOST_REQUIRE_EQUAL(get_balance(charlie, jillcoin), 5 * JILL_PRECISION); + + // Charlie is is willing to sell 5 JILLCOIN for at least 150 SMARTBIT + limit_order_create_operation order_3_op = create_sell_operation(charlie.id, + jillcoin.amount(5 * JILL_PRECISION), + smartbit.amount(150 * SMARTBIT_PRECISION)); + trx.clear(); + trx.operations.push_back(order_3_op); + asset charlie_sell_fee = db.current_fee_schedule().set_fee(trx.operations.back()); + sign(trx, charlie_private_key); + ptx = PUSH_TX(db, trx); // No exception should be thrown + limit_order_id_type order_3_id = ptx.operation_results[0].get(); + + // Order 3 should be completely filled + const limit_order_object *order_3 = db.find(order_3_id); + BOOST_CHECK(order_3 == nullptr); + + // Order 2 should be partially filled and still present on the order books + const limit_order_object *order_2_after = db.find(order_2_id); + BOOST_CHECK(order_2_after != nullptr); + + // Check the new balance of the taker + // Charlie was the taker; he is receiving SMARTBIT + expected_smartbit_fee = smartbit.amount( + 150 * SMARTBIT_PRECISION * smartbit_taker_fee_percent / GRAPHENE_100_PERCENT); + int64_t expected_charlie_balance_after_order_3 = + (150 * SMARTBIT_PRECISION) - charlie_sell_fee.amount.value - expected_smartbit_fee.amount.value; + BOOST_REQUIRE_EQUAL(get_balance(charlie, smartbit), expected_charlie_balance_after_order_3); + BOOST_REQUIRE_EQUAL(get_balance(charlie, jillcoin), 0); + + // Check the new balance of the maker + // Bob was the maker; he is receiving JILLCOIN + asset expected_jill_order_3_fee = jillcoin.amount( + 5 * JILL_PRECISION * jill_maker_fee_percent / GRAPHENE_100_PERCENT); + int64_t expected_bob_balance_after_order_3 = + expected_bob_balance_after_order_2 + + (5 * JILL_PRECISION) - expected_jill_order_3_fee.amount.value; + BOOST_REQUIRE_EQUAL(get_balance(bob, jillcoin), expected_bob_balance_after_order_3); + BOOST_REQUIRE_EQUAL(get_balance(bob, smartbit), 0); + + // Check the asset issuer's accumulated fees + share_type expected_smartbit_fee_after_order_3 = + expected_smartbit_fee_after_order_2 + expected_smartbit_fee.amount; + share_type expected_jill_fee_after_order_3 = expected_jill_fee_after_order_2 + expected_jill_order_3_fee.amount; + BOOST_CHECK(smartbit.dynamic_asset_data_id(db).accumulated_fees == expected_smartbit_fee_after_order_3); + BOOST_CHECK(jillcoin.dynamic_asset_data_id(db).accumulated_fees == expected_jill_fee_after_order_3); + wdump((jillcoin.dynamic_asset_data_id(db).accumulated_fees)(expected_jill_fee_after_order_3)(expected_jill_fee_after_order_2)(expected_jill_fee.amount)); + wdump((get_asset("JCOIN").dynamic_asset_data_id(db).accumulated_fees)); + + } FC_LOG_AND_RETHROW() + } + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file