From 17e6a9baf9ef10a8e4f68fa600332c1c889a2fa9 Mon Sep 17 00:00:00 2001 From: kstdl Date: Mon, 13 Nov 2017 10:14:07 +0300 Subject: [PATCH] ticket_purchase_operation implemented. added lottery_options to asset --- libraries/app/impacted.cpp | 4 + libraries/chain/CMakeLists.txt | 2 + libraries/chain/asset_evaluator.cpp | 5 + libraries/chain/asset_object.cpp | 6 + libraries/chain/db_block.cpp | 31 ++-- libraries/chain/db_init.cpp | 4 +- libraries/chain/db_notify.cpp | 4 + libraries/chain/hardfork.d/CORE-429.hf | 4 + libraries/chain/hardfork.d/GPOS.hf | 3 +- libraries/chain/hardfork.d/SWEEPS.hf | 3 + .../include/graphene/chain/asset_object.hpp | 24 ++- .../graphene/chain/lottery_evaluator.hpp | 43 +++++ .../graphene/chain/protocol/asset_ops.hpp | 38 +++- .../graphene/chain/protocol/lottery_ops.hpp | 65 +++++++ .../graphene/chain/protocol/operations.hpp | 4 +- libraries/chain/lottery_evaluator.cpp | 65 +++++++ libraries/chain/protocol/asset_ops.cpp | 21 ++- libraries/chain/protocol/lottery_ops.cpp | 38 ++++ libraries/chain/witness_evaluator.cpp | 2 +- .../wallet/include/graphene/wallet/wallet.hpp | 8 + libraries/wallet/wallet.cpp | 54 +++++- tests/common/database_fixture.cpp | 13 +- tests/tests/lottery_tests.cpp | 164 ++++++++++++++++++ 23 files changed, 564 insertions(+), 41 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE-429.hf create mode 100644 libraries/chain/hardfork.d/SWEEPS.hf create mode 100644 libraries/chain/include/graphene/chain/lottery_evaluator.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/lottery_ops.hpp create mode 100644 libraries/chain/lottery_evaluator.cpp create mode 100644 libraries/chain/protocol/lottery_ops.cpp create mode 100644 tests/tests/lottery_tests.cpp diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 9d64cf11..98eb7192 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -282,6 +282,10 @@ struct get_impacted_account_visitor _impacted.insert( op.affiliate ); } void operator()( const affiliate_referral_payout_operation& op ) { } + void operator()( const ticket_purchase_operation& op ) + { + _impacted.insert( op.buyer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index a328cf1f..a8d9e5db 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -49,6 +49,7 @@ add_library( graphene_chain protocol/proposal.cpp protocol/withdraw_permission.cpp protocol/asset_ops.cpp + protocol/lottery_ops.cpp protocol/memo.cpp protocol/worker.cpp protocol/custom.cpp @@ -72,6 +73,7 @@ add_library( graphene_chain witness_evaluator.cpp committee_member_evaluator.cpp asset_evaluator.cpp + lottery_evaluator.cpp transfer_evaluator.cpp proposal_evaluator.cpp market_evaluator.cpp diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 2d38e349..0b7aabf5 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -150,6 +150,11 @@ object_id_type asset_create_evaluator::do_apply( const asset_create_operation& o a.symbol = op.symbol; a.precision = op.precision; 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 ); + } if( a.options.core_exchange_rate.base.asset_id.instance.value == 0 ) a.options.core_exchange_rate.quote.asset_id = next_asset_id; else diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index d5ee6059..04dd6597 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -89,6 +89,12 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point } +time_point_sec asset_object::get_lottery_expiration() const +{ + if( lottery_options ) + return lottery_options->end_date; + return time_point_sec(); +} asset asset_object::amount_from_string(string amount_string) const { try { diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index bc8d5ecb..5500ebab 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -465,20 +465,20 @@ signed_block database::_generate_block( pending_block.transaction_merkle_root = pending_block.calculate_merkle_root(); pending_block.witness = witness_id; - // Genesis witnesses start with a default initial secret - if( secret_hash_type::hash( witness_obj.previous_secret ) == witness_obj.next_secret_hash ) { - pending_block.previous_secret = witness_obj.previous_secret; + // Genesis witnesses start with a default initial secret + if( witness_obj.next_secret_hash == secret_hash_type::hash( secret_hash_type() ) ) { + pending_block.previous_secret = secret_hash_type(); } else { - secret_hash_type::encoder last_enc; - fc::raw::pack( last_enc, block_signing_private_key ); - fc::raw::pack( last_enc, witness_obj.previous_secret ); - pending_block.previous_secret = last_enc.result(); - } + secret_hash_type::encoder last_enc; + fc::raw::pack( last_enc, block_signing_private_key ); + fc::raw::pack( last_enc, witness_obj.previous_secret ); + pending_block.previous_secret = last_enc.result(); + } - secret_hash_type::encoder next_enc; - fc::raw::pack( next_enc, block_signing_private_key ); - fc::raw::pack( next_enc, pending_block.previous_secret ); - pending_block.next_secret_hash = secret_hash_type::hash(next_enc.result()); + secret_hash_type::encoder next_enc; + fc::raw::pack( next_enc, block_signing_private_key ); + fc::raw::pack( next_enc, pending_block.previous_secret ); + pending_block.next_secret_hash = secret_hash_type::hash(next_enc.result()); if( !(skip & skip_witness_signature) ) pending_block.sign( block_signing_private_key ); @@ -490,7 +490,7 @@ signed_block database::_generate_block( } push_block( pending_block, skip ); - idump(( get_winner_numbers(asset_id_type(3), 253, 64) )); + return pending_block; } FC_CAPTURE_AND_RETHROW( (witness_id) ) } @@ -741,8 +741,9 @@ const witness_object& database::validate_block_header( uint32_t skip, const sign FC_ASSERT( head_block_time() < next_block.timestamp, "", ("head_block_time",head_block_time())("next",next_block.timestamp)("blocknum",next_block.block_num()) ); const witness_object& witness = next_block.witness(*this); //DLN: TODO: Temporarily commented out to test shuffle vs RNG scheduling algorithm for witnesses, this was causing shuffle agorithm to fail during create_witness test. This should be re-enabled for RNG, and maybe for shuffle too, don't really know for sure. - FC_ASSERT( secret_hash_type::hash( next_block.previous_secret ) == witness.next_secret_hash, "", - ("previous_secret", next_block.previous_secret)("next_secret_hash", witness.next_secret_hash)); + if( next_block.timestamp > HARDFORK_SWEEPS_TIME ) + FC_ASSERT( secret_hash_type::hash( next_block.previous_secret ) == witness.next_secret_hash, "", + ( "previous_secret", next_block.previous_secret )( "next_secret_hash", witness.next_secret_hash ) ); if( !(skip&skip_witness_signature) ) FC_ASSERT( next_block.validate_signee( witness.signing_key ) ); diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index d53055e1..621c9e0a 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -59,6 +59,7 @@ #include #include +#include #include #include #include @@ -237,6 +238,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -852,7 +854,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) std::for_each(genesis_state.initial_witness_candidates.begin(), genesis_state.initial_witness_candidates.end(), [&](const genesis_state_type::initial_witness_type& witness) { witness_create_operation op; - op.initial_secret = secret_hash_type::hash(witness.owner_name); + op.initial_secret = secret_hash_type(); op.witness_account = get_account_id(witness.owner_name); op.block_signing_key = witness.block_signing_key; apply_operation(genesis_eval_state, op); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 53ec524d..f0c5dafc 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -269,6 +269,10 @@ struct get_impacted_account_visitor _impacted.insert( op.affiliate ); } void operator()( const affiliate_referral_payout_operation& op ) { } + void operator()( const ticket_purchase_operation& op ) + { + _impacted.insert( op.buyer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/hardfork.d/CORE-429.hf b/libraries/chain/hardfork.d/CORE-429.hf new file mode 100644 index 00000000..af066fd2 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE-429.hf @@ -0,0 +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 diff --git a/libraries/chain/hardfork.d/GPOS.hf b/libraries/chain/hardfork.d/GPOS.hf index f175ef2c..9628eae0 100644 --- a/libraries/chain/hardfork.d/GPOS.hf +++ b/libraries/chain/hardfork.d/GPOS.hf @@ -1,4 +1,5 @@ // GPOS HARDFORK Friday, March 15, 2019 11:57:28 PM #ifndef HARDFORK_GPOS_TIME #define HARDFORK_GPOS_TIME (fc::time_point_sec( 1552694248 )) -#endif \ No newline at end of file +#endif + diff --git a/libraries/chain/hardfork.d/SWEEPS.hf b/libraries/chain/hardfork.d/SWEEPS.hf new file mode 100644 index 00000000..7dd1485c --- /dev/null +++ b/libraries/chain/hardfork.d/SWEEPS.hf @@ -0,0 +1,3 @@ +#ifndef HARDFORK_SWEEPS_TIME +#define HARDFORK_SWEEPS_TIME (fc::time_point_sec( 1510320000 )) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index d56a41a7..b02e98f3 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -87,6 +87,8 @@ namespace graphene { namespace chain { /// @return true if this is a market-issued asset; false otherwise. bool is_market_issued()const { return bitasset_data_id.valid(); } + /// @return true if this is lottery asset; false otherwise. + bool is_lottery()const { return lottery_options.valid(); } /// @return true if users may request force-settlement of this market-issued asset; false otherwise bool can_force_settle()const { return !(options.flags & disable_force_settle); } /// @return true if the issuer of this market-issued asset may globally settle the asset; false otherwise @@ -124,7 +126,9 @@ namespace graphene { namespace chain { asset_options options; - + // 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; /// 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 @@ -238,15 +242,32 @@ namespace graphene { namespace chain { //typedef flat_index asset_bitasset_data_index; typedef generic_index asset_bitasset_data_index; + // used to sort active_lotteries index + struct lottery_asset_comparer + { + bool operator()(const asset_object& lhs, const asset_object& rhs) const + { + if ( !lhs.is_lottery() ) return false; + if ( !lhs.lottery_options->is_active && !rhs.is_lottery()) return true; // not active lotteries first + if ( !lhs.lottery_options->is_active ) return false; + return lhs.get_lottery_expiration() > rhs.get_lottery_expiration(); + } + }; + struct by_symbol; struct by_type; struct by_issuer; + struct active_lotteries; typedef multi_index_container< asset_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, ordered_unique< tag, member >, ordered_non_unique< tag, member >, + ordered_non_unique< tag, + identity< asset_object >, + lottery_asset_comparer + >, ordered_unique< tag, composite_key< asset_object, const_mem_fun, @@ -371,6 +392,7 @@ FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object), (precision) (issuer) (options) + (lottery_options) (dynamic_asset_data_id) (bitasset_data_id) (buyback_account) diff --git a/libraries/chain/include/graphene/chain/lottery_evaluator.hpp b/libraries/chain/include/graphene/chain/lottery_evaluator.hpp new file mode 100644 index 00000000..9317a4ad --- /dev/null +++ b/libraries/chain/include/graphene/chain/lottery_evaluator.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017 Peerplays, 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 ticket_purchase_evaluator : public evaluator + { + public: + 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 ); + + 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 5ff353a3..b88ea46d 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -29,6 +29,30 @@ namespace graphene { namespace chain { bool is_valid_symbol( const string& symbol ); + struct benefactor { + account_id_type id; + double share; + benefactor() = default; + benefactor ( const benefactor & ) = default; + benefactor( account_id_type _id, double _share ) : id( _id ), share( _share ) {} + }; + + struct lottery_asset_options { + std::vector benefactors; + + // specifying winning tickets as shares that will be issued + 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 * @@ -69,7 +93,7 @@ namespace graphene { namespace chain { flat_set whitelist_markets; /** defines the assets that this asset may not be traded against in the market, must not overlap whitelist */ flat_set blacklist_markets; - + /** * data that describes the meaning/purpose of this asset, fee will be charged proportional to * size of description. @@ -169,6 +193,7 @@ namespace graphene { namespace chain { uint64_t symbol3 = 500000 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t symbol4 = 300000 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t long_symbol = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint64_t lottery_asset = 5000 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10; /// only required for large memos. }; @@ -191,7 +216,8 @@ namespace graphene { namespace chain { optional bitasset_opts; /// For BitAssets, set this to true if the asset implements a @ref prediction_market; false otherwise bool is_prediction_market = false; - extensions_type extensions; + // containing lottery_asset_options now + asset_extension extension; account_id_type fee_payer()const { return issuer; } void validate()const; @@ -398,7 +424,7 @@ namespace graphene { namespace chain { * BitAssets have some options which are not relevant to other asset types. This operation is used to update those * options an an existing BitAsset. * - * @pre @ref issuer MUST be an existing account and MUST match asset_object::issuer on @ref asset_to_update + * @pre @ref issuer MUST be an existing aaccount and MUST match asset_object::issuer on @ref asset_to_update * @pre @ref asset_to_update MUST be a BitAsset, i.e. @ref asset_object::is_market_issued() returns true * @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it * @pre @ref new_options SHALL be internally consistent, as verified by @ref validate() @@ -610,6 +636,10 @@ FC_REFLECT( graphene::chain::bitasset_options, (extensions) ) +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::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_global_settle_operation::fee_parameters_type, (fee) ) @@ -633,7 +663,7 @@ FC_REFLECT( graphene::chain::asset_create_operation, (common_options) (bitasset_opts) (is_prediction_market) - (extensions) + (extension) ) FC_REFLECT( graphene::chain::asset_update_operation, (fee) diff --git a/libraries/chain/include/graphene/chain/protocol/lottery_ops.hpp b/libraries/chain/include/graphene/chain/protocol/lottery_ops.hpp new file mode 100644 index 00000000..875748c2 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/lottery_ops.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2017 Peerplays, 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 { + + /** + * @ingroup operations + */ + struct ticket_purchase_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + }; + + asset fee; + // from what lottery is ticket + asset_id_type lottery; + account_id_type buyer; + // count of tickets to buy + uint64_t tickets_to_buy; + // amount that can spent + asset amount; + + extensions_type extensions; + + account_id_type fee_payer()const { return buyer; } + void validate()const; + share_type calculate_fee( const fee_parameters_type& k )const; + }; + +} } // graphene::chain + + +FC_REFLECT( graphene::chain::ticket_purchase_operation, + (fee) + (lottery) + (buyer) + (tickets_to_buy) + (amount) + (extensions) + ) +FC_REFLECT( graphene::chain::ticket_purchase_operation::fee_parameters_type, (fee) ) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 104a2ec3..15884db1 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -129,7 +130,8 @@ namespace graphene { namespace chain { sport_delete_operation, event_group_delete_operation, affiliate_payout_operation, // VIRTUAL - affiliate_referral_payout_operation // VIRTUAL + affiliate_referral_payout_operation, // VIRTUAL + ticket_purchase_operation > operation; /// @} // operations group diff --git a/libraries/chain/lottery_evaluator.cpp b/libraries/chain/lottery_evaluator.cpp new file mode 100644 index 00000000..b00554fb --- /dev/null +++ b/libraries/chain/lottery_evaluator.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace graphene { namespace chain { + +void_result ticket_purchase_evaluator::do_evaluate( const ticket_purchase_operation& op ) +{ try { + lottery = &op.lottery(db()); + FC_ASSERT( lottery->is_lottery() ); + + 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 ); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type ticket_purchase_evaluator::do_apply( const ticket_purchase_operation& op ) +{ try { + db().adjust_balance( op.buyer, -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; + }); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // graphene::chain diff --git a/libraries/chain/protocol/asset_ops.cpp b/libraries/chain/protocol/asset_ops.cpp index fdf153a3..f3a4d765 100644 --- a/libraries/chain/protocol/asset_ops.cpp +++ b/libraries/chain/protocol/asset_ops.cpp @@ -77,16 +77,19 @@ share_type asset_issue_operation::calculate_fee(const fee_parameters_type& k)con share_type asset_create_operation::calculate_fee(const asset_create_operation::fee_parameters_type& param)const { auto core_fee_required = param.long_symbol; - - switch(symbol.size()) { - case 3: core_fee_required = param.symbol3; - break; - case 4: core_fee_required = param.symbol4; - break; - default: - break; + + if( extension.which() == asset_extension::tag::value ) { + core_fee_required = param.lottery_asset; + } else { + switch(symbol.size()) { + case 3: core_fee_required = param.symbol3; + break; + case 4: core_fee_required = param.symbol4; + break; + default: + break; + } } - // common_options contains several lists and a string. Charge fees for its size core_fee_required += calculate_data_fee( fc::raw::pack_size(*this), param.price_per_kbyte ); diff --git a/libraries/chain/protocol/lottery_ops.cpp b/libraries/chain/protocol/lottery_ops.cpp new file mode 100644 index 00000000..2304e545 --- /dev/null +++ b/libraries/chain/protocol/lottery_ops.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2017 Peerplays, 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 + +namespace graphene { namespace chain { + +void ticket_purchase_operation::validate() const +{ + FC_ASSERT( fee.amount >= 0 ); +} + +share_type ticket_purchase_operation::calculate_fee( const fee_parameters_type& k )const +{ + return k.fee; +} + +} } // namespace graphene::chain diff --git a/libraries/chain/witness_evaluator.cpp b/libraries/chain/witness_evaluator.cpp index 1d0c1e70..a6990a8e 100644 --- a/libraries/chain/witness_evaluator.cpp +++ b/libraries/chain/witness_evaluator.cpp @@ -45,7 +45,7 @@ object_id_type witness_create_evaluator::do_apply( const witness_create_operatio const auto& new_witness_object = db().create( [&]( witness_object& obj ) { obj.witness_account = op.witness_account; - obj.signing_key = op.block_signing_key; + obj.signing_key = op.block_signing_key; obj.previous_secret = op.initial_secret; obj.next_secret_hash = secret_hash_type::hash( op.initial_secret ); obj.vote_id = vote_id; diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index a7189138..79ad8172 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1009,6 +1009,13 @@ class wallet_api fc::optional bitasset_opts, bool broadcast = false); + signed_transaction create_lottery(string issuer, + string symbol, + uint8_t precision, + asset_options common, + fc::optional bitasset_opts, + bool broadcast = false); + /** Issue new shares of an asset. * * @param to_account the name or id of the account to receive the new shares @@ -1940,6 +1947,7 @@ FC_API( graphene::wallet::wallet_api, (transfer2) (get_transaction_id) (create_asset) + (create_lottery) (update_asset) (update_bitasset) (update_dividend_asset) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 15c8fed8..fbc3817a 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1444,6 +1444,40 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (issuer)(symbol)(precision)(common)(bitasset_opts)(broadcast) ) } + + signed_transaction create_lottery(string issuer, + string symbol, + uint8_t precision, + asset_options common, + fc::optional bitasset_opts, + bool broadcast = false) + { try { + account_object issuer_account = get_account( issuer ); + FC_ASSERT(!find_asset(symbol).valid(), "Asset with that symbol already exists!"); + + asset_create_operation create_op; + create_op.issuer = issuer_account.id; + create_op.symbol = symbol; + create_op.precision = precision; + create_op.common_options = common; + create_op.bitasset_opts = bitasset_opts; + + lottery_asset_options lottery_options; + lottery_options.benefactors.push_back( benefactor( issuer_account.id, 0.5 ) ); + lottery_options.end_date = _remote_db->get_dynamic_global_properties().time + fc::minutes(120); + lottery_options.ticket_price = asset(100); + lottery_options.winning_tickets.push_back(0.5); + + create_op.extension = lottery_options; + + signed_transaction tx; + tx.operations.push_back( create_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (issuer)(symbol)(precision)(common)(bitasset_opts)(broadcast) ) } + signed_transaction update_asset(string symbol, optional new_issuer, asset_options new_options, @@ -1770,10 +1804,10 @@ public: witness_create_op.block_signing_key = witness_public_key; witness_create_op.url = url; - // secret_hash_type::encoder enc; - // fc::raw::pack(enc, witness_private_key); - // fc::raw::pack(enc, secret_hash_type()); - witness_create_op.initial_secret = secret_hash_type::hash(owner_account); + secret_hash_type::encoder enc; + fc::raw::pack(enc, witness_private_key); + fc::raw::pack(enc, secret_hash_type()); + witness_create_op.initial_secret = secret_hash_type::hash(enc.result()); if (_remote_db->get_witness_by_account(witness_create_op.witness_account)) @@ -3882,6 +3916,18 @@ signed_transaction wallet_api::create_asset(string issuer, return my->create_asset(issuer, symbol, precision, common, bitasset_opts, broadcast); } +signed_transaction wallet_api::create_lottery(string issuer, + string symbol, + uint8_t precision, + asset_options common, + fc::optional bitasset_opts, + bool broadcast) + +{ +return my->create_lottery(issuer, symbol, precision, common, bitasset_opts, broadcast); +} + + signed_transaction wallet_api::update_asset(string symbol, optional new_issuer, asset_options new_options, diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 5dc155b4..768bf289 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -177,6 +177,7 @@ void database_fixture::verify_asset_supplies( const database& db ) const auto& balance_index = db.get_index_type().indices(); const auto& settle_index = db.get_index_type().indices(); const auto& tournaments_index = db.get_index_type().indices(); + const auto& asst_index = db.get_index_type().indices(); map total_balances; map total_debts; @@ -187,6 +188,10 @@ void database_fixture::verify_asset_supplies( const database& db ) if (t.get_state() != tournament_state::concluded && t.get_state() != tournament_state::registration_period_expired) 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; + for( const account_balance_object& b : balance_index ) total_balances[b.asset_type] += b.balance; for( const force_settlement_object& s : settle_index ) @@ -700,10 +705,10 @@ const witness_object& database_fixture::create_witness( const account_object& ow op.witness_account = owner.id; op.block_signing_key = signing_private_key.get_public_key(); - // secret_hash_type::encoder enc; - // fc::raw::pack(enc, signing_private_key); - // fc::raw::pack(enc, owner.name); - op.initial_secret = secret_hash_type::hash(owner.name); + secret_hash_type::encoder enc; + fc::raw::pack(enc, signing_private_key); + fc::raw::pack(enc, owner.name); + op.initial_secret = secret_hash_type::hash(enc.result()); trx.operations.push_back(op); trx.validate(); diff --git a/tests/tests/lottery_tests.cpp b/tests/tests/lottery_tests.cpp new file mode 100644 index 00000000..ccd74110 --- /dev/null +++ b/tests/tests/lottery_tests.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2017 PBSA, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include + + +#include +#include +#include + +#include "../common/database_fixture.hpp" + +#include + +using namespace graphene::chain; + +BOOST_FIXTURE_TEST_SUITE( lottery_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( create_lottery_asset_test ) +{ + try { + asset_id_type test_asset_id = db.get_index().get_next_id(); + asset_create_operation creator; + creator.issuer = account_id_type(); + creator.fee = asset(); + 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.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; + creator.common_options.flags = charge_market_fee|white_list|override_authority|disable_confidential; + creator.common_options.core_exchange_rate = price({asset(1),asset(1,asset_id_type(1))}); + 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.ticket_price = asset(100); + lottery_options.winning_tickets.push_back(0.5); + 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 ); + auto test_asset = test_asset_id(db); + // idump((test_asset.is_lottery())); + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( lottery_idx_test ) +{ + try { + // generate loterries with different end_dates and is_active_flag + for( int i = 0; i < 26; ++i ) { + generate_blocks(30); + graphene::chain::test::set_expiration( db, trx ); + asset_id_type test_asset_id = db.get_index().get_next_id(); + INVOKE( create_lottery_asset_test ); + auto test_asset = test_asset_id(db); + } + + auto& test_asset_idx = db.get_index_type().indices().get(); + auto test_itr = test_asset_idx.begin(); + bool met_not_active = false; + // check sorting + while( test_itr != test_asset_idx.end() ) { + if( !met_not_active && (!test_itr->is_lottery() || !test_itr->lottery_options->is_active) ) + met_not_active = true; + FC_ASSERT( !met_not_active || met_not_active && (!test_itr->is_lottery() || !test_itr->lottery_options->is_active), "MET ACTIVE LOTTERY AFTER NOT ACTIVE" ); + ++test_itr; + } + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( tickets_purchase_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); + + ticket_purchase_operation tpo; + tpo.fee = asset(); + tpo.buyer = account_id_type(); + 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(); + + BOOST_CHECK( tpo.amount == test_asset.lottery_options->balance ); + BOOST_CHECK( tpo.tickets_to_buy == get_balance( account_id_type(), test_asset.id ) ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( tickets_purchase_fail_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); + + ticket_purchase_operation tpo; + tpo.fee = asset(); + tpo.buyer = account_id_type(); + tpo.lottery = test_asset.id; + tpo.tickets_to_buy = 2; + tpo.amount = asset(100); + trx.operations.push_back(tpo); + BOOST_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); // amount/tickets_to_buy != price + trx.operations.clear(); + + tpo.amount = asset(205); + trx.operations.push_back(tpo); + BOOST_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); // amount/tickets_to_buy != price + + tpo.amount = asset(200, test_asset.id); + trx.operations.push_back(tpo); + BOOST_REQUIRE_THROW( PUSH_TX( db, trx, ~0 ), fc::exception ); // trying to buy in other asset + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END()