diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 8989038d..232f4de2 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -141,11 +141,11 @@ namespace graphene { namespace app { { } - fc::variant network_node_api::get_info() const + fc::variant_object network_node_api::get_info() const { - fc::mutable_variant_object result = _app.p2p_node()->network_get_info(); - result["connection_count"] = _app.p2p_node()->get_connection_count(); - return result; + fc::mutable_variant_object result = _app.p2p_node()->network_get_info(); + result["connection_count"] = _app.p2p_node()->get_connection_count(); + return result; } void network_node_api::add_node(const fc::ip::endpoint& ep) @@ -155,7 +155,22 @@ namespace graphene { namespace app { std::vector network_node_api::get_connected_peers() const { - return _app.p2p_node()->get_connected_peers(); + return _app.p2p_node()->get_connected_peers(); + } + + std::vector network_node_api::get_potential_peers() const + { + return _app.p2p_node()->get_potential_peers(); + } + + fc::variant_object network_node_api::get_advanced_node_parameters() const + { + return _app.p2p_node()->get_advanced_node_parameters(); + } + + void network_node_api::set_advanced_node_parameters(const fc::variant_object& params) + { + return _app.p2p_node()->set_advanced_node_parameters(params); } fc::api login_api::network_broadcast()const diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index bca3ac3c..3d668fa1 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -31,6 +31,8 @@ #include +#define GET_REQUIRED_FEES_MAX_RECURSION 4 + namespace graphene { namespace app { class database_api_impl; @@ -116,7 +118,7 @@ class database_api_impl : public std::enable_shared_from_this bool verify_authority( const signed_transaction& trx )const; bool verify_account_authority( const string& name_or_id, const flat_set& signers )const; processed_transaction validate_transaction( const signed_transaction& trx )const; - vector get_required_fees( const vector& ops, asset_id_type id )const; + vector< fc::variant > get_required_fees( const vector& ops, asset_id_type id )const; // Proposed transactions vector get_proposed_transactions( account_id_type id )const; @@ -1355,18 +1357,86 @@ processed_transaction database_api_impl::validate_transaction( const signed_tran return _db.validate_transaction(trx); } -vector database_api::get_required_fees( const vector& ops, asset_id_type id )const +vector< fc::variant > database_api::get_required_fees( const vector& ops, asset_id_type id )const { return my->get_required_fees( ops, id ); } -vector database_api_impl::get_required_fees( const vector& ops, asset_id_type id )const +/** + * Container method for mutually recursive functions used to + * implement get_required_fees() with potentially nested proposals. + */ +struct get_required_fees_helper { - vector result; + get_required_fees_helper( + const fee_schedule& _current_fee_schedule, + const price& _core_exchange_rate, + uint32_t _max_recursion + ) + : current_fee_schedule(_current_fee_schedule), + core_exchange_rate(_core_exchange_rate), + max_recursion(_max_recursion) + {} + + fc::variant set_op_fees( operation& op ) + { + if( op.which() == operation::tag::value ) + { + return set_proposal_create_op_fees( op ); + } + else + { + asset fee = current_fee_schedule.set_fee( op, core_exchange_rate ); + fc::variant result; + fc::to_variant( fee, result ); + return result; + } + } + + fc::variant set_proposal_create_op_fees( operation& proposal_create_op ) + { + proposal_create_operation& op = proposal_create_op.get(); + std::pair< asset, fc::variants > result; + for( op_wrapper& prop_op : op.proposed_ops ) + { + FC_ASSERT( current_recursion < max_recursion ); + ++current_recursion; + result.second.push_back( set_op_fees( prop_op.op ) ); + --current_recursion; + } + // we need to do this on the boxed version, which is why we use + // two mutually recursive functions instead of a visitor + result.first = current_fee_schedule.set_fee( proposal_create_op, core_exchange_rate ); + fc::variant vresult; + fc::to_variant( result, vresult ); + return vresult; + } + + const fee_schedule& current_fee_schedule; + const price& core_exchange_rate; + uint32_t max_recursion; + uint32_t current_recursion = 0; +}; + +vector< fc::variant > database_api_impl::get_required_fees( const vector& ops, asset_id_type id )const +{ + vector< operation > _ops = ops; + // + // we copy the ops because we need to mutate an operation to reliably + // determine its fee, see #435 + // + + vector< fc::variant > result; result.reserve(ops.size()); - const asset_object& a = id(_db); - for( const auto& op : ops ) - result.push_back( _db.current_fee_schedule().calculate_fee( op, a.options.core_exchange_rate ) ); + const asset_object& a = id(_db); + get_required_fees_helper helper( + _db.current_fee_schedule(), + a.options.core_exchange_rate, + GET_REQUIRED_FEES_MAX_RECURSION ); + for( operation& op : _ops ) + { + result.push_back( helper.set_op_fees( op ) ); + } return result; } diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 46c6d457..30d0d048 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -139,7 +139,7 @@ namespace graphene { namespace app { /** * @brief Return general network information, such as p2p port */ - fc::variant get_info() const; + fc::variant_object get_info() const; /** * @brief add_node Connect to a new peer @@ -149,9 +149,27 @@ namespace graphene { namespace app { /** * @brief Get status of all current connections to peers - */ + */ std::vector get_connected_peers() const; + /** + * @brief Get advanced node parameters, such as desired and max + * number of connections + */ + fc::variant_object get_advanced_node_parameters() const; + + /** + * @brief Set advanced node parameters, such as desired and max + * number of connections + * @param params a JSON object containing the name/value pairs for the parameters to set + */ + void set_advanced_node_parameters(const fc::variant_object& params); + + /** + * @brief Return list of potential peers + */ + std::vector get_potential_peers() const; + private: application& _app; }; @@ -217,6 +235,9 @@ FC_API(graphene::app::network_node_api, (get_info) (add_node) (get_connected_peers) + (get_potential_peers) + (get_advanced_node_parameters) + (set_advanced_node_parameters) ) FC_API(graphene::app::login_api, (login) diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 1b18dd94..e543819a 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -447,7 +447,7 @@ class database_api * For each operation calculate the required fee in the specified asset type. If the asset type does * not have a valid core_exchange_rate */ - vector get_required_fees( const vector& ops, asset_id_type id )const; + vector< fc::variant > get_required_fees( const vector& ops, asset_id_type id )const; /////////////////////////// // Proposed transactions // diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index d26b621c..eb9cda8e 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -20,10 +20,13 @@ */ #include + +#include #include #include +#include #include -#include + #include namespace graphene { namespace chain { @@ -95,6 +98,26 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio object_id_type account_create_evaluator::do_apply( const account_create_operation& o ) { try { + + uint16_t referrer_percent = o.referrer_percent; + bool has_small_percent = ( + (db().head_block_time() <= HARDFORK_453_TIME) + && (o.referrer != o.registrar ) + && (o.referrer_percent != 0 ) + && (o.referrer_percent <= 0x100) + ); + + if( has_small_percent ) + { + if( referrer_percent >= 100 ) + { + wlog( "between 100% and 0x100%: ${o}", ("o", o) ); + } + referrer_percent = referrer_percent*100; + if( referrer_percent > GRAPHENE_100_PERCENT ) + referrer_percent = GRAPHENE_100_PERCENT; + } + const auto& new_acnt_object = db().create( [&]( account_object& obj ){ obj.registrar = o.registrar; obj.referrer = o.referrer; @@ -103,7 +126,7 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio auto& params = db().get_global_properties().parameters; obj.network_fee_percentage = params.network_percent_of_fee; obj.lifetime_referrer_fee_percentage = params.lifetime_referrer_percent_of_fee; - obj.referrer_rewards_percentage = o.referrer_percent; + obj.referrer_rewards_percentage = referrer_percent; obj.name = o.name; obj.owner = o.owner; @@ -112,6 +135,15 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio obj.statistics = db().create([&](account_statistics_object& s){s.owner = obj.id;}).id; }); + if( has_small_percent ) + { + wlog( "Account affected by #453 registered in block ${n}: ${na} reg=${reg} ref=${ref}:${refp} ltr=${ltr}:${ltrp}", + ("n", db().head_block_num()) ("na", new_acnt_object.id) + ("reg", o.registrar) ("ref", o.referrer) ("ltr", new_acnt_object.lifetime_referrer) + ("refp", new_acnt_object.referrer_rewards_percentage) ("ltrp", new_acnt_object.lifetime_referrer_fee_percentage) ); + wlog( "Affected account object is ${o}", ("o", new_acnt_object) ); + } + const auto& dynamic_properties = db().get_dynamic_global_properties(); db().modify(dynamic_properties, [](dynamic_global_property_object& p) { ++p.accounts_registered_this_interval; @@ -243,6 +275,7 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator: // Upgrade from basic account. a.statistics(d).process_fees(a, d); assert(a.is_basic_account(d.head_block_time())); + a.referrer = a.get_id(); a.membership_expiration_date = d.head_block_time() + fc::days(365); } }); diff --git a/libraries/chain/account_object.cpp b/libraries/chain/account_object.cpp index 255ba5b1..a68c8260 100644 --- a/libraries/chain/account_object.cpp +++ b/libraries/chain/account_object.cpp @@ -128,6 +128,14 @@ void account_statistics_object::process_fees(const account_object& a, database& } } +void account_statistics_object::pay_fee( share_type core_fee, share_type cashback_vesting_threshold ) +{ + if( core_fee > cashback_vesting_threshold ) + pending_fees += core_fee; + else + pending_vested_fees += core_fee; +} + void account_object::options_type::validate() const { auto needed_witnesses = num_witness; diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 0a280a81..c1d1456f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -115,6 +116,7 @@ void database::cancel_order( const limit_order_object& order, bool create_virtua } }); adjust_balance(order.seller, refunded); + adjust_balance(order.seller, order.deferred_fee); if( create_virtual_op ) { @@ -272,6 +274,15 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c assert( pays.asset_id != receives.asset_id ); push_applied_operation( fill_order_operation( order.id, order.seller, pays, receives, issuer_fees ) ); + // conditional because cheap integer comparison may allow us to avoid two expensive modify() and object lookups + if( order.deferred_fee > 0 ) + { + modify( seller.statistics(*this), [&]( account_statistics_object& statistics ) + { + statistics.pay_fee( order.deferred_fee, get_global_properties().parameters.cashback_vesting_threshold ); + } ); + } + if( pays == order.amount_for_sale() ) { remove( order ); @@ -281,6 +292,7 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c { modify( order, [&]( limit_order_object& b ) { b.for_sale -= pays.amount; + b.deferred_fee = 0; }); /** * There are times when the AMOUNT_FOR_SALE * SALE_PRICE == 0 which means that we @@ -416,15 +428,8 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan auto limit_itr = limit_price_index.lower_bound( max_price ); auto limit_end = limit_price_index.upper_bound( min_price ); - if( limit_itr == limit_end ) { - /* - if( head_block_num() > 300000 ) - ilog( "no orders below between: ${p} and: ${m}", - ("p", bitasset.current_feed.max_short_squeeze_price()) - ("m", max_price) ); - */ + if( limit_itr == limit_end ) return false; - } auto call_min = price::min( bitasset.options.short_backing_asset, mia.id ); auto call_max = price::max( bitasset.options.short_backing_asset, mia.id ); @@ -434,14 +439,6 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan bool filled_limit = false; bool margin_called = false; - /* - if( head_block_num() >= 11510 && head_block_num() <= 11512) { - idump(("enter loop") ); - auto tmp = call_itr; - while( tmp != call_end ) { edump( (*tmp) ); ++tmp; } - } - */ - while( !check_for_blackswan( mia, enable_black_swan ) && call_itr != call_end ) { bool filled_call = false; @@ -457,9 +454,22 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan match_price.validate(); + // would be margin called, but there is no matching order #436 + bool feed_protected = ( bitasset.current_feed.settlement_price > ~call_itr->call_price ); + if( feed_protected && (head_block_time() > HARDFORK_436_TIME) ) + return margin_called; + + // would be margin called, but there is no matching order if( match_price > ~call_itr->call_price ) return margin_called; + if( feed_protected ) + { + ilog( "Feed protected margin call executing (HARDFORK_436_TIME not here yet)" ); + idump( (*call_itr) ); + idump( (*limit_itr) ); + } + // idump((*call_itr)); // idump((*limit_itr)); diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 878d98cb..ee2f1580 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -74,18 +74,25 @@ database& generic_evaluator::db()const { return trx_state->db(); } } } - void generic_evaluator::pay_fee() - { try { + void generic_evaluator::convert_fee() + { if( fee_asset->get_id() != asset_id_type() ) + { db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& d) { d.accumulated_fees += fee_from_account.amount; d.fee_pool -= core_fee_paid; }); - db().modify(*fee_paying_account_statistics, [&](account_statistics_object& s) { - if( core_fee_paid > db().get_global_properties().parameters.cashback_vesting_threshold ) - s.pending_fees += core_fee_paid; - else - s.pending_vested_fees += core_fee_paid; + } + return; + } + + void generic_evaluator::pay_fee() + { try { + database& d = db(); + /// TODO: db().pay_fee( account_id, core_fee ); + d.modify(*fee_paying_account_statistics, [&](account_statistics_object& s) + { + s.pay_fee( core_fee_paid, d.get_global_properties().parameters.cashback_vesting_threshold ); }); } FC_CAPTURE_AND_RETHROW() } diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 5067292d..102b1071 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -77,6 +77,11 @@ namespace graphene { namespace chain { /// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees void process_fees(const account_object& a, database& d) const; + + /** + * Core fees are paid into the account_statistics_object by this method + */ + void pay_fee( share_type core_fee, share_type cashback_vesting_threshold ); }; /** diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp index 015f10a5..cfbbaea2 100644 --- a/libraries/chain/include/graphene/chain/evaluator.hpp +++ b/libraries/chain/include/graphene/chain/evaluator.hpp @@ -86,6 +86,22 @@ namespace graphene { namespace chain { virtual operation_result evaluate(const operation& op) = 0; virtual operation_result apply(const operation& op) = 0; + /** + * Routes the fee to where it needs to go. The default implementation + * routes the fee to the account_statistics_object of the fee_paying_account. + * + * Before pay_fee() is called, the fee is computed by prepare_fee() and has been + * moved out of the fee_paying_account and (if paid in a non-CORE asset) converted + * by the asset's fee pool. + * + * Therefore, when pay_fee() is called, the fee only exists in this->core_fee_paid. + * So pay_fee() need only increment the receiving balance. + * + * The default implementation simply calls account_statistics_object->pay_fee() to + * increment pending_fees or pending_vested_fees. + */ + virtual void pay_fee(); + database& db()const; //void check_required_authorities(const operation& op); @@ -97,10 +113,24 @@ namespace graphene { namespace chain { * * This method verifies that the fee is valid and sets the object pointer members and the fee fields. It should * be called during do_evaluate. + * + * In particular, core_fee_paid field is set by prepare_fee(). */ void prepare_fee(account_id_type account_id, asset fee); - /// Pays the fee and returns the number of CORE asset that were paid. - void pay_fee(); + + /** + * Convert the fee into BTS through the exchange pool. + * + * Reads core_fee_paid field for how much CORE is deducted from the exchange pool, + * and fee_from_account for how much USD is added to the pool. + * + * Since prepare_fee() does the validation checks ensuring the account and fee pool + * have sufficient balance and the exchange rate is correct, + * those validation checks are not replicated here. + * + * Rather than returning a value, this method fills in core_fee_paid field. + */ + void convert_fee(); object_id_type get_relative_id( object_id_type rel_id )const; @@ -201,11 +231,13 @@ namespace graphene { namespace chain { return eval->do_evaluate(op); } + virtual operation_result apply(const operation& o) final override { auto* eval = static_cast(this); const auto& op = o.get(); + convert_fee(); pay_fee(); auto result = eval->do_apply(op); diff --git a/libraries/chain/include/graphene/chain/hardfork.hpp b/libraries/chain/include/graphene/chain/hardfork.hpp index 41339f1f..4095dd00 100644 --- a/libraries/chain/include/graphene/chain/hardfork.hpp +++ b/libraries/chain/include/graphene/chain/hardfork.hpp @@ -29,3 +29,12 @@ #define HARDFORK_415_TIME (fc::time_point_sec( 1446652800 )) #define HARDFORK_416_TIME (fc::time_point_sec( 1446652800 )) #define HARDFORK_419_TIME (fc::time_point_sec( 1446652800 )) + +// #436 Prevent margin call from being triggered unless feed < call price +#define HARDFORK_436_TIME (fc::time_point_sec( 1450288800 )) + +// #445 Refund create order fees on cancel +#define HARDFORK_445_TIME (fc::time_point_sec( 1450288800 )) + +// #453 Hardfork to retroactively correct referral percentages +#define HARDFORK_453_TIME (fc::time_point_sec( 1450288800 )) diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index f6dfa4a0..53094940 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -45,6 +45,7 @@ namespace graphene { namespace chain { account_id_type seller; share_type for_sale; ///< asset id is sell_price.base.asset_id price sell_price; + share_type deferred_fee; pair get_market()const { @@ -189,6 +190,12 @@ namespace graphene { namespace chain { asset calculate_market_fee( const asset_object* aobj, const asset& trade_amount ); + /** override the default behavior defined by generic_evalautor which is to + * post the fee to fee_paying_account_stats.pending_fees + */ + virtual void pay_fee() override; + + share_type _deferred_fee = 0; const limit_order_create_operation* _op = nullptr; const account_object* _seller = nullptr; const asset_object* _sell_asset = nullptr; @@ -225,7 +232,7 @@ namespace graphene { namespace chain { FC_REFLECT_DERIVED( graphene::chain::limit_order_object, (graphene::db::object), - (expiration)(seller)(for_sale)(sell_price) + (expiration)(seller)(for_sale)(sell_price)(deferred_fee) ) FC_REFLECT_DERIVED( graphene::chain::call_order_object, (graphene::db::object), diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 479dbe93..d782c39e 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -18,12 +18,14 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#include #include #include #include +#include + #include #include + #include namespace graphene { namespace chain { @@ -59,6 +61,14 @@ void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_o return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } +void limit_order_create_evaluator::pay_fee() +{ + if( db().head_block_time() <= HARDFORK_445_TIME ) + generic_evaluator::pay_fee(); + else + _deferred_fee = core_fee_paid; +} + object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op) { try { const auto& seller_stats = _seller->statistics(db()); @@ -76,6 +86,7 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o obj.for_sale = op.amount_to_sell.amount; obj.sell_price = op.get_price(); obj.expiration = op.expiration; + obj.deferred_fee = _deferred_fee; }); limit_order_id_type order_id = new_order_object.id; // save this because we may remove the object by filling it bool filled = db().apply_order(new_order_object); @@ -103,31 +114,6 @@ asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& auto quote_asset = _order->sell_price.quote.asset_id; auto refunded = _order->amount_for_sale(); - if( d.head_block_time() > HARDFORK_393_TIME ) - { - const auto& fees = d.current_fee_schedule(); - asset create_fee = fees.calculate_fee( limit_order_create_operation() ); - - // then the create fee was only the network fee and not the - // full create_fee - const auto& gprops = d.get_global_properties(); - auto cashback_percent = GRAPHENE_100_PERCENT - gprops.parameters.network_percent_of_fee; - auto cashback_amount = (create_fee.amount * cashback_percent) / GRAPHENE_100_PERCENT; - create_fee.amount -= cashback_amount; - - const auto& core_asset_data = asset_id_type(0)(d).dynamic_asset_data_id(d); - d.modify( core_asset_data, [&]( asset_dynamic_data_object& addo ) { - addo.accumulated_fees -= create_fee.amount; - }); - - /** NOTE: this will adjust the users account balance in a way that cannot be derived entirely - * from the operation history. Consider paying this into cashback rewards, except not all - * accounts have a cashback vesting balance object. - */ - d.adjust_balance( o.fee_paying_account, create_fee ); - } - - d.cancel_order(*_order, false /* don't create a virtual op*/); // Possible optimization: order can be called by canceling a limit order iff the canceled order was at the top of the book. diff --git a/libraries/chain/protocol/fee_schedule.cpp b/libraries/chain/protocol/fee_schedule.cpp index 6950aac0..6a8fc11f 100644 --- a/libraries/chain/protocol/fee_schedule.cpp +++ b/libraries/chain/protocol/fee_schedule.cpp @@ -31,6 +31,8 @@ namespace fc //template const graphene::chain::fee_schedule& smart_ref::operator*() const; } +#define MAX_FEE_STABILIZATION_ITERATION 4 + namespace graphene { namespace chain { typedef fc::smart_ref smart_fee_schedule; @@ -138,8 +140,23 @@ namespace graphene { namespace chain { asset fee_schedule::set_fee( operation& op, const price& core_exchange_rate )const { auto f = calculate_fee( op, core_exchange_rate ); - op.visit( set_fee_visitor( f ) ); - return f; + auto f_max = f; + for( int i=0; i get_block( uint32_t num ); /** Returns the number of accounts registered on the blockchain * @returns the number of registered accounts @@ -620,8 +624,9 @@ class wallet_api * portion of the user's transaction fees. This can be the * same as the registrar_account if there is no referrer. * @param referrer_percent the percentage (0 - 100) of the new user's transaction fees - * not claimed by the blockchain that will be distributed to the - * referrer; the rest will be sent to the registrar + * not claimed by the blockchain that will be distributed to the + * referrer; the rest will be sent to the registrar. Will be + * multiplied by GRAPHENE_1_PERCENT when constructing the transaction. * @param broadcast true to broadcast the transaction on the network * @returns the signed transaction registering the account */ @@ -630,7 +635,7 @@ class wallet_api public_key_type active, string registrar_account, string referrer_account, - uint8_t referrer_percent, + uint32_t referrer_percent, bool broadcast = false); /** @@ -847,6 +852,14 @@ class wallet_api signed_transaction borrow_asset(string borrower_name, string amount_to_borrow, string asset_symbol, string amount_of_collateral, bool broadcast = false); + /** Cancel an existing order + * + * @param order_id the id of order to be cancelled + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction canceling the order + */ + signed_transaction cancel_order(object_id_type order_id, bool broadcast = false); + /** Creates a new user-issued or market-issued asset. * * Many options can be changed later using \c update_asset() @@ -1462,6 +1475,7 @@ FC_API( graphene::wallet::wallet_api, (help) (gethelp) (info) + (about) (begin_builder_transaction) (add_operation_to_builder_transaction) (replace_operation_in_builder_transaction) @@ -1488,6 +1502,7 @@ FC_API( graphene::wallet::wallet_api, (create_account_with_brain_key) (sell_asset) (borrow_asset) + (cancel_order) (transfer) (transfer2) (get_transaction_id) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index b963b225..7e5aa121 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -27,6 +27,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -41,6 +45,7 @@ #include #include +#include #include #include #include @@ -55,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -506,6 +512,41 @@ public: result["active_committee_members"] = global_props.active_committee_members; return result; } + + variant_object about() const + { + string client_version( graphene::utilities::git_revision_description ); + const size_t pos = client_version.find( '/' ); + if( pos != string::npos && client_version.size() > pos ) + client_version = client_version.substr( pos + 1 ); + + fc::mutable_variant_object result; + //result["blockchain_name"] = BLOCKCHAIN_NAME; + //result["blockchain_description"] = BTS_BLOCKCHAIN_DESCRIPTION; + result["client_version"] = client_version; + result["graphene_revision"] = graphene::utilities::git_revision_sha; + result["graphene_revision_age"] = fc::get_approximate_relative_time_string( fc::time_point_sec( graphene::utilities::git_revision_unix_timestamp ) ); + result["fc_revision"] = fc::git_revision_sha; + result["fc_revision_age"] = fc::get_approximate_relative_time_string( fc::time_point_sec( fc::git_revision_unix_timestamp ) ); + result["compile_date"] = "compiled on " __DATE__ " at " __TIME__; + result["boost_version"] = boost::replace_all_copy(std::string(BOOST_LIB_VERSION), "_", "."); + result["openssl_version"] = OPENSSL_VERSION_TEXT; + + std::string bitness = boost::lexical_cast(8 * sizeof(int*)) + "-bit"; +#if defined(__APPLE__) + std::string os = "osx"; +#elif defined(__linux__) + std::string os = "linux"; +#elif defined(_MSC_VER) + std::string os = "win32"; +#else + std::string os = "other"; +#endif + result["build"] = os + " " + bitness; + + return result; + } + chain_property_object get_chain_properties() const { return _remote_db->get_chain_properties(); @@ -849,13 +890,17 @@ public: public_key_type active, string registrar_account, string referrer_account, - uint8_t referrer_percent, + uint32_t referrer_percent, bool broadcast = false) { try { FC_ASSERT( !self.is_locked() ); FC_ASSERT( is_valid_name(name) ); account_create_operation account_create_op; + // #449 referrer_percent is on 0-100 scale, if user has larger + // number it means their script is using GRAPHENE_100_PERCENT scale + // instead of 0-100 scale. + FC_ASSERT( referrer_percent <= 100 ); // TODO: process when pay_from_account is ID account_object registrar_account_object = @@ -867,7 +912,7 @@ public: account_object referrer_account_object = this->get_account( referrer_account ); account_create_op.referrer = referrer_account_object.id; - account_create_op.referrer_percent = referrer_percent; + account_create_op.referrer_percent = uint16_t( referrer_percent * GRAPHENE_1_PERCENT ); account_create_op.registrar = registrar_account_id; account_create_op.name = name; @@ -2866,6 +2911,11 @@ variant wallet_api::info() return my->info(); } +variant_object wallet_api::about() const +{ + return my->about(); +} + fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_string, int sequence_number) const { return detail::derive_private_key( prefix_string, sequence_number ); @@ -2876,7 +2926,7 @@ signed_transaction wallet_api::register_account(string name, public_key_type active_pubkey, string registrar_account, string referrer_account, - uint8_t referrer_percent, + uint32_t referrer_percent, bool broadcast) { return my->register_account( name, owner_pubkey, active_pubkey, registrar_account, referrer_account, referrer_percent, broadcast ); @@ -3453,6 +3503,12 @@ signed_transaction wallet_api::borrow_asset(string seller_name, string amount_to return my->borrow_asset(seller_name, amount_to_sell, asset_symbol, amount_of_collateral, broadcast); } +signed_transaction wallet_api::cancel_order(object_id_type order_id, bool broadcast) +{ + FC_ASSERT(!is_locked()); + return my->cancel_order(order_id, broadcast); +} + string wallet_api::get_key_label( public_key_type key )const { auto key_itr = my->_wallet.labeled_keys.get().find(key); diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index a5eda145..980231e8 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -109,9 +109,9 @@ template struct js_name< fc::safe > { static std::string name(){ template<> struct js_name< std::vector > { static std::string name(){ return "bytes()"; } }; -template<> struct js_name< op_wrapper > { static std::string name(){ return "operation "; } }; template<> struct js_name { static std::string name(){ return "bytes 20"; } }; template<> struct js_name { static std::string name(){ return "bytes 28"; } }; +template<> struct js_name { static std::string name(){ return "bytes 32"; } }; template<> struct js_name { static std::string name(){ return "varuint32"; } }; template<> struct js_name { static std::string name(){ return "varint32"; } }; template<> struct js_name< vote_id_type > { static std::string name(){ return "vote_id"; } }; diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index f7431f40..7e8e41f4 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -169,6 +169,7 @@ void database_fixture::verify_asset_supplies( const database& db ) asset for_sale = o.amount_for_sale(); if( for_sale.asset_id == asset_id_type() ) core_in_orders += for_sale.amount; total_balances[for_sale.asset_id] += for_sale.amount; + total_balances[asset_id_type()] += o.deferred_fee; } for( const call_order_object& o : db.get_index_type().indices() ) { @@ -522,6 +523,41 @@ void database_fixture::issue_uia( const account_object& recipient, asset amount trx.operations.clear(); } +void database_fixture::issue_uia( account_id_type recipient_id, asset amount ) +{ + issue_uia( recipient_id(db), amount ); +} + +void database_fixture::change_fees( + const flat_set< fee_parameters >& new_params, + uint32_t new_scale /* = 0 */ + ) +{ + const chain_parameters& current_chain_params = db.get_global_properties().parameters; + const fee_schedule& current_fees = *(current_chain_params.current_fees); + + flat_map< int, fee_parameters > fee_map; + fee_map.reserve( current_fees.parameters.size() ); + for( const fee_parameters& op_fee : current_fees.parameters ) + fee_map[ op_fee.which() ] = op_fee; + for( const fee_parameters& new_fee : new_params ) + fee_map[ new_fee.which() ] = new_fee; + + fee_schedule_type new_fees; + + for( const std::pair< int, fee_parameters >& item : fee_map ) + new_fees.parameters.insert( item.second ); + if( new_scale != 0 ) + new_fees.scale = new_scale; + + chain_parameters new_chain_params = current_chain_params; + new_chain_params.current_fees = new_fees; + + db.modify(db.get_global_properties(), [&](global_property_object& p) { + p.parameters = new_chain_params; + }); +} + const account_object& database_fixture::create_account( const string& name, const public_key_type& key /* = public_key_type() */ diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 9bb218a9..40087fa9 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -225,7 +225,7 @@ struct database_fixture { const account_object& issuer, uint16_t flags ); void issue_uia( const account_object& recipient, asset amount ); - + void issue_uia( account_id_type recipient_id, asset amount ); const account_object& create_account( const string& name, @@ -263,6 +263,7 @@ struct database_fixture { void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() ); void fund_fee_pool( const account_object& from, const asset_object& asset_to_fund, const share_type amount ); void enable_fees(); + void change_fees( const flat_set< fee_parameters >& new_params, uint32_t new_scale = 0 ); void upgrade_to_lifetime_member( account_id_type account ); void upgrade_to_lifetime_member( const account_object& account ); void upgrade_to_annual_member( account_id_type account ); diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index e65cb92b..601a2584 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -163,6 +164,7 @@ BOOST_AUTO_TEST_CASE(asset_claim_fees_test) } + if( db.head_block_time() <= HARDFORK_413_TIME ) { // can't claim before hardfork GRAPHENE_REQUIRE_THROW( claim_fees( izzy_id, _izzy(1) ), fc::exception ); @@ -591,4 +593,129 @@ BOOST_AUTO_TEST_CASE( account_create_fee_scaling ) BOOST_CHECK_EQUAL(db.get_global_properties().parameters.current_fees->get().basic_fee, 1); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( fee_refund_test ) +{ + try + { + ACTORS((alice)(bob)(izzy)); + + int64_t alice_b0 = 1000000, bob_b0 = 1000000; + + 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(); + asset_id_type usd_id = create_user_issued_asset( "IZZYUSD", izzy_id(db), charge_market_fee ).id; + issue_uia( alice_id, asset( alice_b0, usd_id ) ); + issue_uia( bob_id, asset( bob_b0, usd_id ) ); + + int64_t order_create_fee = 537; + int64_t order_cancel_fee = 129; + + generate_block(); + + for( int i=0; i<2; i++ ) + { + if( i == 1 ) + { + generate_blocks( HARDFORK_445_TIME ); + generate_block(); + } + + // 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({ + limit_order_create_operation::fee_parameters_type { order_create_fee }, + limit_order_cancel_operation::fee_parameters_type { order_cancel_fee } + }); + */ + // C++ -- The above commented out statement doesn't work, I don't know why + // so we will use the following rather lengthy initialization instead + { + flat_set< fee_parameters > 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 ); + } + { + limit_order_cancel_operation::fee_parameters_type cancel_fee_params; + cancel_fee_params.fee = order_cancel_fee; + new_fees.insert( cancel_fee_params ); + } + change_fees( new_fees ); + } + + // Alice creates order + // Bob creates order which doesn't match + + // AAAAGGHH create_sell_order reads trx.expiration #469 + set_expiration( db, trx ); + + // Check non-overlapping + + limit_order_id_type ao1_id = create_sell_order( alice_id, asset(1000), asset(1000, usd_id) )->id; + limit_order_id_type bo1_id = create_sell_order( bob_id, asset(500, usd_id), asset(1000) )->id; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - order_create_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 500 ); + + // Bob cancels order + cancel_limit_order( bo1_id(db) ); + + int64_t cancel_net_fee; + if( db.head_block_time() >= HARDFORK_445_TIME ) + cancel_net_fee = order_cancel_fee; + else + cancel_net_fee = order_create_fee + order_cancel_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 ); + + // Alice cancels order + cancel_limit_order( ao1_id(db) ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 ); + + // Check partial fill + const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id) ); + const limit_order_object* bo2 = create_sell_order( bob_id, asset(100, usd_id), asset(500) ); + + BOOST_CHECK( ao2 != nullptr ); + BOOST_CHECK( bo2 == nullptr ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - order_create_fee - 1000 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 + 100 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee - order_create_fee + 500 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 100 ); + + // cancel Alice order, show that entire deferred_fee was consumed by partial match + cancel_limit_order( *ao2 ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - order_create_fee - 500 - order_cancel_fee ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 + 100 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee - order_create_fee + 500 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 100 ); + + // TODO: Check multiple fill + // there really should be a test case involving Alice creating multiple orders matched by single Bob order + // but we'll save that for future cleanup + + // undo above tx's and reset + generate_block(); + db.pop_block(); + } + } + FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index b85b3161..e7a746c0 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -183,23 +184,34 @@ BOOST_AUTO_TEST_CASE( margin_call_limit_test ) borrow( borrower, bitusd.amount(1000), asset(2000)); borrow( borrower2, bitusd.amount(1000), asset(4000) ); - BOOST_REQUIRE_EQUAL( get_balance( borrower, bitusd ), 1000 ); - BOOST_REQUIRE_EQUAL( get_balance( borrower2, bitusd ), 1000 ); - BOOST_REQUIRE_EQUAL( get_balance( borrower , core ), init_balance - 2000 ); - BOOST_REQUIRE_EQUAL( get_balance( borrower2, core ), init_balance - 4000 ); + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 4000 ); // this should trigger margin call that is below the call limit, but above the // protection threshold. BOOST_TEST_MESSAGE( "Creating a margin call that is NOT protected by the max short squeeze price" ); auto order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1400) ); - BOOST_REQUIRE( order == nullptr ); + if( db.head_block_time() <= HARDFORK_436_TIME ) + { + BOOST_CHECK( order == nullptr ); - BOOST_REQUIRE_EQUAL( get_balance( borrower2, core ), init_balance - 4000 + 1400 ); - BOOST_REQUIRE_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 4000 + 1400 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); - BOOST_REQUIRE_EQUAL( get_balance( borrower, core ), init_balance - 2000 + 600 ); - BOOST_REQUIRE_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower, core ), init_balance - 2000 + 600 ); + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + } + else + { + BOOST_CHECK( order != nullptr ); + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 2000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 4000 ); + } BOOST_TEST_MESSAGE( "Creating a margin call that is protected by the max short squeeze price" ); borrow( borrower, bitusd.amount(1000), asset(2000) ); @@ -207,7 +219,7 @@ BOOST_AUTO_TEST_CASE( margin_call_limit_test ) // this should trigger margin call without protection from the price feed. order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1800) ); - BOOST_REQUIRE( order != nullptr ); + BOOST_CHECK( order != nullptr ); } catch( const fc::exception& e) { edump((e.to_detail_string())); throw; diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index 9021f621..b7e9cf11 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -1229,6 +1229,11 @@ BOOST_AUTO_TEST_CASE(zero_second_vbo) upgrade_to_lifetime_member(alice_id); generate_block(); + // Wait for a maintenance interval to ensure we have a full day's budget to work with. + // Otherwise we may not have enough to feed the witnesses and the worker will end up starved if we start late in the day. + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + auto check_vesting_1b = [&](vesting_balance_id_type vbid) { // this function checks that Alice can't draw any right now, @@ -1304,8 +1309,6 @@ BOOST_AUTO_TEST_CASE(zero_second_vbo) // vote it in, wait for one maint. for vote to take effect vesting_balance_id_type vbid = wid(db).worker.get().balance; - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // wait for another maint. for worker to be paid generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); BOOST_CHECK( vbid(db).get_allowed_withdraw(db.head_block_time()) == asset(0) );