polkadot_runtime_common/purchase/
mod.rs1use alloc::vec::Vec;
20use codec::{Decode, Encode};
21use frame_support::{
22 pallet_prelude::*,
23 traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, VestingSchedule},
24};
25use frame_system::pallet_prelude::*;
26pub use pallet::*;
27use scale_info::TypeInfo;
28use sp_core::sr25519;
29use sp_runtime::{
30 traits::{CheckedAdd, Saturating, Verify, Zero},
31 AnySignature, DispatchError, DispatchResult, Permill, RuntimeDebug,
32};
33
34type BalanceOf<T> =
35 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
36
37#[derive(
39 Encode, Decode, DecodeWithMemTracking, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo,
40)]
41pub enum AccountValidity {
42 Invalid,
44 Initiated,
46 Pending,
48 ValidLow,
50 ValidHigh,
52 Completed,
54}
55
56impl Default for AccountValidity {
57 fn default() -> Self {
58 AccountValidity::Invalid
59 }
60}
61
62impl AccountValidity {
63 fn is_valid(&self) -> bool {
64 match self {
65 Self::Invalid => false,
66 Self::Initiated => false,
67 Self::Pending => false,
68 Self::ValidLow => true,
69 Self::ValidHigh => true,
70 Self::Completed => false,
71 }
72 }
73}
74
75#[derive(Encode, Decode, Default, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)]
77pub struct AccountStatus<Balance> {
78 validity: AccountValidity,
81 free_balance: Balance,
83 locked_balance: Balance,
85 signature: Vec<u8>,
87 vat: Permill,
90}
91
92#[frame_support::pallet]
93pub mod pallet {
94 use super::*;
95
96 #[pallet::pallet]
97 #[pallet::without_storage_info]
98 pub struct Pallet<T>(_);
99
100 #[pallet::config]
101 pub trait Config: frame_system::Config {
102 #[allow(deprecated)]
104 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
105
106 type Currency: Currency<Self::AccountId>;
108
109 type VestingSchedule: VestingSchedule<
111 Self::AccountId,
112 Moment = BlockNumberFor<Self>,
113 Currency = Self::Currency,
114 >;
115
116 type ValidityOrigin: EnsureOrigin<Self::RuntimeOrigin>;
118
119 type ConfigurationOrigin: EnsureOrigin<Self::RuntimeOrigin>;
121
122 #[pallet::constant]
124 type MaxStatementLength: Get<u32>;
125
126 #[pallet::constant]
128 type UnlockedProportion: Get<Permill>;
129
130 #[pallet::constant]
132 type MaxUnlocked: Get<BalanceOf<Self>>;
133 }
134
135 #[pallet::event]
136 #[pallet::generate_deposit(pub(super) fn deposit_event)]
137 pub enum Event<T: Config> {
138 AccountCreated { who: T::AccountId },
140 ValidityUpdated { who: T::AccountId, validity: AccountValidity },
142 BalanceUpdated { who: T::AccountId, free: BalanceOf<T>, locked: BalanceOf<T> },
144 PaymentComplete { who: T::AccountId, free: BalanceOf<T>, locked: BalanceOf<T> },
146 PaymentAccountSet { who: T::AccountId },
148 StatementUpdated,
150 UnlockBlockUpdated { block_number: BlockNumberFor<T> },
152 }
153
154 #[pallet::error]
155 pub enum Error<T> {
156 InvalidAccount,
158 ExistingAccount,
160 InvalidSignature,
162 AlreadyCompleted,
164 Overflow,
166 InvalidStatement,
168 InvalidUnlockBlock,
170 VestingScheduleExists,
172 }
173
174 #[pallet::storage]
176 pub(super) type Accounts<T: Config> =
177 StorageMap<_, Blake2_128Concat, T::AccountId, AccountStatus<BalanceOf<T>>, ValueQuery>;
178
179 #[pallet::storage]
181 pub(super) type PaymentAccount<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
182
183 #[pallet::storage]
185 pub(super) type Statement<T> = StorageValue<_, Vec<u8>, ValueQuery>;
186
187 #[pallet::storage]
189 pub(super) type UnlockBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
190
191 #[pallet::hooks]
192 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
193
194 #[pallet::call]
195 impl<T: Config> Pallet<T> {
196 #[pallet::call_index(0)]
202 #[pallet::weight(Weight::from_parts(200_000_000, 0) + T::DbWeight::get().reads_writes(4, 1))]
203 pub fn create_account(
204 origin: OriginFor<T>,
205 who: T::AccountId,
206 signature: Vec<u8>,
207 ) -> DispatchResult {
208 T::ValidityOrigin::ensure_origin(origin)?;
209 ensure!(!Accounts::<T>::contains_key(&who), Error::<T>::ExistingAccount);
211 ensure!(
213 T::VestingSchedule::vesting_balance(&who).is_none(),
214 Error::<T>::VestingScheduleExists
215 );
216
217 Self::verify_signature(&who, &signature)?;
219
220 let status = AccountStatus {
222 validity: AccountValidity::Initiated,
223 signature,
224 free_balance: Zero::zero(),
225 locked_balance: Zero::zero(),
226 vat: Permill::zero(),
227 };
228 Accounts::<T>::insert(&who, status);
229 Self::deposit_event(Event::<T>::AccountCreated { who });
230 Ok(())
231 }
232
233 #[pallet::call_index(1)]
240 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))]
241 pub fn update_validity_status(
242 origin: OriginFor<T>,
243 who: T::AccountId,
244 validity: AccountValidity,
245 ) -> DispatchResult {
246 T::ValidityOrigin::ensure_origin(origin)?;
247 ensure!(Accounts::<T>::contains_key(&who), Error::<T>::InvalidAccount);
248 Accounts::<T>::try_mutate(
249 &who,
250 |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult {
251 ensure!(
252 status.validity != AccountValidity::Completed,
253 Error::<T>::AlreadyCompleted
254 );
255 status.validity = validity;
256 Ok(())
257 },
258 )?;
259 Self::deposit_event(Event::<T>::ValidityUpdated { who, validity });
260 Ok(())
261 }
262
263 #[pallet::call_index(2)]
269 #[pallet::weight(T::DbWeight::get().reads_writes(2, 1))]
270 pub fn update_balance(
271 origin: OriginFor<T>,
272 who: T::AccountId,
273 free_balance: BalanceOf<T>,
274 locked_balance: BalanceOf<T>,
275 vat: Permill,
276 ) -> DispatchResult {
277 T::ValidityOrigin::ensure_origin(origin)?;
278
279 Accounts::<T>::try_mutate(
280 &who,
281 |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult {
282 ensure!(status.validity.is_valid(), Error::<T>::InvalidAccount);
284
285 free_balance.checked_add(&locked_balance).ok_or(Error::<T>::Overflow)?;
286 status.free_balance = free_balance;
287 status.locked_balance = locked_balance;
288 status.vat = vat;
289 Ok(())
290 },
291 )?;
292 Self::deposit_event(Event::<T>::BalanceUpdated {
293 who,
294 free: free_balance,
295 locked: locked_balance,
296 });
297 Ok(())
298 }
299
300 #[pallet::call_index(3)]
307 #[pallet::weight(T::DbWeight::get().reads_writes(4, 2))]
308 pub fn payout(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
309 let payment_account = ensure_signed(origin)?;
311 let test_against = PaymentAccount::<T>::get().ok_or(DispatchError::BadOrigin)?;
312 ensure!(payment_account == test_against, DispatchError::BadOrigin);
313
314 ensure!(
316 T::VestingSchedule::vesting_balance(&who).is_none(),
317 Error::<T>::VestingScheduleExists
318 );
319
320 Accounts::<T>::try_mutate(
321 &who,
322 |status: &mut AccountStatus<BalanceOf<T>>| -> DispatchResult {
323 ensure!(status.validity.is_valid(), Error::<T>::InvalidAccount);
325
326 let total_balance = status
328 .free_balance
329 .checked_add(&status.locked_balance)
330 .ok_or(Error::<T>::Overflow)?;
331 T::Currency::transfer(
332 &payment_account,
333 &who,
334 total_balance,
335 ExistenceRequirement::AllowDeath,
336 )?;
337
338 if !status.locked_balance.is_zero() {
339 let unlock_block = UnlockBlock::<T>::get();
340 let unlocked = (T::UnlockedProportion::get() * status.locked_balance)
343 .min(T::MaxUnlocked::get());
344 let locked = status.locked_balance.saturating_sub(unlocked);
345 let _ = T::VestingSchedule::add_vesting_schedule(
349 &who,
351 locked,
353 locked,
355 unlock_block,
357 );
358 }
359
360 status.validity = AccountValidity::Completed;
363 Self::deposit_event(Event::<T>::PaymentComplete {
364 who: who.clone(),
365 free: status.free_balance,
366 locked: status.locked_balance,
367 });
368 Ok(())
369 },
370 )?;
371 Ok(())
372 }
373
374 #[pallet::call_index(4)]
380 #[pallet::weight(T::DbWeight::get().writes(1))]
381 pub fn set_payment_account(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
382 T::ConfigurationOrigin::ensure_origin(origin)?;
383 PaymentAccount::<T>::put(who.clone());
385 Self::deposit_event(Event::<T>::PaymentAccountSet { who });
386 Ok(())
387 }
388
389 #[pallet::call_index(5)]
393 #[pallet::weight(T::DbWeight::get().writes(1))]
394 pub fn set_statement(origin: OriginFor<T>, statement: Vec<u8>) -> DispatchResult {
395 T::ConfigurationOrigin::ensure_origin(origin)?;
396 ensure!(
397 (statement.len() as u32) < T::MaxStatementLength::get(),
398 Error::<T>::InvalidStatement
399 );
400 Statement::<T>::set(statement);
402 Self::deposit_event(Event::<T>::StatementUpdated);
403 Ok(())
404 }
405
406 #[pallet::call_index(6)]
410 #[pallet::weight(T::DbWeight::get().writes(1))]
411 pub fn set_unlock_block(
412 origin: OriginFor<T>,
413 unlock_block: BlockNumberFor<T>,
414 ) -> DispatchResult {
415 T::ConfigurationOrigin::ensure_origin(origin)?;
416 ensure!(
417 unlock_block > frame_system::Pallet::<T>::block_number(),
418 Error::<T>::InvalidUnlockBlock
419 );
420 UnlockBlock::<T>::set(unlock_block);
422 Self::deposit_event(Event::<T>::UnlockBlockUpdated { block_number: unlock_block });
423 Ok(())
424 }
425 }
426}
427
428impl<T: Config> Pallet<T> {
429 fn verify_signature(who: &T::AccountId, signature: &[u8]) -> Result<(), DispatchError> {
430 let signature: AnySignature = sr25519::Signature::try_from(signature)
432 .map_err(|_| Error::<T>::InvalidSignature)?
433 .into();
434
435 let account_bytes: [u8; 32] = account_to_bytes(who)?;
437 let public_key = sr25519::Public::from_raw(account_bytes);
438
439 let message = Statement::<T>::get();
440
441 match signature.verify(message.as_slice(), &public_key) {
443 true => Ok(()),
444 false => Err(Error::<T>::InvalidSignature)?,
445 }
446 }
447}
448
449fn account_to_bytes<AccountId>(account: &AccountId) -> Result<[u8; 32], DispatchError>
451where
452 AccountId: Encode,
453{
454 let account_vec = account.encode();
455 ensure!(account_vec.len() == 32, "AccountId must be 32 bytes.");
456 let mut bytes = [0u8; 32];
457 bytes.copy_from_slice(&account_vec);
458 Ok(bytes)
459}
460
461pub fn remove_pallet<T>() -> frame_support::weights::Weight
464where
465 T: frame_system::Config,
466{
467 #[allow(deprecated)]
468 use frame_support::migration::remove_storage_prefix;
469 #[allow(deprecated)]
470 remove_storage_prefix(b"Purchase", b"Accounts", b"");
471 #[allow(deprecated)]
472 remove_storage_prefix(b"Purchase", b"PaymentAccount", b"");
473 #[allow(deprecated)]
474 remove_storage_prefix(b"Purchase", b"Statement", b"");
475 #[allow(deprecated)]
476 remove_storage_prefix(b"Purchase", b"UnlockBlock", b"");
477
478 <T as frame_system::Config>::BlockWeights::get().max_block
479}
480
481#[cfg(test)]
482mod mock;
483
484#[cfg(test)]
485mod tests;