Merge branch 'graphene/develop' into prerel-2015-12-07

This commit is contained in:
theoreticalbts 2015-12-07 17:45:19 -05:00
commit 020dc2e789
23 changed files with 562 additions and 92 deletions

View file

@ -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<net::peer_status> network_node_api::get_connected_peers() const
{
return _app.p2p_node()->get_connected_peers();
return _app.p2p_node()->get_connected_peers();
}
std::vector<net::potential_peer_record> 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<network_broadcast_api> login_api::network_broadcast()const

View file

@ -31,6 +31,8 @@
#include <cctype>
#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<database_api_impl>
bool verify_authority( const signed_transaction& trx )const;
bool verify_account_authority( const string& name_or_id, const flat_set<public_key_type>& signers )const;
processed_transaction validate_transaction( const signed_transaction& trx )const;
vector<asset> get_required_fees( const vector<operation>& ops, asset_id_type id )const;
vector< fc::variant > get_required_fees( const vector<operation>& ops, asset_id_type id )const;
// Proposed transactions
vector<proposal_object> 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<asset> database_api::get_required_fees( const vector<operation>& ops, asset_id_type id )const
vector< fc::variant > database_api::get_required_fees( const vector<operation>& ops, asset_id_type id )const
{
return my->get_required_fees( ops, id );
}
vector<asset> database_api_impl::get_required_fees( const vector<operation>& 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<asset> 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<proposal_create_operation>::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<proposal_create_operation>();
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<operation>& 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;
}

View file

@ -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<net::peer_status> 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<net::potential_peer_record> 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)

View file

@ -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<asset> get_required_fees( const vector<operation>& ops, asset_id_type id )const;
vector< fc::variant > get_required_fees( const vector<operation>& ops, asset_id_type id )const;
///////////////////////////
// Proposed transactions //

View file

@ -20,10 +20,13 @@
*/
#include <fc/smart_ref_impl.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/internal_exceptions.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <algorithm>
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>( [&]( 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>([&](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);
}
});

View file

@ -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;

View file

@ -23,6 +23,7 @@
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/market_evaluator.hpp>
#include <fc/uint128.hpp>
@ -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));

View file

@ -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() }

View file

@ -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 );
};
/**

View file

@ -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<DerivedEvaluator*>(this);
const auto& op = o.get<typename DerivedEvaluator::operation_type>();
convert_fee();
pay_fee();
auto result = eval->do_apply(op);

View file

@ -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 ))

View file

@ -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<asset_id_type,asset_id_type> 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),

View file

@ -18,12 +18,14 @@
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <graphene/chain/market_evaluator.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/market_evaluator.hpp>
#include <fc/smart_ref_impl.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <fc/uint128.hpp>
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.

View file

@ -31,6 +31,8 @@ namespace fc
//template const graphene::chain::fee_schedule& smart_ref<graphene::chain::fee_schedule>::operator*() const;
}
#define MAX_FEE_STABILIZATION_ITERATION 4
namespace graphene { namespace chain {
typedef fc::smart_ref<fee_schedule> 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<MAX_FEE_STABILIZATION_ITERATION; i++ )
{
op.visit( set_fee_visitor( f_max ) );
auto f2 = calculate_fee( op, core_exchange_rate );
if( f == f2 )
break;
f_max = std::max( f_max, f2 );
f = f2;
if( i == 0 )
{
// no need for warnings on later iterations
wlog( "set_fee requires multiple iterations to stabilize with core_exchange_rate ${p} on operation ${op}",
("p", core_exchange_rate) ("op", op) );
}
}
return f_max;
}
void chain_parameters::validate()const

@ -1 +1 @@
Subproject commit 1e10d3dc4774426e735eb6aabd8e0aa43799fe30
Subproject commit ad7ecbf061ae74c944c81da993a020c05d53c7b6

View file

@ -274,6 +274,10 @@ class wallet_api
fc::ecc::private_key derive_private_key(const std::string& prefix_string, int sequence_number) const;
variant info();
/** Returns info such as client version, git version of graphene/fc, version of boost, openssl.
* @returns compile time info and client and dependencies versions
*/
variant_object about() const;
optional<signed_block_with_info> 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)

View file

@ -27,6 +27,10 @@
#include <string>
#include <list>
#include <boost/version.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
#include <boost/range/algorithm/unique.hpp>
@ -41,6 +45,7 @@
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <fc/git_revision.hpp>
#include <fc/io/fstream.hpp>
#include <fc/io/json.hpp>
#include <fc/io/stdio.hpp>
@ -55,6 +60,7 @@
#include <graphene/app/api.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <graphene/utilities/git_revision.hpp>
#include <graphene/utilities/key_conversion.hpp>
#include <graphene/utilities/words.hpp>
#include <graphene/wallet/wallet.hpp>
@ -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<std::string>(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<by_key>().find(key);

View file

@ -109,9 +109,9 @@ template<typename T> struct js_name< fc::safe<T> > { static std::string name(){
template<> struct js_name< std::vector<char> > { static std::string name(){ return "bytes()"; } };
template<> struct js_name< op_wrapper > { static std::string name(){ return "operation "; } };
template<> struct js_name<fc::uint160> { static std::string name(){ return "bytes 20"; } };
template<> struct js_name<fc::sha224> { static std::string name(){ return "bytes 28"; } };
template<> struct js_name<fc::sha256> { static std::string name(){ return "bytes 32"; } };
template<> struct js_name<fc::unsigned_int> { static std::string name(){ return "varuint32"; } };
template<> struct js_name<fc::signed_int> { static std::string name(){ return "varint32"; } };
template<> struct js_name< vote_id_type > { static std::string name(){ return "vote_id"; } };

View file

@ -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<call_order_index>().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() */

View file

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

View file

@ -22,6 +22,7 @@
#include <fc/smart_ref_impl.hpp>
#include <fc/uint128.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/market_evaluator.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <boost/test/unit_test.hpp>
@ -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<account_create_operation>().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()

View file

@ -23,6 +23,7 @@
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
@ -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;

View file

@ -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<vesting_balance_worker_type>().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) );