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/account_role.cpp
|
||||
account_role_evaluator.cpp
|
||||
protocol/nft_lottery.cpp
|
||||
nft_lottery_evaluator.cpp
|
||||
nft_lottery_object.cpp
|
||||
|
||||
${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 >() );
|
||||
}
|
||||
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 );
|
||||
|
||||
|
|
|
|||
|
|
@ -710,6 +710,7 @@ void database::_apply_block( const signed_block& next_block )
|
|||
perform_chain_maintenance(next_block, global_props);
|
||||
|
||||
check_ending_lotteries();
|
||||
check_ending_nft_lotteries();
|
||||
|
||||
create_block_summary(next_block);
|
||||
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();
|
||||
}
|
||||
|
||||
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 );
|
||||
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);
|
||||
|
||||
std::vector<uint32_t> result;
|
||||
result.reserve(64);
|
||||
|
||||
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 ) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
auto seeds = get_seeds(instance_value, count_winners);
|
||||
|
||||
for (auto current_seed = seeds.begin(); current_seed != seeds.end(); ++current_seed) {
|
||||
uint8_t winner_num = *current_seed % count_members;
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@
|
|||
#include <graphene/chain/offer_evaluator.hpp>
|
||||
#include <graphene/chain/nft_evaluator.hpp>
|
||||
#include <graphene/chain/account_role_evaluator.hpp>
|
||||
#include <graphene/chain/nft_lottery_evaluator.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_update_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()
|
||||
|
|
|
|||
|
|
@ -306,6 +306,24 @@ void database::check_ending_lotteries()
|
|||
} 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 )
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -353,6 +353,13 @@ struct get_impacted_account_visitor
|
|||
void operator()( const account_role_delete_operation& op ){
|
||||
_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 )
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ namespace graphene { namespace chain {
|
|||
|
||||
void check_lottery_end_by_participants( asset_id_type asset_id );
|
||||
void check_ending_lotteries();
|
||||
void check_ending_nft_lotteries();
|
||||
|
||||
//////////////////// db_getter.cpp ////////////////////
|
||||
|
||||
|
|
@ -278,8 +279,8 @@ namespace graphene { namespace chain {
|
|||
const node_property_object& get_node_properties()const;
|
||||
const fee_schedule& current_fee_schedule()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;
|
||||
std::vector<uint32_t> get_seeds( asset_id_type for_asset, 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( uint32_t instance_value, uint8_t count_winners )const;
|
||||
uint64_t get_random_bits( uint64_t bound );
|
||||
const witness_schedule_object& get_witness_schedule_object()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 {
|
||||
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>
|
||||
{
|
||||
public:
|
||||
|
|
@ -21,6 +31,21 @@ namespace graphene { namespace chain {
|
|||
bool is_transferable = false;
|
||||
bool is_sellable = true;
|
||||
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>
|
||||
|
|
@ -36,8 +61,23 @@ namespace graphene { namespace chain {
|
|||
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_symbol;
|
||||
struct active_nft_lotteries;
|
||||
struct by_nft_lottery;
|
||||
struct by_nft_lottery_owner;
|
||||
using nft_metadata_multi_index_type = multi_index_container<
|
||||
nft_metadata_object,
|
||||
indexed_by<
|
||||
|
|
@ -49,6 +89,34 @@ namespace graphene { namespace chain {
|
|||
>,
|
||||
ordered_unique< tag<by_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
|
||||
|
||||
FC_REFLECT( graphene::chain::nft_lottery_data, (lottery_options)(jackpot)(sweeps_tickets_sold) )
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object),
|
||||
(owner)
|
||||
(name)
|
||||
|
|
@ -97,7 +167,9 @@ FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object)
|
|||
(revenue_split)
|
||||
(is_transferable)
|
||||
(is_sellable)
|
||||
(account_role) )
|
||||
(account_role)
|
||||
(max_supply)
|
||||
(lottery_data) )
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object),
|
||||
(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 {
|
||||
|
||||
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 fee_parameters_type
|
||||
|
|
@ -23,6 +44,10 @@ namespace graphene { namespace chain {
|
|||
bool is_sellable = true;
|
||||
// Accounts 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;
|
||||
|
||||
account_id_type fee_payer()const { return owner; }
|
||||
|
|
@ -133,6 +158,9 @@ namespace graphene { namespace 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_update_operation::fee_parameters_type, (fee) )
|
||||
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_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_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) )
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@
|
|||
#include <graphene/chain/protocol/offer.hpp>
|
||||
#include <graphene/chain/protocol/nft_ops.hpp>
|
||||
#include <graphene/chain/protocol/account_role.hpp>
|
||||
#include <graphene/chain/protocol/nft_lottery.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
|
|
@ -159,7 +160,10 @@ namespace graphene { namespace chain {
|
|||
nft_set_approval_for_all_operation,
|
||||
account_role_create_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;
|
||||
|
||||
/// @} // operations group
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ namespace graphene
|
|||
case operation::tag<account_role_create_operation>::value:
|
||||
case operation::tag<account_role_update_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!");
|
||||
break;
|
||||
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());
|
||||
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();
|
||||
} 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_sellable = op.is_sellable;
|
||||
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;
|
||||
} 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->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();
|
||||
} 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!" );
|
||||
}
|
||||
|
||||
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
|
||||
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(is_valid_nft_token_name(name), "Invalid NFT name 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
|
||||
|
|
|
|||
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/tournament_object.hpp>
|
||||
#include <graphene/chain/offer_object.hpp>
|
||||
#include <graphene/chain/nft_object.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;
|
||||
for( const sweeps_vesting_balance_object& svbo: db.get_index_type< sweeps_vesting_balance_index >().indices() )
|
||||
sweeps_vestings += svbo.balance;
|
||||
|
|
|
|||
Loading…
Reference in a new issue