This repository has been archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
pallet-lottery: add generate_storage_info #10594
Merged
paritytech-processbot
merged 14 commits into
paritytech:master
from
ggwpez:lottery-bounded-len
Jan 11, 2022
Merged
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
8106370
pallet-lottery: add generate_storage_info
ggwpez ebad8a9
pallet-lottery: test call_to_indices with TooManyCalls
ggwpez b6758bb
review: move try_push above transfer
ggwpez df623a9
pallet-lottery: test stop_repeat
ggwpez f0e32fe
pallet-lottery: test do_buy_ticket as white-box
ggwpez 0fd76a9
pallet-lottery: use BoundedVec in bechmarks
ggwpez 91478f1
pallet-lottery: fix zero div panic
ggwpez 9a2c732
review: extend buy_ticket tests
ggwpez 60133a3
review: test buy_ticket AlreadyParticipating
ggwpez 39a11be
fmt
ggwpez 318da78
Merge remote-tracking branch 'parity/master' into lottery-bounded-len
ggwpez 1152f5a
review: use /// comments on private functions
ggwpez c100eec
Merge remote-tracking branch 'parity/master' into lottery-bounded-len
ggwpez 64e1b01
review: use with_bounded_capacity
ggwpez File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ | |
//! users to make those calls on your network. An example of how this could be | ||
//! used is to set validator nominations as a valid lottery call. If the lottery | ||
//! is set to repeat every month, then users would be encouraged to re-nominate | ||
//! validators every month. A user can ony purchase one ticket per valid call | ||
//! validators every month. A user can only purchase one ticket per valid call | ||
//! per lottery. | ||
//! | ||
//! This pallet can be configured to use dynamically set calls or statically set | ||
|
@@ -58,6 +58,8 @@ use codec::{Decode, Encode}; | |
use frame_support::{ | ||
dispatch::{DispatchResult, Dispatchable, GetDispatchInfo}, | ||
ensure, | ||
pallet_prelude::MaxEncodedLen, | ||
storage::bounded_vec::BoundedVec, | ||
traits::{Currency, ExistenceRequirement::KeepAlive, Get, Randomness, ReservableCurrency}, | ||
PalletId, RuntimeDebug, | ||
}; | ||
|
@@ -76,7 +78,9 @@ type BalanceOf<T> = | |
// We use this to uniquely match someone's incoming call with the calls configured for the lottery. | ||
type CallIndex = (u8, u8); | ||
|
||
#[derive(Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo)] | ||
#[derive( | ||
Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, | ||
)] | ||
pub struct LotteryConfig<BlockNumber, Balance> { | ||
/// Price per entry. | ||
price: Balance, | ||
|
@@ -120,6 +124,7 @@ pub mod pallet { | |
|
||
#[pallet::pallet] | ||
#[pallet::generate_store(pub(super) trait Store)] | ||
#[pallet::generate_storage_info] | ||
pub struct Pallet<T>(_); | ||
|
||
/// The pallet's config trait. | ||
|
@@ -209,8 +214,13 @@ pub mod pallet { | |
|
||
/// Users who have purchased a ticket. (Lottery Index, Tickets Purchased) | ||
#[pallet::storage] | ||
pub(crate) type Participants<T: Config> = | ||
StorageMap<_, Twox64Concat, T::AccountId, (u32, Vec<CallIndex>), ValueQuery>; | ||
pub(crate) type Participants<T: Config> = StorageMap< | ||
_, | ||
Twox64Concat, | ||
T::AccountId, | ||
(u32, BoundedVec<CallIndex, T::MaxCalls>), | ||
ValueQuery, | ||
>; | ||
|
||
/// Total number of tickets sold. | ||
#[pallet::storage] | ||
|
@@ -226,7 +236,8 @@ pub mod pallet { | |
/// The calls stored in this pallet to be used in an active lottery if configured | ||
/// by `Config::ValidateCall`. | ||
#[pallet::storage] | ||
pub(crate) type CallIndices<T> = StorageValue<_, Vec<CallIndex>, ValueQuery>; | ||
pub(crate) type CallIndices<T: Config> = | ||
StorageValue<_, BoundedVec<CallIndex, T::MaxCalls>, ValueQuery>; | ||
|
||
#[pallet::hooks] | ||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { | ||
|
@@ -237,10 +248,8 @@ pub mod pallet { | |
config.start.saturating_add(config.length).saturating_add(config.delay); | ||
if payout_block <= n { | ||
let (lottery_account, lottery_balance) = Self::pot(); | ||
let ticket_count = TicketsCount::<T>::get(); | ||
|
||
let winning_number = Self::choose_winner(ticket_count); | ||
let winner = Tickets::<T>::get(winning_number).unwrap_or(lottery_account); | ||
let winner = Self::choose_account().unwrap_or(lottery_account); | ||
// Not much we can do if this fails... | ||
let res = T::Currency::transfer( | ||
&Self::account_id(), | ||
|
@@ -385,7 +394,7 @@ impl<T: Config> Pallet<T> { | |
} | ||
|
||
/// Return the pot account and amount of money in the pot. | ||
// The existential deposit is not part of the pot so lottery account never gets deleted. | ||
/// The existential deposit is not part of the pot so lottery account never gets deleted. | ||
fn pot() -> (T::AccountId, BalanceOf<T>) { | ||
let account_id = Self::account_id(); | ||
let balance = | ||
|
@@ -394,17 +403,19 @@ impl<T: Config> Pallet<T> { | |
(account_id, balance) | ||
} | ||
|
||
// Converts a vector of calls into a vector of call indices. | ||
fn calls_to_indices(calls: &[<T as Config>::Call]) -> Result<Vec<CallIndex>, DispatchError> { | ||
let mut indices = Vec::with_capacity(calls.len()); | ||
/// Converts a vector of calls into a vector of call indices. | ||
fn calls_to_indices( | ||
calls: &[<T as Config>::Call], | ||
) -> Result<BoundedVec<CallIndex, T::MaxCalls>, DispatchError> { | ||
let mut indices = BoundedVec::<CallIndex, T::MaxCalls>::with_bounded_capacity(calls.len()); | ||
for c in calls.iter() { | ||
let index = Self::call_to_index(c)?; | ||
indices.push(index) | ||
indices.try_push(index).map_err(|_| Error::<T>::TooManyCalls)?; | ||
} | ||
Ok(indices) | ||
} | ||
|
||
// Convert a call to it's call index by encoding the call and taking the first two bytes. | ||
/// Convert a call to it's call index by encoding the call and taking the first two bytes. | ||
fn call_to_index(call: &<T as Config>::Call) -> Result<CallIndex, DispatchError> { | ||
let encoded_call = call.encode(); | ||
if encoded_call.len() < 2 { | ||
|
@@ -413,7 +424,7 @@ impl<T: Config> Pallet<T> { | |
return Ok((encoded_call[0], encoded_call[1])) | ||
} | ||
|
||
// Logic for buying a ticket. | ||
/// Logic for buying a ticket. | ||
fn do_buy_ticket(caller: &T::AccountId, call: &<T as Config>::Call) -> DispatchResult { | ||
// Check the call is valid lottery | ||
let config = Lottery::<T>::get().ok_or(Error::<T>::NotConfigured)?; | ||
|
@@ -433,7 +444,7 @@ impl<T: Config> Pallet<T> { | |
let index = LotteryIndex::<T>::get(); | ||
// If lottery index doesn't match, then reset participating calls and index. | ||
if *lottery_index != index { | ||
*participating_calls = Vec::new(); | ||
*participating_calls = Default::default(); | ||
*lottery_index = index; | ||
} else { | ||
// Check that user is not already participating under this call. | ||
|
@@ -442,12 +453,12 @@ impl<T: Config> Pallet<T> { | |
Error::<T>::AlreadyParticipating | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a |
||
); | ||
} | ||
participating_calls.try_push(call_index).map_err(|_| Error::<T>::TooManyCalls)?; | ||
ggwpez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Check user has enough funds and send it to the Lottery account. | ||
T::Currency::transfer(caller, &Self::account_id(), config.price, KeepAlive)?; | ||
shawntabrizi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Create a new ticket. | ||
TicketsCount::<T>::put(new_ticket_count); | ||
Tickets::<T>::insert(ticket_count, caller.clone()); | ||
participating_calls.push(call_index); | ||
shawntabrizi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Ok(()) | ||
}, | ||
)?; | ||
|
@@ -457,8 +468,22 @@ impl<T: Config> Pallet<T> { | |
Ok(()) | ||
} | ||
|
||
// Randomly choose a winner from among the total number of participants. | ||
fn choose_winner(total: u32) -> u32 { | ||
/// Randomly choose a winning ticket and return the account that purchased it. | ||
/// The more tickets an account bought, the higher are its chances of winning. | ||
/// Returns `None` if there is no winner. | ||
fn choose_account() -> Option<T::AccountId> { | ||
match Self::choose_ticket(TicketsCount::<T>::get()) { | ||
None => None, | ||
Some(ticket) => Tickets::<T>::get(ticket), | ||
} | ||
} | ||
|
||
/// Randomly choose a winning ticket from among the total number of tickets. | ||
/// Returns `None` if there are no tickets. | ||
fn choose_ticket(total: u32) -> Option<u32> { | ||
if total == 0 { | ||
return None | ||
} | ||
let mut random_number = Self::generate_random_number(0); | ||
|
||
// Best effort attempt to remove bias from modulus operator. | ||
|
@@ -470,15 +495,15 @@ impl<T: Config> Pallet<T> { | |
random_number = Self::generate_random_number(i); | ||
} | ||
|
||
random_number % total | ||
Some(random_number % total) | ||
} | ||
|
||
// Generate a random number from a given seed. | ||
// Note that there is potential bias introduced by using modulus operator. | ||
// You should call this function with different seed values until the random | ||
// number lies within `u32::MAX - u32::MAX % n`. | ||
// TODO: deal with randomness freshness | ||
// https://github.com/paritytech/substrate/issues/8311 | ||
/// Generate a random number from a given seed. | ||
/// Note that there is potential bias introduced by using modulus operator. | ||
/// You should call this function with different seed values until the random | ||
/// number lies within `u32::MAX - u32::MAX % n`. | ||
/// TODO: deal with randomness freshness | ||
/// https://github.com/paritytech/substrate/issues/8311 | ||
fn generate_random_number(seed: u32) -> u32 { | ||
let (random_seed, _) = T::Randomness::random(&(T::PalletId::get(), seed).encode()); | ||
let random_number = <u32>::decode(&mut random_seed.as_ref()) | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recently added a
bounded_vec!
macro for very basic usages. I think you can easily make it understandvec![_; _]
syntax as well and then use it here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find the macro in the code-base, can you point me to it please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh it is in #10601, maybe you can review it ? :p
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a big deal, we can proceed without it.
Would be good if either of us remembers to come clean it up after the aforementioned PR is merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok thanks.