diff --git a/.gitignore b/.gitignore index 7e477b3d84..f007f63e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ libraries/wallet/Doxyfile libraries/wallet/api_documentation.cpp libraries/wallet/doxygen +programs/build_helpers/cat-parts programs/cli_wallet/cli_wallet programs/js_operation_serializer/js_operation_serializer programs/witness_node/witness_node @@ -39,3 +40,8 @@ object_database/* *.pyc *.pyo + +libraries/egenesis/egenesis_brief.cpp +libraries/egenesis/egenesis_full.cpp +libraries/egenesis/embed_genesis + diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 0e57ef7f5f..271820da18 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -130,6 +130,7 @@ class database_api_impl : public std::enable_shared_from_this bool verify_account_authority( const string& name_or_id, const flat_set& signers )const; processed_transaction validate_transaction( const signed_transaction& trx )const; vector< fc::variant > get_required_fees( const vector& ops, asset_id_type id )const; + asset get_operation_fee( const operation& op, const asset_id_type id = asset_id_type(0) )const; // Proposed transactions vector get_proposed_transactions( account_id_type id )const; @@ -1626,6 +1627,11 @@ vector< fc::variant > database_api::get_required_fees( const vector& return my->get_required_fees( ops, id ); } +asset database_api::get_operation_fee( const operation& op, const asset_id_type id )const +{ + return my->get_operation_fee( op, id ); +} + /** * Container method for mutually recursive functions used to * implement get_required_fees() with potentially nested proposals. @@ -1633,11 +1639,13 @@ vector< fc::variant > database_api::get_required_fees( const vector& struct get_required_fees_helper { get_required_fees_helper( + graphene::chain::database& _db, const fee_schedule& _current_fee_schedule, const price& _core_exchange_rate, uint32_t _max_recursion ) - : current_fee_schedule(_current_fee_schedule), + : db(_db), + current_fee_schedule(_current_fee_schedule), core_exchange_rate(_core_exchange_rate), max_recursion(_max_recursion) {} @@ -1650,7 +1658,9 @@ struct get_required_fees_helper } else { - asset fee = current_fee_schedule.set_fee( op, core_exchange_rate ); + fc::variant extended_fee_parameters; + db.build_extended_fee_parameters( op, extended_fee_parameters ); + asset fee = current_fee_schedule.set_fee_extended( op, extended_fee_parameters, core_exchange_rate ); fc::variant result; fc::to_variant( fee, result ); return result; @@ -1676,6 +1686,7 @@ struct get_required_fees_helper return vresult; } + graphene::chain::database& db; const fee_schedule& current_fee_schedule; const price& core_exchange_rate; uint32_t max_recursion; @@ -1694,6 +1705,7 @@ vector< fc::variant > database_api_impl::get_required_fees( const vector database_api_impl::get_required_fees( const vector get_required_fees( const vector& ops, asset_id_type id )const; + /** + * Get minimum fee required by the given operation. Won't recursively check if the operation + * is a proposal. + * @return the fee + */ + asset get_operation_fee( const operation& op, const asset_id_type id = asset_id_type(0) )const; + /////////////////////////// // Proposed transactions // /////////////////////////// @@ -642,6 +649,7 @@ FC_API(graphene::app::database_api, (verify_account_authority) (validate_transaction) (get_required_fees) + (get_operation_fee) // Proposed transactions (get_proposed_transactions) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 9cc4285dd8..b9005c51d7 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -12,6 +12,7 @@ if( GRAPHENE_DISABLE_UNITY_BUILD ) db_balance.cpp db_block.cpp db_debug.cpp + db_fee.cpp db_getter.cpp db_init.cpp db_maint.cpp diff --git a/libraries/chain/account_object.cpp b/libraries/chain/account_object.cpp index 90d97692a5..18a58e4b6b 100644 --- a/libraries/chain/account_object.cpp +++ b/libraries/chain/account_object.cpp @@ -29,6 +29,7 @@ namespace graphene { namespace chain { +/// @return a * p% / 100% share_type cut_fee(share_type a, uint16_t p) { if( a == 0 || p == 0 ) @@ -42,6 +43,20 @@ share_type cut_fee(share_type a, uint16_t p) return r.to_uint64(); } +/// @return a * p% / t% +share_type cut_fee(share_type a, uint16_t p, uint16_t t) +{ + if( a == 0 || p == 0 ) + return 0; + if( p >= t ) + return a; + + fc::uint128 r(a.value); + r *= p; + r /= t; + return r.to_uint64(); +} + void account_balance_object::adjust_balance(const asset& delta) { assert(delta.asset_id == asset_type); @@ -50,8 +65,11 @@ void account_balance_object::adjust_balance(const asset& delta) void account_statistics_object::process_fees(const account_object& a, database& d) const { - if( pending_fees > 0 || pending_vested_fees > 0 ) + if( pending_fees > 0 || pending_vested_fees > 0 + || pending_fees_to_network > 0 || pending_fees_to_non_network > 0 + || pending_vested_fees_to_non_network > 0) { + // split pending fees among network, lifetime referrer, registrar, referrer auto pay_out_fees = [&](const account_object& account, share_type core_fee_total, bool require_vesting) { // Check the referrer -- if he's no longer a member, pay to the lifetime referrer instead. @@ -94,10 +112,65 @@ void account_statistics_object::process_fees(const account_object& a, database& pay_out_fees(a, pending_fees, true); pay_out_fees(a, pending_vested_fees, false); + + // pay pending network fees to network + auto pay_out_network_fees = [&]( share_type network_fee_total ) + { + +#ifndef NDEBUG + const auto& props = d.get_global_properties(); + + share_type reserved = cut_fee(network_fee_total, props.parameters.reserve_percent_of_fee); + share_type accumulated = network_fee_total - reserved; + assert( accumulated + reserved == network_fee_total ); +#endif + d.modify(asset_dynamic_data_id_type()(d), [network_fee_total](asset_dynamic_data_object& d) { + d.accumulated_fees += network_fee_total; + }); + }; + + pay_out_network_fees(pending_fees_to_network); + + + // split pending non-network fees among lifetime referrer, registrar, referrer + auto pay_out_non_network_fees = [&](const account_object& account, share_type core_fee_total, bool require_vesting) + { + // Check the referrer -- if he's no longer a member, pay to the lifetime referrer instead. + // No need to check the registrar; registrars are required to be lifetime members. + if( account.referrer(d).is_basic_account(d.head_block_time()) ) + d.modify(account, [](account_object& a) { + a.referrer = a.lifetime_referrer; + }); + + share_type lifetime_cut = cut_fee(core_fee_total, + account.lifetime_referrer_fee_percentage, + GRAPHENE_100_PERCENT - account.network_fee_percentage); + share_type referral = core_fee_total - lifetime_cut; + + // Potential optimization: Skip some of this math and object lookups by special casing on the account type. + // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to + // it directly. + share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage); + share_type registrar_cut = referral - referrer_cut; + + d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting); + d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting); + d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting); + + assert( referrer_cut + registrar_cut + lifetime_cut == core_fee_total ); + }; + + pay_out_non_network_fees(a, pending_fees_to_non_network, true); + pay_out_non_network_fees(a, pending_vested_fees_to_non_network, false); + d.modify(*this, [&](account_statistics_object& s) { - s.lifetime_fees_paid += pending_fees + pending_vested_fees; + s.lifetime_fees_paid += ( pending_fees + pending_vested_fees + pending_fees_to_network + + pending_fees_to_non_network + pending_vested_fees_to_non_network ); s.pending_fees = 0; s.pending_vested_fees = 0; + s.pending_fees_to_network = 0; + s.pending_fees_to_non_network = 0; + s.pending_vested_fees_to_non_network = 0; }); } } @@ -110,6 +183,46 @@ void account_statistics_object::pay_fee( share_type core_fee, share_type cashbac pending_vested_fees += core_fee; } +void account_statistics_object::pay_fee_pre_split_network( share_type core_fee, + share_type cashback_vesting_threshold, + share_type network_fee ) +{ + if( core_fee <= 0 ) return; + share_type new_network_fee = network_fee; + if( core_fee < network_fee ) new_network_fee = core_fee; + pending_fees_to_network += new_network_fee; + if( core_fee > cashback_vesting_threshold ) + pending_fees_to_non_network += ( core_fee - new_network_fee ); + else + pending_vested_fees_to_non_network += ( core_fee - new_network_fee ); +} + +fc::uint128_t account_statistics_object::compute_coin_seconds_earned(const asset& balance, fc::time_point_sec now)const +{ + if( now <= coin_seconds_earned_last_update ) + return coin_seconds_earned; + int64_t delta_seconds = (now - coin_seconds_earned_last_update).to_seconds(); + + fc::uint128_t delta_coin_seconds = balance.amount.value; + delta_coin_seconds *= delta_seconds; + + return (coin_seconds_earned + delta_coin_seconds); +} + +void account_statistics_object::update_coin_seconds_earned(const asset& balance, fc::time_point_sec now) +{ + if( now <= coin_seconds_earned_last_update ) + return; + coin_seconds_earned = compute_coin_seconds_earned(balance, now); + coin_seconds_earned_last_update = now; +} + +void account_statistics_object::set_coin_seconds_earned(const fc::uint128_t new_coin_seconds, fc::time_point_sec now) +{ + coin_seconds_earned = new_coin_seconds; + coin_seconds_earned_last_update = now; +} + set account_member_index::get_account_members(const account_object& a)const { set result; diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 25daa7cdd9..e082d19739 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -105,6 +105,16 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o FC_ASSERT( op.precision == op.bitasset_opts->short_backing_asset(d).precision ); } + // #583 BSIP10 hard fork check + if( d.head_block_time() <= HARDFORK_583_TIME ) + { + for( const auto& e : op.common_options.extensions ) + { + FC_ASSERT( e.which() != asset_options::future_extensions::tag::value, + "Asset ${s} has an extension which requires hardfork #583.", ("s",op.symbol) ); + } + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -233,6 +243,13 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) const asset_object& a = o.asset_to_update(d); auto a_copy = a; a_copy.options = o.new_options; + // check whether hard fork time passed + if( d.head_block_time() > HARDFORK_583_TIME ) + { + // if new options contain a null CER, replace it with current CER before validate + if( o.new_options.core_exchange_rate.is_null() ) + a_copy.options.core_exchange_rate = a.options.core_exchange_rate; + } a_copy.validate(); if( o.new_issuer ) @@ -275,6 +292,16 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) for( auto id : o.new_options.blacklist_authorities ) d.get_object(id); + // #583 BSIP10 hard fork check + if( d.head_block_time() <= HARDFORK_583_TIME ) + { + for( const auto& e : o.new_options.extensions ) + { + FC_ASSERT( e.which() != asset_options::future_extensions::tag::value, + "Asset ${s} has an extension which requires hardfork #583.", ("s",a.symbol) ); + } + } + return void_result(); } FC_CAPTURE_AND_RETHROW((o)) } @@ -297,7 +324,11 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) d.modify(*asset_to_update, [&](asset_object& a) { if( o.new_issuer ) a.issuer = *o.new_issuer; - a.options = o.new_options; + // make a copy of new_options; if CER is null, replace it with current CER + auto copy_new_options = o.new_options; + if( copy_new_options.core_exchange_rate.is_null() ) + copy_new_options.core_exchange_rate = a.options.core_exchange_rate; + a.options = copy_new_options; }); return void_result(); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index d5ee605988..26fd5b4f60 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -158,3 +158,40 @@ string asset_object::amount_to_string(share_type amount) const result += "." + fc::to_string(scaled_precision.value + decimals).erase(0,1); return result; } + +asset_transfer_fee_mode asset_object::get_transfer_fee_mode() const +{ + // if asset's CER is not set, return default mode + if( id != asset_id_type(0) && options.core_exchange_rate.is_null() ) + return GRAPHENE_DEFAULT_TRANSFER_FEE_MODE; + // find the mode + if( options.extensions.size() > 0 ) + { + for( const asset_options::future_extensions& e : options.extensions ) + { + if( e.which() == asset_options::future_extensions::tag::value ) + return e.get().transfer_fee_mode; + } + } + return GRAPHENE_DEFAULT_TRANSFER_FEE_MODE; +} + +void asset_object::set_transfer_fee_mode(const asset_transfer_fee_mode new_mode) +{ + // firstly look for fee mode in options, if found, change it + for( asset_options::future_extensions& e : options.extensions ) + { + if( e.which() == asset_options::future_extensions::tag::value ) + { + e.get().transfer_fee_mode = new_mode; + return; + } + } + // if not found, add it + if( new_mode == GRAPHENE_DEFAULT_TRANSFER_FEE_MODE ) + return; + struct asset_options::ext::transfer_fee_mode_options new_options; + new_options.transfer_fee_mode = new_mode; + asset_options::future_extensions e = new_options; + options.extensions.emplace_hint( options.extensions.end(), e ); +} diff --git a/libraries/chain/committee_member_evaluator.cpp b/libraries/chain/committee_member_evaluator.cpp index 4e7eb827e5..f145cf6d0f 100644 --- a/libraries/chain/committee_member_evaluator.cpp +++ b/libraries/chain/committee_member_evaluator.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -77,6 +78,18 @@ void_result committee_member_update_global_parameters_evaluator::do_evaluate(con { try { FC_ASSERT(trx_state->_is_proposed_trx); + database& d = db(); + + // #603 hard fork check + if( d.head_block_time() <= HARDFORK_603_TIME ) + { + for( const auto& e : o.new_parameters.extensions ) + { + FC_ASSERT( e.which() != chain_parameters::parameter_extension::tag::value, + "New parameters contain an extension which requires hardfork #603." ); + } + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } @@ -89,4 +102,39 @@ void_result committee_member_update_global_parameters_evaluator::do_apply(const return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } +void_result committee_member_update_core_asset_evaluator::do_evaluate(const committee_member_update_core_asset_operation& o) +{ try { + FC_ASSERT(trx_state->_is_proposed_trx); + + database& d = db(); + + // #583 BSIP10 hard fork check + if( d.head_block_time() <= HARDFORK_583_TIME ) + FC_THROW( "Operation requires hardfork #583" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +void_result committee_member_update_core_asset_evaluator::do_apply(const committee_member_update_core_asset_operation& o) +{ try { + database& d = db(); + + const asset_object& a = d.get_core_asset(); + + auto a_copy = a; + a_copy.options = o.new_options; + auto new_mode = a_copy.get_transfer_fee_mode(); + + a_copy = a; + a_copy.options.market_fee_percent = o.new_options.market_fee_percent; + a_copy.options.max_market_fee = o.new_options.max_market_fee; + a_copy.set_transfer_fee_mode( new_mode ); + + d.modify(a, [&](asset_object& ao) { + ao.options = a_copy.options; + }); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + } } // graphene::chain diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index aa9f61273b..d989447b00 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -25,6 +25,7 @@ #include "db_balance.cpp" #include "db_block.cpp" #include "db_debug.cpp" +#include "db_fee.cpp" #include "db_getter.cpp" #include "db_init.cpp" #include "db_maint.cpp" diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index a70f077bb6..dc57e35c5a 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -68,12 +68,36 @@ void database::adjust_balance(account_id_type account, asset delta ) b.asset_type = delta.asset_id; b.balance = delta.amount.value; }); + // Update coin_seconds_earned and etc + if( head_block_time() > HARDFORK_603_TIME ) + { + // TODO how to deal with smart coins? + if( delta.asset_id == asset_id_type() ) + { + modify( account(*this).statistics(*this), [&](account_statistics_object& s) { + s.set_coin_seconds_earned( 0, head_block_time() ); + }); + } + } } else { if( delta.amount < 0 ) FC_ASSERT( itr->get_balance() >= -delta, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", ("a",account(*this).name)("b",to_pretty_string(itr->get_balance()))("r",to_pretty_string(-delta))); + const asset original_balance = itr->get_balance(); modify(*itr, [delta](account_balance_object& b) { b.adjust_balance(delta); }); + // Update coin_seconds_earned and etc + if( head_block_time() > HARDFORK_603_TIME ) + { + // TODO how to deal with smart coins? + if( delta.asset_id == asset_id_type() ) + { + modify( account(*this).statistics(*this), [&](account_statistics_object& s) { + //TODO will coin_seconds_earned expire? seems hard to calculate + s.update_coin_seconds_earned( original_balance, head_block_time() ); + }); + } + } } } FC_CAPTURE_AND_RETHROW( (account)(delta) ) } diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index e141cd08bb..718fdbe635 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -283,7 +283,7 @@ processed_transaction database::push_proposal(const proposal_object& proposal) { _applied_ops.resize( old_applied_ops_size ); } - elog( "e", ("e",e.to_detail_string() ) ); + elog( "${e}", ("e",e.to_detail_string() ) ); throw; } diff --git a/libraries/chain/db_fee.cpp b/libraries/chain/db_fee.cpp new file mode 100644 index 0000000000..722ec9469d --- /dev/null +++ b/libraries/chain/db_fee.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +namespace graphene { namespace chain { + +void database::build_extended_fee_parameters( const operation& op, variant& v ) +{ + switch( op.which() ) + { + case operation::tag::value : + { + const transfer_operation& o = op.get(); + const asset_object& a = get( o.amount.asset_id ); + transfer_operation::extended_calculate_fee_parameters e; + e.transferring_asset_transfer_fee_mode = a.get_transfer_fee_mode(); + to_variant( e, v ); + break; + } + case operation::tag::value : + { + const transfer_v2_operation& o = op.get(); + const asset_object& a = get( o.amount.asset_id ); + transfer_v2_operation::extended_calculate_fee_parameters e; + e.scale = current_fee_schedule().scale; + e.transferring_asset_transfer_fee_mode = a.get_transfer_fee_mode(); + e.transferring_asset_core_exchange_rate = a.options.core_exchange_rate; + to_variant( e, v ); + break; + } + default: + break; + } +} + +} } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 914b3fa8a3..37b90b18e9 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -171,6 +171,8 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index a4127c25b6..fe127eabe5 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -79,6 +79,35 @@ database& generic_evaluator::db()const { return trx_state->db(); } } } + void generic_evaluator::prepare_fee_from_coin_seconds(const operation& o) + { + const auto& fee_options = db().get_global_properties().parameters.get_coin_seconds_as_fees_options(); + const auto& max_op_fee = fee_options.max_fee_from_coin_seconds_by_operation; + if( max_op_fee.size() > o.which() && max_op_fee[o.which()] > 0 ) // if fee can be paid with coin seconds + { + const asset& core_balance = db().get_balance( fee_paying_account->get_id(), asset_id_type() ); + const auto payer_membership = fee_paying_account->get_membership( db().head_block_time() ); + coin_seconds_earned = fee_paying_account_statistics->compute_coin_seconds_earned( + core_balance, db().head_block_time() ); + if( coin_seconds_earned > 0 ) // if payer have some coin seconds to pay + { + coin_seconds_as_fees_rate = fee_options.coin_seconds_as_fees_rate[payer_membership]; + fc::uint128_t coin_seconds_to_fees = coin_seconds_earned; + coin_seconds_to_fees /= coin_seconds_as_fees_rate.value; + fees_accumulated_from_coin_seconds = coin_seconds_to_fees.to_uint64(); + + share_type max_fees_allowed = fee_options.max_accumulated_fees_from_coin_seconds[payer_membership]; + if( fees_accumulated_from_coin_seconds > max_fees_allowed ) // if accumulated too many coin seconds, truncate + { + fees_accumulated_from_coin_seconds = max_fees_allowed; + coin_seconds_earned = fc::uint128_t( max_fees_allowed.value ); + coin_seconds_earned *= coin_seconds_as_fees_rate.value; + } + max_fees_payable_with_coin_seconds = std::min( fees_accumulated_from_coin_seconds, max_op_fee[o.which()] ); + } + } + } + void generic_evaluator::convert_fee() { if( !trx_state->skip_fee ) { @@ -92,6 +121,11 @@ database& generic_evaluator::db()const { return trx_state->db(); } } } + void generic_evaluator::pay_fee( const operation& op ) + { + pay_fee(); + } + void generic_evaluator::pay_fee() { try { if( !trx_state->skip_fee ) { @@ -121,11 +155,29 @@ database& generic_evaluator::db()const { return trx_state->db(); } share_type generic_evaluator::calculate_fee_for_operation(const operation& op) const { - return db().current_fee_schedule().calculate_fee( op ).amount; + variant v; + db().build_extended_fee_parameters( op, v ); + return db().current_fee_schedule().calculate_fee_extended( op, v ).amount; } + void generic_evaluator::db_adjust_balance(const account_id_type& fee_payer, asset fee_from_account) { db().adjust_balance(fee_payer, fee_from_account); } + void generic_evaluator::pay_fee_with_coin_seconds() + { try { + if( !trx_state->skip_fee ) { + database& d = db(); + // deduct fees from coin_seconds_earned + if( fees_paid_with_coin_seconds > 0 ) + { + fc::uint128_t coin_seconds_consumed( fees_paid_with_coin_seconds.value ); + coin_seconds_consumed *= coin_seconds_as_fees_rate.value; + d.modify(*fee_paying_account_statistics, [&](account_statistics_object& o) { + o.set_coin_seconds_earned( coin_seconds_earned - coin_seconds_consumed, d.head_block_time() ); + }); + } + } + } FC_CAPTURE_AND_RETHROW() } } } diff --git a/libraries/chain/hardfork.d/583.hf b/libraries/chain/hardfork.d/583.hf new file mode 100644 index 0000000000..5b4682aaf8 --- /dev/null +++ b/libraries/chain/hardfork.d/583.hf @@ -0,0 +1,4 @@ +// #583 [BSIP10] percentage based transfer fee +#ifndef HARDFORK_583_TIME +#define HARDFORK_583_TIME (fc::time_point_sec( 1450378800 )) +#endif diff --git a/libraries/chain/hardfork.d/603.hf b/libraries/chain/hardfork.d/603.hf new file mode 100644 index 0000000000..7a457949b8 --- /dev/null +++ b/libraries/chain/hardfork.d/603.hf @@ -0,0 +1,4 @@ +// #603 simple rate limited free transaction +#ifndef HARDFORK_603_TIME +#define HARDFORK_603_TIME (fc::time_point_sec( 1450378800 )) +#endif diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 789dac2ddd..b70cf66cd7 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #pragma once +#include #include #include #include @@ -77,14 +78,69 @@ namespace graphene { namespace chain { * available for withdrawal) rather than requiring the normal vesting period. */ share_type pending_vested_fees; + /** + * Tracks the fees paid by this account which will be disseminated to network. See also @ref pending_fees + */ + share_type pending_fees_to_network; + /** + * Tracks the fees paid by this account which will be disseminated to non-network parties. + * See also @ref pending_fees + */ + share_type pending_fees_to_non_network; + /** + * Same as @ref pending_fees_to_non_network, except these fees will be paid out as pre-vested cash-back + * (immediately available for withdrawal) rather than requiring the normal vesting period. + */ + share_type pending_vested_fees_to_non_network; + + /** + * Tracks the coin-seconds earned by this account. Lazy updating. + * actual_coin_seconds_earned = coin_seconds_earned + current_balance * (now - coin_seconds_earned_last_update) + */ + // TODO maybe better to use a struct to store coin_seconds_earned and coin_seconds_earned_last_update + // and related functions e.g. compute_coin_seconds_earned and update_coin_seconds_earned + fc::uint128_t coin_seconds_earned; - /// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees + /** + * Tracks the most recent time when @ref coin_seconds_earned was updated. + */ + fc::time_point_sec coin_seconds_earned_last_update = HARDFORK_603_TIME; + + /// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees and etc. 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 ); + + /** + * Core fees which will split a fixed amount to network are paid into the account_statistics_object + * by this method + */ + void pay_fee_pre_split_network( share_type core_fee, share_type cashback_vesting_threshold, share_type network_fee ); + + /** + * Compute coin_seconds_earned. Used to + * non-destructively figure out how many coin seconds + * are available. + */ + // TODO use a public funtion to do this job as well as same job in vesting_balance_object + fc::uint128_t compute_coin_seconds_earned(const asset& balance, fc::time_point_sec now)const; + + /** + * Update coin_seconds_earned and + * coin_seconds_earned_last_update fields due to passing of time + */ + // TODO use a public funtion to do this job and same job in vesting_balance_object + void update_coin_seconds_earned(const asset& balance, fc::time_point_sec now); + + /** + * Update coin_seconds_earned and + * coin_seconds_earned_last_update fields with new data + */ + void set_coin_seconds_earned(const fc::uint128_t new_coin_seconds, fc::time_point_sec now); + }; /** @@ -257,6 +313,13 @@ namespace graphene { namespace chain { { return !is_basic_account(now); } + /// @return membership status of the account. + account_membership get_membership(time_point_sec now)const + { + if( is_basic_account( now ) ) return basic_account; + if( is_lifetime_member() ) return lifetime_member; + return annual_member; + } account_id_type get_id()const { return id; } }; @@ -388,5 +451,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (total_core_in_orders) (lifetime_fees_paid) (pending_fees)(pending_vested_fees) + (pending_fees_to_network)(pending_fees_to_non_network)(pending_vested_fees_to_non_network) + (coin_seconds_earned)(coin_seconds_earned_last_update) ) diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index f893f34afe..7136ece5f0 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -125,6 +125,11 @@ namespace graphene { namespace chain { asset_options options; + /// @return transfer fee mode of this asset, or @ref GRAPHENE_DEFAULT_TRANSFER_FEE_MODE if not set + asset_transfer_fee_mode get_transfer_fee_mode()const; + /// Set transfer fee mode of this asset + void set_transfer_fee_mode(const asset_transfer_fee_mode new_mode); + /// Current supply, fee pool, and collected fees are stored in a separate object as they change frequently. asset_dynamic_data_id_type dynamic_asset_data_id; /// Extra data associated with BitAssets. This field is non-null if and only if is_market_issued() returns true diff --git a/libraries/chain/include/graphene/chain/committee_member_evaluator.hpp b/libraries/chain/include/graphene/chain/committee_member_evaluator.hpp index d6ad7b69f8..92194f73eb 100644 --- a/libraries/chain/include/graphene/chain/committee_member_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/committee_member_evaluator.hpp @@ -54,4 +54,13 @@ namespace graphene { namespace chain { void_result do_apply( const committee_member_update_global_parameters_operation& o ); }; + class committee_member_update_core_asset_evaluator : public evaluator + { + public: + typedef committee_member_update_core_asset_operation operation_type; + + void_result do_evaluate( const committee_member_update_core_asset_operation& o ); + void_result do_apply( const committee_member_update_core_asset_operation& o ); + }; + } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 74b7d2b54b..1b0fddaf34 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -74,6 +74,9 @@ #define GRAPHENE_DEFAULT_MAX_ASSET_WHITELIST_AUTHORITIES 10 #define GRAPHENE_DEFAULT_MAX_ASSET_FEED_PUBLISHERS 10 +#define GRAPHENE_DEFAULT_TRANSFER_FEE_MODE asset_transfer_fee_mode_flat +#define GRAPHENE_DEFAULT_TRANSFER_FEE_PERCENT (GRAPHENE_100_PERCENT/1000) // 0.1% + /** * These ratios are fixed point numbers with a denominator of GRAPHENE_COLLATERAL_RATIO_DENOM, the * minimum maitenance collateral is therefore 1.001x and the default diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index b73b8931b0..d4d5d3e649 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -240,6 +240,13 @@ namespace graphene { namespace chain { void update_witness_schedule(); + //////////////////// db_fee.cpp //////////////////// + /** + * Base on the given operation, construct the extended parameter + * which is required for calculating fee (extended), save to the variant. + */ + void build_extended_fee_parameters(const operation& o, variant& v); + //////////////////// db_getter.cpp //////////////////// const chain_id_type& get_chain_id()const; diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp index af90517eca..dfa0300d2e 100644 --- a/libraries/chain/include/graphene/chain/evaluator.hpp +++ b/libraries/chain/include/graphene/chain/evaluator.hpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -63,6 +64,8 @@ namespace graphene { namespace chain { * The default implementation simply calls account_statistics_object->pay_fee() to * increment pending_fees or pending_vested_fees. */ + virtual void pay_fee(const operation& op); + /** Keep the function with no parameter here for backward compatibility */ virtual void pay_fee(); database& db()const; @@ -95,6 +98,16 @@ namespace graphene { namespace chain { */ void convert_fee(); + /** + * Compute how much can be paid with coin-seconds, need to call after prepare_fee(). + */ + void prepare_fee_from_coin_seconds(const operation& o); + + /** + * Pay fee with coin-seconds + */ + void pay_fee_with_coin_seconds(); + object_id_type get_relative_id( object_id_type rel_id )const; /** @@ -115,6 +128,13 @@ namespace graphene { namespace chain { const asset_object* fee_asset = nullptr; const asset_dynamic_data_object* fee_asset_dyn_data = nullptr; transaction_evaluation_state* trx_state; + // fields for computing fees paid with coin seconds + share_type max_fees_payable_with_coin_seconds = 0; + share_type fees_accumulated_from_coin_seconds = 0; + share_type fees_paid_with_coin_seconds = 0; + share_type coin_seconds_as_fees_rate = 0; + fc::uint128_t coin_seconds_earned = 0; + }; class op_evaluator @@ -147,13 +167,21 @@ namespace graphene { namespace chain { const auto& op = o.get(); prepare_fee(op.fee_payer(), op.fee); + + if( db().head_block_time() > HARDFORK_603_TIME ) + prepare_fee_from_coin_seconds(o); + if( !trx_state->skip_fee_schedule_check ) { share_type required_fee = calculate_fee_for_operation(op); - GRAPHENE_ASSERT( core_fee_paid >= required_fee, + GRAPHENE_ASSERT( core_fee_paid + max_fees_payable_with_coin_seconds >= required_fee, insufficient_fee, "Insufficient Fee Paid", + ("payable_from_coin_seconds", max_fees_payable_with_coin_seconds) ("core_fee_paid",core_fee_paid)("required", required_fee) ); + // if some fees are paid with coin seconds + if( core_fee_paid < required_fee ) + fees_paid_with_coin_seconds = required_fee - core_fee_paid; } return eval->do_evaluate(op); @@ -165,7 +193,8 @@ namespace graphene { namespace chain { const auto& op = o.get(); convert_fee(); - pay_fee(); + pay_fee(o); + pay_fee_with_coin_seconds(); auto result = eval->do_apply(op); diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index 3f5ede199e..7d59556f39 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -75,11 +75,43 @@ namespace graphene { namespace chain { * size of description. */ string description; + + /** + * struct for override extensions + */ + struct ext + { + /** container of transfer fee mode */ + struct transfer_fee_mode_options + { + /** + * transfer fee mode, would be: + * * flat fee mode + * * simple CER based percentage fee mode + */ + asset_transfer_fee_mode transfer_fee_mode = GRAPHENE_DEFAULT_TRANSFER_FEE_MODE; + }; + }; + + /** + * override future_extensions here + */ + typedef static_variant future_extensions; + + /** + * override extensions_type here + */ + typedef flat_set extensions_type; + extensions_type extensions; /// Perform internal consistency checks. /// @throws fc::exception if any check fails void validate()const; + + /// Perform internal consistency checks, but don't check core_exchange_rate. + /// @throws fc::exception if any check fails + void validate_except_cer()const; }; /** @@ -265,8 +297,10 @@ namespace graphene { namespace chain { * * @pre @ref issuer SHALL be an existing account and MUST match asset_object::issuer on @ref asset_to_update * @pre @ref fee SHALL be nonnegative, and @ref issuer MUST have a sufficient balance to pay it - * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate() - * @post @ref asset_to_update will have options matching those of new_options + * @pre @ref new_options SHALL be internally consistent except that core_exchange_rate can be null, + * as verified by @ref validate_except_cer() + * @post @ref asset_to_update will have options matching those of new_options, except that core_exchange_rate + * won't be updated if core_exchange_rate in new_options is null */ struct asset_update_operation : public base_operation { @@ -448,6 +482,9 @@ namespace graphene { namespace chain { FC_REFLECT( graphene::chain::asset_claim_fees_operation, (fee)(issuer)(amount_to_claim)(extensions) ) FC_REFLECT( graphene::chain::asset_claim_fees_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::asset_options::ext::transfer_fee_mode_options, (transfer_fee_mode) ) +FC_REFLECT_TYPENAME( graphene::chain::asset_options::future_extensions ) +FC_REFLECT_TYPENAME( graphene::chain::asset_options::extensions_type ) FC_REFLECT( graphene::chain::asset_options, (max_supply) (market_fee_percent) diff --git a/libraries/chain/include/graphene/chain/protocol/base.hpp b/libraries/chain/include/graphene/chain/protocol/base.hpp index 52240b934a..17fc326e2e 100644 --- a/libraries/chain/include/graphene/chain/protocol/base.hpp +++ b/libraries/chain/include/graphene/chain/protocol/base.hpp @@ -90,12 +90,21 @@ namespace graphene { namespace chain { { return params.fee; } + // Extended calculate_fee function calculate_fee_extended() will be defined in derived classes. + // The default implementation would just call calculate_fee(), but it will never be called, + // only causes trouble, so don't define it here. + // + // template + // fee_type calculate_fee_extended(const T& params, const variant& extended_params)const + // { return { this->calculate_fee( params ) }; } void get_required_authorities( vector& )const{} void get_required_active_authorities( flat_set& )const{} void get_required_owner_authorities( flat_set& )const{} void validate()const{} static uint64_t calculate_data_fee( uint64_t bytes, uint64_t price_per_kbyte ); + /// Whether fee is scalable after calculated + virtual bool is_fee_scalable() const { return true; } }; /** diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index 4dbd6c15ff..68abc018af 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -36,7 +36,6 @@ namespace fc { namespace graphene { namespace chain { - typedef static_variant<> parameter_extension; struct chain_parameters { /** using a smart ref breaks the circular dependency created between operations and the fee schedule */ @@ -69,14 +68,80 @@ namespace graphene { namespace chain { uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH; + + /** + * struct for override extensions + */ + struct ext + { + /** container of coin_seconds to fees parameters */ + struct coin_seconds_as_fees_options + { + /** + * Rates of converting coin_seconds to core asset to pay fees, + * indexed by membership type: [basic_account, lifetime_member, annual_member] + */ + // + // Theoretically, 100 tps means 8.64M transactions a day. + // Total quantity of core asset is 3.7BB, so 1 transaction = 3.7BB/8.64M ~= 428 core assets, + // which means with 428 core assets you can do 1 transaction a day. + // If we set conversion rate to 5000, and set transaction fee to 1, then about 1/10 of network + // capacity can be used freely. A whale with 10M core assets can accumulate same value as + // 1 core asset of coin seconds in 43.2 seconds. An average user with 10K core assets can + // accumulate same value as 1 core asset of coin seconds in 43200 seconds = half a day. + // + // TODO consider nonlinear conversion? + std::vector coin_seconds_as_fees_rate { 86400*20000, + 86400*5000, + 86400*10000 + }; + /** + * Maximum values of accumulated fees which can be paid with accumulated coin_seconds, + * indexed by membership type: [basic_account, lifetime_member, annual_member] + */ + std::vector max_accumulated_fees_from_coin_seconds { GRAPHENE_BLOCKCHAIN_PRECISION * 10, + GRAPHENE_BLOCKCHAIN_PRECISION * 40, + GRAPHENE_BLOCKCHAIN_PRECISION * 20 + }; + /** + * Maximum values of fees can be paid with coin_seconds, indexed by operation_id. + * Default value is all 0. + */ + std::vector max_fee_from_coin_seconds_by_operation; + }; + }; + + typedef static_variant parameter_extension; + typedef flat_set extensions_type; extensions_type extensions; /** defined in fee_schedule.cpp */ void validate()const; + + /** @return the coin_seconds_as_fee_options object set in extensions, or default values if not set */ + const ext::coin_seconds_as_fees_options get_coin_seconds_as_fees_options()const + { + if( extensions.size() > 0 ) + { + for( const parameter_extension& e : extensions ) + { + if( e.which() == parameter_extension::tag::value ) + return e.get(); + } + } + return ext::coin_seconds_as_fees_options(); + } }; } } // graphene::chain +FC_REFLECT( graphene::chain::chain_parameters::ext::coin_seconds_as_fees_options, + (coin_seconds_as_fees_rate) + (max_accumulated_fees_from_coin_seconds) + (max_fee_from_coin_seconds_by_operation) + ) +FC_REFLECT_TYPENAME( graphene::chain::chain_parameters::parameter_extension ) +FC_REFLECT_TYPENAME( graphene::chain::chain_parameters::extensions_type ) FC_REFLECT( graphene::chain::chain_parameters, (current_fees) (block_interval) diff --git a/libraries/chain/include/graphene/chain/protocol/committee_member.hpp b/libraries/chain/include/graphene/chain/protocol/committee_member.hpp index 7718836723..05e860a556 100644 --- a/libraries/chain/include/graphene/chain/protocol/committee_member.hpp +++ b/libraries/chain/include/graphene/chain/protocol/committee_member.hpp @@ -24,6 +24,7 @@ #pragma once #include #include +#include namespace graphene { namespace chain { @@ -91,12 +92,41 @@ namespace graphene { namespace chain { void validate()const; }; + /** + * @brief Used by committee_members to update some options of CORE asset. + * @ingroup operations + * + * This operation allows the committee_members to update following options of CORE asset on the blockchain: + * * market_fee_percent + * * max_market_fee + * * extensions::transfer_fee_mode + * + * Note that accumulated fees of CORE asset will be automatically moved to reserve pool in maintenance interval. + * + * This operation may only be used in a proposed transaction, and a proposed transaction which contains this + * operation must have a review period specified in the current global parameters before it may be accepted. + */ + struct committee_member_update_core_asset_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + /// new options + asset_options new_options; + /// for future extensions + extensions_type extensions; + + account_id_type fee_payer()const { return account_id_type(); } + void validate()const; + }; + /// TODO: committee_member_resign_operation : public base_operation } } // graphene::chain FC_REFLECT( graphene::chain::committee_member_create_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::committee_member_update_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::committee_member_update_global_parameters_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::committee_member_update_core_asset_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::committee_member_create_operation, @@ -104,3 +134,4 @@ FC_REFLECT( graphene::chain::committee_member_create_operation, FC_REFLECT( graphene::chain::committee_member_update_operation, (fee)(committee_member)(committee_member_account)(new_url) ) FC_REFLECT( graphene::chain::committee_member_update_global_parameters_operation, (fee)(new_parameters) ); +FC_REFLECT( graphene::chain::committee_member_update_core_asset_operation, (fee)(new_options)(extensions) ); diff --git a/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp b/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp index e250ab1737..2ab03ff14f 100644 --- a/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp +++ b/libraries/chain/include/graphene/chain/protocol/fee_schedule.hpp @@ -48,7 +48,27 @@ namespace graphene { namespace chain { * and then calculates the appropriate fee. */ asset calculate_fee( const operation& op, const price& core_exchange_rate = price::unit_price() )const; + /** + * Finds the appropriate fee parameter struct for the operation + * and then calculates the appropriate fee with extended parameters. + */ + asset calculate_fee_extended( const operation& op, + const variant& extended, + const price& core_exchange_rate = price::unit_price() )const; + /// Sets fee for the operation asset set_fee( operation& op, const price& core_exchange_rate = price::unit_price() )const; + /// Sets fee for the operation with extended parameters to calculate the fee + asset set_fee_extended( operation& op, + const variant& extended, + const price& core_exchange_rate = price::unit_price() )const; + /// Finds the appropriate fee parameter struct for the operation + fee_parameters find_op_fee_parameters( const operation& op )const; + /// Scale a fee + fc::uint128 scale_fee( const uint64_t base_value )const; + /// Convert a scaled fee into specified asset + asset convert_fee( const fc::uint128& scaled, const price& core_exchange_rate )const; + /// Scale and convert a fee + asset scale_and_convert_fee( const uint64_t base_value, const price& core_exchange_rate )const; void zero_all_fees(); diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 7f2639f15a..304e126fb9 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -91,7 +91,9 @@ namespace graphene { namespace chain { transfer_from_blind_operation, asset_settle_cancel_operation, // VIRTUAL asset_claim_fees_operation, - fba_distribute_operation // VIRTUAL + fba_distribute_operation, // VIRTUAL + transfer_v2_operation, + committee_member_update_core_asset_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/transfer.hpp b/libraries/chain/include/graphene/chain/protocol/transfer.hpp index f4417bb747..5ddbc0e50e 100644 --- a/libraries/chain/include/graphene/chain/protocol/transfer.hpp +++ b/libraries/chain/include/graphene/chain/protocol/transfer.hpp @@ -44,8 +44,74 @@ namespace graphene { namespace chain { struct transfer_operation : public base_operation { struct fee_parameters_type { - uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; - uint32_t price_per_kbyte = 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. + }; + struct extended_calculate_fee_parameters { + /// Transfer fee mode of transferring asset + asset_transfer_fee_mode transferring_asset_transfer_fee_mode = asset_transfer_fee_mode_flat; + /// data field for future extensions + extensions_type extensions; + }; + + asset fee; + /// Account to transfer asset from + account_id_type from; + /// Account to transfer asset to + account_id_type to; + /// The amount of asset to transfer from @ref from to @ref to + asset amount; + + /// User provided data encrypted to the memo key of the "to" account + optional memo; + extensions_type extensions; + + account_id_type fee_payer()const { return from; } + void validate()const; + /// The original calculate_fee function. Deprecated in this operation. + share_type calculate_fee(const fee_parameters_type& k)const; + /// Extended calculate fee function + share_type calculate_fee_extended(const fee_parameters_type& k, const variant& extended)const; + }; + + /** + * @ingroup operations + * + * @brief Transfers an amount of one asset from one account to another, support simple percentage based fee mode + * + * Fees are paid by the "from" account + * + * @pre amount.amount > 0 + * @pre fee.amount >= 0 + * @pre from != to + * @post from account's balance will be reduced by fee and amount + * @post to account's balance will be increased by amount + * @return n/a + */ + struct transfer_v2_operation : public base_operation + { + struct fee_parameters_type { + /// fee required when transferring asset with flat fee mode + uint64_t flat_fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. + /// Minimum fee amount, take effect when transfer asset with percentage based fee mode + uint32_t percentage_min_fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; + /// Maximum fee amount, take effect when transfer asset with percentage based fee mode + uint32_t percentage_max_fee = 300 * GRAPHENE_BLOCKCHAIN_PRECISION; + /// the percentage, take effect when transfer asset with percentage based fee mode + uint16_t percentage = GRAPHENE_DEFAULT_TRANSFER_FEE_PERCENT; + /// data field for future extensions + extensions_type extensions; + }; + struct extended_calculate_fee_parameters { + /// Scale + uint32_t scale = 1; + /// Transfer fee mode of transferring asset + asset_transfer_fee_mode transferring_asset_transfer_fee_mode = asset_transfer_fee_mode_flat; + /// core_exchange_rate of transferring asset + price transferring_asset_core_exchange_rate = price::unit_price(); + /// data field for future extensions + extensions_type extensions; }; asset fee; @@ -62,7 +128,13 @@ namespace graphene { namespace chain { account_id_type fee_payer()const { return from; } void validate()const; + /// The original calculate_fee function. Deprecated in this operation. share_type calculate_fee(const fee_parameters_type& k)const; + /// Extended calculate fee function. + /// Calculate fee and scale. Since it's better that percentage not scale, we scale inside. + share_type calculate_fee_extended(const fee_parameters_type& k, const variant& extended)const; + /// Override is_fee_scalable() to return false + virtual bool is_fee_scalable() const override { return false; } }; /** @@ -101,7 +173,15 @@ namespace graphene { namespace chain { }} // graphene::chain FC_REFLECT( graphene::chain::transfer_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::transfer_v2_operation::fee_parameters_type, + (flat_fee)(price_per_kbyte)(percentage_min_fee)(percentage_max_fee)(percentage)(extensions) ) FC_REFLECT( graphene::chain::override_transfer_operation::fee_parameters_type, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::chain::override_transfer_operation, (fee)(issuer)(from)(to)(amount)(memo)(extensions) ) FC_REFLECT( graphene::chain::transfer_operation, (fee)(from)(to)(amount)(memo)(extensions) ) +FC_REFLECT( graphene::chain::transfer_v2_operation, (fee)(from)(to)(amount)(memo)(extensions) ) + +FC_REFLECT( graphene::chain::transfer_operation::extended_calculate_fee_parameters, + (transferring_asset_transfer_fee_mode)(extensions) ) +FC_REFLECT( graphene::chain::transfer_v2_operation::extended_calculate_fee_parameters, + (scale)(transferring_asset_transfer_fee_mode)(transferring_asset_core_exchange_rate)(extensions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 5237fcad53..258da354b3 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -100,6 +100,19 @@ namespace graphene { namespace chain { |witness_fed_asset|committee_fed_asset; const static uint32_t UIA_ASSET_ISSUER_PERMISSION_MASK = charge_market_fee|white_list|override_authority|transfer_restricted|disable_confidential; + enum asset_transfer_fee_mode + { + asset_transfer_fee_mode_flat = 0, + asset_transfer_fee_mode_percentage_simple = 1 + }; + + enum account_membership + { + basic_account = 0, + lifetime_member = 1, + annual_member = 2 + }; + enum reserved_spaces { relative_protocol_ids = 0, @@ -404,3 +417,14 @@ FC_REFLECT_ENUM( graphene::chain::asset_issuer_permission_flags, (witness_fed_asset) (committee_fed_asset) ) + +FC_REFLECT_ENUM( graphene::chain::asset_transfer_fee_mode, + (asset_transfer_fee_mode_flat) + (asset_transfer_fee_mode_percentage_simple) + ) + +FC_REFLECT_ENUM( graphene::chain::account_membership, + (basic_account) + (lifetime_member) + (annual_member) + ) diff --git a/libraries/chain/include/graphene/chain/transfer_evaluator.hpp b/libraries/chain/include/graphene/chain/transfer_evaluator.hpp index 900ab0741f..16cb61fd4d 100644 --- a/libraries/chain/include/graphene/chain/transfer_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/transfer_evaluator.hpp @@ -37,6 +37,18 @@ namespace graphene { namespace chain { void_result do_apply( const transfer_operation& o ); }; + class transfer_v2_evaluator : public evaluator + { + public: + typedef transfer_v2_operation operation_type; + + void_result do_evaluate( const transfer_v2_operation& o ); + void_result do_apply( const transfer_v2_operation& o ); + + protected: + virtual void pay_fee( const operation& op ) override; + }; + class override_transfer_evaluator : public evaluator { public: diff --git a/libraries/chain/protocol/asset_ops.cpp b/libraries/chain/protocol/asset_ops.cpp index 1626a2a926..5fb88049ac 100644 --- a/libraries/chain/protocol/asset_ops.cpp +++ b/libraries/chain/protocol/asset_ops.cpp @@ -114,10 +114,16 @@ void asset_update_operation::validate()const FC_ASSERT( fee.amount >= 0 ); if( new_issuer ) FC_ASSERT(issuer != *new_issuer); - new_options.validate(); + // firstly check new_options except CER + new_options.validate_except_cer(); - asset dummy = asset(1, asset_to_update) * new_options.core_exchange_rate; - FC_ASSERT(dummy.asset_id == asset_id_type()); + // if CER is not null, check it + if( !new_options.core_exchange_rate.is_null() ) + { + new_options.core_exchange_rate.validate(); + asset dummy = asset(1, asset_to_update) * new_options.core_exchange_rate; + FC_ASSERT(dummy.asset_id == asset_id_type()); + } } share_type asset_update_operation::calculate_fee(const asset_update_operation::fee_parameters_type& k)const @@ -199,6 +205,14 @@ void bitasset_options::validate() const } void asset_options::validate()const +{ + core_exchange_rate.validate(); + FC_ASSERT( core_exchange_rate.base.asset_id.instance.value == 0 || + core_exchange_rate.quote.asset_id.instance.value == 0 ); + validate_except_cer(); +} + +void asset_options::validate_except_cer()const { FC_ASSERT( max_supply > 0 ); FC_ASSERT( max_supply <= GRAPHENE_MAX_SHARE_SUPPLY ); @@ -210,9 +224,6 @@ void asset_options::validate()const FC_ASSERT( !(flags & global_settle) ); // the witness_fed and committee_fed flags cannot be set simultaneously FC_ASSERT( (flags & (witness_fed_asset | committee_fed_asset)) != (witness_fed_asset | committee_fed_asset) ); - core_exchange_rate.validate(); - FC_ASSERT( core_exchange_rate.base.asset_id.instance.value == 0 || - core_exchange_rate.quote.asset_id.instance.value == 0 ); if(!whitelist_authorities.empty() || !blacklist_authorities.empty()) FC_ASSERT( flags & white_list ); diff --git a/libraries/chain/protocol/committee_member.cpp b/libraries/chain/protocol/committee_member.cpp index 4c8c5d2591..6e7b614ef4 100644 --- a/libraries/chain/protocol/committee_member.cpp +++ b/libraries/chain/protocol/committee_member.cpp @@ -44,4 +44,15 @@ void committee_member_update_global_parameters_operation::validate() const new_parameters.validate(); } +void committee_member_update_core_asset_operation::validate() const +{ + // Validate fee field + FC_ASSERT( fee.amount >= 0 ); + // Validate market_fee_percent and max_market_fee in new_options + FC_ASSERT( new_options.market_fee_percent <= GRAPHENE_100_PERCENT ); + FC_ASSERT( new_options.max_market_fee >= 0 && new_options.max_market_fee <= GRAPHENE_MAX_SHARE_SUPPLY ); + // extensions::transfer_fee_mode in new_options doesn't need to be validated. + // Other fields in new_options will be ignored, so don't need to be validated. +} + } } // graphene::chain diff --git a/libraries/chain/protocol/fee_schedule.cpp b/libraries/chain/protocol/fee_schedule.cpp index ab8f6532f8..56a49df26b 100644 --- a/libraries/chain/protocol/fee_schedule.cpp +++ b/libraries/chain/protocol/fee_schedule.cpp @@ -24,6 +24,7 @@ #include #include #include +#include namespace fc { @@ -87,6 +88,60 @@ namespace graphene { namespace chain { { return op.calculate_fee( param.get() ).value; } + + }; + + BOOST_TTI_HAS_MEMBER_FUNCTION(calculate_fee_extended) + + struct calc_fee_extended_visitor + { + typedef uint64_t result_type; + + const fee_parameters& param; + const variant& extended; + + calc_fee_extended_visitor( const fee_parameters& p, const variant& e ) : param(p), extended(e) {} + + template + result_type calculate_operation_fee( const OpType& op, std::true_type )const + { + auto& fee_param = param.get(); + return op.calculate_fee_extended( fee_param, extended ).value; + } + + template + result_type calculate_operation_fee( const OpType& op, std::false_type )const + { + auto& fee_param = param.get(); + return op.calculate_fee( fee_param ).value; + } + + template + result_type operator()( const OpType& op )const + { + const bool b = has_member_function_calculate_fee_extended< + OpType, + share_type, + boost::mpl::vector, + boost::function_types::const_qualified + >::value; + return calculate_operation_fee( op, std::integral_constant() ); + } + + }; + + struct is_fee_scalable_visitor + { + typedef bool result_type; + + is_fee_scalable_visitor() {} + + template + result_type operator()( const OpType& op )const + { + return op.is_fee_scalable(); + } + }; struct set_fee_visitor @@ -122,17 +177,51 @@ namespace graphene { namespace chain { this->scale = 0; } - asset fee_schedule::calculate_fee( const operation& op, const price& core_exchange_rate )const + fee_parameters fee_schedule::find_op_fee_parameters( const operation& op )const { - //idump( (op)(core_exchange_rate) ); fee_parameters params; params.set_which(op.which()); auto itr = parameters.find(params); if( itr != parameters.end() ) params = *itr; + return params; + } + + asset fee_schedule::calculate_fee( const operation& op, const price& core_exchange_rate )const + { + //idump( (op)(core_exchange_rate) ); + const fee_parameters& params = find_op_fee_parameters( op ); auto base_value = op.visit( calc_fee_visitor( params ) ); + return scale_and_convert_fee( base_value, core_exchange_rate ); + } + + asset fee_schedule::calculate_fee_extended( const operation& op, + const variant& extended, + const price& core_exchange_rate )const + { + //idump( (op)(extended)(core_exchange_rate) ); + const fee_parameters& params = find_op_fee_parameters( op ); + auto base_value = op.visit( calc_fee_extended_visitor( params, extended ) ); + bool is_fee_scalable = op.visit( is_fee_scalable_visitor() ); + if( is_fee_scalable ) return scale_and_convert_fee( base_value, core_exchange_rate ); + else return convert_fee( base_value, core_exchange_rate ); + } + + asset fee_schedule::scale_and_convert_fee( const uint64_t base_value, const price& core_exchange_rate )const + { + //idump( (base_value)(core_exchange_rate) ); + return convert_fee( scale_fee( base_value ), core_exchange_rate ); + } + + fc::uint128 fee_schedule::scale_fee( const uint64_t base_value )const + { auto scaled = fc::uint128(base_value) * scale; scaled /= GRAPHENE_100_PERCENT; + //idump( (base_value)(scaled) ); FC_ASSERT( scaled <= GRAPHENE_MAX_SHARE_SUPPLY ); - //idump( (base_value)(scaled)(core_exchange_rate) ); + return scaled; + } + + asset fee_schedule::convert_fee( const fc::uint128& scaled, const price& core_exchange_rate )const + { auto result = asset( scaled.to_uint64(), asset_id_type(0) ) * core_exchange_rate; //FC_ASSERT( result * core_exchange_rate >= asset( scaled.to_uint64()) ); @@ -165,6 +254,28 @@ namespace graphene { namespace chain { return f_max; } + asset fee_schedule::set_fee_extended( operation& op, const variant& extended, const price& core_exchange_rate )const + { + auto f = calculate_fee_extended( op, extended, core_exchange_rate ); + auto f_max = f; + for( int i=0; ivalidate(); diff --git a/libraries/chain/protocol/transfer.cpp b/libraries/chain/protocol/transfer.cpp index 3dfe4eb727..ddc59f2235 100644 --- a/libraries/chain/protocol/transfer.cpp +++ b/libraries/chain/protocol/transfer.cpp @@ -27,13 +27,27 @@ namespace graphene { namespace chain { share_type transfer_operation::calculate_fee( const fee_parameters_type& schedule )const { - share_type core_fee_required = schedule.fee; + FC_THROW( "Deprecated. Use calculate_fee_extended( const fee_parameters_type& schedule, const variant& extended ) instead." ); +} + +share_type transfer_operation::calculate_fee_extended( const fee_parameters_type& schedule, const variant& extended )const +{ + share_type core_fee_required; + extended_calculate_fee_parameters p; + from_variant( extended, p ); + if( p.transferring_asset_transfer_fee_mode == asset_transfer_fee_mode_flat ) // flat fee mode + { + core_fee_required = schedule.fee; + } + else // other fee modes + { + FC_THROW( "transfer_operation doesn't support asset with non-flat fee mode." ); + } if( memo ) core_fee_required += calculate_data_fee( fc::raw::pack_size(memo), schedule.price_per_kbyte ); return core_fee_required; } - void transfer_operation::validate()const { FC_ASSERT( fee.amount >= 0 ); @@ -42,6 +56,64 @@ void transfer_operation::validate()const } +share_type transfer_v2_operation::calculate_fee( const fee_parameters_type& schedule )const +{ + FC_THROW( "Use calculate_fee_extended( const fee_parameters_type& schedule, const variant& extended ) instead." ); +} + +share_type transfer_v2_operation::calculate_fee_extended( const fee_parameters_type& schedule, + const variant& extended )const +{ + share_type core_fee_required; + extended_calculate_fee_parameters p; + from_variant( extended, p ); + uint32_t scale = p.scale; + if( p.transferring_asset_transfer_fee_mode == asset_transfer_fee_mode_flat ) // flat fee mode + { + auto core_fee_128 = fc::uint128(schedule.flat_fee); + core_fee_128 *= scale; + core_fee_128 /= GRAPHENE_100_PERCENT; + core_fee_required = core_fee_128.to_uint64(); + } + else if( p.transferring_asset_transfer_fee_mode == asset_transfer_fee_mode_percentage_simple ) // simple percentage fee mode + { + // need to know CER of amount.asset_id so that fee can be calculated + // fee = amount.amount * asset.CER * transfer_v2_operation.fee_parameters_type.percentage + auto core_amount = amount * p.transferring_asset_core_exchange_rate; + auto core_fee_amount = fc::uint128(core_amount.amount.value); + core_fee_amount *= schedule.percentage; + core_fee_amount /= GRAPHENE_100_PERCENT; + core_fee_required = core_fee_amount.to_uint64(); + auto min_fee_128 = fc::uint128( schedule.percentage_min_fee ); + min_fee_128 *= scale; + min_fee_128 /= GRAPHENE_100_PERCENT; + auto min_fee_64 = min_fee_128.to_uint64(); + auto max_fee_128 = fc::uint128( schedule.percentage_max_fee ); + max_fee_128 *= scale; + max_fee_128 /= GRAPHENE_100_PERCENT; + auto max_fee_64 = max_fee_128.to_uint64(); + if( core_fee_required < min_fee_64 ) core_fee_required = min_fee_64; + if( core_fee_required > max_fee_64 ) core_fee_required = max_fee_64; + } + if( memo ) + { + auto memo_fee_128 = fc::uint128( calculate_data_fee( fc::raw::pack_size(memo), schedule.price_per_kbyte ) ); + memo_fee_128 *= scale; + memo_fee_128 /= GRAPHENE_100_PERCENT; + core_fee_required += memo_fee_128.to_uint64(); + } + return core_fee_required; +} + + +void transfer_v2_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0 ); + FC_ASSERT( from != to ); + FC_ASSERT( amount.amount > 0 ); +} + + share_type override_transfer_operation::calculate_fee( const fee_parameters_type& schedule )const { diff --git a/libraries/chain/transfer_evaluator.cpp b/libraries/chain/transfer_evaluator.cpp index accc6ca3d1..319086ce51 100644 --- a/libraries/chain/transfer_evaluator.cpp +++ b/libraries/chain/transfer_evaluator.cpp @@ -81,7 +81,93 @@ void_result transfer_evaluator::do_apply( const transfer_operation& o ) return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } +void_result transfer_v2_evaluator::do_evaluate( const transfer_v2_operation& op ) +{ try { + + const database& d = db(); + + // #583 BSIP10 hard fork check + if( d.head_block_time() <= HARDFORK_583_TIME ) + FC_THROW( "Operation requires hardfork #583" ); + + const account_object& from_account = op.from(d); + const account_object& to_account = op.to(d); + const asset_object& asset_type = op.amount.asset_id(d); + + try { + + GRAPHENE_ASSERT( + is_authorized_asset( d, from_account, asset_type ), + transfer_from_account_not_whitelisted, + "'from' account ${from} is not whitelisted for asset ${asset}", + ("from",op.from) + ("asset",op.amount.asset_id) + ); + GRAPHENE_ASSERT( + is_authorized_asset( d, to_account, asset_type ), + transfer_to_account_not_whitelisted, + "'to' account ${to} is not whitelisted for asset ${asset}", + ("to",op.to) + ("asset",op.amount.asset_id) + ); + if( asset_type.is_transfer_restricted() ) + { + GRAPHENE_ASSERT( + from_account.id == asset_type.issuer || to_account.id == asset_type.issuer, + transfer_restricted_transfer_asset, + "Asset {asset} has transfer_restricted flag enabled", + ("asset", op.amount.asset_id) + ); + } + + bool insufficient_balance = d.get_balance( from_account, asset_type ).amount >= op.amount.amount; + FC_ASSERT( insufficient_balance, + "Insufficient Balance: ${balance}, unable to transfer '${total_transfer}' from account '${a}' to '${t}'", + ("a",from_account.name)("t",to_account.name)("total_transfer",d.to_pretty_string(op.amount))("balance",d.to_pretty_string(d.get_balance(from_account, asset_type))) ); + + return void_result(); + } FC_RETHROW_EXCEPTIONS( error, "Unable to transfer ${a} from ${f} to ${t}", ("a",d.to_pretty_string(op.amount))("f",op.from(d).name)("t",op.to(d).name) ); + +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result transfer_v2_evaluator::do_apply( const transfer_v2_operation& o ) +{ try { + db().adjust_balance( o.from, -o.amount ); + db().adjust_balance( o.to, o.amount ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + + +void transfer_v2_evaluator::pay_fee( const operation& op ) +{ try { + if( !trx_state->skip_fee ) { + const transfer_v2_operation& o = op.get(); + database& d = db(); + const asset_object& asset_type = o.amount.asset_id(d); + const auto vesting_threshold = d.get_global_properties().parameters.cashback_vesting_threshold; + const auto fee_mode = asset_type.get_transfer_fee_mode(); + if( fee_mode == asset_transfer_fee_mode_flat ) + { + d.modify(*fee_paying_account_statistics, [&](account_statistics_object& s) + { + s.pay_fee( core_fee_paid, vesting_threshold ); + }); + } + else if( fee_mode == asset_transfer_fee_mode_percentage_simple ) + { + const auto& params = d.current_fee_schedule().find_op_fee_parameters( o ); + const auto& param = params.get(); + auto scaled_min_fee = fc::uint128( param.percentage_min_fee ); + scaled_min_fee *= d.current_fee_schedule().scale; + scaled_min_fee /= GRAPHENE_100_PERCENT; + d.modify(*fee_paying_account_statistics, [&](account_statistics_object& s) + { + s.pay_fee_pre_split_network( core_fee_paid, vesting_threshold, scaled_min_fee.to_uint64() ); + }); + } + } +} FC_CAPTURE_AND_RETHROW( (op) ) } void_result override_transfer_evaluator::do_evaluate( const override_transfer_operation& op ) { try { diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 3dd8b9e3e6..1312817769 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -61,6 +61,7 @@ #include #include +#include #include #include #include @@ -117,6 +118,7 @@ struct operation_printer std::string operator()(const T& op)const; std::string operator()(const transfer_operation& op)const; + std::string operator()(const transfer_v2_operation& op)const; std::string operator()(const transfer_from_blind_operation& op)const; std::string operator()(const transfer_to_blind_operation& op)const; std::string operator()(const account_create_operation& op)const; @@ -491,10 +493,27 @@ class wallet_api_impl return ob.template as(); } + struct set_fee_visitor + { + typedef void result_type; + asset _fee; + + set_fee_visitor( asset f ):_fee(f){} + + template + void operator()( OpType& op )const + { + op.fee = _fee; + } + }; + void set_operation_fees( signed_transaction& tx, const fee_schedule& s ) { for( auto& op : tx.operations ) - s.set_fee(op); + { + asset required_fee = _remote_db->get_operation_fee( op, asset_id_type(0) ); + op.visit( set_fee_visitor( required_fee ) ); + } } variant info() const @@ -841,7 +860,11 @@ class wallet_api_impl if( fee_asset_obj.get_id() != asset_id_type() ) { for( auto& op : _builder_transactions[handle].operations ) - total_fee += gprops.current_fees->set_fee( op, fee_asset_obj.options.core_exchange_rate ); + { + asset required_fee = _remote_db->get_operation_fee( op, fee_asset_obj.id ); + op.visit( set_fee_visitor( required_fee ) ); + total_fee += required_fee; + } FC_ASSERT((total_fee * fee_asset_obj.options.core_exchange_rate).amount <= get_object(fee_asset_obj.dynamic_asset_data_id).fee_pool, @@ -849,7 +872,11 @@ class wallet_api_impl ("asset", fee_asset_obj.symbol)); } else { for( auto& op : _builder_transactions[handle].operations ) - total_fee += gprops.current_fees->set_fee( op ); + { + asset required_fee = _remote_db->get_operation_fee( op, asset_id_type(0) ); + op.visit( set_fee_visitor( required_fee ) ); + total_fee += required_fee; + } } return total_fee; @@ -1992,10 +2019,10 @@ class wallet_api_impl return sign_transaction(trx, broadcast); } FC_CAPTURE_AND_RETHROW((order_id)) } - signed_transaction transfer(string from, string to, string amount, - string asset_symbol, string memo, bool broadcast = false) - { try { - FC_ASSERT( !self.is_locked() ); + template + signed_transaction build_transfer_trx( string from, string to, string amount, + string asset_symbol, string memo, bool broadcast = false ) + { fc::optional asset_obj = get_asset(asset_symbol); FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol)); @@ -2004,20 +2031,19 @@ class wallet_api_impl account_id_type from_id = from_account.id; account_id_type to_id = get_account_id(to); - transfer_operation xfer_op; - + T xfer_op; xfer_op.from = from_id; xfer_op.to = to_id; xfer_op.amount = asset_obj->amount_from_string(amount); if( memo.size() ) - { - xfer_op.memo = memo_data(); - xfer_op.memo->from = from_account.options.memo_key; - xfer_op.memo->to = to_account.options.memo_key; - xfer_op.memo->set_message(get_private_key(from_account.options.memo_key), - to_account.options.memo_key, memo); - } + { + xfer_op.memo = memo_data(); + xfer_op.memo->from = from_account.options.memo_key; + xfer_op.memo->to = to_account.options.memo_key; + xfer_op.memo->set_message(get_private_key(from_account.options.memo_key), + to_account.options.memo_key, memo); + } signed_transaction tx; tx.operations.push_back(xfer_op); @@ -2025,6 +2051,23 @@ class wallet_api_impl tx.validate(); return sign_transaction(tx, broadcast); + } + + signed_transaction transfer(string from, string to, string amount, + string asset_symbol, string memo, bool broadcast = false) + { try { + FC_ASSERT( !self.is_locked() ); + // check #583 BSIP10 hard fork time + if( time_point_sec(time_point::now()) <= HARDFORK_583_TIME ) + { + return build_transfer_trx( from, to, amount, + asset_symbol, memo, broadcast ); + } + else + { + return build_transfer_trx( from, to, amount, + asset_symbol, memo, broadcast ); + } } FC_CAPTURE_AND_RETHROW( (from)(to)(amount)(asset_symbol)(memo)(broadcast) ) } signed_transaction issue_asset(string to_account, string amount, string symbol, @@ -2543,6 +2586,34 @@ string operation_printer::operator()(const transfer_operation& op) const return memo; } +string operation_printer::operator()(const transfer_v2_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), "Memo is encrypted to a key ${k} not in this wallet.", + ("k", 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; + } catch (const fc::exception& e) { + out << " -- could not decrypt memo"; + elog("Error when decrypting memo: ${e}", ("e", e.to_detail_string())); + } + } + } + fee(op.fee); + return memo; +} + std::string operation_printer::operator()(const account_create_operation& op) const { out << "Create Account '" << op.name << "'";