From 45e717c1811e931e19faa9c3683494d32f4e521a Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Thu, 28 Jan 2016 20:02:37 -0500 Subject: [PATCH 01/11] HARDFORK: fix for hung cancel order --- libraries/chain/db_block.cpp | 4 +-- libraries/chain/db_update.cpp | 9 +++--- libraries/chain/evaluator.cpp | 29 ++++++++++--------- .../chain/transaction_evaluation_state.hpp | 1 + 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 770951b6..d0ce18b4 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -279,14 +279,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_update.cpp b/libraries/chain/db_update.cpp index 2b6ba2e7..8a5ea106 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -152,14 +152,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() { @@ -250,7 +250,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); @@ -263,6 +263,7 @@ void database::clear_expired_orders() const limit_order_object& order = *limit_index.begin(); canceler.fee_paying_account = order.seller; canceler.order = order.id; + cancel_context.skip_fee = true; apply_operation(cancel_context, canceler); } }); @@ -362,7 +363,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 ee2f1580..4f763efa 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -76,24 +76,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/include/graphene/chain/transaction_evaluation_state.hpp b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp index f31cc93c..4c28e122 100644 --- a/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp +++ b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp @@ -42,5 +42,6 @@ namespace graphene { namespace chain { const signed_transaction* _trx = nullptr; database* _db = nullptr; bool _is_proposed_trx = false; + bool skip_fee = false; }; } } // namespace graphene::chain From 3646754fe59149ae688f82243b4fe41486c96ba3 Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Thu, 28 Jan 2016 20:11:46 -0500 Subject: [PATCH 02/11] HARDFORK - auto canceled orders still pay fee --- libraries/chain/db_update.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 8a5ea106..76dc1df9 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -263,7 +263,7 @@ void database::clear_expired_orders() const limit_order_object& order = *limit_index.begin(); canceler.fee_paying_account = order.seller; canceler.order = order.id; - cancel_context.skip_fee = true; + canceler.fee = current_fee_schedule().calculate_fee( canceler ); apply_operation(cancel_context, canceler); } }); From 59503acde9eef6520fc177c1cde71fe486d72533 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Tue, 2 Feb 2016 13:58:58 -0500 Subject: [PATCH 03/11] Cap auto-cancel fees at deferred_fee #549 --- libraries/chain/db_update.cpp | 11 +++++++++++ libraries/chain/include/graphene/chain/evaluator.hpp | 12 ++++++++---- .../graphene/chain/transaction_evaluation_state.hpp | 1 + 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 76dc1df9..3855c50b 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -264,6 +264,17 @@ void database::clear_expired_orders() 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); } }); diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp index cfbbaea2..a80a2df8 100644 --- a/libraries/chain/include/graphene/chain/evaluator.hpp +++ b/libraries/chain/include/graphene/chain/evaluator.hpp @@ -20,6 +20,7 @@ */ #pragma once #include +#include #include namespace graphene { namespace chain { @@ -224,10 +225,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/transaction_evaluation_state.hpp b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp index 4c28e122..4a5b5183 100644 --- a/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp +++ b/libraries/chain/include/graphene/chain/transaction_evaluation_state.hpp @@ -43,5 +43,6 @@ namespace graphene { namespace chain { database* _db = nullptr; bool _is_proposed_trx = false; bool skip_fee = false; + bool skip_fee_schedule_check = false; }; } } // namespace graphene::chain From ad339d2729d2331c562b264ef7f4ed7beff8c390 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Fri, 5 Feb 2016 17:29:13 -0500 Subject: [PATCH 04/11] Defer something-for-nothing culling for taker orders until the order is unmatched #555 --- libraries/chain/db_market.cpp | 54 +++++++++++++------ libraries/chain/hardfork.d/555.hf | 4 ++ .../chain/include/graphene/chain/database.hpp | 2 +- 3 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 libraries/chain/hardfork.d/555.hf 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/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/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index e5956fec..a4dfdfc6 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 ); From 68a0ffa1aa7ffd66940c10578c300e61ad6aa71f Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Wed, 10 Feb 2016 16:24:56 -0500 Subject: [PATCH 05/11] Make is_authorized_asset a free-floating method #566 The following sed commands were used to port existing call sites to the new interface: sed -i -e 's/\([a-zA-Z0-9_]\+\)->is_authorized_asset[(] \([a-zA-Z0-9_*]\+\), d [)]/is_authorized_asset( d, *\1, \2 )/' libraries/chain/*.cpp sed -i -e 's/\([a-zA-Z0-9_]\+\)[.]is_authorized_asset[(] \([a-zA-Z0-9_*]\+\), d [)]/is_authorized_asset( d, \1, \2 )/' libraries/chain/*.cpp sed -i -e 's/\([a-zA-Z0-9_]\+\)[(]db[)][.]is_authorized_asset[(]\([a-zA-Z0-9_*]\+\)[(]db[)], db[)]/is_authorized_asset( db, \1(db), \2(db) )/' tests/tests/uia_tests.cpp sed -i -e 's/\([a-zA-Z0-9_]\+\)[.]is_authorized_asset[(]\([a-zA-Z0-9_*]\+\), db[)]/is_authorized_asset( db, \1, \2 )/' tests/tests/uia_tests.cpp No new functionality is added by this commit, it is simply re-organizing the existing code in a different place. --- libraries/chain/CMakeLists.txt | 2 + libraries/chain/account_object.cpp | 29 --------- libraries/chain/asset_evaluator.cpp | 5 +- libraries/chain/evaluator.cpp | 3 +- .../include/graphene/chain/account_object.hpp | 6 -- .../graphene/chain/is_authorized_asset.hpp | 39 +++++++++++ libraries/chain/is_authorized_asset.cpp | 65 +++++++++++++++++++ libraries/chain/market_evaluator.cpp | 15 +++-- libraries/chain/transfer_evaluator.cpp | 13 ++-- .../chain/withdraw_permission_evaluator.cpp | 9 +-- tests/tests/uia_tests.cpp | 9 +-- 11 files changed, 137 insertions(+), 58 deletions(-) create mode 100644 libraries/chain/include/graphene/chain/is_authorized_asset.hpp create mode 100644 libraries/chain/is_authorized_asset.cpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 876c8c1d..fdd2bb00 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -84,6 +84,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_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..30f5b83a 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -155,7 +156,7 @@ void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o ) 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); @@ -191,7 +192,7 @@ void_result asset_reserve_evaluator::do_evaluate( const asset_reserve_operation& 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); diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 041e413a..bbf7521c 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) ); } diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index a3f2b3ab..fd5da61f 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -233,12 +233,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; } }; 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..1c7569c0 --- /dev/null +++ b/libraries/chain/include/graphene/chain/is_authorized_asset.hpp @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * @return true if the account is whitelisted and not blacklisted to transact in the provided asset; false + * otherwise. + */ + +bool is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj); + +} } diff --git a/libraries/chain/is_authorized_asset.cpp b/libraries/chain/is_authorized_asset.cpp new file mode 100644 index 00000000..0dda6ece --- /dev/null +++ b/libraries/chain/is_authorized_asset.cpp @@ -0,0 +1,65 @@ +/* + * 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 { + +bool is_authorized_asset( + const database& d, + const account_object& acct, + const asset_object& asset_obj) +{ + if( d.head_block_time() > HARDFORK_416_TIME ) + { + if( !(asset_obj.options.flags & white_list) ) + return true; + } + + 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; +} + +} } diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 10e2cb98..d21f6fe0 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 @@ -51,13 +54,13 @@ void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_o 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 ) ); + if( _sell_asset->options.flags & white_list ) FC_ASSERT( is_authorized_asset( d, *_seller, *_sell_asset ) ); + if( _receive_asset->options.flags & white_list ) FC_ASSERT( is_authorized_asset( d, *_seller, *_receive_asset ) ); } 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", diff --git a/libraries/chain/transfer_evaluator.cpp b/libraries/chain/transfer_evaluator.cpp index 25304963..0f6e2419 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 ) @@ -42,14 +43,14 @@ void_result transfer_evaluator::do_evaluate( const transfer_operation& op ) if( asset_type.options.flags & white_list ) { GRAPHENE_ASSERT( - from_account.is_authorized_asset( asset_type, d ), + 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( - to_account.is_authorized_asset( asset_type, d ), + 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) @@ -60,7 +61,7 @@ void_result transfer_evaluator::do_evaluate( const transfer_operation& op ) 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 @@ -112,14 +113,14 @@ void_result override_transfer_evaluator::do_evaluate( const override_transfer_op 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..9014282d 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 { @@ -75,16 +76,16 @@ void_result withdraw_permission_claim_evaluator::do_evaluate(const withdraw_perm { 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 ) ); + FC_ASSERT( is_authorized_asset( d, to, _asset ) ); + FC_ASSERT( is_authorized_asset( d, from, _asset ) ); } } 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 ) ); + FC_ASSERT( is_authorized_asset( d, to, _asset ) ); + FC_ASSERT( is_authorized_asset( d, from, _asset ) ); } return void_result(); diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index 8876f23e..442d5080 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; From 130b54116cce52fc718f5490e9d328e80ee00302 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Feb 2016 02:38:18 -0500 Subject: [PATCH 06/11] Inline fast path of is_authorized_asset() #566 --- .../graphene/chain/is_authorized_asset.hpp | 17 ++++++++++++++++- libraries/chain/is_authorized_asset.cpp | 8 ++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/libraries/chain/include/graphene/chain/is_authorized_asset.hpp b/libraries/chain/include/graphene/chain/is_authorized_asset.hpp index 1c7569c0..5062136e 100644 --- a/libraries/chain/include/graphene/chain/is_authorized_asset.hpp +++ b/libraries/chain/include/graphene/chain/is_authorized_asset.hpp @@ -29,11 +29,26 @@ 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. */ -bool is_authorized_asset(const database& d, const account_object& acct, const asset_object& asset_obj); +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); + + if( fast_check ) + return true; + + bool slow_check = detail::_is_authorized_asset( d, acct, asset_obj ); + return slow_check; +} } } diff --git a/libraries/chain/is_authorized_asset.cpp b/libraries/chain/is_authorized_asset.cpp index 0dda6ece..585b1da6 100644 --- a/libraries/chain/is_authorized_asset.cpp +++ b/libraries/chain/is_authorized_asset.cpp @@ -30,7 +30,9 @@ namespace graphene { namespace chain { -bool is_authorized_asset( +namespace detail { + +bool _is_authorized_asset( const database& d, const account_object& acct, const asset_object& asset_obj) @@ -62,4 +64,6 @@ bool is_authorized_asset( return false; } -} } +} // detail + +} } // graphene::chain From bb47f4c71c5e0d64962033100a1f9d0c4081ddd4 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Feb 2016 02:57:03 -0500 Subject: [PATCH 07/11] Remove now-redundant white_list bit check from _is_authorized_asset() #566 --- libraries/chain/is_authorized_asset.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libraries/chain/is_authorized_asset.cpp b/libraries/chain/is_authorized_asset.cpp index 585b1da6..8168a505 100644 --- a/libraries/chain/is_authorized_asset.cpp +++ b/libraries/chain/is_authorized_asset.cpp @@ -37,12 +37,6 @@ bool _is_authorized_asset( const account_object& acct, const asset_object& asset_obj) { - if( d.head_block_time() > HARDFORK_416_TIME ) - { - if( !(asset_obj.options.flags & white_list) ) - return true; - } - for( const auto id : acct.blacklisting_accounts ) { if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() ) From 6f7f2605c1568464f852e930c75b5510f2d51337 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Feb 2016 03:12:18 -0500 Subject: [PATCH 08/11] Remove now-redundant white_list bit check from evaluators #566 --- libraries/chain/asset_evaluator.cpp | 12 +---- libraries/chain/market_evaluator.cpp | 12 +---- libraries/chain/transfer_evaluator.cpp | 44 ++++++++----------- .../chain/withdraw_permission_evaluator.cpp | 21 ++------- 4 files changed, 26 insertions(+), 63 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 30f5b83a..9a467bec 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -153,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( is_authorized_asset( d, *to_account, a ) ); - } + 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 ); @@ -189,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( is_authorized_asset( d, *from_account, a ) ); - } + 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/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index d21f6fe0..27c31ae4 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -52,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( is_authorized_asset( d, *_seller, *_sell_asset ) ); - if( _receive_asset->options.flags & white_list ) FC_ASSERT( is_authorized_asset( d, *_seller, *_receive_asset ) ); - } - else - { - FC_ASSERT( is_authorized_asset( d, *_seller, *_sell_asset ) ); - FC_ASSERT( is_authorized_asset( d, *_seller, *_receive_asset ) ); - } + 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/transfer_evaluator.cpp b/libraries/chain/transfer_evaluator.cpp index 0f6e2419..4ec9e3e9 100644 --- a/libraries/chain/transfer_evaluator.cpp +++ b/libraries/chain/transfer_evaluator.cpp @@ -40,28 +40,24 @@ void_result transfer_evaluator::do_evaluate( const transfer_operation& op ) try { - if( asset_type.options.flags & white_list ) - { - 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) - ); - } + 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( d.head_block_time() <= HARDFORK_419_TIME ) { - if( fee_asset_type.options.flags & white_list ) - FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); + 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 @@ -111,16 +107,12 @@ void_result override_transfer_evaluator::do_evaluate( const override_transfer_op 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( is_authorized_asset( d, to_account, asset_type ) ); - FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); - } + 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( is_authorized_asset( d, from_account, asset_type ) ); + 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 9014282d..d001b441 100644 --- a/libraries/chain/withdraw_permission_evaluator.cpp +++ b/libraries/chain/withdraw_permission_evaluator.cpp @@ -70,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( is_authorized_asset( d, to, _asset ) ); - FC_ASSERT( is_authorized_asset( d, from, _asset ) ); - } - } - else - { - 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 ) ); - } + 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) ) } From e4f7483ec3c6016b065cfc58a5ec346cbce7e1cc Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Feb 2016 03:23:15 -0500 Subject: [PATCH 09/11] transfer_evaluator.cpp: Remove unused variable and redundant check #566 This check was the pre-419 check of whether the fee asset is authorized, which was (due to a typo) buggily checking the sent asset rather then the fee asset. --- libraries/chain/transfer_evaluator.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libraries/chain/transfer_evaluator.cpp b/libraries/chain/transfer_evaluator.cpp index 4ec9e3e9..accc6ca3 100644 --- a/libraries/chain/transfer_evaluator.cpp +++ b/libraries/chain/transfer_evaluator.cpp @@ -36,7 +36,6 @@ 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 { @@ -55,12 +54,6 @@ void_result transfer_evaluator::do_evaluate( const transfer_operation& op ) ("asset",op.amount.asset_id) ); - if( d.head_block_time() <= HARDFORK_419_TIME ) - { - 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 - if( asset_type.is_transfer_restricted() ) { GRAPHENE_ASSERT( @@ -105,7 +98,6 @@ 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); FC_ASSERT( is_authorized_asset( d, to_account, asset_type ) ); FC_ASSERT( is_authorized_asset( d, from_account, asset_type ) ); From c33fe35e4eca74b90045a191bc5925987b1dc987 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Feb 2016 04:59:28 -0500 Subject: [PATCH 10/11] Implement buyback accounts #538 --- libraries/app/api.cpp | 2 + libraries/chain/CMakeLists.txt | 1 + libraries/chain/account_evaluator.cpp | 25 ++++++ libraries/chain/buyback.cpp | 45 +++++++++++ libraries/chain/db_init.cpp | 2 + libraries/chain/db_maint.cpp | 80 +++++++++++++++++++ libraries/chain/hardfork.d/538.hf | 4 + .../include/graphene/chain/account_object.hpp | 8 ++ .../include/graphene/chain/asset_object.hpp | 3 + .../chain/include/graphene/chain/buyback.hpp | 34 ++++++++ .../include/graphene/chain/buyback_object.hpp | 67 ++++++++++++++++ .../chain/include/graphene/chain/config.hpp | 1 + .../chain/include/graphene/chain/database.hpp | 6 +- .../include/graphene/chain/exceptions.hpp | 3 + .../graphene/chain/is_authorized_asset.hpp | 1 + .../graphene/chain/protocol/account.hpp | 12 ++- .../graphene/chain/protocol/authority.hpp | 5 ++ .../graphene/chain/protocol/buyback.hpp | 52 ++++++++++++ .../include/graphene/chain/protocol/types.hpp | 6 +- libraries/chain/is_authorized_asset.cpp | 7 ++ libraries/chain/protocol/account.cpp | 13 +++ 21 files changed, 373 insertions(+), 4 deletions(-) create mode 100644 libraries/chain/buyback.cpp create mode 100644 libraries/chain/hardfork.d/538.hf create mode 100644 libraries/chain/include/graphene/chain/buyback.hpp create mode 100644 libraries/chain/include/graphene/chain/buyback_object.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/buyback.hpp 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 dce52c29..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 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/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_init.cpp b/libraries/chain/db_init.cpp index e603b3eb..1b9a37c0 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 c00a4226..9721ae45 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 @@ -522,10 +524,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/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/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index e2d74363..0af36acc 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) @@ -358,6 +365,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_object, (whitelisting_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 ea127026..e9a0b9bb 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 @@ -261,4 +263,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 a4dfdfc6..25d1b2c9 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -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/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 index 5062136e..3d99ae0f 100644 --- a/libraries/chain/include/graphene/chain/is_authorized_asset.hpp +++ b/libraries/chain/include/graphene/chain/is_authorized_asset.hpp @@ -43,6 +43,7 @@ bool _is_authorized_asset(const database& d, const account_object& acct, const a 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; 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/is_authorized_asset.cpp b/libraries/chain/is_authorized_asset.cpp index 8168a505..6ec9643d 100644 --- a/libraries/chain/is_authorized_asset.cpp +++ b/libraries/chain/is_authorized_asset.cpp @@ -37,6 +37,13 @@ bool _is_authorized_asset( 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() ) 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 ); + } + } } From 1a51d873293e0ecc8fd198daabec0e6ef7b43e14 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Feb 2016 04:59:35 -0500 Subject: [PATCH 11/11] Implement test for buyback accounts #538 --- tests/tests/operation_tests2.cpp | 137 +++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index 7adf3a1c..6275a64e 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()