From 4e9e3ae054daac7e126d52b89fac1947799dd4b3 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Sun, 14 Feb 2016 20:28:37 -0500 Subject: [PATCH 1/3] Allow asset to update permission flags when no supply exists #572 --- libraries/chain/asset_evaluator.cpp | 9 ++++++--- libraries/chain/hardfork.d/572.hf | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 libraries/chain/hardfork.d/572.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 17113d9c..3476d2b1 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -259,9 +259,12 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) } } - // new issuer_permissions must be subset of old issuer permissions - FC_ASSERT(!(o.new_options.issuer_permissions & ~a.options.issuer_permissions), - "Cannot reinstate previously revoked issuer permissions on an asset."); + if( (d.head_block_time() < HARDFORK_572_TIME) || (a.dynamic_asset_data_id(d).current_supply != 0) ) + { + // new issuer_permissions must be subset of old issuer permissions + FC_ASSERT(!(o.new_options.issuer_permissions & ~a.options.issuer_permissions), + "Cannot reinstate previously revoked issuer permissions on an asset."); + } // changed flags must be subset of old issuer permissions FC_ASSERT(!((o.new_options.flags ^ a.options.flags) & ~a.options.issuer_permissions), diff --git a/libraries/chain/hardfork.d/572.hf b/libraries/chain/hardfork.d/572.hf new file mode 100644 index 00000000..f054225f --- /dev/null +++ b/libraries/chain/hardfork.d/572.hf @@ -0,0 +1,4 @@ +// #572 Allow asset to update permission flags when no supply exists +#ifndef HARDFORK_572_TIME +#define HARDFORK_572_TIME (fc::time_point_sec( 1450378800 )) +#endif From 5dd56bd82486d61defcb74d0f2c53b550cb948fe Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Mon, 15 Feb 2016 14:35:16 -0500 Subject: [PATCH 2/3] Implement FBA fee routing for STEALTH #563 --- libraries/app/api.cpp | 2 + libraries/app/impacted.cpp | 5 + libraries/chain/CMakeLists.txt | 1 + libraries/chain/account_evaluator.cpp | 18 ++- libraries/chain/confidential_evaluator.cpp | 26 ++++- libraries/chain/db_init.cpp | 34 ++++++ libraries/chain/db_maint.cpp | 92 ++++++++++++++++ libraries/chain/evaluator.cpp | 15 +++ libraries/chain/fba_object.cpp | 103 ++++++++++++++++++ libraries/chain/hardfork.d/563.hf | 4 + .../include/graphene/chain/account_object.hpp | 9 ++ .../graphene/chain/confidential_evaluator.hpp | 6 + .../include/graphene/chain/evaluator.hpp | 5 + .../graphene/chain/fba_accumulator_id.hpp | 41 +++++++ .../include/graphene/chain/fba_object.hpp | 52 +++++++++ .../include/graphene/chain/protocol/fba.hpp | 47 ++++++++ .../graphene/chain/protocol/operations.hpp | 6 +- .../include/graphene/chain/protocol/types.hpp | 9 +- .../include/graphene/chain/vote_count.hpp | 5 + 19 files changed, 472 insertions(+), 8 deletions(-) create mode 100644 libraries/chain/fba_object.cpp create mode 100644 libraries/chain/hardfork.d/563.hf create mode 100644 libraries/chain/include/graphene/chain/fba_accumulator_id.hpp create mode 100644 libraries/chain/include/graphene/chain/fba_object.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/fba.hpp diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index d8793423..35cf3880 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -348,6 +348,8 @@ namespace graphene { namespace app { break; case impl_buyback_object_type: break; + case impl_fba_accumulator_object_type: + break; } } return result; diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index b85d4573..85787423 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -198,6 +198,11 @@ struct get_impacted_account_visitor _impacted.insert( op.account ); } + void operator()( const fba_distribute_operation& op ) + { + _impacted.insert( op.account_id ); + } + }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index d0cbc20b..c38bd80b 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -81,6 +81,7 @@ add_library( graphene_chain account_object.cpp asset_object.cpp + fba_object.cpp proposal_object.cpp vesting_balance_object.cpp diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 03fe26c7..1f29bb0a 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -260,14 +260,28 @@ void_result account_update_evaluator::do_apply( const account_update_operation& database& d = db(); bool sa_before, sa_after; d.modify( *acnt, [&](account_object& a){ - if( o.owner ) a.owner = *o.owner; - if( o.active ) a.active = *o.active; + if( o.owner ) + { + a.owner = *o.owner; + a.top_n_control_flags = 0; + } + if( o.active ) + { + a.active = *o.active; + a.top_n_control_flags = 0; + } if( o.new_options ) a.options = *o.new_options; sa_before = a.has_special_authority(); if( o.extensions.value.owner_special_authority.valid() ) + { a.owner_special_authority = *(o.extensions.value.owner_special_authority); + a.top_n_control_flags = 0; + } if( o.extensions.value.active_special_authority.valid() ) + { a.active_special_authority = *(o.extensions.value.active_special_authority); + a.top_n_control_flags = 0; + } sa_after = a.has_special_authority(); }); diff --git a/libraries/chain/confidential_evaluator.cpp b/libraries/chain/confidential_evaluator.cpp index bcfef1e9..9946b492 100644 --- a/libraries/chain/confidential_evaluator.cpp +++ b/libraries/chain/confidential_evaluator.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include namespace graphene { namespace chain { @@ -67,6 +69,13 @@ void_result transfer_to_blind_evaluator::do_apply( const transfer_to_blind_opera return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } +void transfer_to_blind_evaluator::pay_fee() +{ + if( db().head_block_time() >= HARDFORK_563_TIME ) + pay_fba_fee( fba_accumulator_id_transfer_to_blind ); + else + generic_evaluator::pay_fee(); +} void_result transfer_from_blind_evaluator::do_evaluate( const transfer_from_blind_operation& o ) { try { @@ -104,9 +113,13 @@ void_result transfer_from_blind_evaluator::do_apply( const transfer_from_blind_o return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } - - - +void transfer_from_blind_evaluator::pay_fee() +{ + if( db().head_block_time() >= HARDFORK_563_TIME ) + pay_fba_fee( fba_accumulator_id_transfer_from_blind ); + else + generic_evaluator::pay_fee(); +} void_result blind_transfer_evaluator::do_evaluate( const blind_transfer_operation& o ) { try { @@ -157,5 +170,12 @@ void_result blind_transfer_evaluator::do_apply( const blind_transfer_operation& return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } +void blind_transfer_evaluator::pay_fee() +{ + if( db().head_block_time() >= HARDFORK_563_TIME ) + pay_fba_fee( fba_accumulator_id_blind_transfer ); + else + generic_evaluator::pay_fee(); +} } } // graphene::chain diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 1b9a37c0..302abef4 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -23,6 +23,7 @@ */ #include +#include #include #include @@ -33,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -212,6 +214,8 @@ void database::initialize_indexes() add_index< primary_index > >(); add_index< primary_index< special_authority_index > >(); add_index< primary_index< buyback_index > >(); + + add_index< primary_index< simple_index< fba_accumulator_object > > >(); } void database::init_genesis(const genesis_state_type& genesis_state) @@ -664,6 +668,36 @@ void database::init_genesis(const genesis_state_type& genesis_state) wso.current_shuffled_witnesses.push_back( wid ); }); + // Create FBA counters + create([&]( fba_accumulator_object& acc ) + { + FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_to_blind ) ); + acc.accumulated_fba_fees = 0; +#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET + acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; +#endif + }); + + create([&]( fba_accumulator_object& acc ) + { + FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_blind_transfer ) ); + acc.accumulated_fba_fees = 0; +#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET + acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; +#endif + }); + + create([&]( fba_accumulator_object& acc ) + { + FC_ASSERT( acc.id == fba_accumulator_id_type( fba_accumulator_id_transfer_from_blind ) ); + acc.accumulated_fba_fees = 0; +#ifdef GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET + acc.designated_asset = GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET; +#endif + }); + + FC_ASSERT( get_index().get_next_id() == fba_accumulator_id_type( fba_accumulator_id_count ) ); + debug_dump(); _undo_db.enable(); diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 9721ae45..9eabb1d9 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -519,11 +521,100 @@ void update_top_n_authorities( database& db ) db.modify( acct, [&]( account_object& a ) { vc.finish( is_owner ? a.owner : a.active ); + if( !vc.is_empty() ) + a.top_n_control_flags |= (is_owner ? account_object::top_n_control_owner : account_object::top_n_control_active); } ); } } ); } +void split_fba_balance( + database& db, + uint64_t fba_id, + uint16_t network_pct, + uint16_t designated_asset_buyback_pct, + uint16_t designated_asset_issuer_pct +) +{ + FC_ASSERT( uint32_t(network_pct) + uint32_t(designated_asset_buyback_pct) + uint32_t(designated_asset_issuer_pct) == GRAPHENE_100_PERCENT ); + const fba_accumulator_object& fba = fba_accumulator_id_type( fba_id )(db); + if( fba.accumulated_fba_fees == 0 ) + return; + + const asset_object& core = asset_id_type(0)(db); + const asset_dynamic_data_object& core_dd = core.dynamic_asset_data_id(db); + + if( !fba.is_configured(db) ) + { + ilog( "${n} core given to network at block ${b} due to non-configured FBA", ("n", fba.accumulated_fba_fees)("b", db.head_block_time()) ); + db.modify( core_dd, [&]( asset_dynamic_data_object& _core_dd ) + { + _core_dd.current_supply -= fba.accumulated_fba_fees; + } ); + db.modify( fba, [&]( fba_accumulator_object& _fba ) + { + _fba.accumulated_fba_fees = 0; + } ); + return; + } + + fc::uint128_t buyback_amount_128 = fba.accumulated_fba_fees.value; + buyback_amount_128 *= designated_asset_buyback_pct; + buyback_amount_128 /= GRAPHENE_100_PERCENT; + share_type buyback_amount = buyback_amount_128.to_uint64(); + + fc::uint128_t issuer_amount_128 = fba.accumulated_fba_fees.value; + issuer_amount_128 *= designated_asset_issuer_pct; + issuer_amount_128 /= GRAPHENE_100_PERCENT; + share_type issuer_amount = issuer_amount_128.to_uint64(); + + // this assert should never fail + FC_ASSERT( buyback_amount + issuer_amount <= fba.accumulated_fba_fees ); + + share_type network_amount = fba.accumulated_fba_fees - (buyback_amount + issuer_amount); + + const asset_object& designated_asset = (*fba.designated_asset)(db); + + if( network_amount != 0 ) + { + db.modify( core_dd, [&]( asset_dynamic_data_object& _core_dd ) + { + _core_dd.current_supply -= network_amount; + } ); + } + + fba_distribute_operation vop; + vop.account_id = *designated_asset.buyback_account; + vop.fba_id = fba.id; + vop.amount = buyback_amount; + if( vop.amount != 0 ) + { + db.adjust_balance( *designated_asset.buyback_account, asset(buyback_amount) ); + db.push_applied_operation(vop); + } + + vop.account_id = designated_asset.issuer; + vop.fba_id = fba.id; + vop.amount = issuer_amount; + if( vop.amount != 0 ) + { + db.adjust_balance( designated_asset.issuer, asset(issuer_amount) ); + db.push_applied_operation(vop); + } + + db.modify( fba, [&]( fba_accumulator_object& _fba ) + { + _fba.accumulated_fba_fees = 0; + } ); +} + +void distribute_fba_balances( database& db ) +{ + split_fba_balance( db, fba_accumulator_id_transfer_to_blind , 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); + split_fba_balance( db, fba_accumulator_id_blind_transfer , 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); + split_fba_balance( db, fba_accumulator_id_transfer_from_blind, 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT ); +} + void create_buyback_orders( database& db ) { const auto& bbo_idx = db.get_index_type< buyback_index >().indices().get(); @@ -604,6 +695,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g { const auto& gpo = get_global_properties(); + distribute_fba_balances(*this); create_buyback_orders(*this); struct vote_tally_helper { diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 06e31c21..b27d3960 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -103,4 +104,18 @@ database& generic_evaluator::db()const { return trx_state->db(); } } } FC_CAPTURE_AND_RETHROW() } + void generic_evaluator::pay_fba_fee( uint64_t fba_id ) + { + database& d = db(); + const fba_accumulator_object& fba = d.get< fba_accumulator_object >( fba_accumulator_id_type( fba_id ) ); + if( !fba.is_configured(d) ) + { + generic_evaluator::pay_fee(); + return; + } + d.modify( fba, [&]( fba_accumulator_object& _fba ) + { + _fba.accumulated_fba_fees += core_fee_paid; + } ); + } } } diff --git a/libraries/chain/fba_object.cpp b/libraries/chain/fba_object.cpp new file mode 100644 index 00000000..2febca45 --- /dev/null +++ b/libraries/chain/fba_object.cpp @@ -0,0 +1,103 @@ +/* + * 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 + +namespace graphene { namespace chain { + +bool fba_accumulator_object::is_configured( const database& db )const +{ + if( !designated_asset.valid() ) + { + ilog( "FBA fee in block ${b} not paid because designated asset was not configured", ("b", db.head_block_num()) ); + return false; + } + const asset_object* dasset = db.find(*designated_asset); + if( dasset == nullptr ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: designated asset does not exist", ("b", db.head_block_num()) ); + return false; + } + if( dasset->is_market_issued() ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: FBA is a BitAsset", ("b", db.head_block_num()) ); + return false; + } + + const uint16_t allowed_flags = charge_market_fee; + + // check enabled issuer_permissions bits is subset of allowed_flags bits + if( (dasset->options.issuer_permissions & allowed_flags) != dasset->options.issuer_permissions ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: Disallowed permissions enabled", ("b", db.head_block_num()) ); + return false; + } + + // check enabled issuer_permissions bits is subset of allowed_flags bits + if( (dasset->options.flags & allowed_flags) != dasset->options.flags ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: Disallowed flags enabled", ("b", db.head_block_num()) ); + return false; + } + + if( !dasset->buyback_account.valid() ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: designated asset does not have a buyback account", ("b", db.head_block_num()) ); + return false; + } + const account_object& issuer_acct = dasset->issuer(db); + + if( issuer_acct.owner_special_authority.which() != special_authority::tag< top_holders_special_authority >::value ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: designated asset issuer has not set owner top_n control", ("b", db.head_block_num()) ); + return false; + } + if( issuer_acct.active_special_authority.which() != special_authority::tag< top_holders_special_authority >::value ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: designated asset issuer has not set active top_n control", ("b", db.head_block_num()) ); + return false; + } + if( issuer_acct.owner_special_authority.get< top_holders_special_authority >().asset != *designated_asset ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: designated asset issuer's top_n_control is not set to designated asset", ("b", db.head_block_num()) ); + return false; + } + if( issuer_acct.active_special_authority.get< top_holders_special_authority >().asset != *designated_asset ) + { + ilog( "FBA fee in block ${b} not paid because of FBA misconfiguration: designated asset issuer's top_n_control is not set to designated asset", ("b", db.head_block_num()) ); + return false; + } + + if( issuer_acct.top_n_control_flags != (account_object::top_n_control_owner | account_object::top_n_control_active) ) + { + ilog( "FBA fee in block ${b} not paid because designated asset's top_n control has not yet activated (wait until next maintenance interval)", ("b", db.head_block_num()) ); + return false; + } + + return true; +} + +} } diff --git a/libraries/chain/hardfork.d/563.hf b/libraries/chain/hardfork.d/563.hf new file mode 100644 index 00000000..892214ed --- /dev/null +++ b/libraries/chain/hardfork.d/563.hf @@ -0,0 +1,4 @@ +// #563 Stealth fee routing +#ifndef HARDFORK_563_TIME +#define HARDFORK_563_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 0af36acc..11f0f586 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -208,6 +208,14 @@ namespace graphene { namespace chain { special_authority owner_special_authority = no_special_authority(); special_authority active_special_authority = no_special_authority(); + /** + * This flag is set when the top_n logic sets both authorities, + * and gets reset when authority or special_authority is set. + */ + uint8_t top_n_control_flags = 0; + static const uint8_t top_n_control_owner = 1; + static const uint8_t top_n_control_active = 2; + /** * 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. @@ -365,6 +373,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_object, (whitelisting_accounts)(blacklisted_accounts) (cashback_vb) (owner_special_authority)(active_special_authority) + (top_n_control_flags) (allowed_assets) ) diff --git a/libraries/chain/include/graphene/chain/confidential_evaluator.hpp b/libraries/chain/include/graphene/chain/confidential_evaluator.hpp index 1f047c09..93e0e78e 100644 --- a/libraries/chain/include/graphene/chain/confidential_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/confidential_evaluator.hpp @@ -37,6 +37,8 @@ class transfer_to_blind_evaluator : public evaluator @@ -46,6 +48,8 @@ class transfer_from_blind_evaluator : public evaluator @@ -55,6 +59,8 @@ class blind_transfer_evaluator : public evaluator void_result do_evaluate( const blind_transfer_operation& o ); void_result do_apply( const blind_transfer_operation& o ) ; + + virtual void pay_fee() override; }; } } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/evaluator.hpp b/libraries/chain/include/graphene/chain/evaluator.hpp index 4271be58..8019e8f6 100644 --- a/libraries/chain/include/graphene/chain/evaluator.hpp +++ b/libraries/chain/include/graphene/chain/evaluator.hpp @@ -138,6 +138,11 @@ namespace graphene { namespace chain { object_id_type get_relative_id( object_id_type rel_id )const; + /** + * pay_fee() for FBA subclass should simply call this method + */ + void pay_fba_fee( uint64_t fba_id ); + asset fee_from_account; share_type core_fee_paid; const account_object* fee_paying_account = nullptr; diff --git a/libraries/chain/include/graphene/chain/fba_accumulator_id.hpp b/libraries/chain/include/graphene/chain/fba_accumulator_id.hpp new file mode 100644 index 00000000..9bc0cf23 --- /dev/null +++ b/libraries/chain/include/graphene/chain/fba_accumulator_id.hpp @@ -0,0 +1,41 @@ +/* + * 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 { + +/** + * An object will be created at genesis for each of these FBA accumulators. + */ +enum graphene_fba_accumulator_id_enum +{ + fba_accumulator_id_transfer_to_blind = 0, + fba_accumulator_id_blind_transfer, + fba_accumulator_id_transfer_from_blind, + fba_accumulator_id_count +}; + +} } diff --git a/libraries/chain/include/graphene/chain/fba_object.hpp b/libraries/chain/include/graphene/chain/fba_object.hpp new file mode 100644 index 00000000..aec9e9cd --- /dev/null +++ b/libraries/chain/include/graphene/chain/fba_object.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 +#include +#include + +namespace graphene { namespace chain { + +class database; + +/** + * fba_accumulator_object accumulates fees to be paid out via buyback or other FBA mechanism. + */ + +class fba_accumulator_object : public graphene::db::abstract_object< fba_accumulator_object > +{ + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_fba_accumulator_object_type; + + share_type accumulated_fba_fees; + optional< asset_id_type > designated_asset; + + bool is_configured( const database& db )const; +}; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::fba_accumulator_object, (graphene::db::object), (accumulated_fba_fees)(designated_asset) ) diff --git a/libraries/chain/include/graphene/chain/protocol/fba.hpp b/libraries/chain/include/graphene/chain/protocol/fba.hpp new file mode 100644 index 00000000..7460ca8d --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/fba.hpp @@ -0,0 +1,47 @@ +/* + * 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 fba_distribute_operation : public base_operation +{ + struct fee_parameters_type {}; + + asset fee; // always zero + account_id_type account_id; + fba_accumulator_id_type fba_id; + share_type amount; + + account_id_type fee_payer()const { return account_id; } + void validate()const { FC_ASSERT( false ); } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } +}; + +} } + +FC_REFLECT( graphene::chain::fba_distribute_operation::fee_parameters_type, ) + +FC_REFLECT( graphene::chain::fba_distribute_operation, (fee)(account_id)(fba_id)(amount) ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 66a2ced3..7f2639f1 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include #include #include #include @@ -36,7 +38,6 @@ #include #include #include -#include namespace graphene { namespace chain { @@ -89,7 +90,8 @@ namespace graphene { namespace chain { blind_transfer_operation, transfer_from_blind_operation, asset_settle_cancel_operation, // VIRTUAL - asset_claim_fees_operation + asset_claim_fees_operation, + fba_distribute_operation // VIRTUAL > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index fac66d7a..5237fcad 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -154,7 +154,8 @@ namespace graphene { namespace chain { impl_witness_schedule_object_type, impl_budget_record_object_type, impl_special_authority_object_type, - impl_buyback_object_type + impl_buyback_object_type, + impl_fba_accumulator_object_type }; //typedef fc::unsigned_int object_id_type; @@ -205,6 +206,7 @@ namespace graphene { namespace chain { class budget_record_object; class special_authority_object; class buyback_object; + class fba_accumulator_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; @@ -224,6 +226,7 @@ namespace graphene { namespace chain { 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 object_id< implementation_ids, impl_fba_accumulator_object_type, fba_accumulator_object > fba_accumulator_id_type; typedef fc::array symbol_type; typedef fc::ripemd160 block_id_type; @@ -355,6 +358,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_budget_record_object_type) (impl_special_authority_object_type) (impl_buyback_object_type) + (impl_fba_accumulator_object_type) ) FC_REFLECT_TYPENAME( graphene::chain::share_type ) @@ -384,6 +388,9 @@ FC_REFLECT_TYPENAME( graphene::chain::block_summary_id_type ) FC_REFLECT_TYPENAME( graphene::chain::account_transaction_history_id_type ) FC_REFLECT_TYPENAME( graphene::chain::budget_record_id_type ) FC_REFLECT_TYPENAME( graphene::chain::special_authority_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::buyback_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::fba_accumulator_id_type ) + FC_REFLECT( graphene::chain::void_t, ) FC_REFLECT_ENUM( graphene::chain::asset_issuer_permission_flags, diff --git a/libraries/chain/include/graphene/chain/vote_count.hpp b/libraries/chain/include/graphene/chain/vote_count.hpp index bb23b840..f76a784d 100644 --- a/libraries/chain/include/graphene/chain/vote_count.hpp +++ b/libraries/chain/include/graphene/chain/vote_count.hpp @@ -63,6 +63,11 @@ struct vote_counter out_auth = auth; } + bool is_empty()const + { + return (total_votes == 0); + } + uint64_t last_votes = std::numeric_limits::max(); uint64_t total_votes = 0; int8_t bitshift = -1; From 36a8c3d6f1c35faa736e176650c298126184cfc1 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Mon, 15 Feb 2016 14:58:01 -0500 Subject: [PATCH 3/3] Unit test for STEALTH fee routing #563 --- .../chain/include/graphene/chain/config.hpp | 3 + tests/common/database_fixture.cpp | 21 ++ tests/common/database_fixture.hpp | 3 + tests/tests/fee_tests.cpp | 211 ++++++++++++++++++ 4 files changed, 238 insertions(+) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 5f8772d1..74b7d2b5 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -167,3 +167,6 @@ /// Sentinel value used in the scheduler. #define GRAPHENE_NULL_WITNESS (graphene::chain::witness_id_type(0)) ///@} + +// hack for unit test +#define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(1)) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 7a9e7ce4..44cd64da 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -195,6 +196,8 @@ void database_fixture::verify_asset_supplies( const database& db ) } for( const vesting_balance_object& vbo : db.get_index_type< vesting_balance_index >().indices() ) total_balances[ vbo.balance.asset_id ] += vbo.balance.amount; + for( const fba_accumulator_object& fba : db.get_index_type< simple_index< fba_accumulator_object > >() ) + total_balances[ asset_id_type() ] += fba.accumulated_fba_fees; total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget; @@ -1033,6 +1036,24 @@ int64_t database_fixture::get_balance( const account_object& account, const asse return db.get_balance(account.get_id(), a.get_id()).amount.value; } +vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const +{ + vector< operation_history_object > result; + const auto& stats = account_id(db).statistics(db); + if(stats.most_recent_op == account_transaction_history_id_type()) + return result; + + const account_transaction_history_object* node = &stats.most_recent_op(db); + while( true ) + { + result.push_back( node->operation_id(db) ); + if(node->next == account_transaction_history_id_type()) + break; + node = db.find(node->next); + } + return result; +} + namespace test { void set_expiration( const database& db, transaction& tx ) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 0cb9198d..241fd079 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -28,6 +28,8 @@ #include #include +#include + #include using namespace graphene::db; @@ -278,6 +280,7 @@ struct database_fixture { void print_joint_market( const string& syma, const string& symb )const; int64_t get_balance( account_id_type account, asset_id_type a )const; int64_t get_balance( const account_object& account, const asset_object& a )const; + vector< operation_history_object > get_operation_history( account_id_type account_id )const; }; namespace test { diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index 3e5e191a..9d7f0281 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -24,7 +24,12 @@ #include #include + #include + +#include + +#include #include #include @@ -721,4 +726,210 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( stealth_fba_test ) +{ + try + { + ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin)(tom) ); + upgrade_to_lifetime_member(philbin_id); + + generate_blocks( HARDFORK_538_TIME ); + generate_blocks( HARDFORK_555_TIME ); + generate_blocks( HARDFORK_563_TIME ); + generate_blocks( HARDFORK_572_TIME ); + + // Philbin (registrar who registers Rex) + + // Izzy (initial issuer of stealth asset, will later transfer to Tom) + // Alice, Bob, Chloe, Dan (ABCD) + // Rex (recycler -- buyback account for stealth asset) + // Tom (owner of stealth asset who will be set as top_n authority) + + // Izzy creates STEALTH + asset_id_type stealth_id = create_user_issued_asset( "STEALTH", izzy_id(db), + disable_confidential | transfer_restricted | override_authority | white_list | charge_market_fee ).id; + + /* + // this is disabled because it doesn't work, our modify() is probably being overwritten by undo + + // + // Init blockchain with stealth ID's + // On a real chain, this would be done with #define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET + // causing the designated_asset fields of these objects to be set at genesis, but for + // this test we modify the db directly. + // + auto set_fba_asset = [&]( uint64_t fba_acc_id, asset_id_type asset_id ) + { + db.modify( fba_accumulator_id_type(fba_acc_id)(db), [&]( fba_accumulator_object& fba ) + { + fba.designated_asset = asset_id; + } ); + }; + + set_fba_asset( fba_accumulator_id_transfer_to_blind , stealth_id ); + set_fba_asset( fba_accumulator_id_blind_transfer , stealth_id ); + set_fba_asset( fba_accumulator_id_transfer_from_blind, stealth_id ); + */ + + // Izzy kills some permission bits (this somehow happened to the real STEALTH in production) + { + asset_update_operation update_op; + update_op.issuer = izzy_id; + update_op.asset_to_update = stealth_id; + asset_options new_options; + new_options = stealth_id(db).options; + new_options.issuer_permissions = charge_market_fee; + new_options.flags = disable_confidential | transfer_restricted | override_authority | white_list | charge_market_fee; + // after fixing #579 you should be able to delete the following line + new_options.core_exchange_rate = price( asset( 1, stealth_id ), asset( 1, asset_id_type() ) ); + update_op.new_options = new_options; + signed_transaction tx; + tx.operations.push_back( update_op ); + set_expiration( db, tx ); + sign( tx, izzy_private_key ); + PUSH_TX( db, tx ); + } + + // Izzy transfers issuer duty to Tom + { + asset_update_operation update_op; + update_op.issuer = izzy_id; + update_op.asset_to_update = stealth_id; + update_op.new_issuer = tom_id; + // new_options should be optional, but isn't...the following line should be unnecessary #580 + update_op.new_options = stealth_id(db).options; + signed_transaction tx; + tx.operations.push_back( update_op ); + set_expiration( db, tx ); + sign( tx, izzy_private_key ); + PUSH_TX( db, tx ); + } + + // Tom re-enables the permission bits to clear the flags, then clears them again + // Allowed by #572 when current_supply == 0 + { + asset_update_operation update_op; + update_op.issuer = tom_id; + update_op.asset_to_update = stealth_id; + asset_options new_options; + new_options = stealth_id(db).options; + new_options.issuer_permissions = new_options.flags | charge_market_fee; + update_op.new_options = new_options; + signed_transaction tx; + // enable perms is one op + tx.operations.push_back( update_op ); + + new_options.issuer_permissions = charge_market_fee; + new_options.flags = charge_market_fee; + update_op.new_options = new_options; + // reset wrongly set flags and reset permissions can be done in a single op + tx.operations.push_back( update_op ); + + set_expiration( db, tx ); + sign( tx, tom_private_key ); + PUSH_TX( db, tx ); + } + + // Philbin registers Rex who will be the asset's buyback, including sig from the new issuer (Tom) + account_id_type rex_id; + { + buyback_account_options bbo; + bbo.asset_to_buy = stealth_id; + bbo.asset_to_buy_issuer = tom_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(); + + signed_transaction tx; + tx.operations.push_back( create_op ); + set_expiration( db, tx ); + sign( tx, philbin_private_key ); + sign( tx, tom_private_key ); + + processed_transaction ptx = PUSH_TX( db, tx ); + rex_id = ptx.operation_results.back().get< object_id_type >(); + } + + // Tom issues some asset to Alice and Bob + set_expiration( db, trx ); // #11 + issue_uia( alice_id, asset( 1000, stealth_id ) ); + issue_uia( bob_id, asset( 1000, stealth_id ) ); + + // Tom sets his authority to the top_n of the asset + { + top_holders_special_authority top2; + top2.num_top_holders = 2; + top2.asset = stealth_id; + + account_update_operation op; + op.account = tom_id; + op.extensions.value.active_special_authority = top2; + op.extensions.value.owner_special_authority = top2; + + signed_transaction tx; + tx.operations.push_back( op ); + + set_expiration( db, tx ); + sign( tx, tom_private_key ); + + PUSH_TX( db, tx ); + } + + // Wait until the next maintenance interval for top_n to take effect + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // Do a blind op to add some fees to the pool. + fund( chloe_id(db), asset( 100000, asset_id_type() ) ); + + auto create_transfer_to_blind = [&]( account_id_type account, asset amount, const std::string& key ) -> transfer_to_blind_operation + { + fc::ecc::private_key blind_key = fc::ecc::private_key::regenerate( fc::sha256::hash( key+"-privkey" ) ); + public_key_type blind_pub = blind_key.get_public_key(); + + fc::sha256 secret = fc::sha256::hash( key+"-secret" ); + fc::sha256 nonce = fc::sha256::hash( key+"-nonce" ); + + transfer_to_blind_operation op; + blind_output blind_out; + blind_out.owner = authority( 1, blind_pub, 1 ); + blind_out.commitment = fc::ecc::blind( secret, amount.amount.value ); + blind_out.range_proof = fc::ecc::range_proof_sign( 0, blind_out.commitment, secret, nonce, 0, 0, amount.amount.value ); + + op.amount = amount; + op.from = account; + op.blinding_factor = fc::ecc::blind_sum( {secret}, 1 ); + op.outputs = {blind_out}; + + return op; + }; + + { + transfer_to_blind_operation op = create_transfer_to_blind( chloe_id, asset( 5000, asset_id_type() ), "chloe-key" ); + op.fee = asset( 1000, asset_id_type() ); + + signed_transaction tx; + tx.operations.push_back( op ); + set_expiration( db, tx ); + sign( tx, chloe_private_key ); + + PUSH_TX( db, tx ); + } + + // wait until next maint interval + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + idump( ( get_operation_history( chloe_id ) ) ); + idump( ( get_operation_history( rex_id ) ) ); + idump( ( get_operation_history( tom_id ) ) ); + } + catch( const fc::exception& e ) + { + elog( "caught exception ${e}", ("e", e.to_detail_string()) ); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END()