1#![cfg_attr(not(feature = "std"), no_std)]
49
50mod benchmarking;
51#[cfg(test)]
52mod mock;
53#[cfg(test)]
54mod tests;
55pub mod weights;
56
57extern crate alloc;
58
59use alloc::{boxed::Box, vec::Vec};
60use codec::{Decode, Encode};
61use frame_support::{
62 dispatch::{DispatchResult, GetDispatchInfo},
63 ensure,
64 pallet_prelude::MaxEncodedLen,
65 storage::bounded_vec::BoundedVec,
66 traits::{Currency, ExistenceRequirement::KeepAlive, Get, Randomness, ReservableCurrency},
67 PalletId,
68};
69pub use pallet::*;
70use sp_runtime::{
71 traits::{AccountIdConversion, Dispatchable, Saturating, Zero},
72 ArithmeticError, DispatchError, RuntimeDebug,
73};
74pub use weights::WeightInfo;
75
76type BalanceOf<T> =
77 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
78
79type CallIndex = (u8, u8);
82
83#[derive(
84 Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
85)]
86pub struct LotteryConfig<BlockNumber, Balance> {
87 price: Balance,
89 start: BlockNumber,
91 length: BlockNumber,
93 delay: BlockNumber,
96 repeat: bool,
98}
99
100pub trait ValidateCall<T: Config> {
101 fn validate_call(call: &<T as Config>::RuntimeCall) -> bool;
102}
103
104impl<T: Config> ValidateCall<T> for () {
105 fn validate_call(_: &<T as Config>::RuntimeCall) -> bool {
106 false
107 }
108}
109
110impl<T: Config> ValidateCall<T> for Pallet<T> {
111 fn validate_call(call: &<T as Config>::RuntimeCall) -> bool {
112 let valid_calls = CallIndices::<T>::get();
113 let call_index = match Self::call_to_index(call) {
114 Ok(call_index) => call_index,
115 Err(_) => return false,
116 };
117 valid_calls.iter().any(|c| call_index == *c)
118 }
119}
120
121#[frame_support::pallet]
122pub mod pallet {
123 use super::*;
124 use frame_support::pallet_prelude::*;
125 use frame_system::pallet_prelude::*;
126
127 #[pallet::pallet]
128 pub struct Pallet<T>(_);
129
130 #[pallet::config]
132 pub trait Config: frame_system::Config {
133 #[pallet::constant]
135 type PalletId: Get<PalletId>;
136
137 type RuntimeCall: Parameter
139 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
140 + GetDispatchInfo
141 + From<frame_system::Call<Self>>;
142
143 type Currency: ReservableCurrency<Self::AccountId>;
145
146 type Randomness: Randomness<Self::Hash, BlockNumberFor<Self>>;
148
149 #[allow(deprecated)]
151 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
152
153 type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
155
156 #[pallet::constant]
158 type MaxCalls: Get<u32>;
159
160 type ValidateCall: ValidateCall<Self>;
166
167 #[pallet::constant]
171 type MaxGenerateRandom: Get<u32>;
172
173 type WeightInfo: WeightInfo;
175 }
176
177 #[pallet::event]
178 #[pallet::generate_deposit(pub(super) fn deposit_event)]
179 pub enum Event<T: Config> {
180 LotteryStarted,
182 CallsUpdated,
184 Winner { winner: T::AccountId, lottery_balance: BalanceOf<T> },
186 TicketBought { who: T::AccountId, call_index: CallIndex },
188 }
189
190 #[pallet::error]
191 pub enum Error<T> {
192 NotConfigured,
194 InProgress,
196 AlreadyEnded,
198 InvalidCall,
200 AlreadyParticipating,
202 TooManyCalls,
204 EncodingFailed,
206 }
207
208 #[pallet::storage]
209 pub(crate) type LotteryIndex<T> = StorageValue<_, u32, ValueQuery>;
210
211 #[pallet::storage]
213 pub(crate) type Lottery<T: Config> =
214 StorageValue<_, LotteryConfig<BlockNumberFor<T>, BalanceOf<T>>>;
215
216 #[pallet::storage]
218 pub(crate) type Participants<T: Config> = StorageMap<
219 _,
220 Twox64Concat,
221 T::AccountId,
222 (u32, BoundedVec<CallIndex, T::MaxCalls>),
223 ValueQuery,
224 >;
225
226 #[pallet::storage]
228 pub(crate) type TicketsCount<T> = StorageValue<_, u32, ValueQuery>;
229
230 #[pallet::storage]
235 pub(crate) type Tickets<T: Config> = StorageMap<_, Twox64Concat, u32, T::AccountId>;
236
237 #[pallet::storage]
240 pub(crate) type CallIndices<T: Config> =
241 StorageValue<_, BoundedVec<CallIndex, T::MaxCalls>, ValueQuery>;
242
243 #[pallet::hooks]
244 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
245 fn on_initialize(n: BlockNumberFor<T>) -> Weight {
246 Lottery::<T>::mutate(|mut lottery| -> Weight {
247 if let Some(config) = &mut lottery {
248 let payout_block =
249 config.start.saturating_add(config.length).saturating_add(config.delay);
250 if payout_block <= n {
251 let (lottery_account, lottery_balance) = Self::pot();
252
253 let winner = Self::choose_account().unwrap_or(lottery_account);
254 let res = T::Currency::transfer(
256 &Self::account_id(),
257 &winner,
258 lottery_balance,
259 KeepAlive,
260 );
261 debug_assert!(res.is_ok());
262
263 Self::deposit_event(Event::<T>::Winner { winner, lottery_balance });
264
265 TicketsCount::<T>::kill();
266
267 if config.repeat {
268 LotteryIndex::<T>::mutate(|index| *index = index.saturating_add(1));
270 config.start = n;
272 return T::WeightInfo::on_initialize_repeat()
273 } else {
274 *lottery = None;
276 return T::WeightInfo::on_initialize_end()
277 }
278 }
282 }
283 T::DbWeight::get().reads(1)
284 })
285 }
286 }
287
288 #[pallet::call]
289 impl<T: Config> Pallet<T> {
290 #[pallet::call_index(0)]
302 #[pallet::weight(
303 T::WeightInfo::buy_ticket()
304 .saturating_add(call.get_dispatch_info().call_weight)
305 )]
306 pub fn buy_ticket(
307 origin: OriginFor<T>,
308 call: Box<<T as Config>::RuntimeCall>,
309 ) -> DispatchResult {
310 let caller = ensure_signed(origin.clone())?;
311 call.clone().dispatch(origin).map_err(|e| e.error)?;
312
313 let _ = Self::do_buy_ticket(&caller, &call);
314 Ok(())
315 }
316
317 #[pallet::call_index(1)]
324 #[pallet::weight(T::WeightInfo::set_calls(calls.len() as u32))]
325 pub fn set_calls(
326 origin: OriginFor<T>,
327 calls: Vec<<T as Config>::RuntimeCall>,
328 ) -> DispatchResult {
329 T::ManagerOrigin::ensure_origin(origin)?;
330 ensure!(calls.len() <= T::MaxCalls::get() as usize, Error::<T>::TooManyCalls);
331 if calls.is_empty() {
332 CallIndices::<T>::kill();
333 } else {
334 let indices = Self::calls_to_indices(&calls)?;
335 CallIndices::<T>::put(indices);
336 }
337 Self::deposit_event(Event::<T>::CallsUpdated);
338 Ok(())
339 }
340
341 #[pallet::call_index(2)]
352 #[pallet::weight(T::WeightInfo::start_lottery())]
353 pub fn start_lottery(
354 origin: OriginFor<T>,
355 price: BalanceOf<T>,
356 length: BlockNumberFor<T>,
357 delay: BlockNumberFor<T>,
358 repeat: bool,
359 ) -> DispatchResult {
360 T::ManagerOrigin::ensure_origin(origin)?;
361 Lottery::<T>::try_mutate(|lottery| -> DispatchResult {
362 ensure!(lottery.is_none(), Error::<T>::InProgress);
363 let index = LotteryIndex::<T>::get();
364 let new_index = index.checked_add(1).ok_or(ArithmeticError::Overflow)?;
365 let start = frame_system::Pallet::<T>::block_number();
366 *lottery = Some(LotteryConfig { price, start, length, delay, repeat });
368 LotteryIndex::<T>::put(new_index);
369 Ok(())
370 })?;
371 let lottery_account = Self::account_id();
373 if T::Currency::total_balance(&lottery_account).is_zero() {
374 let _ =
375 T::Currency::deposit_creating(&lottery_account, T::Currency::minimum_balance());
376 }
377 Self::deposit_event(Event::<T>::LotteryStarted);
378 Ok(())
379 }
380
381 #[pallet::call_index(3)]
386 #[pallet::weight(T::WeightInfo::stop_repeat())]
387 pub fn stop_repeat(origin: OriginFor<T>) -> DispatchResult {
388 T::ManagerOrigin::ensure_origin(origin)?;
389 Lottery::<T>::mutate(|mut lottery| {
390 if let Some(config) = &mut lottery {
391 config.repeat = false
392 }
393 });
394 Ok(())
395 }
396 }
397}
398
399impl<T: Config> Pallet<T> {
400 pub fn account_id() -> T::AccountId {
405 T::PalletId::get().into_account_truncating()
406 }
407
408 fn pot() -> (T::AccountId, BalanceOf<T>) {
411 let account_id = Self::account_id();
412 let balance =
413 T::Currency::free_balance(&account_id).saturating_sub(T::Currency::minimum_balance());
414
415 (account_id, balance)
416 }
417
418 fn calls_to_indices(
420 calls: &[<T as Config>::RuntimeCall],
421 ) -> Result<BoundedVec<CallIndex, T::MaxCalls>, DispatchError> {
422 let mut indices = BoundedVec::<CallIndex, T::MaxCalls>::with_bounded_capacity(calls.len());
423 for c in calls.iter() {
424 let index = Self::call_to_index(c)?;
425 indices.try_push(index).map_err(|_| Error::<T>::TooManyCalls)?;
426 }
427 Ok(indices)
428 }
429
430 fn call_to_index(call: &<T as Config>::RuntimeCall) -> Result<CallIndex, DispatchError> {
432 let encoded_call = call.encode();
433 if encoded_call.len() < 2 {
434 return Err(Error::<T>::EncodingFailed.into())
435 }
436 Ok((encoded_call[0], encoded_call[1]))
437 }
438
439 fn do_buy_ticket(caller: &T::AccountId, call: &<T as Config>::RuntimeCall) -> DispatchResult {
441 let config = Lottery::<T>::get().ok_or(Error::<T>::NotConfigured)?;
443 let block_number = frame_system::Pallet::<T>::block_number();
444 ensure!(
445 block_number < config.start.saturating_add(config.length),
446 Error::<T>::AlreadyEnded
447 );
448 ensure!(T::ValidateCall::validate_call(call), Error::<T>::InvalidCall);
449 let call_index = Self::call_to_index(call)?;
450 let ticket_count = TicketsCount::<T>::get();
451 let new_ticket_count = ticket_count.checked_add(1).ok_or(ArithmeticError::Overflow)?;
452 Participants::<T>::try_mutate(
454 &caller,
455 |(lottery_index, participating_calls)| -> DispatchResult {
456 let index = LotteryIndex::<T>::get();
457 if *lottery_index != index {
459 *participating_calls = Default::default();
460 *lottery_index = index;
461 } else {
462 ensure!(
464 !participating_calls.iter().any(|c| call_index == *c),
465 Error::<T>::AlreadyParticipating
466 );
467 }
468 participating_calls.try_push(call_index).map_err(|_| Error::<T>::TooManyCalls)?;
469 T::Currency::transfer(caller, &Self::account_id(), config.price, KeepAlive)?;
471 TicketsCount::<T>::put(new_ticket_count);
473 Tickets::<T>::insert(ticket_count, caller.clone());
474 Ok(())
475 },
476 )?;
477
478 Self::deposit_event(Event::<T>::TicketBought { who: caller.clone(), call_index });
479
480 Ok(())
481 }
482
483 fn choose_account() -> Option<T::AccountId> {
487 match Self::choose_ticket(TicketsCount::<T>::get()) {
488 None => None,
489 Some(ticket) => Tickets::<T>::get(ticket),
490 }
491 }
492
493 fn choose_ticket(total: u32) -> Option<u32> {
496 if total == 0 {
497 return None
498 }
499 let mut random_number = Self::generate_random_number(0);
500
501 for i in 1..T::MaxGenerateRandom::get() {
503 if random_number < u32::MAX - u32::MAX % total {
504 break
505 }
506
507 random_number = Self::generate_random_number(i);
508 }
509
510 Some(random_number % total)
511 }
512
513 fn generate_random_number(seed: u32) -> u32 {
520 let (random_seed, _) = T::Randomness::random(&(T::PalletId::get(), seed).encode());
521 let random_number = <u32>::decode(&mut random_seed.as_ref())
522 .expect("secure hashes should always be bigger than u32; qed");
523 random_number
524 }
525}