diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index d651fcd7..46b23da1 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -237,6 +237,17 @@ struct get_impacted_account_visitor { _impacted.insert( op.buyer ); } + void operator()( const lottery_reward_operation& op ) { + _impacted.insert( op.winner ); + } + void operator()( const lottery_end_operation& op ) { + for( auto participant : op.participants ) { + _impacted.insert(participant.first); + } + } + void operator()( const sweeps_vesting_claim_operation& op ) { + _impacted.insert( op.account ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index d0b9d8b2..b122da89 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -88,6 +88,7 @@ add_library( graphene_chain account_object.cpp asset_object.cpp + lottery.cpp fba_object.cpp proposal_object.cpp vesting_balance_object.cpp diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 6de70af3..fc24c655 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o { auto dotpos = op.symbol.rfind( '.' ); if( dotpos != std::string::npos ) + { auto prefix = op.symbol.substr( 0, dotpos ); auto asset_symbol_itr = asset_indx.find( prefix ); @@ -117,7 +119,11 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o FC_ASSERT( op.bitasset_opts ); FC_ASSERT( op.precision == op.bitasset_opts->short_backing_asset(d).precision ); } - + + if( op.extension.which() == asset_extension::tag::value ) { + FC_ASSERT( op.common_options.max_supply >= 5 ); + op.extension.get().validate(); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -164,8 +170,11 @@ object_id_type asset_create_evaluator::do_apply( const asset_create_operation& o a.options = op.common_options; if( op.extension.which() == asset_extension::tag::value ) { a.precision = 0; - a.lottery_options = op.extension.get(); - a.lottery_options->balance = asset( 0, a.lottery_options->ticket_price.asset_id ); + a.lottery_options = op.extension.get(); //a.lottery_options->balance = asset( 0, a.lottery_options->ticket_price.asset_id ); + a.lottery_options->owner = a.id; + db().create([&](lottery_balance_object& lbo) { + lbo.lottery_id = a.id; + }); } if( a.options.core_exchange_rate.base.asset_id.instance.value == 0 ) a.options.core_exchange_rate.quote.asset_id = next_asset_id; @@ -187,6 +196,7 @@ void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o ) const asset_object& a = o.asset_to_issue.asset_id(d); FC_ASSERT( o.issuer == a.issuer ); FC_ASSERT( !a.is_market_issued(), "Cannot manually issue a market-issued asset." ); + FC_ASSERT( !a.is_lottery(), "Cannot manually issue a lottery asset." ); to_account = &o.issue_to_account(d); FC_ASSERT( is_authorized_asset( d, *to_account, a ) ); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 04dd6597..7266d6bb 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -43,7 +43,7 @@ share_type asset_bitasset_data_object::max_force_settlement_volume(share_type cu return volume.to_uint64(); } -void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point_sec current_time) +void asset_bitasset_data_object::update_median_feeds(time_point_sec current_time) { current_feed_publication_time = current_time; vector> current_feeds; @@ -164,3 +164,130 @@ string asset_object::amount_to_string(share_type amount) const result += "." + fc::to_string(scaled_precision.value + decimals).erase(0,1); return result; } + + +vector asset_object::get_holders( database& db ) const +{ + auto& asset_bal_idx = db.get_index_type< account_balance_index >().indices().get< by_asset_balance >(); + + uint64_t max_supply = get_id()(db).options.max_supply.value; + + vector holders; // repeating if balance > 1 + holders.reserve(max_supply); + const auto range = asset_bal_idx.equal_range( boost::make_tuple( get_id() ) ); + for( const account_balance_object& bal : boost::make_iterator_range( range.first, range.second ) ) + for( uint64_t balance = bal.balance.value; balance > 0; --balance) + holders.push_back( bal.owner ); + return holders; +} + +void asset_object::distribute_benefactors_part( database& db ) +{ + transaction_evaluation_state eval( &db ); + uint64_t jackpot = get_id()( db ).dynamic_data( db ).current_supply.value * lottery_options->ticket_price.amount.value; + + for( auto benefactor : lottery_options->benefactors ) { + lottery_reward_operation reward_op; + reward_op.lottery = get_id(); + reward_op.winner = benefactor.id; + reward_op.is_benefactor_reward = true; + reward_op.win_percentage = benefactor.share; + reward_op.amount = asset( jackpot * benefactor.share / GRAPHENE_100_PERCENT, db.get_balance(id).asset_id ); + db.apply_operation(eval, reward_op); + } +} + +map< account_id_type, vector< uint16_t > > asset_object::distribute_winners_part( database& db ) +{ + transaction_evaluation_state eval( &db ); + + auto holders = get_holders( db ); + FC_ASSERT( dynamic_data( db ).current_supply == holders.size() ); + map > structurized_participants; + for( account_id_type holder : holders ) + { + if( !structurized_participants.count( holder ) ) + structurized_participants.emplace( holder, vector< uint16_t >() ); + } + uint64_t jackpot = get_id()( db ).dynamic_data( db ).current_supply.value * lottery_options->ticket_price.amount.value; + auto winner_numbers = db.get_winner_numbers( get_id(), holders.size(), lottery_options->winning_tickets.size() ); + + auto& tickets( lottery_options->winning_tickets ); + + if( holders.size() < tickets.size() ) { + uint16_t percents_to_distribute = 0; + for( auto i = tickets.begin() + holders.size(); i != tickets.end(); ) { + percents_to_distribute += *i; + i = tickets.erase(i); + } + for( auto t = tickets.begin(); t != tickets.begin() + holders.size(); ++t ) + *t += percents_to_distribute / holders.size(); + } + + for( int c = 0; c < winner_numbers.size(); ++c ) { + auto winner_num = winner_numbers[c]; + lottery_reward_operation reward_op; + reward_op.lottery = get_id(); + reward_op.winner = holders[winner_num]; + reward_op.win_percentage = tickets[c]; + reward_op.amount = asset( jackpot * tickets[c] * ( 1. - db.get_global_properties().parameters.sweeps_distribution_percentage / (double)GRAPHENE_100_PERCENT ) / GRAPHENE_100_PERCENT , db.get_balance(id).asset_id ); + db.apply_operation(eval, reward_op); + + structurized_participants[ holders[ winner_num ] ].push_back( tickets[c] ); + } + return structurized_participants; +} + +void asset_object::distribute_sweeps_holders_part( database& db ) +{ + transaction_evaluation_state eval( &db ); + + auto& asset_bal_idx = db.get_index_type< account_balance_index >().indices().get< by_asset_balance >(); + + auto global_params = db.get_global_properties().parameters; + uint64_t distribution_asset_supply = global_params.sweeps_distribution_asset( db ).dynamic_data( db ).current_supply.value; + const auto range = asset_bal_idx.equal_range( boost::make_tuple( global_params.sweeps_distribution_asset ) ); + + uint64_t holders_sum = 0; + for( const account_balance_object& holder_balance : boost::make_iterator_range( range.first, range.second ) ) + { + int64_t holder_part = db.get_balance(id).amount.value / (double)distribution_asset_supply * holder_balance.balance.value * SWEEPS_VESTING_BALANCE_MULTIPLIER; + db.adjust_sweeps_vesting_balance( holder_balance.owner, holder_part ); + holders_sum += holder_part; + } + uint64_t balance_rest = db.get_balance( get_id() ).amount.value * SWEEPS_VESTING_BALANCE_MULTIPLIER - holders_sum; + db.adjust_sweeps_vesting_balance( SWEEPS_ACCUMULATOR_ACCOUNT, balance_rest ); + db.adjust_balance( get_id(), -db.get_balance( get_id() ) ); +} + +void asset_object::end_lottery( database& db ) +{ + idump(( "end_lottery" )); + transaction_evaluation_state eval(&db); + + FC_ASSERT( is_lottery() ); + FC_ASSERT( lottery_options->is_active && ( lottery_options->end_date <= db.head_block_time() ) ); + + auto participants = distribute_winners_part( db ); + if( participants.size() > 0) { + distribute_benefactors_part( db ); + distribute_sweeps_holders_part( db ); + } + + lottery_end_operation end_op; + end_op.lottery = id; + end_op.participants = participants; + db.apply_operation(eval, end_op); +} + +void lottery_balance_object::adjust_balance( const asset& delta ) +{ + FC_ASSERT( delta.asset_id == balance.asset_id ); + balance += delta; +} + +void sweeps_vesting_balance_object::adjust_balance( const asset& delta ) +{ + FC_ASSERT( delta.asset_id == asset_id ); + balance += delta.amount.value; +} diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index a70f077b..6cf88681 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -45,6 +46,15 @@ asset database::get_balance(const account_object& owner, const asset_object& ass return get_balance(owner.get_id(), asset_obj.get_id()); } +asset database::get_balance(asset_id_type lottery_id)const +{ + auto& index = get_index_type().indices().get(); + auto itr = index.find( lottery_id ); + if( itr == index.end() ) + return asset(0, asset_id_type( )); + return itr->get_balance(); +} + string database::to_pretty_string( const asset& a )const { return a.asset_id(*this).amount_to_pretty_string(a.amount); @@ -78,6 +88,63 @@ void database::adjust_balance(account_id_type account, asset delta ) } FC_CAPTURE_AND_RETHROW( (account)(delta) ) } + +void database::adjust_balance(asset_id_type lottery_id, asset delta) +{ + if( delta.amount == 0 ) + return; + + auto& index = get_index_type().indices().get(); + auto itr = index.find(lottery_id); + if(itr == index.end()) + { + FC_ASSERT( delta.amount > 0, "Insufficient Balance: ${a}'s balance is less than required ${r}", + ("a",lottery_id) + ("b","test") + ("r",to_pretty_string(-delta))); + create([lottery_id,&delta](lottery_balance_object& b) { + b.lottery_id = lottery_id; + b.balance = asset(delta.amount, delta.asset_id); + }); + } else { + if( delta.amount < 0 ) + FC_ASSERT( itr->get_balance() >= -delta, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", ("a",lottery_id)("b",to_pretty_string(itr->get_balance()))("r",to_pretty_string(-delta))); + modify(*itr, [delta](lottery_balance_object& b) { + b.adjust_balance(delta); + }); + } +} + + +void database::adjust_sweeps_vesting_balance(account_id_type account, int64_t delta) +{ + if( delta == 0 ) + return; + + asset_id_type asset_id = get_global_properties().parameters.sweeps_distribution_asset; + + auto& index = get_index_type().indices().get(); + auto itr = index.find(account); + if(itr == index.end()) + { + FC_ASSERT( delta > 0, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", + ("a",account) + ("b","test") + ("r",-delta)); + create([account,&delta,&asset_id](sweeps_vesting_balance_object& b) { + b.owner = account; + b.asset_id = asset_id; + b.balance = delta; + }); + } else { + if( delta < 0 ) + FC_ASSERT( itr->get_balance() >= -delta, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", ("a",account)("b",itr->get_balance())("r",-delta)); + modify(*itr, [&delta,&asset_id](sweeps_vesting_balance_object& b) { + b.adjust_balance( asset( delta, asset_id ) ); + }); + } +} + optional< vesting_balance_id_type > database::deposit_lazy_vesting( const optional< vesting_balance_id_type >& ovbid, share_type amount, uint32_t req_vesting_seconds, diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 3cf79d71..69df3783 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -536,7 +536,9 @@ void database::_apply_block( const signed_block& next_block ) // Are we at the maintenance interval? if( maint_needed ) perform_chain_maintenance(next_block, global_props); - + + check_ending_lotteries(); + create_block_summary(next_block); clear_expired_transactions(); clear_expired_proposals(); diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index dc3a023a..99fa054f 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -31,6 +31,7 @@ #include #include +#include namespace graphene { namespace chain { @@ -99,7 +100,7 @@ uint32_t database::last_non_undoable_block_num() const return head_block_num() - _undo_db.size(); } -std::vector database::get_seeds(asset_id_type for_asset, uint32_t count_winners) const +std::vector database::get_seeds(asset_id_type for_asset, uint8_t count_winners) const { FC_ASSERT( count_winners <= 64 ); std::string salted_string = std::string(_random_number_generator._seed) + std::to_string(for_asset.instance.value); @@ -117,21 +118,22 @@ std::vector database::get_seeds(asset_id_type for_asset, uint32_t coun return result; } -const std::unordered_set database::get_winner_numbers( asset_id_type for_asset, uint8_t count_members, uint32_t count_winners ) const +const std::vector database::get_winner_numbers( asset_id_type for_asset, uint32_t count_members, uint8_t count_winners ) const { - std::unordered_set result; + std::vector result; + if( count_members < count_winners ) count_winners = count_members; + if( count_winners == 0 ) return result; result.reserve(count_winners); auto seeds = get_seeds(for_asset, count_winners); for (auto current_seed = seeds.begin(); current_seed != seeds.end(); ++current_seed) { uint8_t winner_num = *current_seed % count_members; - int iter_count = 0; - while (result.count(winner_num)) { + while( std::find(result.begin(), result.end(), winner_num) != result.end() ) { *current_seed = (*current_seed * 1103515245 + 12345) / 65536; //using gcc's consts for pseudorandom winner_num = *current_seed % count_members; } - result.emplace(winner_num); + result.push_back(winner_num); if (result.size() >= count_winners) break; } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index ffb019af..5c8897eb 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -182,6 +182,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -236,6 +239,10 @@ void database::initialize_indexes() add_index< primary_index< simple_index< fba_accumulator_object > > >(); add_index< primary_index >(); add_index< primary_index >(); + + add_index< primary_index >(); + add_index< primary_index >(); + } void database::init_genesis(const genesis_state_type& genesis_state) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 6371c61f..466d4698 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -185,4 +185,25 @@ void database::close(bool rewind) _fork_db.reset(); } + +void database::check_ending_lotteries() +{ + const auto& lotteries_idx = get_index_type().indices().get(); + for( auto checking_asset: lotteries_idx ) + { + if( !checking_asset.is_lottery() ) break; + if( !checking_asset.lottery_options->is_active ) break; + if( checking_asset.lottery_options->end_date >= head_block_time() ) break; + checking_asset.end_lottery(*this); + } +} + +void database::check_lottery_end_by_participants( asset_id_type asset_id ) +{ + asset_object asset_to_check = asset_id( *this ); + if( !asset_to_check.is_lottery() ) return; + if( !asset_to_check.lottery_options->ending_on_soldout ) return; + +} + } } diff --git a/libraries/chain/hardfork.d/CORE-429.hf b/libraries/chain/hardfork.d/CORE-429.hf index af066fd2..7b9cc1da 100644 --- a/libraries/chain/hardfork.d/CORE-429.hf +++ b/libraries/chain/hardfork.d/CORE-429.hf @@ -1,4 +1,4 @@ // bitshares-core #429 rounding issue when creating assets #ifndef HARDFORK_CORE_429_TIME #define HARDFORK_CORE_429_TIME (fc::time_point_sec( 1510320000 )) -#endif \ No newline at end of file +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 7bccf791..fbcfd134 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -40,8 +40,9 @@ namespace graphene { namespace chain { class account_object; class database; + class transaction_evaluation_state; using namespace graphene::db; - + /** * @brief tracks the asset information that changes frequently * @ingroup object @@ -116,7 +117,7 @@ namespace graphene { namespace chain { /// Convert an asset to a textual representation with symbol, i.e. "123.45 USD" string amount_to_pretty_string(const asset &amount)const { FC_ASSERT(amount.asset_id == id); return amount_to_pretty_string(amount.amount); } - + /// Ticker symbol for this asset, i.e. "USD" string symbol; /// Maximum number of digits after the decimal point (must be <= 12) @@ -129,6 +130,12 @@ namespace graphene { namespace chain { // Extra data associated with lottery options. This field is non-null if is_lottery() returns true optional lottery_options; time_point_sec get_lottery_expiration() const; + vector get_holders( database& db ) const; + void distribute_benefactors_part( database& db ); + map< account_id_type, vector< uint16_t > > distribute_winners_part( database& db ); + void distribute_sweeps_holders_part( database& db ); + void end_lottery( database& db ); + /// Current supply, fee pool, and collected fees are stored in a separate object as they change frequently. asset_dynamic_data_id_type dynamic_asset_data_id; /// Extra data associated with BitAssets. This field is non-null if and only if is_market_issued() returns true @@ -351,8 +358,81 @@ namespace graphene { namespace chain { > total_distributed_dividend_balance_object_multi_index_type; typedef generic_index total_distributed_dividend_balance_object_index; + + + /** + * @ingroup object + */ + class lottery_balance_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = impl_lottery_balance_object_type; + + asset_id_type lottery_id; + asset balance; + + asset get_balance()const { return balance; } + void adjust_balance(const asset& delta); + }; + + + struct by_owner; + + /** + * @ingroup object_index + */ + using lottery_balance_index_type = multi_index_container< + lottery_balance_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_non_unique< tag, + member + > + > + >; + + /** + * @ingroup object_index + */ + using lottery_balance_index = generic_index; + + + class sweeps_vesting_balance_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = impl_sweeps_vesting_balance_object_type; + account_id_type owner; + uint64_t balance; + asset_id_type asset_id; + time_point_sec last_claim_date; + + uint64_t get_balance()const { return balance; } + void adjust_balance(const asset& delta); + asset available_for_claim() const { return asset( balance / SWEEPS_VESTING_BALANCE_MULTIPLIER , asset_id ); } + }; + + /** + * @ingroup object_index + */ + using sweeps_vesting_balance_index_type = multi_index_container< + sweeps_vesting_balance_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_non_unique< tag, + member + > + > + >; + + /** + * @ingroup object_index + */ + using sweeps_vesting_balance_index = generic_index; + } } // graphene::chain FC_REFLECT_DERIVED( graphene::chain::asset_dynamic_data_object, (graphene::db::object), @@ -395,3 +475,9 @@ FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object), (buyback_account) (dividend_data_id) ) + +FC_REFLECT_DERIVED( graphene::chain::lottery_balance_object, (graphene::db::object), + (lottery_id)(balance) ) + +FC_REFLECT_DERIVED( graphene::chain::sweeps_vesting_balance_object, (graphene::db::object), + (owner)(balance)(asset_id)(last_claim_date) ) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index a7bdea7e..d34f417d 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -194,3 +194,8 @@ #define TOURNAMENT_MAX_WHITELIST_LENGTH 1000 #define TOURNAMENT_MAX_START_TIME_IN_FUTURE (60*60*24*7*4) // 1 month #define TOURNAMENT_MAX_START_DELAY (60*60*24*7) // 1 week + +#define SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE (2*GRAPHENE_1_PERCENT) +#define SWEEPS_DEFAULT_DISTRIBUTION_ASSET asset_id_type(0) +#define SWEEPS_VESTING_BALANCE_MULTIPLIER 100000000 +#define SWEEPS_ACCUMULATOR_ACCOUNT account_id_type(0) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 31f24628..56c61675 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -243,6 +243,9 @@ namespace graphene { namespace chain { vector get_near_witness_schedule()const; void update_witness_schedule(); void update_witness_schedule(const signed_block& next_block); + + void check_lottery_end_by_participants( asset_id_type asset_id ); + void check_ending_lotteries(); //////////////////// db_getter.cpp //////////////////// @@ -253,8 +256,8 @@ namespace graphene { namespace chain { const dynamic_global_property_object& get_dynamic_global_properties()const; const node_property_object& get_node_properties()const; const fee_schedule& current_fee_schedule()const; - const std::unordered_set get_winner_numbers(asset_id_type for_asset, uint8_t count_members, uint32_t count_winners)const; - std::vector get_seeds(asset_id_type for_asset, uint32_t count_winners)const; + const std::vector get_winner_numbers( asset_id_type for_asset, uint32_t count_members, uint8_t count_winners ) const; + std::vector get_seeds( asset_id_type for_asset, uint8_t count_winners )const; uint64_t get_random_bits( uint64_t bound ); time_point_sec head_block_time()const; @@ -293,13 +296,26 @@ namespace graphene { namespace chain { asset get_balance(account_id_type owner, asset_id_type asset_id)const; /// This is an overloaded method. asset get_balance(const account_object& owner, const asset_object& asset_obj)const; - + /** + * @brief Get balance connected with lottery asset; if assset isnt lottery - return asset(0, 0) + */ + asset get_balance(asset_id_type lottery_id)const; /** * @brief Adjust a particular account's balance in a given asset by a delta * @param account ID of account whose balance should be adjusted * @param delta Asset ID and amount to adjust balance by */ void adjust_balance(account_id_type account, asset delta); + /** + * @brief Adjust a lottery's balance in a given asset by a delta + * @param asset ID(should be lottery) balance should be adjusted + * @param delta Asset ID and amount to adjust balance by + */ + void adjust_balance(asset_id_type lottery_id, asset delta); + /** + * @brief Adjust a particular account's sweeps vesting balance in a given asset by a delta + */ + void adjust_sweeps_vesting_balance(account_id_type account, int64_t delta); /** * @brief Helper to make lazy deposit to CDD VBO. @@ -423,7 +439,7 @@ namespace graphene { namespace chain { private: void _apply_block( const signed_block& next_block ); processed_transaction _apply_transaction( const signed_transaction& trx ); - + ///Steps involved in applying a new block ///@{ @@ -456,7 +472,7 @@ namespace graphene { namespace chain { void update_active_witnesses(); void update_active_committee_members(); void update_worker_votes(); - + template void perform_account_maintenance(std::tuple helpers); ///@} diff --git a/libraries/chain/include/graphene/chain/lottery_evaluator.hpp b/libraries/chain/include/graphene/chain/lottery_evaluator.hpp index 9317a4ad..65c97d85 100644 --- a/libraries/chain/include/graphene/chain/lottery_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/lottery_evaluator.hpp @@ -34,10 +34,46 @@ namespace graphene { namespace chain { typedef ticket_purchase_operation operation_type; void_result do_evaluate( const ticket_purchase_operation& o ); - object_id_type do_apply( const ticket_purchase_operation& o ); + void_result do_apply( const ticket_purchase_operation& o ); const asset_object* lottery; const asset_dynamic_data_object* asset_dynamic_data; }; + class lottery_reward_evaluator : public evaluator + { + public: + typedef lottery_reward_operation operation_type; + + void_result do_evaluate( const lottery_reward_operation& o ); + void_result do_apply( const lottery_reward_operation& o ); + + const asset_object* lottery; + const asset_dynamic_data_object* asset_dynamic_data; + }; + + class lottery_end_evaluator : public evaluator + { + public: + typedef lottery_end_operation operation_type; + + void_result do_evaluate( const lottery_end_operation& o ); + void_result do_apply( const lottery_end_operation& o ); + + const asset_object* lottery; + const asset_dynamic_data_object* asset_dynamic_data; + }; + + class sweeps_vesting_claim_evaluator : public evaluator + { + public: + typedef sweeps_vesting_claim_operation operation_type; + + void_result do_evaluate( const sweeps_vesting_claim_operation& o ); + void_result do_apply( const sweeps_vesting_claim_operation& o ); + +// const asset_object* lottery; +// const asset_dynamic_data_object* asset_dynamic_data; + }; + } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index 32921a24..4bdfdfdd 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -26,32 +26,34 @@ #include namespace graphene { namespace chain { + class database; bool is_valid_symbol( const string& symbol ); - struct benefactor { + struct benefactor { account_id_type id; - double share; + uint16_t share; // percent * GRAPHENE_1_PERCENT benefactor() = default; - benefactor ( const benefactor & ) = default; - benefactor( account_id_type _id, double _share ) : id( _id ), share( _share ) {} + benefactor( const benefactor & ) = default; + benefactor( account_id_type _id, uint16_t _share ) : id( _id ), share( _share ) {} }; - struct lottery_asset_options { + struct lottery_asset_options + { std::vector benefactors; - + asset_id_type owner; // specifying winning tickets as shares that will be issued - std::vector winning_tickets; + std::vector winning_tickets; asset ticket_price; - asset balance; time_point_sec end_date; bool ending_on_soldout; bool is_active; - + void validate()const; }; typedef static_variant< void_t, lottery_asset_options > asset_extension; + /** * @brief The asset_options struct contains options available on all assets in the network @@ -596,10 +598,28 @@ namespace graphene { namespace chain { account_id_type fee_payer()const { return issuer; } void validate()const; }; - + + struct sweeps_vesting_claim_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type account; + asset amount_to_claim; + extensions_type extensions; + + + account_id_type fee_payer()const { return account; } + void validate()const {}; + }; } } // graphene::chain +FC_REFLECT( graphene::chain::sweeps_vesting_claim_operation, (fee)(account)(amount_to_claim)(extensions) ) +FC_REFLECT( graphene::chain::sweeps_vesting_claim_operation::fee_parameters_type, (fee) ) + FC_REFLECT( graphene::chain::asset_claim_fees_operation, (fee)(issuer)(amount_to_claim)(extensions) ) FC_REFLECT( graphene::chain::asset_claim_fees_operation::fee_parameters_type, (fee) ) @@ -638,7 +658,7 @@ FC_REFLECT( graphene::chain::bitasset_options, FC_REFLECT( graphene::chain::benefactor, (id)(share) ) -FC_REFLECT( graphene::chain::lottery_asset_options, (benefactors)(winning_tickets)(ticket_price)(balance)(end_date)(ending_on_soldout)(is_active) ) +FC_REFLECT( graphene::chain::lottery_asset_options, (benefactors)(winning_tickets)(ticket_price)(end_date)(ending_on_soldout)(is_active) ) FC_REFLECT( graphene::chain::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index aa847a69..efe3772e 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -85,6 +85,11 @@ namespace graphene { namespace chain { uint32_t maximum_tournament_start_time_in_future = TOURNAMENT_MAX_START_TIME_IN_FUTURE; uint32_t maximum_tournament_start_delay = TOURNAMENT_MAX_START_DELAY; uint16_t maximum_tournament_number_of_wins = TOURNAMENT_MAX_NUMBER_OF_WINS; + // + uint16_t sweeps_distribution_percentage = SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE; + asset_id_type sweeps_distribution_asset = SWEEPS_DEFAULT_DISTRIBUTION_ASSET; + + extensions_type extensions; /** defined in fee_schedule.cpp */ @@ -136,5 +141,7 @@ FC_REFLECT( graphene::chain::chain_parameters, (maximum_tournament_start_time_in_future) (maximum_tournament_start_delay) (maximum_tournament_number_of_wins) + (sweeps_distribution_percentage) + (sweeps_distribution_asset) (extensions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/lottery_ops.hpp b/libraries/chain/include/graphene/chain/protocol/lottery_ops.hpp index 875748c2..1d238a55 100644 --- a/libraries/chain/include/graphene/chain/protocol/lottery_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/lottery_ops.hpp @@ -23,8 +23,9 @@ */ #pragma once #include +#include -namespace graphene { namespace chain { +namespace graphene { namespace chain { /** * @ingroup operations @@ -51,8 +52,53 @@ namespace graphene { namespace chain { share_type calculate_fee( const fee_parameters_type& k )const; }; -} } // graphene::chain + struct lottery_reward_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + }; + asset fee; + // from what lottery is ticket + asset_id_type lottery; + // winner account + account_id_type winner; + // amount that won + asset amount; + // percentage of jackpot that user won + uint16_t win_percentage; + // true if recieved from benefators section of lottery; false otherwise + bool is_benefactor_reward; + + extensions_type extensions; + + account_id_type fee_payer()const { return account_id_type(); } + void validate()const {}; + share_type calculate_fee( const fee_parameters_type& k )const { return k.fee; }; + }; + + struct lottery_end_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + }; + + asset fee; + // from what lottery is ticket + asset_id_type lottery; + +// std::vector participants; + + map > participants; + + extensions_type extensions; + + account_id_type fee_payer()const { return account_id_type(); } + void validate() const {} + share_type calculate_fee( const fee_parameters_type& k )const { return k.fee; } + }; + +} } // graphene::chain FC_REFLECT( graphene::chain::ticket_purchase_operation, (fee) @@ -62,4 +108,25 @@ FC_REFLECT( graphene::chain::ticket_purchase_operation, (amount) (extensions) ) -FC_REFLECT( graphene::chain::ticket_purchase_operation::fee_parameters_type, (fee) ) \ No newline at end of file +FC_REFLECT( graphene::chain::ticket_purchase_operation::fee_parameters_type, (fee) ) + + +FC_REFLECT( graphene::chain::lottery_reward_operation, + (fee) + (lottery) + (winner) + (amount) + (win_percentage) + (is_benefactor_reward) + (extensions) + ) +FC_REFLECT( graphene::chain::lottery_reward_operation::fee_parameters_type, (fee) ) + + +FC_REFLECT( graphene::chain::lottery_end_operation, + (fee) + (lottery) + (participants) + (extensions) + ) +FC_REFLECT( graphene::chain::lottery_end_operation::fee_parameters_type, (fee) ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 4203e1ed..18727556 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -101,7 +101,10 @@ namespace graphene { namespace chain { asset_dividend_distribution_operation, // VIRTUAL tournament_payout_operation, // VIRTUAL tournament_leave_operation, - ticket_purchase_operation + ticket_purchase_operation, + lottery_reward_operation, + lottery_end_operation, + sweeps_vesting_claim_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index e8ccfcf5..79d02378 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -162,7 +162,9 @@ namespace graphene { namespace chain { impl_fba_accumulator_object_type, impl_asset_dividend_data_type, impl_pending_dividend_payout_balance_for_holder_object_type, - impl_distributed_dividend_balance_data_type + impl_distributed_dividend_balance_data_type, + impl_lottery_balance_object_type, + impl_sweeps_vesting_balance_object_type }; //typedef fc::unsigned_int object_id_type; @@ -190,7 +192,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; typedef object_id< protocol_ids, force_settlement_object_type, force_settlement_object> force_settlement_id_type; - typedef object_id< protocol_ids, committee_member_object_type, committee_member_object> committee_member_id_type; + typedef object_id< protocol_ids, committee_member_object_type, committee_member_object> committee_member_id_type; typedef object_id< protocol_ids, witness_object_type, witness_object> witness_id_type; typedef object_id< protocol_ids, limit_order_object_type, limit_order_object> limit_order_id_type; typedef object_id< protocol_ids, call_order_object_type, call_order_object> call_order_id_type; @@ -225,28 +227,34 @@ namespace graphene { namespace chain { class tournament_details_object; class asset_dividend_data_object; class pending_dividend_payout_balance_for_holder_object; + class lottery_balance_object; + class sweeps_vesting_balance_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; - typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> asset_dynamic_data_id_type; - typedef object_id< implementation_ids, impl_asset_bitasset_data_type, asset_bitasset_data_object> asset_bitasset_data_id_type; - typedef object_id< implementation_ids, impl_asset_dividend_data_type, asset_dividend_data_object> asset_dividend_data_id_type; - typedef object_id< implementation_ids, impl_pending_dividend_payout_balance_for_holder_object_type, pending_dividend_payout_balance_for_holder_object> pending_dividend_payout_balance_for_holder_object_type; - typedef object_id< implementation_ids, impl_account_balance_object_type, account_balance_object> account_balance_id_type; - typedef object_id< implementation_ids, impl_account_statistics_object_type,account_statistics_object> account_statistics_id_type; - typedef object_id< implementation_ids, impl_transaction_object_type, transaction_object> transaction_obj_id_type; - typedef object_id< implementation_ids, impl_block_summary_object_type, block_summary_object> block_summary_id_type; + 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; + typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> asset_dynamic_data_id_type; + typedef object_id< implementation_ids, impl_asset_bitasset_data_type, asset_bitasset_data_object> asset_bitasset_data_id_type; + typedef object_id< implementation_ids, impl_asset_dividend_data_type, asset_dividend_data_object> asset_dividend_data_id_type; + typedef object_id< implementation_ids, + impl_pending_dividend_payout_balance_for_holder_object_type, + pending_dividend_payout_balance_for_holder_object> pending_dividend_payout_balance_for_holder_object_type; + typedef object_id< implementation_ids, impl_account_balance_object_type, account_balance_object> account_balance_id_type; + typedef object_id< implementation_ids, impl_account_statistics_object_type, account_statistics_object> account_statistics_id_type; + typedef object_id< implementation_ids, impl_transaction_object_type, transaction_object> transaction_obj_id_type; + typedef object_id< implementation_ids, impl_block_summary_object_type, block_summary_object> block_summary_id_type; typedef object_id< implementation_ids, impl_account_transaction_history_object_type, - account_transaction_history_object> account_transaction_history_id_type; - typedef object_id< implementation_ids, impl_chain_property_object_type, chain_property_object> chain_property_id_type; - typedef object_id< implementation_ids, impl_witness_schedule_object_type, witness_schedule_object> witness_schedule_id_type; - 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 object_id< implementation_ids, impl_fba_accumulator_object_type, fba_accumulator_object > fba_accumulator_id_type; + account_transaction_history_object> account_transaction_history_id_type; + typedef object_id< implementation_ids, impl_chain_property_object_type, chain_property_object> chain_property_id_type; + typedef object_id< implementation_ids, impl_witness_schedule_object_type, witness_schedule_object> witness_schedule_id_type; + 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 object_id< implementation_ids, impl_fba_accumulator_object_type, fba_accumulator_object > fba_accumulator_id_type; + typedef object_id< implementation_ids, impl_lottery_balance_object_type, lottery_balance_object > lottery_balance_id_type; + typedef object_id< implementation_ids, impl_sweeps_vesting_balance_object_type, sweeps_vesting_balance_object> sweeps_vesting_balance_id_type; typedef fc::array symbol_type; typedef fc::ripemd160 block_id_type; @@ -388,6 +396,8 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_asset_dividend_data_type) (impl_pending_dividend_payout_balance_for_holder_object_type) (impl_distributed_dividend_balance_data_type) + (impl_lottery_balance_object_type) + (impl_sweeps_vesting_balance_object_type) ) FC_REFLECT_TYPENAME( graphene::chain::share_type ) diff --git a/libraries/chain/lottery_evaluator.cpp b/libraries/chain/lottery_evaluator.cpp index b00554fb..b64bdde1 100644 --- a/libraries/chain/lottery_evaluator.cpp +++ b/libraries/chain/lottery_evaluator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * Copyright (c) 2017 Peerplays, Inc., and contributors. * * The MIT License * @@ -41,25 +41,91 @@ void_result ticket_purchase_evaluator::do_evaluate( const ticket_purchase_operat lottery = &op.lottery(db()); FC_ASSERT( lottery->is_lottery() ); - asset_dynamic_data = &lottery->dynamic_asset_data_id(db()); + asset_dynamic_data = &lottery->dynamic_asset_data_id(db()); FC_ASSERT( asset_dynamic_data->current_supply < lottery->options.max_supply ); auto lottery_options = *lottery->lottery_options; FC_ASSERT( lottery_options.is_active ); FC_ASSERT( lottery_options.ticket_price.asset_id == op.amount.asset_id ); FC_ASSERT( (double)op.amount.amount.value / lottery_options.ticket_price.amount.value == (double)op.tickets_to_buy ); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -object_id_type ticket_purchase_evaluator::do_apply( const ticket_purchase_operation& op ) +void_result ticket_purchase_evaluator::do_apply( const ticket_purchase_operation& op ) { try { db().adjust_balance( op.buyer, -op.amount ); + db().adjust_balance( op.lottery, op.amount ); db().adjust_balance( op.buyer, asset( op.tickets_to_buy, lottery->id ) ); db().modify( *asset_dynamic_data, [&]( asset_dynamic_data_object& data ){ data.current_supply += op.tickets_to_buy; }); - db().modify( *lottery, [&]( asset_object& ao ){ - ao.lottery_options->balance += op.amount; - }); + db().check_lottery_end_by_participants( op.lottery ); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } +void_result lottery_reward_evaluator::do_evaluate( const lottery_reward_operation& op ) +{ try { + lottery = &op.lottery(db()); + FC_ASSERT( lottery->is_lottery() ); + + auto lottery_options = *lottery->lottery_options; + FC_ASSERT( lottery_options.is_active ); + FC_ASSERT( db().get_balance(op.lottery).amount > 0 ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result lottery_reward_evaluator::do_apply( const lottery_reward_operation& op ) +{ try { + db().adjust_balance( op.lottery, -op.amount); + db().adjust_balance( op.winner, op.amount ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result lottery_end_evaluator::do_evaluate( const lottery_end_operation& op ) +{ try { + lottery = &op.lottery(db()); + FC_ASSERT( lottery->is_lottery() ); + + asset_dynamic_data = &lottery->dynamic_asset_data_id(db()); + + auto lottery_options = *lottery->lottery_options; + FC_ASSERT( lottery_options.is_active ); + FC_ASSERT( db().get_balance(lottery->get_id()).amount == 0 ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result lottery_end_evaluator::do_apply( const lottery_end_operation& op ) +{ try { + db().modify( *asset_dynamic_data, [&]( asset_dynamic_data_object& data ) { + data.current_supply = 0; + }); + for( auto account_info : op.participants ) + { + db().adjust_balance( account_info.first, -db().get_balance( account_info.first, op.lottery ) ); + } + db().modify( *lottery, [](asset_object& ao) { + ao.lottery_options->is_active = false; + }); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result sweeps_vesting_claim_evaluator::do_evaluate( const sweeps_vesting_claim_operation& op ) +{ try { + const auto& sweeps_vesting_index = db().get_index_type().indices().get(); + auto vesting = sweeps_vesting_index.find(op.account); + FC_ASSERT( vesting != sweeps_vesting_index.end() ); + FC_ASSERT( op.amount_to_claim <= vesting->available_for_claim() ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result sweeps_vesting_claim_evaluator::do_apply( const sweeps_vesting_claim_operation& op ) +{ try { + db().adjust_sweeps_vesting_balance( op.account, -op.amount_to_claim.amount.value * SWEEPS_VESTING_BALANCE_MULTIPLIER ); + db().adjust_balance( op.account, op.amount_to_claim ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + + + } } // graphene::chain diff --git a/libraries/chain/protocol/asset_ops.cpp b/libraries/chain/protocol/asset_ops.cpp index f3a4d765..02ee278d 100644 --- a/libraries/chain/protocol/asset_ops.cpp +++ b/libraries/chain/protocol/asset_ops.cpp @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #include +#include namespace graphene { namespace chain { @@ -247,4 +248,18 @@ void asset_claim_fees_operation::validate()const { FC_ASSERT( amount_to_claim.amount > 0 ); } + +void lottery_asset_options::validate() const +{ + FC_ASSERT( winning_tickets.size() <= 64 ); + uint16_t total = 0; + for( auto benefactor : benefactors ) { + total += benefactor.share; + } + for( auto share : winning_tickets ) { + total += share; + } + FC_ASSERT( total == GRAPHENE_100_PERCENT, "distribution amount not equals GRAPHENE_100_PERCENT" ); +} + } } // namespace graphene::chain diff --git a/libraries/chain/protocol/fee_schedule.cpp b/libraries/chain/protocol/fee_schedule.cpp index abded517..183950f3 100644 --- a/libraries/chain/protocol/fee_schedule.cpp +++ b/libraries/chain/protocol/fee_schedule.cpp @@ -124,7 +124,7 @@ namespace graphene { namespace chain { asset fee_schedule::calculate_fee( const operation& op, const price& core_exchange_rate )const { - //idump( (op)(core_exchange_rate) ); + //+( (op)(core_exchange_rate) ); fee_parameters params; params.set_which(op.which()); auto itr = parameters.find(params); if( itr != parameters.end() ) params = *itr; diff --git a/libraries/chain/protocol/lottery_ops.cpp b/libraries/chain/protocol/lottery_ops.cpp index 2304e545..d4f11fc4 100644 --- a/libraries/chain/protocol/lottery_ops.cpp +++ b/libraries/chain/protocol/lottery_ops.cpp @@ -28,6 +28,7 @@ namespace graphene { namespace chain { void ticket_purchase_operation::validate() const { FC_ASSERT( fee.amount >= 0 ); + FC_ASSERT( tickets_to_buy > 0 ); } share_type ticket_purchase_operation::calculate_fee( const fee_parameters_type& k )const diff --git a/libraries/db/include/graphene/db/index.hpp b/libraries/db/include/graphene/db/index.hpp index 7ecf5a66..e994299f 100644 --- a/libraries/db/include/graphene/db/index.hpp +++ b/libraries/db/include/graphene/db/index.hpp @@ -108,6 +108,8 @@ namespace graphene { namespace db { const object& get( object_id_type id )const { auto maybe_found = find( id ); + if (maybe_found == nullptr) + idump(("fail")); FC_ASSERT( maybe_found != nullptr, "Unable to find Object", ("id",id) ); return *maybe_found; } diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 1c9dab04..95f652c1 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -171,9 +171,10 @@ void database_fixture::verify_asset_supplies( const database& db ) total_balances[t.options.buy_in.asset_id] += t.prize_pool; for( const asset_object& ai : asst_index) - if (ai.is_lottery()) - total_balances[ ai.lottery_options->balance.asset_id ] += ai.lottery_options->balance.amount; - + if (ai.is_lottery()) { + asset balance = db.get_balance( ai.get_id() ); + total_balances[ balance.asset_id ] += balance.amount; + } for( const account_balance_object& b : balance_index ) total_balances[b.asset_type] += b.balance; for( const force_settlement_object& s : settle_index ) @@ -214,13 +215,18 @@ void database_fixture::verify_asset_supplies( const database& db ) for( const fba_accumulator_object& fba : db.get_index_type< simple_index< fba_accumulator_object > >() ) total_balances[ asset_id_type() ] += fba.accumulated_fba_fees; + uint64_t sweeps_vestings = 0; + for( const sweeps_vesting_balance_object& svbo: db.get_index_type< sweeps_vesting_balance_index >().indices() ) + sweeps_vestings += svbo.balance; + + total_balances[db.get_global_properties().parameters.sweeps_distribution_asset] += sweeps_vestings / SWEEPS_VESTING_BALANCE_MULTIPLIER; total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget; for( const auto& item : total_debts ) { BOOST_CHECK_EQUAL(item.first(db).dynamic_asset_data_id(db).current_supply.value, item.second.value); } - + BOOST_CHECK_EQUAL( core_in_orders.value , reported_core_in_orders.value ); BOOST_CHECK_EQUAL( total_balances[asset_id_type()].value , core_asset_data.current_supply.value - core_asset_data.confidential_supply.value); // wlog("*** End asset supply verification ***"); diff --git a/tests/tests/lottery_tests.cpp b/tests/tests/lottery_tests.cpp index ccd74110..6d1b8c42 100644 --- a/tests/tests/lottery_tests.cpp +++ b/tests/tests/lottery_tests.cpp @@ -42,6 +42,8 @@ BOOST_FIXTURE_TEST_SUITE( lottery_tests, database_fixture ) BOOST_AUTO_TEST_CASE( create_lottery_asset_test ) { try { + generate_block(); + idump((db.head_block_time())); asset_id_type test_asset_id = db.get_index().get_next_id(); asset_create_operation creator; creator.issuer = account_id_type(); @@ -49,7 +51,7 @@ BOOST_AUTO_TEST_CASE( create_lottery_asset_test ) char symbol[5] = "LOT"; symbol[3] = (char)('A' - 1 + test_asset_id.instance.value); symbol[4] = '\0'; // symbol depending on asset_id creator.symbol = symbol; - creator.common_options.max_supply = 20; + creator.common_options.max_supply = 200; creator.precision = 0; creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ creator.common_options.issuer_permissions = charge_market_fee|white_list|override_authority|transfer_restricted|disable_confidential; @@ -58,18 +60,21 @@ BOOST_AUTO_TEST_CASE( create_lottery_asset_test ) creator.common_options.whitelist_authorities = creator.common_options.blacklist_authorities = {account_id_type()}; lottery_asset_options lottery_options; - lottery_options.benefactors.push_back( benefactor( account_id_type(), 0.5 ) ); - lottery_options.end_date = db.get_dynamic_global_properties().time + fc::minutes(5); + lottery_options.benefactors.push_back( benefactor( account_id_type(), 25 * GRAPHENE_1_PERCENT ) ); + lottery_options.end_date = db.head_block_time() + fc::minutes(5); lottery_options.ticket_price = asset(100); - lottery_options.winning_tickets.push_back(0.5); + lottery_options.winning_tickets = { 5 * GRAPHENE_1_PERCENT, 5 * GRAPHENE_1_PERCENT, 5 * GRAPHENE_1_PERCENT, 10 * GRAPHENE_1_PERCENT, 10 * GRAPHENE_1_PERCENT, 10 * GRAPHENE_1_PERCENT, 10 * GRAPHENE_1_PERCENT, 10 * GRAPHENE_1_PERCENT, 10 * GRAPHENE_1_PERCENT }; lottery_options.is_active = test_asset_id.instance.value % 2; creator.extension = lottery_options; trx.operations.push_back(std::move(creator)); PUSH_TX( db, trx, ~0 ); + idump((lottery_options.end_date)); + idump((db.head_block_time())); + generate_block(); + auto test_asset = test_asset_id(db); - // idump((test_asset.is_lottery())); } catch (fc::exception& e) { edump((e.to_detail_string())); throw; @@ -120,9 +125,10 @@ BOOST_AUTO_TEST_CASE( tickets_purchase_test ) trx.operations.push_back(std::move(tpo)); graphene::chain::test::set_expiration(db, trx); PUSH_TX( db, trx, ~0 ); + generate_block(); trx.operations.clear(); - BOOST_CHECK( tpo.amount == test_asset.lottery_options->balance ); + BOOST_CHECK( tpo.amount == db.get_balance( test_asset.get_id() ) ); BOOST_CHECK( tpo.tickets_to_buy == get_balance( account_id_type(), test_asset.id ) ); } catch (fc::exception& e) { @@ -161,4 +167,295 @@ BOOST_AUTO_TEST_CASE( tickets_purchase_fail_test ) } } +BOOST_AUTO_TEST_CASE( lottery_end_by_stage_test ) +{ + try { + asset_id_type test_asset_id = db.get_index().get_next_id(); + INVOKE( create_lottery_asset_test ); + auto test_asset = test_asset_id(db); + for( int i = 1; i < 17; ++i ) { + if( i == 4 || i == 1 || i == 16 || i == 15 ) continue; + if( i != 0 ) + transfer(account_id_type(), account_id_type(i), asset(100000)); + ticket_purchase_operation tpo; + tpo.fee = asset(); + tpo.buyer = account_id_type(i); + tpo.lottery = test_asset.id; + tpo.tickets_to_buy = i; + tpo.amount = asset(100 * (i)); + trx.operations.push_back(std::move(tpo)); + graphene::chain::test::set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + generate_block(); + trx.operations.clear(); + } + test_asset = test_asset_id(db); + uint64_t benefactor_balance_before_end = db.get_balance( account_id_type(), asset_id_type() ).amount.value; + uint64_t jackpot = db.get_balance( test_asset.get_id() ).amount.value; + uint16_t winners_part = 0; + for( uint16_t win: test_asset.lottery_options->winning_tickets ) + winners_part += win; + + uint16_t participants_percents_sum = 0; + auto participants = test_asset.distribute_winners_part( db ); + for( auto p : participants ) + for( auto e : p.second) + participants_percents_sum += e; + + BOOST_CHECK( participants_percents_sum == winners_part ); + BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == (jackpot * (GRAPHENE_100_PERCENT - winners_part) / (double)GRAPHENE_100_PERCENT) + jackpot * winners_part * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT / (double)GRAPHENE_100_PERCENT ); + test_asset.distribute_benefactors_part( db ); + BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == jackpot * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT * winners_part / (double)GRAPHENE_100_PERCENT ); + test_asset.distribute_sweeps_holders_part( db ); + BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == 0 ); + + uint64_t benefactor_recieved = db.get_balance( account_id_type(), asset_id_type() ).amount.value - benefactor_balance_before_end; + test_asset = test_asset_id(db); + BOOST_CHECK(jackpot * test_asset.lottery_options->benefactors[0].share / GRAPHENE_100_PERCENT == benefactor_recieved); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + + +BOOST_AUTO_TEST_CASE( lottery_end_by_stage_with_fractional_test ) +{ + + try { + asset_id_type test_asset_id = db.get_index().get_next_id(); + INVOKE( create_lottery_asset_test ); + db.modify(test_asset_id(db), [&](asset_object& ao) { + ao.lottery_options->is_active = true; + }); + auto test_asset = test_asset_id(db); + for( int i = 1; i < 17; ++i ) { + if( i == 4 ) continue; + if( i != 0 ) + transfer(account_id_type(), account_id_type(i), asset(100000)); + ticket_purchase_operation tpo; + tpo.fee = asset(); + tpo.buyer = account_id_type(i); + tpo.lottery = test_asset.id; + tpo.tickets_to_buy = i; + tpo.amount = asset(100 * (i)); + trx.operations.push_back(std::move(tpo)); + graphene::chain::test::set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + generate_block(); + trx.operations.clear(); + } + test_asset = test_asset_id(db); + uint64_t benefactor_balance_before_end = db.get_balance( account_id_type(), asset_id_type() ).amount.value; + uint64_t jackpot = db.get_balance( test_asset.get_id() ).amount.value; + uint16_t winners_part = 0; + for( uint16_t win: test_asset.lottery_options->winning_tickets ) + winners_part += win; + + uint16_t participants_percents_sum = 0; + auto participants = test_asset.distribute_winners_part( db ); + for( auto p : participants ) + for( auto e : p.second) + participants_percents_sum += e; + + BOOST_CHECK( participants_percents_sum == winners_part ); + // balance should be bigger than expected because of rouning during distribution + BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value > (jackpot * (GRAPHENE_100_PERCENT - winners_part) / (double)GRAPHENE_100_PERCENT) + jackpot * winners_part * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT / (double)GRAPHENE_100_PERCENT ); + test_asset.distribute_benefactors_part( db ); + BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value > jackpot * SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE / (double)GRAPHENE_100_PERCENT * winners_part / (double)GRAPHENE_100_PERCENT ); + test_asset.distribute_sweeps_holders_part( db ); + // but at the end is always equals 0 + BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == 0 ); + + uint64_t benefactor_recieved = db.get_balance( account_id_type(), asset_id_type() ).amount.value - benefactor_balance_before_end; + test_asset = test_asset_id(db); + BOOST_CHECK(jackpot * test_asset.lottery_options->benefactors[0].share / GRAPHENE_100_PERCENT == benefactor_recieved); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( lottery_end_test ) +{ + try { + asset_id_type test_asset_id = db.get_index().get_next_id(); + INVOKE( create_lottery_asset_test ); + auto test_asset = test_asset_id(db); + for( int i = 1; i < 17; ++i ) { + if( i == 4 ) continue; + if( i != 0 ) + transfer(account_id_type(), account_id_type(i), asset(100000)); + ticket_purchase_operation tpo; + tpo.fee = asset(); + tpo.buyer = account_id_type(i); + tpo.lottery = test_asset.id; + tpo.tickets_to_buy = i; + tpo.amount = asset(100 * (i)); + trx.operations.push_back(std::move(tpo)); + graphene::chain::test::set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + test_asset = test_asset_id(db); + uint64_t creator_balance_before_end = db.get_balance( account_id_type(), asset_id_type() ).amount.value; + uint64_t jackpot = db.get_balance( test_asset.get_id() ).amount.value; + uint16_t winners_part = 0; + for( uint8_t win: test_asset.lottery_options->winning_tickets ) + winners_part += win; + + while( db.head_block_time() < ( test_asset.lottery_options->end_date + fc::seconds(30) ) ) + generate_block(); + + BOOST_CHECK( db.get_balance( test_asset.get_id() ).amount.value == 0 ); + uint64_t creator_recieved = db.get_balance( account_id_type(), asset_id_type() ).amount.value - creator_balance_before_end; + test_asset = test_asset_id(db); + BOOST_CHECK(jackpot * test_asset.lottery_options->benefactors[0].share / GRAPHENE_100_PERCENT == creator_recieved); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( claim_sweeps_vesting_balance_test ) +{ + try { + asset_id_type test_asset_id = db.get_index().get_next_id(); + INVOKE( lottery_end_test ); + auto test_asset = test_asset_id(db); + account_id_type benefactor = test_asset.lottery_options->benefactors[0].id; + const auto& svbo_index = db.get_index_type().indices().get(); + auto benefactor_svbo = svbo_index.find(benefactor); + BOOST_CHECK( benefactor_svbo != svbo_index.end() ); + + auto balance_before_claim = db.get_balance( benefactor, SWEEPS_DEFAULT_DISTRIBUTION_ASSET ); + auto available_for_claim = benefactor_svbo->available_for_claim(); + sweeps_vesting_claim_operation claim; + claim.account = benefactor; + claim.amount_to_claim = available_for_claim; + trx.clear(); + graphene::chain::test::set_expiration(db, trx); + trx.operations.push_back(claim); + PUSH_TX( db, trx, ~0 ); + generate_block(); + + BOOST_CHECK( db.get_balance( benefactor, SWEEPS_DEFAULT_DISTRIBUTION_ASSET ) - balance_before_claim == available_for_claim ); + benefactor_svbo = svbo_index.find(benefactor); + BOOST_CHECK( benefactor_svbo->available_for_claim().amount == 0 ); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( more_winners_then_participants_test ) +{ + try { + asset_id_type test_asset_id = db.get_index().get_next_id(); + INVOKE( create_lottery_asset_test ); + auto test_asset = test_asset_id(db); + for( int i = 1; i < 4; ++i ) { + if( i == 4 ) continue; + if( i != 0 ) + transfer(account_id_type(), account_id_type(i), asset(1000000)); + ticket_purchase_operation tpo; + tpo.fee = asset(); + tpo.buyer = account_id_type(i); + tpo.lottery = test_asset.id; + tpo.tickets_to_buy = 1; + tpo.amount = asset(100); + trx.operations.push_back(std::move(tpo)); + graphene::chain::test::set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + test_asset = test_asset_id(db); + auto holders = test_asset.get_holders(db); + idump(( db.get_balance(test_asset.get_id()) )); + auto participants = test_asset.distribute_winners_part( db ); + test_asset.distribute_benefactors_part( db ); + test_asset.distribute_sweeps_holders_part( db ); + generate_block(); + idump(( db.get_balance(test_asset.get_id()) )); + idump(( participants )); + for( auto p: participants ) { + idump(( get_operation_history(p.first) )); + } + auto benefactor_history = get_operation_history( account_id_type() ); + for( auto h: benefactor_history ) { + idump((h)); + } + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( ending_by_date_test ) +{ + try { + asset_id_type test_asset_id = db.get_index().get_next_id(); + INVOKE( create_lottery_asset_test ); + auto test_asset = test_asset_id(db); + for( int i = 1; i < 4; ++i ) { + if( i == 4 ) continue; + if( i != 0 ) + transfer(account_id_type(), account_id_type(i), asset(1000000)); + ticket_purchase_operation tpo; + tpo.fee = asset(); + tpo.buyer = account_id_type(i); + tpo.lottery = test_asset.id; + tpo.tickets_to_buy = 1; + tpo.amount = asset(100); + trx.operations.push_back(std::move(tpo)); + graphene::chain::test::set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + test_asset = test_asset_id(db); + auto holders = test_asset.get_holders(db); + idump(( db.get_balance(test_asset.get_id()) )); + while( db.head_block_time() < ( test_asset.lottery_options->end_date + fc::seconds(30) ) ) + generate_block(); + idump(( db.get_balance(test_asset.get_id()) )); + vector participants = { account_id_type(1), account_id_type(2), account_id_type(3) }; + for( auto p: participants ) { + idump(( get_operation_history(p) )); + } + auto benefactor_history = get_operation_history( account_id_type() ); + for( auto h: benefactor_history ) { + if( h.op.which() == operation::tag::value ) { + auto reward_op = h.op.get(); + idump((reward_op)); + BOOST_CHECK( reward_op.is_benefactor_reward ); + BOOST_CHECK( reward_op.amount.amount.value == 75 ); + BOOST_CHECK( reward_op.amount.asset_id == test_asset.lottery_options->ticket_price.asset_id ); + break; + } + } + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( try_to_end_empty_lottery_test ) +{ + try { + asset_id_type test_asset_id = db.get_index().get_next_id(); + INVOKE( create_lottery_asset_test ); + auto test_asset = test_asset_id(db); + while( db.head_block_time() < ( test_asset.lottery_options->end_date + fc::seconds(30) ) ) + generate_block(); + test_asset = test_asset_id(db); + BOOST_CHECK( !test_asset.lottery_options->is_active ); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END()