diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 2080aa2e..d8793423 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -346,6 +346,8 @@ namespace graphene { namespace app { break; case impl_special_authority_object_type: break; + case impl_buyback_object_type: + break; } } return result; diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 2ce2f99d..d0cbc20b 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -77,6 +77,7 @@ add_library( graphene_chain worker_evaluator.cpp confidential_evaluator.cpp special_authority.cpp + buyback.cpp account_object.cpp asset_object.cpp @@ -85,6 +86,8 @@ add_library( graphene_chain block_database.cpp + is_authorized_asset.cpp + ${HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp" ) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index f1c77d10..03fe26c7 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -25,6 +25,8 @@ #include #include +#include +#include #include #include #include @@ -80,6 +82,8 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio evaluate_special_authority( d, *op.extensions.value.owner_special_authority ); if( op.extensions.value.active_special_authority.valid() ) evaluate_special_authority( d, *op.extensions.value.active_special_authority ); + if( op.extensions.value.buyback_options.valid() ) + evaluate_buyback_account_options( d, *op.extensions.value.buyback_options ); uint32_t max_vote_id = global_props.next_available_vote_id; @@ -115,6 +119,7 @@ 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 { + database& d = db(); uint16_t referrer_percent = o.referrer_percent; bool has_small_percent = ( (db().head_block_time() <= HARDFORK_453_TIME) @@ -154,6 +159,11 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio obj.owner_special_authority = *(o.extensions.value.owner_special_authority); if( o.extensions.value.active_special_authority.valid() ) obj.active_special_authority = *(o.extensions.value.active_special_authority); + if( o.extensions.value.buyback_options.valid() ) + { + obj.allowed_assets = o.extensions.value.buyback_options->markets; + obj.allowed_assets->emplace( o.extensions.value.buyback_options->asset_to_buy ); + } }); if( has_small_percent ) @@ -186,6 +196,21 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio } ); } + if( o.extensions.value.buyback_options.valid() ) + { + asset_id_type asset_to_buy = o.extensions.value.buyback_options->asset_to_buy; + + d.create< buyback_object >( [&]( buyback_object& bo ) + { + bo.asset_to_buy = asset_to_buy; + } ); + + d.modify( asset_to_buy(d), [&]( asset_object& a ) + { + a.buyback_account = new_acnt_object.id; + } ); + } + return new_acnt_object.id; } FC_CAPTURE_AND_RETHROW((o)) } diff --git a/libraries/chain/account_object.cpp b/libraries/chain/account_object.cpp index 1006cdcb..90d97692 100644 --- a/libraries/chain/account_object.cpp +++ b/libraries/chain/account_object.cpp @@ -42,35 +42,6 @@ share_type cut_fee(share_type a, uint16_t p) return r.to_uint64(); } -bool account_object::is_authorized_asset(const asset_object& asset_obj, const database& d) const -{ - if( d.head_block_time() > HARDFORK_416_TIME ) - { - if( !(asset_obj.options.flags & white_list) ) - return true; - } - - for( const auto id : blacklisting_accounts ) - { - if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() ) - return false; - } - - if( d.head_block_time() > HARDFORK_415_TIME ) - { - if( asset_obj.options.whitelist_authorities.size() == 0 ) - return true; - } - - for( const auto id : whitelisting_accounts ) - { - if( asset_obj.options.whitelist_authorities.find(id) != asset_obj.options.whitelist_authorities.end() ) - return true; - } - - return false; -} - void account_balance_object::adjust_balance(const asset& delta) { assert(delta.asset_id == asset_type); diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 17113d9c..9a467bec 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -152,11 +153,7 @@ void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o ) FC_ASSERT( !a.is_market_issued(), "Cannot manually issue a market-issued asset." ); to_account = &o.issue_to_account(d); - - if( a.options.flags & white_list ) - { - FC_ASSERT( to_account->is_authorized_asset( a, d ) ); - } + FC_ASSERT( is_authorized_asset( d, *to_account, a ) ); asset_dyn_data = &a.dynamic_asset_data_id(d); FC_ASSERT( (asset_dyn_data->current_supply + o.asset_to_issue.amount) <= a.options.max_supply ); @@ -188,11 +185,7 @@ void_result asset_reserve_evaluator::do_evaluate( const asset_reserve_operation& ); from_account = &o.payer(d); - - if( a.options.flags & white_list ) - { - FC_ASSERT( from_account->is_authorized_asset( a, d ) ); - } + FC_ASSERT( is_authorized_asset( d, *from_account, a ) ); asset_dyn_data = &a.dynamic_asset_data_id(d); FC_ASSERT( (asset_dyn_data->current_supply - o.amount_to_reserve.amount) >= 0 ); diff --git a/libraries/chain/buyback.cpp b/libraries/chain/buyback.cpp new file mode 100644 index 00000000..09341fe7 --- /dev/null +++ b/libraries/chain/buyback.cpp @@ -0,0 +1,45 @@ +/* + * 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 +#include +#include +#include + +namespace graphene { namespace chain { + +void evaluate_buyback_account_options( const database& db, const buyback_account_options& bbo ) +{ + FC_ASSERT( db.head_block_time() >= HARDFORK_538_TIME ); + const asset_object& a = bbo.asset_to_buy(db); + GRAPHENE_ASSERT( a.issuer == bbo.asset_to_buy_issuer, + account_create_buyback_incorrect_issuer, "Incorrect asset issuer specified in buyback_account_options", ("asset", a)("bbo", bbo) ); + GRAPHENE_ASSERT( !a.buyback_account.valid(), + account_create_buyback_already_exists, "Cannot create buyback for asset which already has buyback", ("asset", a)("bbo", bbo) ); + // TODO: Replace with chain parameter #554 + GRAPHENE_ASSERT( bbo.markets.size() < GRAPHENE_DEFAULT_MAX_BUYBACK_MARKETS, + account_create_buyback_too_many_markets, "Too many buyback markets", ("asset", a)("bbo", bbo) ); +} + +} } diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 41299655..0bb841ee 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -296,14 +296,14 @@ signed_block database::generate_block( const fc::ecc::private_key& block_signing_private_key, uint32_t skip /* = 0 */ ) -{ +{ try { signed_block result; detail::with_skip_flags( *this, skip, [&]() { result = _generate_block( when, witness_id, block_signing_private_key ); } ); return result; -} +} FC_CAPTURE_AND_RETHROW() } signed_block database::_generate_block( fc::time_point_sec when, diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 85c3cc92..fd1f4e00 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -210,6 +211,7 @@ void database::initialize_indexes() add_index< primary_index > >(); add_index< primary_index > >(); add_index< primary_index< special_authority_index > >(); + add_index< primary_index< buyback_index > >(); } void database::init_genesis(const genesis_state_type& genesis_state) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 093b7591..314fe9f3 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -33,9 +33,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -515,10 +517,88 @@ void update_top_n_authorities( database& db ) } ); } +void create_buyback_orders( database& db ) +{ + const auto& bbo_idx = db.get_index_type< buyback_index >().indices().get(); + const auto& bal_idx = db.get_index_type< account_balance_index >().indices().get< by_account_asset >(); + + for( const buyback_object& bbo : bbo_idx ) + { + const asset_object& asset_to_buy = bbo.asset_to_buy(db); + assert( asset_to_buy.buyback_account.valid() ); + + const account_object& buyback_account = (*(asset_to_buy.buyback_account))(db); + asset_id_type next_asset = asset_id_type(); + + if( !buyback_account.allowed_assets.valid() ) + { + wlog( "skipping buyback account ${b} at block ${n} because allowed_assets does not exist", ("b", buyback_account)("n", db.head_block_num()) ); + continue; + } + + while( true ) + { + auto it = bal_idx.lower_bound( boost::make_tuple( buyback_account.id, next_asset ) ); + if( it == bal_idx.end() ) + break; + if( it->owner != buyback_account.id ) + break; + asset_id_type asset_to_sell = it->asset_type; + share_type amount_to_sell = it->balance; + next_asset = asset_to_sell + 1; + if( asset_to_sell == asset_to_buy.id ) + continue; + if( amount_to_sell == 0 ) + continue; + if( buyback_account.allowed_assets->find( asset_to_sell ) == buyback_account.allowed_assets->end() ) + { + wlog( "buyback account ${b} not selling disallowed holdings of asset ${a} at block ${n}", ("b", buyback_account)("a", asset_to_sell)("n", db.head_block_num()) ); + continue; + } + + try + { + transaction_evaluation_state buyback_context(&db); + buyback_context.skip_fee_schedule_check = true; + + limit_order_create_operation create_vop; + create_vop.fee = asset( 0, asset_id_type() ); + create_vop.seller = buyback_account.id; + create_vop.amount_to_sell = asset( amount_to_sell, asset_to_sell ); + create_vop.min_to_receive = asset( 1, asset_to_buy.id ); + create_vop.expiration = time_point_sec::maximum(); + create_vop.fill_or_kill = false; + + limit_order_id_type order_id = db.apply_operation( buyback_context, create_vop ).get< object_id_type >(); + + if( db.find( order_id ) != nullptr ) + { + limit_order_cancel_operation cancel_vop; + cancel_vop.fee = asset( 0, asset_id_type() ); + cancel_vop.order = order_id; + cancel_vop.fee_paying_account = buyback_account.id; + + db.apply_operation( buyback_context, cancel_vop ); + } + } + catch( const fc::exception& e ) + { + // we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account + wlog( "Skipping buyback processing selling ${as} for ${ab} for buyback account ${b} at block ${n}; exception was ${e}", + ("as", asset_to_sell)("ab", asset_to_buy)("b", buyback_account)("n", db.head_block_num())("e", e.to_detail_string()) ); + continue; + } + } + } + return; +} + void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { const auto& gpo = get_global_properties(); + create_buyback_orders(*this); + struct vote_tally_helper { database& d; const global_property_object& props; diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index edcfc2c3..2c8251ca 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -132,6 +132,27 @@ void database::cancel_order( const limit_order_object& order, bool create_virtua remove(order); } +bool maybe_cull_small_order( database& db, const limit_order_object& order ) +{ + /** + * There are times when the AMOUNT_FOR_SALE * SALE_PRICE == 0 which means that we + * have hit the limit where the seller is asking for nothing in return. When this + * happens we must refund any balance back to the seller, it is too small to be + * sold at the sale price. + * + * If the order is a taker order (as opposed to a maker order), so the price is + * set by the counterparty, this check is deferred until the order becomes unmatched + * (see #555) -- however, detecting this condition is the responsibility of the caller. + */ + if( order.amount_to_receive().amount == 0 ) + { + ilog( "applied epsilon logic" ); + db.cancel_order(order); + return true; + } + return false; +} + bool database::apply_order(const limit_order_object& new_order_object, bool allow_black_swan) { auto order_id = new_order_object.id; @@ -171,7 +192,15 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo check_call_orders(sell_asset, allow_black_swan); check_call_orders(receive_asset, allow_black_swan); - return find_object(order_id) == nullptr; + const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); + if( updated_order_object == nullptr ) + return true; + if( head_block_time() <= HARDFORK_555_TIME ) + return false; + // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() being called by match() above + // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that was done too eagerly before, and + // this is the point it's deferred to. + return maybe_cull_small_order( *this, *updated_order_object ); } /** @@ -218,8 +247,8 @@ int database::match( const limit_order_object& usd, const OrderType& core, const core_pays == core.amount_for_sale() ); int result = 0; - result |= fill_order( usd, usd_pays, usd_receives ); - result |= fill_order( core, core_pays, core_receives ) << 1; + result |= fill_order( usd, usd_pays, usd_receives, false ); + result |= fill_order( core, core_pays, core_receives, true ) << 1; assert( result != 0 ); return result; } @@ -263,8 +292,10 @@ asset database::match( const call_order_object& call, return call_receives; } FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) } -bool database::fill_order( const limit_order_object& order, const asset& pays, const asset& receives ) +bool database::fill_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small ) { try { + cull_if_small |= (head_block_time() < HARDFORK_555_TIME); + FC_ASSERT( order.amount_for_sale().asset_id == pays.asset_id ); FC_ASSERT( pays.asset_id != receives.asset_id ); @@ -297,17 +328,8 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c b.for_sale -= pays.amount; b.deferred_fee = 0; }); - /** - * There are times when the AMOUNT_FOR_SALE * SALE_PRICE == 0 which means that we - * have hit the limit where the seller is asking for nothing in return. When this - * happens we must refund any balance back to the seller, it is too small to be - * sold at the sale price. - */ - if( order.amount_to_receive().amount == 0 ) - { - cancel_order(order); - return true; - } + if( cull_if_small ) + return maybe_cull_small_order( *this, order ); return false; } } FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) } @@ -517,7 +539,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan fill_order(*old_call_itr, call_pays, call_receives); auto old_limit_itr = filled_limit ? limit_itr++ : limit_itr; - fill_order(*old_limit_itr, order_pays, order_receives); + fill_order(*old_limit_itr, order_pays, order_receives, true); } // whlie call_itr != call_end diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index dd14340c..da96eef9 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -156,14 +156,14 @@ void database::update_last_irreversible_block() } void database::clear_expired_transactions() -{ +{ try { //Look for expired transactions in the deduplication list, and remove them. //Transactions must have expired by at least two forking windows in order to be removed. auto& transaction_idx = static_cast(get_mutable_index(implementation_ids, impl_transaction_object_type)); const auto& dedupe_index = transaction_idx.indices().get(); while( (!dedupe_index.empty()) && (head_block_time() > dedupe_index.rbegin()->trx.expiration) ) transaction_idx.remove(*dedupe_index.rbegin()); -} +} FC_CAPTURE_AND_RETHROW() } void database::clear_expired_proposals() { @@ -254,7 +254,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s } void database::clear_expired_orders() -{ +{ try { detail::with_skip_flags( *this, get_node_properties().skip_flags | skip_authority_check, [&](){ transaction_evaluation_state cancel_context(this); @@ -267,6 +267,18 @@ void database::clear_expired_orders() const limit_order_object& order = *limit_index.begin(); canceler.fee_paying_account = order.seller; canceler.order = order.id; + canceler.fee = current_fee_schedule().calculate_fee( canceler ); + if( canceler.fee.amount > order.deferred_fee ) + { + // Cap auto-cancel fees at deferred_fee; see #549 + wlog( "At block ${b}, fee for clearing expired order ${oid} was capped at deferred_fee ${fee}", ("b", head_block_num())("oid", order.id)("fee", order.deferred_fee) ); + canceler.fee = asset( order.deferred_fee, asset_id_type() ); + } + // we know the fee for this op is set correctly since it is set by the chain. + // this allows us to avoid a hung chain: + // - if #549 case above triggers + // - if the fee is incorrect, which may happen due to #435 (although since cancel is a fixed-fee op, it shouldn't) + cancel_context.skip_fee_schedule_check = true; apply_operation(cancel_context, canceler); } }); @@ -409,7 +421,7 @@ void database::clear_expired_orders() } } } -} +} FC_CAPTURE_AND_RETHROW() } void database::update_expired_feeds() { diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 041e413a..06e31c21 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -61,7 +62,7 @@ database& generic_evaluator::db()const { return trx_state->db(); } if( d.head_block_time() > HARDFORK_419_TIME ) { - FC_ASSERT( fee_paying_account->is_authorized_asset( *fee_asset, d ), "Account ${acct} '${name}' attempted to pay fee by using asset ${a} '${sym}', which is unauthorized due to whitelist / blacklist", + FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *fee_asset ), "Account ${acct} '${name}' attempted to pay fee by using asset ${a} '${sym}', which is unauthorized due to whitelist / blacklist", ("acct", fee_paying_account->id)("name", fee_paying_account->name)("a", fee_asset->id)("sym", fee_asset->symbol) ); } @@ -79,24 +80,27 @@ database& generic_evaluator::db()const { return trx_state->db(); } 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; - }); + if( !trx_state->skip_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; + }); + } } - 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 ); - }); + if( !trx_state->skip_fee ) { + database& d = db(); + /// TODO: db().pay_fee( account_id, core_fee ); + d.modify(*fee_paying_account_statistics, [&](account_statistics_object& s) + { + s.pay_fee( core_fee_paid, d.get_global_properties().parameters.cashback_vesting_threshold ); + }); + } } FC_CAPTURE_AND_RETHROW() } } } diff --git a/libraries/chain/hardfork.d/538.hf b/libraries/chain/hardfork.d/538.hf new file mode 100644 index 00000000..68da2c43 --- /dev/null +++ b/libraries/chain/hardfork.d/538.hf @@ -0,0 +1,4 @@ +// #538 Buyback accounts +#ifndef HARDFORK_538_TIME +#define HARDFORK_538_TIME (fc::time_point_sec( 1455127200 )) +#endif diff --git a/libraries/chain/hardfork.d/555.hf b/libraries/chain/hardfork.d/555.hf new file mode 100644 index 00000000..28959f11 --- /dev/null +++ b/libraries/chain/hardfork.d/555.hf @@ -0,0 +1,4 @@ +// #555 Buyback accounts +#ifndef HARDFORK_555_TIME +#define HARDFORK_555_TIME (fc::time_point_sec( 1455127200 )) +#endif diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 403e671a..2b2fcd76 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -208,6 +208,13 @@ namespace graphene { namespace chain { special_authority owner_special_authority = no_special_authority(); special_authority active_special_authority = no_special_authority(); + /** + * This is a set of assets which the account is allowed to have. + * This is utilized to restrict buyback accounts to the assets that trade in their markets. + * In the future we may expand this to allow accounts to e.g. voluntarily restrict incoming transfers. + */ + optional< flat_set > allowed_assets; + bool has_special_authority()const { return (owner_special_authority.which() != special_authority::tag< no_special_authority >::value) @@ -243,12 +250,6 @@ namespace graphene { namespace chain { return !is_basic_account(now); } - /** - * @return true if this account is whitelisted and not blacklisted to transact in the provided asset; false - * otherwise. - */ - bool is_authorized_asset(const asset_object& asset_obj, const database& d)const; - account_id_type get_id()const { return id; } }; @@ -364,6 +365,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_object, (whitelisted_accounts)(blacklisted_accounts) (cashback_vb) (owner_special_authority)(active_special_authority) + (allowed_assets) ) FC_REFLECT_DERIVED( graphene::chain::account_balance_object, diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index ea7e6fa4..f893f34a 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -130,6 +130,8 @@ namespace graphene { namespace chain { /// Extra data associated with BitAssets. This field is non-null if and only if is_market_issued() returns true optional bitasset_data_id; + optional buyback_account; + asset_id_type get_id()const { return id; } void validate()const @@ -266,4 +268,5 @@ FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object), (options) (dynamic_asset_data_id) (bitasset_data_id) + (buyback_account) ) diff --git a/libraries/chain/include/graphene/chain/buyback.hpp b/libraries/chain/include/graphene/chain/buyback.hpp new file mode 100644 index 00000000..5b7a8746 --- /dev/null +++ b/libraries/chain/include/graphene/chain/buyback.hpp @@ -0,0 +1,34 @@ +/* + * 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. + */ +#pragma once + +#include + +namespace graphene { namespace chain { + +class database; + +void evaluate_buyback_account_options( const database& db, const buyback_account_options& auth ); + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/buyback_object.hpp b/libraries/chain/include/graphene/chain/buyback_object.hpp new file mode 100644 index 00000000..de84b0e2 --- /dev/null +++ b/libraries/chain/include/graphene/chain/buyback_object.hpp @@ -0,0 +1,67 @@ +/* + * 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. + */ +#pragma once + +#include +#include +#include + +namespace graphene { namespace chain { + +/** + * buyback_authority_object only exists to help with a specific indexing problem. + * We want to be able to iterate over all assets that have a buyback program. + * However, assets which have a buyback program are very rare. So rather + * than indexing asset_object by the buyback field (requiring additional + * bookkeeping for every asset), we instead maintain a buyback_object + * pointing to each asset which has buyback (requiring additional + * bookkeeping only for every asset which has buyback). + * + * This class is an implementation detail. + */ + +class buyback_object : public graphene::db::abstract_object< buyback_object > +{ + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_buyback_object_type; + + asset_id_type asset_to_buy; +}; + +struct by_asset; + +typedef multi_index_container< + buyback_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, member< buyback_object, asset_id_type, &buyback_object::asset_to_buy> > + > +> buyback_multi_index_type; + +typedef generic_index< buyback_object, buyback_multi_index_type > buyback_index; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::buyback_object, (asset_to_buy) ) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index f1bbc3f7..5f8772d1 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -107,6 +107,7 @@ #define GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD GRAPHENE_BLOCKCHAIN_PRECISION * 100; #define GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE 1000 #define GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS 4 +#define GRAPHENE_DEFAULT_MAX_BUYBACK_MARKETS 4 #define GRAPHENE_MAX_WORKER_NAME_LENGTH 63 diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index e5956fec..25d1b2c9 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -370,7 +370,7 @@ namespace graphene { namespace chain { /** * @return true if the order was completely filled and thus freed. */ - bool fill_order( const limit_order_object& order, const asset& pays, const asset& receives ); + bool fill_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small ); bool fill_order( const call_order_object& order, const asset& pays, const asset& receives ); bool fill_order( const force_settlement_object& settle, const asset& pays, const asset& receives ); @@ -412,12 +412,14 @@ namespace graphene { namespace chain { //////////////////// db_block.cpp //////////////////// + public: + // these were formerly private, but they have a fairly well-defined API, so let's make them public void apply_block( const signed_block& next_block, uint32_t skip = skip_nothing ); processed_transaction apply_transaction( const signed_transaction& trx, uint32_t skip = skip_nothing ); + operation_result apply_operation( transaction_evaluation_state& eval_state, const operation& op ); + private: void _apply_block( const signed_block& next_block ); processed_transaction _apply_transaction( const signed_transaction& trx ); - operation_result apply_operation( transaction_evaluation_state& eval_state, const operation& op ); - ///Steps involved in applying a new block ///@{ diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp index fe033dfc..4271be58 100644 --- a/libraries/chain/include/graphene/chain/evaluator.hpp +++ b/libraries/chain/include/graphene/chain/evaluator.hpp @@ -23,6 +23,7 @@ */ #pragma once #include +#include #include namespace graphene { namespace chain { @@ -227,10 +228,13 @@ namespace graphene { namespace chain { const auto& op = o.get(); prepare_fee(op.fee_payer(), op.fee); - GRAPHENE_ASSERT( core_fee_paid >= db().current_fee_schedule().calculate_fee( op ).amount, - insufficient_fee, - "Insufficient Fee Paid", - ("core_fee_paid",core_fee_paid)("required",db().current_fee_schedule().calculate_fee( op ).amount) ); + if( !trx_state->skip_fee_schedule_check ) + { + GRAPHENE_ASSERT( core_fee_paid >= db().current_fee_schedule().calculate_fee( op ).amount, + insufficient_fee, + "Insufficient Fee Paid", + ("core_fee_paid",core_fee_paid)("required",db().current_fee_schedule().calculate_fee( op ).amount) ); + } return eval->do_evaluate(op); } diff --git a/libraries/chain/include/graphene/chain/exceptions.hpp b/libraries/chain/include/graphene/chain/exceptions.hpp index e739b761..2e07ca26 100644 --- a/libraries/chain/include/graphene/chain/exceptions.hpp +++ b/libraries/chain/include/graphene/chain/exceptions.hpp @@ -104,6 +104,9 @@ namespace graphene { namespace chain { GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_create ); GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( max_auth_exceeded, account_create, 1, "Exceeds max authority fan-out" ) GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( auth_account_not_found, account_create, 2, "Auth account not found" ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_incorrect_issuer, account_create, 3, "Incorrect issuer specified for account" ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_already_exists, account_create, 4, "Cannot create buyback for asset which already has buyback" ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( buyback_too_many_markets, account_create, 5, "Too many buyback markets" ) GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( account_update ); GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( max_auth_exceeded, account_update, 1, "Exceeds max authority fan-out" ) diff --git a/libraries/chain/include/graphene/chain/is_authorized_asset.hpp b/libraries/chain/include/graphene/chain/is_authorized_asset.hpp new file mode 100644 index 00000000..3d99ae0f --- /dev/null +++ b/libraries/chain/include/graphene/chain/is_authorized_asset.hpp @@ -0,0 +1,55 @@ +/* + * 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. + */ +#pragma once + +namespace graphene { namespace chain { + +class account_object; +class asset_object; +class database; + +namespace detail { + +bool _is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj); + +} + +/** + * @return true if the account is whitelisted and not blacklisted to transact in the provided asset; false + * otherwise. + */ + +inline bool is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj) +{ + bool fast_check = !(asset_obj.options.flags & white_list); + fast_check &= !(acct.allowed_assets.valid()); + + if( fast_check ) + return true; + + bool slow_check = detail::_is_authorized_asset( d, acct, asset_obj ); + return slow_check; +} + +} } diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp index 987568b1..4e5fdaed 100644 --- a/libraries/chain/include/graphene/chain/protocol/account.hpp +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -23,6 +23,7 @@ */ #pragma once #include +#include #include #include #include @@ -69,6 +70,7 @@ namespace graphene { namespace chain { optional< void_t > null_ext; optional< special_authority > owner_special_authority; optional< special_authority > active_special_authority; + optional< buyback_account_options > buyback_options; }; struct fee_parameters_type @@ -98,6 +100,14 @@ namespace graphene { namespace chain { account_id_type fee_payer()const { return registrar; } void validate()const; share_type calculate_fee(const fee_parameters_type& )const; + + void get_required_active_authorities( flat_set& a )const + { + // registrar should be required anyway as it is the fee_payer(), but we insert it here just to be sure + a.insert( registrar ); + if( extensions.value.buyback_options.valid() ) + a.insert( extensions.value.buyback_options->asset_to_buy_issuer ); + } }; /** @@ -258,7 +268,7 @@ FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listi FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing, (no_listing)(white_listed)(black_listed)(white_and_black_listed)) -FC_REFLECT(graphene::chain::account_create_operation::ext, (null_ext)(owner_special_authority)(active_special_authority) ) +FC_REFLECT(graphene::chain::account_create_operation::ext, (null_ext)(owner_special_authority)(active_special_authority)(buyback_options) ) FC_REFLECT( graphene::chain::account_create_operation, (fee)(registrar) (referrer)(referrer_percent) diff --git a/libraries/chain/include/graphene/chain/protocol/authority.hpp b/libraries/chain/include/graphene/chain/protocol/authority.hpp index e144f6c3..b6ef60d7 100644 --- a/libraries/chain/include/graphene/chain/protocol/authority.hpp +++ b/libraries/chain/include/graphene/chain/protocol/authority.hpp @@ -109,6 +109,11 @@ namespace graphene { namespace chain { uint32_t num_auths()const { return account_auths.size() + key_auths.size() + address_auths.size(); } void clear() { account_auths.clear(); key_auths.clear(); } + static authority null_authority() + { + return authority( 1, GRAPHENE_NULL_ACCOUNT, 1 ); + } + uint32_t weight_threshold = 0; flat_map account_auths; flat_map key_auths; diff --git a/libraries/chain/include/graphene/chain/protocol/buyback.hpp b/libraries/chain/include/graphene/chain/protocol/buyback.hpp new file mode 100644 index 00000000..6adad52d --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/buyback.hpp @@ -0,0 +1,52 @@ +/* + * 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. + */ +#pragma once + +#include + +namespace graphene { namespace chain { + +struct buyback_account_options +{ + /** + * The asset to buy. + */ + asset_id_type asset_to_buy; + + /** + * Issuer of the asset. Must sign the transaction, must match issuer + * of specified asset. + */ + account_id_type asset_to_buy_issuer; + + /** + * What assets the account is willing to buy with. + * Other assets will just sit there since the account has null authority. + */ + flat_set< asset_id_type > markets; +}; + +} } + +FC_REFLECT( graphene::chain::buyback_account_options, (asset_to_buy)(asset_to_buy_issuer)(markets) ); diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 009a7980..fac66d7a 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -153,7 +153,8 @@ namespace graphene { namespace chain { impl_chain_property_object_type, impl_witness_schedule_object_type, impl_budget_record_object_type, - impl_special_authority_object_type + impl_special_authority_object_type, + impl_buyback_object_type }; //typedef fc::unsigned_int object_id_type; @@ -203,6 +204,7 @@ namespace graphene { namespace chain { class witness_schedule_object; class budget_record_object; class special_authority_object; + class buyback_object; typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type; typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type; @@ -221,6 +223,7 @@ namespace graphene { namespace chain { typedef object_id< implementation_ids, impl_budget_record_object_type, budget_record_object > budget_record_id_type; typedef object_id< implementation_ids, impl_blinded_balance_object_type, blinded_balance_object > blinded_balance_id_type; typedef object_id< implementation_ids, impl_special_authority_object_type, special_authority_object > special_authority_id_type; + typedef object_id< implementation_ids, impl_buyback_object_type, buyback_object > buyback_id_type; typedef fc::array symbol_type; typedef fc::ripemd160 block_id_type; @@ -351,6 +354,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_witness_schedule_object_type) (impl_budget_record_object_type) (impl_special_authority_object_type) + (impl_buyback_object_type) ) FC_REFLECT_TYPENAME( graphene::chain::share_type ) diff --git a/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp index 05be582e..5ffb4bb7 100644 --- a/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp +++ b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp @@ -45,5 +45,7 @@ namespace graphene { namespace chain { const signed_transaction* _trx = nullptr; database* _db = nullptr; bool _is_proposed_trx = false; + bool skip_fee = false; + bool skip_fee_schedule_check = false; }; } } // namespace graphene::chain diff --git a/libraries/chain/is_authorized_asset.cpp b/libraries/chain/is_authorized_asset.cpp new file mode 100644 index 00000000..6ec9643d --- /dev/null +++ b/libraries/chain/is_authorized_asset.cpp @@ -0,0 +1,70 @@ +/* + * 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 +#include + +#include +#include + +namespace graphene { namespace chain { + +namespace detail { + +bool _is_authorized_asset( + const database& d, + const account_object& acct, + const asset_object& asset_obj) +{ + if( acct.allowed_assets.valid() ) + { + if( acct.allowed_assets->find( asset_obj.id ) == acct.allowed_assets->end() ) + return false; + // must still pass other checks even if it is in allowed_assets + } + + for( const auto id : acct.blacklisting_accounts ) + { + if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() ) + return false; + } + + if( d.head_block_time() > HARDFORK_415_TIME ) + { + if( asset_obj.options.whitelist_authorities.size() == 0 ) + return true; + } + + for( const auto id : acct.whitelisting_accounts ) + { + if( asset_obj.options.whitelist_authorities.find(id) != asset_obj.options.whitelist_authorities.end() ) + return true; + } + + return false; +} + +} // detail + +} } // graphene::chain diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 10e2cb98..27c31ae4 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -23,11 +23,14 @@ */ #include #include +#include + +#include + #include #include #include -#include -#include +#include #include @@ -49,16 +52,8 @@ void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_o if( _sell_asset->options.blacklist_markets.size() ) FC_ASSERT( _sell_asset->options.blacklist_markets.find(_receive_asset->id) == _sell_asset->options.blacklist_markets.end() ); - if( d.head_block_time() <= HARDFORK_416_TIME ) - { - if( _sell_asset->options.flags & white_list ) FC_ASSERT( _seller->is_authorized_asset( *_sell_asset, d ) ); - if( _receive_asset->options.flags & white_list ) FC_ASSERT( _seller->is_authorized_asset( *_receive_asset, d ) ); - } - else - { - FC_ASSERT( _seller->is_authorized_asset( *_sell_asset, d ) ); - FC_ASSERT( _seller->is_authorized_asset( *_receive_asset, d ) ); - } + FC_ASSERT( is_authorized_asset( d, *_seller, *_sell_asset ) ); + FC_ASSERT( is_authorized_asset( d, *_seller, *_receive_asset ) ); FC_ASSERT( d.get_balance( *_seller, *_sell_asset ) >= op.amount_to_sell, "insufficient balance", ("balance",d.get_balance(*_seller,*_sell_asset))("amount_to_sell",op.amount_to_sell) ); diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp index 1c3fa007..b3ad9e00 100644 --- a/libraries/chain/protocol/account.cpp +++ b/libraries/chain/protocol/account.cpp @@ -190,6 +190,19 @@ void account_create_operation::validate()const validate_special_authority( *extensions.value.owner_special_authority ); if( extensions.value.active_special_authority.valid() ) validate_special_authority( *extensions.value.active_special_authority ); + if( extensions.value.buyback_options.valid() ) + { + FC_ASSERT( !(extensions.value.owner_special_authority.valid()) ); + FC_ASSERT( !(extensions.value.active_special_authority.valid()) ); + FC_ASSERT( owner == authority::null_authority() ); + FC_ASSERT( active == authority::null_authority() ); + size_t n_markets = extensions.value.buyback_options->markets.size(); + FC_ASSERT( n_markets > 0 ); + for( const asset_id_type m : extensions.value.buyback_options->markets ) + { + FC_ASSERT( m != extensions.value.buyback_options->asset_to_buy ); + } + } } diff --git a/libraries/chain/transfer_evaluator.cpp b/libraries/chain/transfer_evaluator.cpp index 25304963..accc6ca3 100644 --- a/libraries/chain/transfer_evaluator.cpp +++ b/libraries/chain/transfer_evaluator.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace graphene { namespace chain { void_result transfer_evaluator::do_evaluate( const transfer_operation& op ) @@ -35,34 +36,23 @@ void_result transfer_evaluator::do_evaluate( const transfer_operation& op ) 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); - const asset_object& fee_asset_type = op.fee.asset_id(d); try { - if( asset_type.options.flags & white_list ) - { - GRAPHENE_ASSERT( - from_account.is_authorized_asset( asset_type, d ), - transfer_from_account_not_whitelisted, - "'from' account ${from} is not whitelisted for asset ${asset}", - ("from",op.from) - ("asset",op.amount.asset_id) - ); - GRAPHENE_ASSERT( - to_account.is_authorized_asset( asset_type, d ), - transfer_to_account_not_whitelisted, - "'to' account ${to} is not whitelisted for asset ${asset}", - ("to",op.to) - ("asset",op.amount.asset_id) - ); - } - - if( d.head_block_time() <= HARDFORK_419_TIME ) - { - if( fee_asset_type.options.flags & white_list ) - FC_ASSERT( from_account.is_authorized_asset( asset_type, d ) ); - } - // the above becomes no-op after hardfork because this check will then be performed in evaluator + 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() ) { @@ -108,18 +98,13 @@ void_result override_transfer_evaluator::do_evaluate( const override_transfer_op const account_object& from_account = op.from(d); const account_object& to_account = op.to(d); - const asset_object& fee_asset_type = op.fee.asset_id(d); - if( asset_type.options.flags & white_list ) - { - FC_ASSERT( to_account.is_authorized_asset( asset_type, d ) ); - FC_ASSERT( from_account.is_authorized_asset( asset_type, d ) ); - } + FC_ASSERT( is_authorized_asset( d, to_account, asset_type ) ); + FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); if( d.head_block_time() <= HARDFORK_419_TIME ) { - if( fee_asset_type.options.flags & white_list ) - FC_ASSERT( from_account.is_authorized_asset( asset_type, d ) ); + FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); } // the above becomes no-op after hardfork because this check will then be performed in evaluator diff --git a/libraries/chain/withdraw_permission_evaluator.cpp b/libraries/chain/withdraw_permission_evaluator.cpp index 0fdc4607..d001b441 100644 --- a/libraries/chain/withdraw_permission_evaluator.cpp +++ b/libraries/chain/withdraw_permission_evaluator.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -69,23 +70,10 @@ void_result withdraw_permission_claim_evaluator::do_evaluate(const withdraw_perm const asset_object& _asset = op.amount_to_withdraw.asset_id(d); if( _asset.is_transfer_restricted() ) FC_ASSERT( _asset.issuer == permit.authorized_account || _asset.issuer == permit.withdraw_from_account ); - if( d.head_block_time() <= HARDFORK_416_TIME ) - { - if( _asset.options.flags & white_list ) - { - const account_object& from = op.withdraw_to_account(d); - const account_object& to = permit.authorized_account(d); - FC_ASSERT( to.is_authorized_asset( _asset, d ) ); - FC_ASSERT( from.is_authorized_asset( _asset, d ) ); - } - } - else - { - const account_object& from = op.withdraw_to_account(d); - const account_object& to = permit.authorized_account(d); - FC_ASSERT( to.is_authorized_asset( _asset, d ) ); - FC_ASSERT( from.is_authorized_asset( _asset, d ) ); - } + const account_object& from = op.withdraw_to_account(d); + const account_object& to = permit.authorized_account(d); + FC_ASSERT( is_authorized_asset( d, to, _asset ) ); + FC_ASSERT( is_authorized_asset( d, from, _asset ) ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index bddbe5ae..6d08d686 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -1483,4 +1483,141 @@ BOOST_AUTO_TEST_CASE( top_n_special ) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( buyback ) +{ + ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin) ); + upgrade_to_lifetime_member(philbin_id); + + generate_blocks( HARDFORK_538_TIME ); + generate_blocks( HARDFORK_555_TIME ); + + try + { + { + // + // Izzy (issuer) + // Alice, Bob, Chloe, Dan (ABCD) + // Rex (recycler -- buyback account) + // Philbin (registrar) + // + + asset_id_type nono_id = create_user_issued_asset( "NONO", izzy_id(db), 0 ).id; + asset_id_type buyme_id = create_user_issued_asset( "BUYME", izzy_id(db), 0 ).id; + + // Create a buyback account + account_id_type rex_id; + { + buyback_account_options bbo; + bbo.asset_to_buy = buyme_id; + bbo.asset_to_buy_issuer = izzy_id; + bbo.markets.emplace( asset_id_type() ); + account_create_operation create_op = make_account( "rex" ); + create_op.registrar = philbin_id; + create_op.extensions.value.buyback_options = bbo; + create_op.owner = authority::null_authority(); + create_op.active = authority::null_authority(); + + // Let's break it... + + signed_transaction tx; + tx.operations.push_back( create_op ); + set_expiration( db, tx ); + + tx.operations.back().get< account_create_operation >().extensions.value.buyback_options->asset_to_buy_issuer = alice_id; + sign( tx, alice_private_key ); + sign( tx, philbin_private_key ); + + // Alice and Philbin signed, but asset issuer is invalid + GRAPHENE_CHECK_THROW( db.push_transaction(tx), account_create_buyback_incorrect_issuer ); + + tx.signatures.clear(); + tx.operations.back().get< account_create_operation >().extensions.value.buyback_options->asset_to_buy_issuer = izzy_id; + sign( tx, philbin_private_key ); + + // Izzy didn't sign + GRAPHENE_CHECK_THROW( db.push_transaction(tx), tx_missing_active_auth ); + sign( tx, izzy_private_key ); + + // OK + processed_transaction ptx = db.push_transaction( tx ); + rex_id = ptx.operation_results.back().get< object_id_type >(); + + // Try to create another account rex2 which is bbo on same asset + tx.signatures.clear(); + tx.operations.back().get< account_create_operation >().name = "rex2"; + sign( tx, izzy_private_key ); + sign( tx, philbin_private_key ); + GRAPHENE_CHECK_THROW( db.push_transaction(tx), account_create_buyback_already_exists ); + } + + // issue some BUYME to Alice + // we need to set_expiration() before issue_uia() because the latter doens't call it #11 + set_expiration( db, trx ); // #11 + issue_uia( alice_id, asset( 1000, buyme_id ) ); + issue_uia( alice_id, asset( 1000, nono_id ) ); + + // Alice wants to sell 100 BUYME for 1000 BTS, a middle price. + limit_order_id_type order_id_mid = create_sell_order( alice_id, asset( 100, buyme_id ), asset( 1000, asset_id_type() ) )->id; + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + // no success because buyback has none for sale + BOOST_CHECK( order_id_mid(db).for_sale == 100 ); + + // but we can send some to buyback + fund( rex_id(db), asset( 100, asset_id_type() ) ); + // no action until next maint + BOOST_CHECK( order_id_mid(db).for_sale == 100 ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + // partial fill, Alice now sells 90 BUYME for 900 BTS. + BOOST_CHECK( order_id_mid(db).for_sale == 90 ); + + // TODO check burn amount + + // aagh more state in trx + set_expiration( db, trx ); // #11 + + // Selling 10 BUYME for 50 BTS, a low price. + limit_order_id_type order_id_low = create_sell_order( alice_id, asset( 10, buyme_id ), asset( 50, asset_id_type() ) )->id; + // Selling 10 BUYME for 150 BTS, a high price. + limit_order_id_type order_id_high = create_sell_order( alice_id, asset( 10, buyme_id ), asset( 150, asset_id_type() ) )->id; + + fund( rex_id(db), asset( 250, asset_id_type() ) ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + BOOST_CHECK( db.find( order_id_low ) == nullptr ); + BOOST_CHECK( db.find( order_id_mid ) != nullptr ); + BOOST_CHECK( db.find( order_id_high ) != nullptr ); + + // 250 CORE in rex 90 BUYME in mid order 10 BUYME in low order + // 50 CORE goes to low order, buy 10 for 50 CORE + // 200 CORE goes to mid order, buy 20 for 200 CORE + // 70 BUYME in mid order 0 BUYME in low order + + idump( (order_id_mid(db)) ); + BOOST_CHECK( order_id_mid(db).for_sale == 70 ); + BOOST_CHECK( order_id_high(db).for_sale == 10 ); + + BOOST_CHECK( get_balance( rex_id, asset_id_type() ) == 0 ); + + // clear out the books -- 700 left on mid order, 150 left on high order, so 2000 BTS should result in 1150 left over + + fund( rex_id(db), asset( 2000, asset_id_type() ) ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + idump( (get_balance( rex_id, asset_id_type() )) ); + + BOOST_CHECK( get_balance( rex_id, asset_id_type() ) == 1150 ); + + GRAPHENE_CHECK_THROW( transfer( alice_id, rex_id, asset( 1, nono_id ) ), fc::exception ); + // TODO: Check cancellation works for account which is BTS-restricted + } + + } FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index ddb20d2d..d6dc83cb 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -161,7 +162,7 @@ BOOST_AUTO_TEST_CASE( issue_whitelist_uia ) } PUSH_TX( db, trx, ~0 ); - BOOST_CHECK(nathan_id(db).is_authorized_asset(uia_id(db), db)); + BOOST_CHECK(is_authorized_asset( db, nathan_id(db), uia_id(db) )); BOOST_CHECK_EQUAL(get_balance(nathan_id, uia_id), 1000); // Make a whitelist, now it should fail @@ -265,7 +266,7 @@ BOOST_AUTO_TEST_CASE( transfer_whitelist_uia ) wop.account_to_list = nathan.id; trx.operations.back() = wop; PUSH_TX( db, trx, ~0 ); - BOOST_CHECK( !(nathan.is_authorized_asset(advanced, db)) ); + BOOST_CHECK( !(is_authorized_asset( db, nathan, advanced )) ); BOOST_TEST_MESSAGE( "Attempting to transfer from nathan after blacklisting, should fail" ); op.amount = advanced.amount(50); @@ -323,7 +324,7 @@ BOOST_AUTO_TEST_CASE( transfer_whitelist_uia ) trx.operations.back() = op; //Fail because nathan is blacklisted - BOOST_CHECK(!nathan.is_authorized_asset(advanced, db)); + BOOST_CHECK(!is_authorized_asset( db, nathan, advanced )); GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception); //Remove nathan from committee's whitelist, add him to dan's. This should not authorize him to hold ADVANCED. @@ -340,7 +341,7 @@ BOOST_AUTO_TEST_CASE( transfer_whitelist_uia ) trx.operations.back() = op; //Fail because nathan is not whitelisted - BOOST_CHECK(!nathan.is_authorized_asset(advanced, db)); + BOOST_CHECK(!is_authorized_asset( db, nathan, advanced )); GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception); burn.payer = dan.id;