nft_lottery1 - working code with tests

This commit is contained in:
sierra19XX 2020-09-24 03:13:34 +10:00
parent 56a5e676f0
commit be20d28e30
21 changed files with 655 additions and 11 deletions

View file

@ -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}

View file

@ -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 );

View file

@ -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

View file

@ -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;

View file

@ -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()

View file

@ -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 {

View file

@ -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 )

View file

@ -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;

View file

@ -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

View file

@ -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)

View file

@ -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))

View file

@ -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) )

View file

@ -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

View file

@ -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:

View file

@ -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) ) }

View 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

View 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

View file

@ -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 {

View file

@ -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

View 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

View file

@ -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;