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, Debug, DispatchError,
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(Encode, Decode, Default, Eq, PartialEq, Debug, scale_info::TypeInfo, MaxEncodedLen)]
84pub struct LotteryConfig<BlockNumber, Balance> {
85 price: Balance,
87 start: BlockNumber,
89 length: BlockNumber,
91 delay: BlockNumber,
94 repeat: bool,
96}
97
98pub trait ValidateCall<T: Config> {
99 fn validate_call(call: &<T as Config>::RuntimeCall) -> bool;
100}
101
102impl<T: Config> ValidateCall<T> for () {
103 fn validate_call(_: &<T as Config>::RuntimeCall) -> bool {
104 false
105 }
106}
107
108impl<T: Config> ValidateCall<T> for Pallet<T> {
109 fn validate_call(call: &<T as Config>::RuntimeCall) -> bool {
110 let valid_calls = CallIndices::<T>::get();
111 let call_index = match Self::call_to_index(call) {
112 Ok(call_index) => call_index,
113 Err(_) => return false,
114 };
115 valid_calls.iter().any(|c| call_index == *c)
116 }
117}
118
119#[frame_support::pallet]
120pub mod pallet {
121 use super::*;
122 use frame_support::pallet_prelude::*;
123 use frame_system::pallet_prelude::*;
124
125 #[pallet::pallet]
126 pub struct Pallet<T>(_);
127
128 #[pallet::config]
130 pub trait Config: frame_system::Config {
131 #[pallet::constant]
133 type PalletId: Get<PalletId>;
134
135 type RuntimeCall: Parameter
137 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
138 + GetDispatchInfo
139 + From<frame_system::Call<Self>>;
140
141 type Currency: ReservableCurrency<Self::AccountId>;
143
144 type Randomness: Randomness<Self::Hash, BlockNumberFor<Self>>;
146
147 #[allow(deprecated)]
149 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
150
151 type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
153
154 #[pallet::constant]
156 type MaxCalls: Get<u32>;
157
158 type ValidateCall: ValidateCall<Self>;
164
165 #[pallet::constant]
169 type MaxGenerateRandom: Get<u32>;
170
171 type WeightInfo: WeightInfo;
173 }
174
175 #[pallet::event]
176 #[pallet::generate_deposit(pub(super) fn deposit_event)]
177 pub enum Event<T: Config> {
178 LotteryStarted,
180 CallsUpdated,
182 Winner { winner: T::AccountId, lottery_balance: BalanceOf<T> },
184 TicketBought { who: T::AccountId, call_index: CallIndex },
186 }
187
188 #[pallet::error]
189 pub enum Error<T> {
190 NotConfigured,
192 InProgress,
194 AlreadyEnded,
196 InvalidCall,
198 AlreadyParticipating,
200 TooManyCalls,
202 EncodingFailed,
204 }
205
206 #[pallet::storage]
207 pub(crate) type LotteryIndex<T> = StorageValue<_, u32, ValueQuery>;
208
209 #[pallet::storage]
211 pub(crate) type Lottery<T: Config> =
212 StorageValue<_, LotteryConfig<BlockNumberFor<T>, BalanceOf<T>>>;
213
214 #[pallet::storage]
216 pub(crate) type Participants<T: Config> = StorageMap<
217 _,
218 Twox64Concat,
219 T::AccountId,
220 (u32, BoundedVec<CallIndex, T::MaxCalls>),
221 ValueQuery,
222 >;
223
224 #[pallet::storage]
226 pub(crate) type TicketsCount<T> = StorageValue<_, u32, ValueQuery>;
227
228 #[pallet::storage]
233 pub(crate) type Tickets<T: Config> = StorageMap<_, Twox64Concat, u32, T::AccountId>;
234
235 #[pallet::storage]
238 pub(crate) type CallIndices<T: Config> =
239 StorageValue<_, BoundedVec<CallIndex, T::MaxCalls>, ValueQuery>;
240
241 #[pallet::hooks]
242 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
243 fn on_initialize(n: BlockNumberFor<T>) -> Weight {
244 Lottery::<T>::mutate(|mut lottery| -> Weight {
245 if let Some(config) = &mut lottery {
246 let payout_block =
247 config.start.saturating_add(config.length).saturating_add(config.delay);
248 if payout_block <= n {
249 let (lottery_account, lottery_balance) = Self::pot();
250
251 let winner = Self::choose_account().unwrap_or(lottery_account);
252 let res = T::Currency::transfer(
254 &Self::account_id(),
255 &winner,
256 lottery_balance,
257 KeepAlive,
258 );
259 debug_assert!(res.is_ok());
260
261 Self::deposit_event(Event::<T>::Winner { winner, lottery_balance });
262
263 TicketsCount::<T>::kill();
264
265 if config.repeat {
266 LotteryIndex::<T>::mutate(|index| *index = index.saturating_add(1));
268 config.start = n;
270 return T::WeightInfo::on_initialize_repeat()
271 } else {
272 *lottery = None;
274 return T::WeightInfo::on_initialize_end()
275 }
276 }
280 }
281 T::DbWeight::get().reads(1)
282 })
283 }
284 }
285
286 #[pallet::call]
287 impl<T: Config> Pallet<T> {
288 #[pallet::call_index(0)]
300 #[pallet::weight(
301 T::WeightInfo::buy_ticket()
302 .saturating_add(call.get_dispatch_info().call_weight)
303 )]
304 pub fn buy_ticket(
305 origin: OriginFor<T>,
306 call: Box<<T as Config>::RuntimeCall>,
307 ) -> DispatchResult {
308 let caller = ensure_signed(origin.clone())?;
309 call.clone().dispatch(origin).map_err(|e| e.error)?;
310
311 let _ = Self::do_buy_ticket(&caller, &call);
312 Ok(())
313 }
314
315 #[pallet::call_index(1)]
322 #[pallet::weight(T::WeightInfo::set_calls(calls.len() as u32))]
323 pub fn set_calls(
324 origin: OriginFor<T>,
325 calls: Vec<<T as Config>::RuntimeCall>,
326 ) -> DispatchResult {
327 T::ManagerOrigin::ensure_origin(origin)?;
328 ensure!(calls.len() <= T::MaxCalls::get() as usize, Error::<T>::TooManyCalls);
329 if calls.is_empty() {
330 CallIndices::<T>::kill();
331 } else {
332 let indices = Self::calls_to_indices(&calls)?;
333 CallIndices::<T>::put(indices);
334 }
335 Self::deposit_event(Event::<T>::CallsUpdated);
336 Ok(())
337 }
338
339 #[pallet::call_index(2)]
350 #[pallet::weight(T::WeightInfo::start_lottery())]
351 pub fn start_lottery(
352 origin: OriginFor<T>,
353 price: BalanceOf<T>,
354 length: BlockNumberFor<T>,
355 delay: BlockNumberFor<T>,
356 repeat: bool,
357 ) -> DispatchResult {
358 T::ManagerOrigin::ensure_origin(origin)?;
359 Lottery::<T>::try_mutate(|lottery| -> DispatchResult {
360 ensure!(lottery.is_none(), Error::<T>::InProgress);
361 let index = LotteryIndex::<T>::get();
362 let new_index = index.checked_add(1).ok_or(ArithmeticError::Overflow)?;
363 let start = frame_system::Pallet::<T>::block_number();
364 *lottery = Some(LotteryConfig { price, start, length, delay, repeat });
366 LotteryIndex::<T>::put(new_index);
367 Ok(())
368 })?;
369 let lottery_account = Self::account_id();
371 if T::Currency::total_balance(&lottery_account).is_zero() {
372 let _ =
373 T::Currency::deposit_creating(&lottery_account, T::Currency::minimum_balance());
374 }
375 Self::deposit_event(Event::<T>::LotteryStarted);
376 Ok(())
377 }
378
379 #[pallet::call_index(3)]
384 #[pallet::weight(T::WeightInfo::stop_repeat())]
385 pub fn stop_repeat(origin: OriginFor<T>) -> DispatchResult {
386 T::ManagerOrigin::ensure_origin(origin)?;
387 Lottery::<T>::mutate(|mut lottery| {
388 if let Some(config) = &mut lottery {
389 config.repeat = false
390 }
391 });
392 Ok(())
393 }
394 }
395}
396
397impl<T: Config> Pallet<T> {
398 pub fn account_id() -> T::AccountId {
403 T::PalletId::get().into_account_truncating()
404 }
405
406 fn pot() -> (T::AccountId, BalanceOf<T>) {
409 let account_id = Self::account_id();
410 let balance =
411 T::Currency::free_balance(&account_id).saturating_sub(T::Currency::minimum_balance());
412
413 (account_id, balance)
414 }
415
416 fn calls_to_indices(
418 calls: &[<T as Config>::RuntimeCall],
419 ) -> Result<BoundedVec<CallIndex, T::MaxCalls>, DispatchError> {
420 let mut indices = BoundedVec::<CallIndex, T::MaxCalls>::with_bounded_capacity(calls.len());
421 for c in calls.iter() {
422 let index = Self::call_to_index(c)?;
423 indices.try_push(index).map_err(|_| Error::<T>::TooManyCalls)?;
424 }
425 Ok(indices)
426 }
427
428 fn call_to_index(call: &<T as Config>::RuntimeCall) -> Result<CallIndex, DispatchError> {
430 let encoded_call = call.encode();
431 if encoded_call.len() < 2 {
432 return Err(Error::<T>::EncodingFailed.into())
433 }
434 Ok((encoded_call[0], encoded_call[1]))
435 }
436
437 fn do_buy_ticket(caller: &T::AccountId, call: &<T as Config>::RuntimeCall) -> DispatchResult {
439 let config = Lottery::<T>::get().ok_or(Error::<T>::NotConfigured)?;
441 let block_number = frame_system::Pallet::<T>::block_number();
442 ensure!(
443 block_number < config.start.saturating_add(config.length),
444 Error::<T>::AlreadyEnded
445 );
446 ensure!(T::ValidateCall::validate_call(call), Error::<T>::InvalidCall);
447 let call_index = Self::call_to_index(call)?;
448 let ticket_count = TicketsCount::<T>::get();
449 let new_ticket_count = ticket_count.checked_add(1).ok_or(ArithmeticError::Overflow)?;
450 Participants::<T>::try_mutate(
452 &caller,
453 |(lottery_index, participating_calls)| -> DispatchResult {
454 let index = LotteryIndex::<T>::get();
455 if *lottery_index != index {
457 *participating_calls = Default::default();
458 *lottery_index = index;
459 } else {
460 ensure!(
462 !participating_calls.iter().any(|c| call_index == *c),
463 Error::<T>::AlreadyParticipating
464 );
465 }
466 participating_calls.try_push(call_index).map_err(|_| Error::<T>::TooManyCalls)?;
467 T::Currency::transfer(caller, &Self::account_id(), config.price, KeepAlive)?;
469 TicketsCount::<T>::put(new_ticket_count);
471 Tickets::<T>::insert(ticket_count, caller.clone());
472 Ok(())
473 },
474 )?;
475
476 Self::deposit_event(Event::<T>::TicketBought { who: caller.clone(), call_index });
477
478 Ok(())
479 }
480
481 fn choose_account() -> Option<T::AccountId> {
485 match Self::choose_ticket(TicketsCount::<T>::get()) {
486 None => None,
487 Some(ticket) => Tickets::<T>::get(ticket),
488 }
489 }
490
491 fn choose_ticket(total: u32) -> Option<u32> {
494 if total == 0 {
495 return None
496 }
497 let mut random_number = Self::generate_random_number(0);
498
499 for i in 1..T::MaxGenerateRandom::get() {
501 if random_number < u32::MAX - u32::MAX % total {
502 break
503 }
504
505 random_number = Self::generate_random_number(i);
506 }
507
508 Some(random_number % total)
509 }
510
511 fn generate_random_number(seed: u32) -> u32 {
518 let (random_seed, _) = T::Randomness::random(&(T::PalletId::get(), seed).encode());
519 let random_number = <u32>::decode(&mut random_seed.as_ref())
520 .expect("secure hashes should always be bigger than u32; qed");
521 random_number
522 }
523}