nft_lottery1 - working code with tests
This commit is contained in:
parent
56a5e676f0
commit
be20d28e30
21 changed files with 655 additions and 11 deletions
|
|
@ -126,6 +126,9 @@ add_library( graphene_chain
|
||||||
protocol/nft.cpp
|
protocol/nft.cpp
|
||||||
protocol/account_role.cpp
|
protocol/account_role.cpp
|
||||||
account_role_evaluator.cpp
|
account_role_evaluator.cpp
|
||||||
|
protocol/nft_lottery.cpp
|
||||||
|
nft_lottery_evaluator.cpp
|
||||||
|
nft_lottery_object.cpp
|
||||||
|
|
||||||
${HEADERS}
|
${HEADERS}
|
||||||
${PROTOCOL_HEADERS}
|
${PROTOCOL_HEADERS}
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ map< account_id_type, vector< uint16_t > > asset_object::distribute_winners_part
|
||||||
structurized_participants.emplace( holder, vector< uint16_t >() );
|
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;
|
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 winner_numbers = db.get_winner_numbers( get_id().instance.value, holders.size(), lottery_options->winning_tickets.size() );
|
||||||
|
|
||||||
auto& tickets( lottery_options->winning_tickets );
|
auto& tickets( lottery_options->winning_tickets );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -710,6 +710,7 @@ void database::_apply_block( const signed_block& next_block )
|
||||||
perform_chain_maintenance(next_block, global_props);
|
perform_chain_maintenance(next_block, global_props);
|
||||||
|
|
||||||
check_ending_lotteries();
|
check_ending_lotteries();
|
||||||
|
check_ending_nft_lotteries();
|
||||||
|
|
||||||
create_block_summary(next_block);
|
create_block_summary(next_block);
|
||||||
place_delayed_bets(); // must happen after update_global_dynamic_data() updates the time
|
place_delayed_bets(); // must happen after update_global_dynamic_data() updates the time
|
||||||
|
|
|
||||||
|
|
@ -109,17 +109,17 @@ uint32_t database::last_non_undoable_block_num() const
|
||||||
return head_block_num() - _undo_db.size();
|
return head_block_num() - _undo_db.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint32_t> database::get_seeds(asset_id_type for_asset, uint8_t count_winners) const
|
std::vector<uint32_t> database::get_seeds( uint32_t instance_value, uint8_t count_winners ) const
|
||||||
{
|
{
|
||||||
FC_ASSERT( count_winners <= 64 );
|
FC_ASSERT( count_winners <= 64 );
|
||||||
std::string salted_string = std::string(_random_number_generator._seed) + std::to_string(for_asset.instance.value);
|
std::string salted_string = std::string(_random_number_generator._seed) + std::to_string(instance_value);
|
||||||
uint32_t* seeds = (uint32_t*)(fc::sha256::hash(salted_string)._hash);
|
uint32_t* seeds = (uint32_t*)(fc::sha256::hash(salted_string)._hash);
|
||||||
|
|
||||||
std::vector<uint32_t> result;
|
std::vector<uint32_t> result;
|
||||||
result.reserve(64);
|
result.reserve(64);
|
||||||
|
|
||||||
for( int s = 0; s < 8; ++s ) {
|
for( int s = 0; s < 8; ++s ) {
|
||||||
uint32_t* sub_seeds = ( uint32_t* ) fc::sha256::hash( std::to_string( seeds[s] ) + std::to_string( for_asset.instance.value ) )._hash;
|
uint32_t* sub_seeds = ( uint32_t* ) fc::sha256::hash( std::to_string( seeds[s] ) + std::to_string( instance_value ) )._hash;
|
||||||
for( int ss = 0; ss < 8; ++ss ) {
|
for( int ss = 0; ss < 8; ++ss ) {
|
||||||
result.push_back(sub_seeds[ss]);
|
result.push_back(sub_seeds[ss]);
|
||||||
}
|
}
|
||||||
|
|
@ -127,14 +127,14 @@ std::vector<uint32_t> database::get_seeds(asset_id_type for_asset, uint8_t count
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<uint32_t> database::get_winner_numbers( asset_id_type for_asset, uint32_t count_members, uint8_t count_winners ) const
|
const std::vector<uint32_t> database::get_winner_numbers( uint32_t instance_value, uint32_t count_members, uint8_t count_winners ) const
|
||||||
{
|
{
|
||||||
std::vector<uint32_t> result;
|
std::vector<uint32_t> result;
|
||||||
if( count_members < count_winners ) count_winners = count_members;
|
if( count_members < count_winners ) count_winners = count_members;
|
||||||
if( count_winners == 0 ) return result;
|
if( count_winners == 0 ) return result;
|
||||||
result.reserve(count_winners);
|
result.reserve(count_winners);
|
||||||
|
|
||||||
auto seeds = get_seeds(for_asset, count_winners);
|
auto seeds = get_seeds(instance_value, count_winners);
|
||||||
|
|
||||||
for (auto current_seed = seeds.begin(); current_seed != seeds.end(); ++current_seed) {
|
for (auto current_seed = seeds.begin(); current_seed != seeds.end(); ++current_seed) {
|
||||||
uint8_t winner_num = *current_seed % count_members;
|
uint8_t winner_num = *current_seed % count_members;
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@
|
||||||
#include <graphene/chain/offer_evaluator.hpp>
|
#include <graphene/chain/offer_evaluator.hpp>
|
||||||
#include <graphene/chain/nft_evaluator.hpp>
|
#include <graphene/chain/nft_evaluator.hpp>
|
||||||
#include <graphene/chain/account_role_evaluator.hpp>
|
#include <graphene/chain/account_role_evaluator.hpp>
|
||||||
|
#include <graphene/chain/nft_lottery_evaluator.hpp>
|
||||||
|
|
||||||
#include <graphene/chain/protocol/fee_schedule.hpp>
|
#include <graphene/chain/protocol/fee_schedule.hpp>
|
||||||
|
|
||||||
|
|
@ -283,6 +284,9 @@ void database::initialize_evaluators()
|
||||||
register_evaluator<account_role_create_evaluator>();
|
register_evaluator<account_role_create_evaluator>();
|
||||||
register_evaluator<account_role_update_evaluator>();
|
register_evaluator<account_role_update_evaluator>();
|
||||||
register_evaluator<account_role_delete_evaluator>();
|
register_evaluator<account_role_delete_evaluator>();
|
||||||
|
register_evaluator<nft_lottery_token_purchase_evaluator>();
|
||||||
|
register_evaluator<nft_lottery_reward_evaluator>();
|
||||||
|
register_evaluator<nft_lottery_end_evaluator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void database::initialize_indexes()
|
void database::initialize_indexes()
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,24 @@ void database::check_ending_lotteries()
|
||||||
} catch( ... ) {}
|
} catch( ... ) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void database::check_ending_nft_lotteries()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const auto &nft_lotteries_idx = get_index_type<nft_metadata_index>().indices().get<active_nft_lotteries>();
|
||||||
|
for (auto checking_token : nft_lotteries_idx)
|
||||||
|
{
|
||||||
|
FC_ASSERT(checking_token.is_lottery());
|
||||||
|
const auto &lottery_options = checking_token.lottery_data->lottery_options;
|
||||||
|
FC_ASSERT(lottery_options.is_active);
|
||||||
|
// Check the current supply of lottery tokens
|
||||||
|
auto current_supply = checking_token.get_token_current_supply(*this);
|
||||||
|
if ((lottery_options.ending_on_soldout && (current_supply == checking_token.max_supply)) ||
|
||||||
|
(lottery_options.end_date != time_point_sec() && (lottery_options.end_date <= head_block_time())))
|
||||||
|
checking_token.end_lottery(*this);
|
||||||
|
}
|
||||||
|
} catch( ... ) {}
|
||||||
|
}
|
||||||
|
|
||||||
void database::check_lottery_end_by_participants( asset_id_type asset_id )
|
void database::check_lottery_end_by_participants( asset_id_type asset_id )
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -353,6 +353,13 @@ struct get_impacted_account_visitor
|
||||||
void operator()( const account_role_delete_operation& op ){
|
void operator()( const account_role_delete_operation& op ){
|
||||||
_impacted.insert( op.owner );
|
_impacted.insert( op.owner );
|
||||||
}
|
}
|
||||||
|
void operator()( const nft_lottery_token_purchase_operation& op ){
|
||||||
|
_impacted.insert( op.buyer );
|
||||||
|
}
|
||||||
|
void operator()( const nft_lottery_reward_operation& op ) {
|
||||||
|
_impacted.insert( op.winner );
|
||||||
|
}
|
||||||
|
void operator()( const nft_lottery_end_operation& op ) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
|
void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,7 @@ namespace graphene { namespace chain {
|
||||||
|
|
||||||
void check_lottery_end_by_participants( asset_id_type asset_id );
|
void check_lottery_end_by_participants( asset_id_type asset_id );
|
||||||
void check_ending_lotteries();
|
void check_ending_lotteries();
|
||||||
|
void check_ending_nft_lotteries();
|
||||||
|
|
||||||
//////////////////// db_getter.cpp ////////////////////
|
//////////////////// db_getter.cpp ////////////////////
|
||||||
|
|
||||||
|
|
@ -278,8 +279,8 @@ namespace graphene { namespace chain {
|
||||||
const node_property_object& get_node_properties()const;
|
const node_property_object& get_node_properties()const;
|
||||||
const fee_schedule& current_fee_schedule()const;
|
const fee_schedule& current_fee_schedule()const;
|
||||||
const account_statistics_object& get_account_stats_by_owner( account_id_type owner )const;
|
const account_statistics_object& get_account_stats_by_owner( account_id_type owner )const;
|
||||||
const std::vector<uint32_t> get_winner_numbers( asset_id_type for_asset, uint32_t count_members, uint8_t count_winners ) const;
|
const std::vector<uint32_t> get_winner_numbers( uint32_t instance_value, uint32_t count_members, uint8_t count_winners ) const;
|
||||||
std::vector<uint32_t> get_seeds( asset_id_type for_asset, uint8_t count_winners )const;
|
std::vector<uint32_t> get_seeds( uint32_t instance_value, uint8_t count_winners )const;
|
||||||
uint64_t get_random_bits( uint64_t bound );
|
uint64_t get_random_bits( uint64_t bound );
|
||||||
const witness_schedule_object& get_witness_schedule_object()const;
|
const witness_schedule_object& get_witness_schedule_object()const;
|
||||||
bool item_locked(const nft_id_type& item)const;
|
bool item_locked(const nft_id_type& item)const;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
#pragma once
|
||||||
|
#include <graphene/chain/protocol/operations.hpp>
|
||||||
|
#include <graphene/chain/evaluator.hpp>
|
||||||
|
#include <graphene/chain/database.hpp>
|
||||||
|
|
||||||
|
namespace graphene
|
||||||
|
{
|
||||||
|
namespace chain
|
||||||
|
{
|
||||||
|
|
||||||
|
class nft_lottery_token_purchase_evaluator : public evaluator<nft_lottery_token_purchase_evaluator>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef nft_lottery_token_purchase_operation operation_type;
|
||||||
|
|
||||||
|
void_result do_evaluate(const nft_lottery_token_purchase_operation &o);
|
||||||
|
object_id_type do_apply(const nft_lottery_token_purchase_operation &o);
|
||||||
|
};
|
||||||
|
|
||||||
|
class nft_lottery_reward_evaluator : public evaluator<nft_lottery_reward_evaluator>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef nft_lottery_reward_operation operation_type;
|
||||||
|
|
||||||
|
void_result do_evaluate(const nft_lottery_reward_operation &o);
|
||||||
|
void_result do_apply(const nft_lottery_reward_operation &o);
|
||||||
|
};
|
||||||
|
|
||||||
|
class nft_lottery_end_evaluator : public evaluator<nft_lottery_end_evaluator>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef nft_lottery_end_operation operation_type;
|
||||||
|
|
||||||
|
void_result do_evaluate(const nft_lottery_end_operation &o);
|
||||||
|
void_result do_apply(const nft_lottery_end_operation &o);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chain
|
||||||
|
} // namespace graphene
|
||||||
|
|
@ -6,6 +6,16 @@
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
using namespace graphene::db;
|
using namespace graphene::db;
|
||||||
|
|
||||||
|
struct nft_lottery_data
|
||||||
|
{
|
||||||
|
nft_lottery_data() {}
|
||||||
|
nft_lottery_data(const nft_lottery_options &options)
|
||||||
|
: lottery_options(options), jackpot(asset(0, options.ticket_price.asset_id)) {}
|
||||||
|
nft_lottery_options lottery_options;
|
||||||
|
asset jackpot;
|
||||||
|
share_type sweeps_tickets_sold;
|
||||||
|
};
|
||||||
|
|
||||||
class nft_metadata_object : public abstract_object<nft_metadata_object>
|
class nft_metadata_object : public abstract_object<nft_metadata_object>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -21,6 +31,21 @@ namespace graphene { namespace chain {
|
||||||
bool is_transferable = false;
|
bool is_transferable = false;
|
||||||
bool is_sellable = true;
|
bool is_sellable = true;
|
||||||
optional<account_role_id_type> account_role;
|
optional<account_role_id_type> account_role;
|
||||||
|
share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY;
|
||||||
|
optional<nft_lottery_data> lottery_data;
|
||||||
|
|
||||||
|
nft_metadata_id_type get_id() const { return id; }
|
||||||
|
bool is_lottery() const { return lottery_data.valid(); }
|
||||||
|
uint32_t get_owner_num() const { return owner.instance.value; }
|
||||||
|
time_point_sec get_lottery_expiration() const;
|
||||||
|
asset get_lottery_jackpot() const;
|
||||||
|
share_type get_token_current_supply(database &db) const;
|
||||||
|
vector<account_id_type> get_holders(database &db) const;
|
||||||
|
vector<uint64_t> get_ticket_ids(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);
|
||||||
};
|
};
|
||||||
|
|
||||||
class nft_object : public abstract_object<nft_object>
|
class nft_object : public abstract_object<nft_object>
|
||||||
|
|
@ -36,8 +61,23 @@ namespace graphene { namespace chain {
|
||||||
std::string token_uri;
|
std::string token_uri;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct nft_lottery_comparer
|
||||||
|
{
|
||||||
|
bool operator()(const nft_metadata_object& lhs, const nft_metadata_object& rhs) const
|
||||||
|
{
|
||||||
|
if ( !lhs.is_lottery() ) return false;
|
||||||
|
if ( !lhs.lottery_data->lottery_options.is_active && !rhs.is_lottery()) return true; // not active lotteries first, just assets then
|
||||||
|
if ( !lhs.lottery_data->lottery_options.is_active ) return false;
|
||||||
|
if ( lhs.lottery_data->lottery_options.is_active && ( !rhs.is_lottery() || !rhs.lottery_data->lottery_options.is_active ) ) return true;
|
||||||
|
return lhs.get_lottery_expiration() > rhs.get_lottery_expiration();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct by_name;
|
struct by_name;
|
||||||
struct by_symbol;
|
struct by_symbol;
|
||||||
|
struct active_nft_lotteries;
|
||||||
|
struct by_nft_lottery;
|
||||||
|
struct by_nft_lottery_owner;
|
||||||
using nft_metadata_multi_index_type = multi_index_container<
|
using nft_metadata_multi_index_type = multi_index_container<
|
||||||
nft_metadata_object,
|
nft_metadata_object,
|
||||||
indexed_by<
|
indexed_by<
|
||||||
|
|
@ -49,6 +89,34 @@ namespace graphene { namespace chain {
|
||||||
>,
|
>,
|
||||||
ordered_unique< tag<by_symbol>,
|
ordered_unique< tag<by_symbol>,
|
||||||
member<nft_metadata_object, std::string, &nft_metadata_object::symbol>
|
member<nft_metadata_object, std::string, &nft_metadata_object::symbol>
|
||||||
|
>,
|
||||||
|
ordered_non_unique< tag<active_nft_lotteries>,
|
||||||
|
identity< nft_metadata_object >,
|
||||||
|
nft_lottery_comparer
|
||||||
|
>,
|
||||||
|
ordered_unique< tag<by_nft_lottery>,
|
||||||
|
composite_key<
|
||||||
|
nft_metadata_object,
|
||||||
|
const_mem_fun<nft_metadata_object, bool, &nft_metadata_object::is_lottery>,
|
||||||
|
member<object, object_id_type, &object::id>
|
||||||
|
>,
|
||||||
|
composite_key_compare<
|
||||||
|
std::greater< bool >,
|
||||||
|
std::greater< object_id_type >
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
ordered_unique< tag<by_nft_lottery_owner>,
|
||||||
|
composite_key<
|
||||||
|
nft_metadata_object,
|
||||||
|
const_mem_fun<nft_metadata_object, bool, &nft_metadata_object::is_lottery>,
|
||||||
|
const_mem_fun<nft_metadata_object, uint32_t, &nft_metadata_object::get_owner_num>,
|
||||||
|
member<object, object_id_type, &object::id>
|
||||||
|
>,
|
||||||
|
composite_key_compare<
|
||||||
|
std::greater< bool >,
|
||||||
|
std::greater< uint32_t >,
|
||||||
|
std::greater< object_id_type >
|
||||||
|
>
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
|
|
@ -88,6 +156,8 @@ namespace graphene { namespace chain {
|
||||||
|
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
||||||
|
FC_REFLECT( graphene::chain::nft_lottery_data, (lottery_options)(jackpot)(sweeps_tickets_sold) )
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object),
|
FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object),
|
||||||
(owner)
|
(owner)
|
||||||
(name)
|
(name)
|
||||||
|
|
@ -97,7 +167,9 @@ FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object)
|
||||||
(revenue_split)
|
(revenue_split)
|
||||||
(is_transferable)
|
(is_transferable)
|
||||||
(is_sellable)
|
(is_sellable)
|
||||||
(account_role) )
|
(account_role)
|
||||||
|
(max_supply)
|
||||||
|
(lottery_data) )
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object),
|
FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object),
|
||||||
(nft_metadata_id)
|
(nft_metadata_id)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
#pragma once
|
||||||
|
#include <graphene/chain/protocol/base.hpp>
|
||||||
|
#include <graphene/chain/protocol/types.hpp>
|
||||||
|
|
||||||
|
namespace graphene
|
||||||
|
{
|
||||||
|
namespace chain
|
||||||
|
{
|
||||||
|
struct nft_lottery_token_purchase_operation : public base_operation
|
||||||
|
{
|
||||||
|
struct fee_parameters_type
|
||||||
|
{
|
||||||
|
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION;
|
||||||
|
};
|
||||||
|
asset fee;
|
||||||
|
// Lottery NFT Metadata
|
||||||
|
nft_metadata_id_type lottery_id;
|
||||||
|
// Buyer purchasing lottery tickets
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct nft_lottery_reward_operation : public base_operation
|
||||||
|
{
|
||||||
|
struct fee_parameters_type
|
||||||
|
{
|
||||||
|
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION;
|
||||||
|
};
|
||||||
|
|
||||||
|
asset fee;
|
||||||
|
// Lottery NFT Metadata
|
||||||
|
nft_metadata_id_type lottery_id;
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
uint64_t winner_ticket_id;
|
||||||
|
|
||||||
|
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 nft_lottery_end_operation : public base_operation
|
||||||
|
{
|
||||||
|
struct fee_parameters_type
|
||||||
|
{
|
||||||
|
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION;
|
||||||
|
};
|
||||||
|
|
||||||
|
asset fee;
|
||||||
|
// Lottery NFT Metadata
|
||||||
|
nft_metadata_id_type lottery_id;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace chain
|
||||||
|
} // namespace graphene
|
||||||
|
|
||||||
|
FC_REFLECT(graphene::chain::nft_lottery_token_purchase_operation::fee_parameters_type, (fee))
|
||||||
|
FC_REFLECT(graphene::chain::nft_lottery_reward_operation::fee_parameters_type, (fee))
|
||||||
|
FC_REFLECT(graphene::chain::nft_lottery_end_operation::fee_parameters_type, (fee))
|
||||||
|
FC_REFLECT(graphene::chain::nft_lottery_token_purchase_operation, (fee)(lottery_id)(buyer)(tickets_to_buy)(amount)(extensions))
|
||||||
|
FC_REFLECT(graphene::chain::nft_lottery_reward_operation, (fee)(lottery_id)(winner)(amount)(win_percentage)(is_benefactor_reward)(winner_ticket_id)(extensions))
|
||||||
|
FC_REFLECT(graphene::chain::nft_lottery_end_operation, (fee)(lottery_id)(extensions))
|
||||||
|
|
@ -4,6 +4,27 @@
|
||||||
|
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
|
|
||||||
|
struct nft_lottery_benefactor {
|
||||||
|
account_id_type id;
|
||||||
|
uint16_t share; // percent * GRAPHENE_1_PERCENT
|
||||||
|
nft_lottery_benefactor() = default;
|
||||||
|
nft_lottery_benefactor( const nft_lottery_benefactor & ) = default;
|
||||||
|
nft_lottery_benefactor( account_id_type _id, uint16_t _share ) : id( _id ), share( _share ) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct nft_lottery_options
|
||||||
|
{
|
||||||
|
std::vector<nft_lottery_benefactor> benefactors;
|
||||||
|
// specifying winning tickets as shares that will be issued
|
||||||
|
std::vector<uint16_t> winning_tickets;
|
||||||
|
asset ticket_price;
|
||||||
|
time_point_sec end_date;
|
||||||
|
bool ending_on_soldout;
|
||||||
|
bool is_active;
|
||||||
|
|
||||||
|
void validate() const;
|
||||||
|
};
|
||||||
|
|
||||||
struct nft_metadata_create_operation : public base_operation
|
struct nft_metadata_create_operation : public base_operation
|
||||||
{
|
{
|
||||||
struct fee_parameters_type
|
struct fee_parameters_type
|
||||||
|
|
@ -23,6 +44,10 @@ namespace graphene { namespace chain {
|
||||||
bool is_sellable = true;
|
bool is_sellable = true;
|
||||||
// Accounts Role
|
// Accounts Role
|
||||||
optional<account_role_id_type> account_role;
|
optional<account_role_id_type> account_role;
|
||||||
|
// Max number of NFTs that can be minted from the metadata
|
||||||
|
optional<share_type> max_supply;
|
||||||
|
// Lottery configuration
|
||||||
|
optional<nft_lottery_options> lottery_options;
|
||||||
extensions_type extensions;
|
extensions_type extensions;
|
||||||
|
|
||||||
account_id_type fee_payer()const { return owner; }
|
account_id_type fee_payer()const { return owner; }
|
||||||
|
|
@ -133,6 +158,9 @@ namespace graphene { namespace chain {
|
||||||
|
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
||||||
|
FC_REFLECT( graphene::chain::nft_lottery_benefactor, (id)(share) )
|
||||||
|
FC_REFLECT( graphene::chain::nft_lottery_options, (benefactors)(winning_tickets)(ticket_price)(end_date)(ending_on_soldout)(is_active) )
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::nft_metadata_create_operation::fee_parameters_type, (fee) (price_per_kbyte) )
|
FC_REFLECT( graphene::chain::nft_metadata_create_operation::fee_parameters_type, (fee) (price_per_kbyte) )
|
||||||
FC_REFLECT( graphene::chain::nft_metadata_update_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::nft_metadata_update_operation::fee_parameters_type, (fee) )
|
||||||
FC_REFLECT( graphene::chain::nft_mint_operation::fee_parameters_type, (fee) (price_per_kbyte) )
|
FC_REFLECT( graphene::chain::nft_mint_operation::fee_parameters_type, (fee) (price_per_kbyte) )
|
||||||
|
|
@ -140,7 +168,7 @@ FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_ty
|
||||||
FC_REFLECT( graphene::chain::nft_approve_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::nft_approve_operation::fee_parameters_type, (fee) )
|
||||||
FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_type, (fee) )
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) )
|
FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (max_supply) (lottery_options) (extensions) )
|
||||||
FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) )
|
FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) )
|
||||||
FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) (extensions) )
|
FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) (extensions) )
|
||||||
FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) (extensions) )
|
FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) (extensions) )
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
#include <graphene/chain/protocol/offer.hpp>
|
#include <graphene/chain/protocol/offer.hpp>
|
||||||
#include <graphene/chain/protocol/nft_ops.hpp>
|
#include <graphene/chain/protocol/nft_ops.hpp>
|
||||||
#include <graphene/chain/protocol/account_role.hpp>
|
#include <graphene/chain/protocol/account_role.hpp>
|
||||||
|
#include <graphene/chain/protocol/nft_lottery.hpp>
|
||||||
|
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
|
|
||||||
|
|
@ -159,7 +160,10 @@ namespace graphene { namespace chain {
|
||||||
nft_set_approval_for_all_operation,
|
nft_set_approval_for_all_operation,
|
||||||
account_role_create_operation,
|
account_role_create_operation,
|
||||||
account_role_update_operation,
|
account_role_update_operation,
|
||||||
account_role_delete_operation
|
account_role_delete_operation,
|
||||||
|
nft_lottery_token_purchase_operation,
|
||||||
|
nft_lottery_reward_operation,
|
||||||
|
nft_lottery_end_operation
|
||||||
> operation;
|
> operation;
|
||||||
|
|
||||||
/// @} // operations group
|
/// @} // operations group
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ namespace graphene
|
||||||
case operation::tag<account_role_create_operation>::value:
|
case operation::tag<account_role_create_operation>::value:
|
||||||
case operation::tag<account_role_update_operation>::value:
|
case operation::tag<account_role_update_operation>::value:
|
||||||
case operation::tag<account_role_delete_operation>::value:
|
case operation::tag<account_role_delete_operation>::value:
|
||||||
|
case operation::tag<nft_lottery_token_purchase_operation>::value:
|
||||||
|
case operation::tag<nft_lottery_reward_operation>::value:
|
||||||
|
case operation::tag<nft_lottery_end_operation>::value:
|
||||||
FC_ASSERT(block_time >= HARDFORK_NFT_TIME, "Custom permissions and roles not allowed on this operation yet!");
|
FC_ASSERT(block_time >= HARDFORK_NFT_TIME, "Custom permissions and roles not allowed on this operation yet!");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,15 @@ void_result nft_metadata_create_evaluator::do_evaluate( const nft_metadata_creat
|
||||||
const auto& ar_obj = (*op.account_role)(db());
|
const auto& ar_obj = (*op.account_role)(db());
|
||||||
FC_ASSERT(ar_obj.owner == op.owner, "Only the Account Role created by the owner can be attached");
|
FC_ASSERT(ar_obj.owner == op.owner, "Only the Account Role created by the owner can be attached");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lottery Related
|
||||||
|
if (!op.lottery_options) {
|
||||||
|
return void_result();
|
||||||
|
}
|
||||||
|
FC_ASSERT((*op.lottery_options).end_date > now || (*op.lottery_options).end_date == time_point_sec());
|
||||||
|
if (op.max_supply) {
|
||||||
|
FC_ASSERT(*op.max_supply >= 5);
|
||||||
|
}
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
|
|
@ -39,6 +48,12 @@ object_id_type nft_metadata_create_evaluator::do_apply( const nft_metadata_creat
|
||||||
obj.is_transferable = op.is_transferable;
|
obj.is_transferable = op.is_transferable;
|
||||||
obj.is_sellable = op.is_sellable;
|
obj.is_sellable = op.is_sellable;
|
||||||
obj.account_role = op.account_role;
|
obj.account_role = op.account_role;
|
||||||
|
if (op.max_supply) {
|
||||||
|
obj.max_supply = *op.max_supply;
|
||||||
|
}
|
||||||
|
if (op.lottery_options) {
|
||||||
|
obj.lottery_data = nft_lottery_data(*op.lottery_options);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return new_nft_metadata_object.id;
|
return new_nft_metadata_object.id;
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
@ -110,6 +125,7 @@ void_result nft_mint_evaluator::do_evaluate( const nft_mint_operation& op )
|
||||||
FC_ASSERT( itr_nft_md != idx_nft_md.end(), "NFT metadata not found" );
|
FC_ASSERT( itr_nft_md != idx_nft_md.end(), "NFT metadata not found" );
|
||||||
FC_ASSERT( itr_nft_md->owner == op.payer, "Only metadata owner can mint NFT" );
|
FC_ASSERT( itr_nft_md->owner == op.payer, "Only metadata owner can mint NFT" );
|
||||||
|
|
||||||
|
FC_ASSERT(itr_nft_md->get_token_current_supply(db()) < itr_nft_md->max_supply, "NFTs can't be minted more than max_supply");
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
|
|
|
||||||
130
libraries/chain/nft_lottery_evaluator.cpp
Normal file
130
libraries/chain/nft_lottery_evaluator.cpp
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
#include <graphene/chain/nft_lottery_evaluator.hpp>
|
||||||
|
#include <graphene/chain/nft_object.hpp>
|
||||||
|
#include <graphene/chain/protocol/operations.hpp>
|
||||||
|
#include <graphene/chain/account_role_object.hpp>
|
||||||
|
#include <graphene/chain/hardfork.hpp>
|
||||||
|
|
||||||
|
namespace graphene
|
||||||
|
{
|
||||||
|
namespace chain
|
||||||
|
{
|
||||||
|
void_result nft_lottery_token_purchase_evaluator::do_evaluate(const nft_lottery_token_purchase_operation &op)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const database &d = db();
|
||||||
|
auto now = d.head_block_time();
|
||||||
|
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
|
||||||
|
op.buyer(d);
|
||||||
|
const auto &lottery_md_obj = op.lottery_id(d);
|
||||||
|
FC_ASSERT(lottery_md_obj.is_lottery(), "Not a lottery type");
|
||||||
|
if (lottery_md_obj.account_role)
|
||||||
|
{
|
||||||
|
const auto &ar_idx = d.get_index_type<account_role_index>().indices().get<by_id>();
|
||||||
|
auto ar_itr = ar_idx.find(*lottery_md_obj.account_role);
|
||||||
|
if (ar_itr != ar_idx.end())
|
||||||
|
{
|
||||||
|
FC_ASSERT(d.account_role_valid(*ar_itr, op.buyer, get_type()), "Account role not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lottery_options = lottery_md_obj.lottery_data->lottery_options;
|
||||||
|
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 nft_lottery_token_purchase_evaluator::do_apply(const nft_lottery_token_purchase_operation &op)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
transaction_evaluation_state nft_mint_context(&db());
|
||||||
|
nft_mint_context.skip_fee_schedule_check = true;
|
||||||
|
const auto &lottery_md_obj = op.lottery_id(db());
|
||||||
|
nft_id_type nft_id;
|
||||||
|
for (size_t i = 0; i < op.tickets_to_buy; i++)
|
||||||
|
{
|
||||||
|
nft_mint_operation mint_op;
|
||||||
|
mint_op.payer = lottery_md_obj.owner;
|
||||||
|
mint_op.nft_metadata_id = lottery_md_obj.id;
|
||||||
|
mint_op.owner = op.buyer;
|
||||||
|
nft_id = db().apply_operation(nft_mint_context, mint_op).get<object_id_type>();
|
||||||
|
}
|
||||||
|
db().adjust_balance(op.buyer, -op.amount);
|
||||||
|
db().modify(lottery_md_obj, [&](nft_metadata_object &obj) {
|
||||||
|
obj.lottery_data->jackpot += op.amount;
|
||||||
|
});
|
||||||
|
return nft_id;
|
||||||
|
}
|
||||||
|
FC_CAPTURE_AND_RETHROW((op))
|
||||||
|
}
|
||||||
|
|
||||||
|
void_result nft_lottery_reward_evaluator::do_evaluate(const nft_lottery_reward_operation &op)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const database &d = db();
|
||||||
|
auto now = d.head_block_time();
|
||||||
|
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
|
||||||
|
op.winner(d);
|
||||||
|
|
||||||
|
const auto &lottery_md_obj = op.lottery_id(d);
|
||||||
|
FC_ASSERT(lottery_md_obj.is_lottery());
|
||||||
|
|
||||||
|
const auto &lottery_options = lottery_md_obj.lottery_data->lottery_options;
|
||||||
|
FC_ASSERT(lottery_options.is_active);
|
||||||
|
FC_ASSERT(lottery_md_obj.get_lottery_jackpot() >= op.amount);
|
||||||
|
return void_result();
|
||||||
|
}
|
||||||
|
FC_CAPTURE_AND_RETHROW((op))
|
||||||
|
}
|
||||||
|
|
||||||
|
void_result nft_lottery_reward_evaluator::do_apply(const nft_lottery_reward_operation &op)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto &lottery_md_obj = op.lottery_id(db());
|
||||||
|
db().adjust_balance(op.winner, op.amount);
|
||||||
|
db().modify(lottery_md_obj, [&](nft_metadata_object &obj) {
|
||||||
|
obj.lottery_data->jackpot -= op.amount;
|
||||||
|
});
|
||||||
|
return void_result();
|
||||||
|
}
|
||||||
|
FC_CAPTURE_AND_RETHROW((op))
|
||||||
|
}
|
||||||
|
|
||||||
|
void_result nft_lottery_end_evaluator::do_evaluate(const nft_lottery_end_operation &op)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const database &d = db();
|
||||||
|
auto now = d.head_block_time();
|
||||||
|
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
|
||||||
|
const auto &lottery_md_obj = op.lottery_id(d);
|
||||||
|
FC_ASSERT(lottery_md_obj.is_lottery());
|
||||||
|
|
||||||
|
const auto &lottery_options = lottery_md_obj.lottery_data->lottery_options;
|
||||||
|
FC_ASSERT(lottery_options.is_active);
|
||||||
|
FC_ASSERT(lottery_md_obj.get_lottery_jackpot().amount == 0);
|
||||||
|
return void_result();
|
||||||
|
}
|
||||||
|
FC_CAPTURE_AND_RETHROW((op))
|
||||||
|
}
|
||||||
|
|
||||||
|
void_result nft_lottery_end_evaluator::do_apply(const nft_lottery_end_operation &op)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const auto &lottery_md_obj = op.lottery_id(db());
|
||||||
|
db().modify(lottery_md_obj, [&](nft_metadata_object &obj) {
|
||||||
|
obj.lottery_data->sweeps_tickets_sold = obj.get_token_current_supply(db());
|
||||||
|
obj.lottery_data->lottery_options.is_active = false;
|
||||||
|
});
|
||||||
|
return void_result();
|
||||||
|
}
|
||||||
|
FC_CAPTURE_AND_RETHROW((op))
|
||||||
|
}
|
||||||
|
} // namespace chain
|
||||||
|
} // namespace graphene
|
||||||
170
libraries/chain/nft_lottery_object.cpp
Normal file
170
libraries/chain/nft_lottery_object.cpp
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
#include <graphene/chain/database.hpp>
|
||||||
|
#include <graphene/chain/nft_object.hpp>
|
||||||
|
|
||||||
|
namespace graphene
|
||||||
|
{
|
||||||
|
namespace chain
|
||||||
|
{
|
||||||
|
time_point_sec nft_metadata_object::get_lottery_expiration() const
|
||||||
|
{
|
||||||
|
if (lottery_data)
|
||||||
|
return lottery_data->lottery_options.end_date;
|
||||||
|
return time_point_sec();
|
||||||
|
}
|
||||||
|
|
||||||
|
asset nft_metadata_object::get_lottery_jackpot() const
|
||||||
|
{
|
||||||
|
if (lottery_data)
|
||||||
|
return lottery_data->jackpot;
|
||||||
|
return asset();
|
||||||
|
}
|
||||||
|
|
||||||
|
share_type nft_metadata_object::get_token_current_supply(database &db) const
|
||||||
|
{
|
||||||
|
share_type current_supply;
|
||||||
|
const auto &idx_lottery_by_md = db.get_index_type<nft_index>().indices().get<by_metadata>();
|
||||||
|
auto lottery_range = idx_lottery_by_md.equal_range(id);
|
||||||
|
current_supply = std::distance(lottery_range.first, lottery_range.second);
|
||||||
|
return current_supply;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<account_id_type> nft_metadata_object::get_holders(database &db) const
|
||||||
|
{
|
||||||
|
const auto &idx_lottery_by_md = db.get_index_type<nft_index>().indices().get<by_metadata>();
|
||||||
|
auto lottery_range = idx_lottery_by_md.equal_range(id);
|
||||||
|
vector<account_id_type> holders;
|
||||||
|
holders.reserve(std::distance(lottery_range.first, lottery_range.second));
|
||||||
|
std::for_each(lottery_range.first, lottery_range.second,
|
||||||
|
[&](const nft_object &ticket) {
|
||||||
|
holders.emplace_back(ticket.owner);
|
||||||
|
});
|
||||||
|
return holders;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<uint64_t> nft_metadata_object::get_ticket_ids(database &db) const
|
||||||
|
{
|
||||||
|
const auto &idx_lottery_by_md = db.get_index_type<nft_index>().indices().get<by_metadata>();
|
||||||
|
auto lottery_range = idx_lottery_by_md.equal_range(id);
|
||||||
|
vector<uint64_t> tickets;
|
||||||
|
tickets.reserve(std::distance(lottery_range.first, lottery_range.second));
|
||||||
|
std::for_each(lottery_range.first, lottery_range.second,
|
||||||
|
[&](const nft_object &ticket) {
|
||||||
|
tickets.emplace_back(ticket.id.instance());
|
||||||
|
});
|
||||||
|
return tickets;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nft_metadata_object::distribute_benefactors_part(database &db)
|
||||||
|
{
|
||||||
|
transaction_evaluation_state eval(&db);
|
||||||
|
const auto &lottery_options = lottery_data->lottery_options;
|
||||||
|
share_type jackpot = lottery_options.ticket_price.amount * get_token_current_supply(db).value;
|
||||||
|
|
||||||
|
for (auto benefactor : lottery_options.benefactors)
|
||||||
|
{
|
||||||
|
nft_lottery_reward_operation reward_op;
|
||||||
|
reward_op.lottery_id = id;
|
||||||
|
reward_op.winner = benefactor.id;
|
||||||
|
reward_op.is_benefactor_reward = true;
|
||||||
|
reward_op.win_percentage = benefactor.share;
|
||||||
|
reward_op.amount = asset(jackpot.value * benefactor.share / GRAPHENE_100_PERCENT, lottery_options.ticket_price.asset_id);
|
||||||
|
db.apply_operation(eval, reward_op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map<account_id_type, vector<uint16_t>> nft_metadata_object::distribute_winners_part(database &db)
|
||||||
|
{
|
||||||
|
transaction_evaluation_state eval(&db);
|
||||||
|
auto current_supply = get_token_current_supply(db);
|
||||||
|
auto &lottery_options = lottery_data->lottery_options;
|
||||||
|
|
||||||
|
auto holders = get_holders(db);
|
||||||
|
vector<uint64_t> ticket_ids = get_ticket_ids(db);
|
||||||
|
FC_ASSERT(current_supply.value == (int64_t)holders.size());
|
||||||
|
FC_ASSERT(lottery_data->jackpot.amount.value == current_supply.value * lottery_options.ticket_price.amount.value);
|
||||||
|
map<account_id_type, vector<uint16_t>> structurized_participants;
|
||||||
|
for (account_id_type holder : holders)
|
||||||
|
{
|
||||||
|
if (!structurized_participants.count(holder))
|
||||||
|
structurized_participants.emplace(holder, vector<uint16_t>());
|
||||||
|
}
|
||||||
|
uint64_t jackpot = lottery_data->jackpot.amount.value;
|
||||||
|
auto winner_numbers = db.get_winner_numbers(get_id().instance.value, 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();
|
||||||
|
}
|
||||||
|
auto sweeps_distribution_percentage = db.get_global_properties().parameters.sweeps_distribution_percentage();
|
||||||
|
for (size_t c = 0; c < winner_numbers.size(); ++c)
|
||||||
|
{
|
||||||
|
auto winner_num = winner_numbers[c];
|
||||||
|
nft_lottery_reward_operation reward_op;
|
||||||
|
reward_op.lottery_id = id;
|
||||||
|
reward_op.is_benefactor_reward = false;
|
||||||
|
reward_op.winner = holders[winner_num];
|
||||||
|
if (ticket_ids.size() > winner_num)
|
||||||
|
{
|
||||||
|
reward_op.winner_ticket_id = ticket_ids[winner_num];
|
||||||
|
}
|
||||||
|
reward_op.win_percentage = tickets[c];
|
||||||
|
reward_op.amount = asset(jackpot * tickets[c] * (1. - sweeps_distribution_percentage / (double)GRAPHENE_100_PERCENT) / GRAPHENE_100_PERCENT, lottery_options.ticket_price.asset_id);
|
||||||
|
db.apply_operation(eval, reward_op);
|
||||||
|
|
||||||
|
structurized_participants[holders[winner_num]].push_back(tickets[c]);
|
||||||
|
}
|
||||||
|
return structurized_participants;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nft_metadata_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 sweeps_params = db.get_global_properties().parameters;
|
||||||
|
uint64_t distribution_asset_supply = sweeps_params.sweeps_distribution_asset()(db).dynamic_data(db).current_supply.value;
|
||||||
|
const auto range = asset_bal_idx.equal_range(boost::make_tuple(sweeps_params.sweeps_distribution_asset()));
|
||||||
|
asset remaining_jackpot = get_id()(db).lottery_data->jackpot;
|
||||||
|
uint64_t holders_sum = 0;
|
||||||
|
for (const account_balance_object &holder_balance : boost::make_iterator_range(range.first, range.second))
|
||||||
|
{
|
||||||
|
int64_t holder_part = remaining_jackpot.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 = remaining_jackpot.amount.value * SWEEPS_VESTING_BALANCE_MULTIPLIER - holders_sum;
|
||||||
|
db.adjust_sweeps_vesting_balance(sweeps_params.sweeps_vesting_accumulator_account(), balance_rest);
|
||||||
|
db.modify(get_id()(db), [&](nft_metadata_object &obj) {
|
||||||
|
obj.lottery_data->jackpot -= remaining_jackpot;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void nft_metadata_object::end_lottery(database &db)
|
||||||
|
{
|
||||||
|
transaction_evaluation_state eval(&db);
|
||||||
|
const auto &lottery_options = lottery_data->lottery_options;
|
||||||
|
|
||||||
|
FC_ASSERT(is_lottery());
|
||||||
|
FC_ASSERT(lottery_options.is_active && (lottery_options.end_date <= db.head_block_time() || lottery_options.ending_on_soldout));
|
||||||
|
|
||||||
|
auto participants = distribute_winners_part(db);
|
||||||
|
if (participants.size() > 0)
|
||||||
|
{
|
||||||
|
distribute_benefactors_part(db);
|
||||||
|
distribute_sweeps_holders_part(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
nft_lottery_end_operation end_op;
|
||||||
|
end_op.lottery_id = get_id();
|
||||||
|
db.apply_operation(eval, end_op);
|
||||||
|
}
|
||||||
|
} // namespace chain
|
||||||
|
} // namespace graphene
|
||||||
|
|
@ -208,6 +208,17 @@ struct proposal_operation_hardfork_visitor
|
||||||
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_delete_operation not allowed yet!" );
|
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_delete_operation not allowed yet!" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()(const nft_lottery_token_purchase_operation &v) const {
|
||||||
|
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_lottery_token_purchase_operation not allowed yet!" );
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(const nft_lottery_reward_operation &v) const {
|
||||||
|
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_lottery_reward_operation not allowed yet!" );
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(const nft_lottery_end_operation &v) const {
|
||||||
|
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_lottery_end_operation not allowed yet!" );
|
||||||
|
}
|
||||||
|
|
||||||
// loop and self visit in proposals
|
// loop and self visit in proposals
|
||||||
void operator()(const proposal_create_operation &v) const {
|
void operator()(const proposal_create_operation &v) const {
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@ void nft_metadata_create_operation::validate() const
|
||||||
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
|
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
|
||||||
FC_ASSERT(is_valid_nft_token_name(name), "Invalid NFT name provided");
|
FC_ASSERT(is_valid_nft_token_name(name), "Invalid NFT name provided");
|
||||||
FC_ASSERT(is_valid_nft_token_name(symbol), "Invalid NFT symbol provided");
|
FC_ASSERT(is_valid_nft_token_name(symbol), "Invalid NFT symbol provided");
|
||||||
|
if (lottery_options)
|
||||||
|
{
|
||||||
|
(*lottery_options).validate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void nft_metadata_update_operation::validate() const
|
void nft_metadata_update_operation::validate() const
|
||||||
|
|
|
||||||
38
libraries/chain/protocol/nft_lottery.cpp
Normal file
38
libraries/chain/protocol/nft_lottery.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#include <graphene/chain/protocol/nft_ops.hpp>
|
||||||
|
#include <graphene/chain/protocol/nft_lottery.hpp>
|
||||||
|
#include <graphene/chain/protocol/operations.hpp>
|
||||||
|
|
||||||
|
namespace graphene
|
||||||
|
{
|
||||||
|
namespace chain
|
||||||
|
{
|
||||||
|
|
||||||
|
void nft_lottery_options::validate() const
|
||||||
|
{
|
||||||
|
FC_ASSERT(winning_tickets.size() <= 64);
|
||||||
|
FC_ASSERT(ticket_price.amount >= 1);
|
||||||
|
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");
|
||||||
|
FC_ASSERT(ending_on_soldout == true || end_date != time_point_sec(), "lottery may not end");
|
||||||
|
}
|
||||||
|
|
||||||
|
share_type nft_lottery_token_purchase_operation::calculate_fee(const fee_parameters_type &k) const
|
||||||
|
{
|
||||||
|
return k.fee;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nft_lottery_token_purchase_operation::validate() const
|
||||||
|
{
|
||||||
|
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
|
||||||
|
FC_ASSERT(tickets_to_buy > 0);
|
||||||
|
}
|
||||||
|
} // namespace chain
|
||||||
|
} // namespace graphene
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
#include <graphene/chain/event_object.hpp>
|
#include <graphene/chain/event_object.hpp>
|
||||||
#include <graphene/chain/tournament_object.hpp>
|
#include <graphene/chain/tournament_object.hpp>
|
||||||
#include <graphene/chain/offer_object.hpp>
|
#include <graphene/chain/offer_object.hpp>
|
||||||
|
#include <graphene/chain/nft_object.hpp>
|
||||||
|
|
||||||
#include <graphene/utilities/tempdir.hpp>
|
#include <graphene/utilities/tempdir.hpp>
|
||||||
|
|
||||||
|
|
@ -323,6 +324,14 @@ void database_fixture::verify_asset_supplies( const database& db )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const nft_metadata_object &o : db.get_index_type<nft_metadata_index>().indices())
|
||||||
|
{
|
||||||
|
if (o.lottery_data)
|
||||||
|
{
|
||||||
|
total_balances[o.lottery_data->jackpot.asset_id] += o.lottery_data->jackpot.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t sweeps_vestings = 0;
|
uint64_t sweeps_vestings = 0;
|
||||||
for( const sweeps_vesting_balance_object& svbo: db.get_index_type< sweeps_vesting_balance_index >().indices() )
|
for( const sweeps_vesting_balance_object& svbo: db.get_index_type< sweeps_vesting_balance_index >().indices() )
|
||||||
sweeps_vestings += svbo.balance;
|
sweeps_vestings += svbo.balance;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue