polkadot_sdk_docs_first_pallet/
lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
21
22#[docify::export]
23#[frame::pallet(dev_mode)]
24pub mod shell_pallet {
25 use frame::prelude::*;
26
27 #[pallet::config]
28 pub trait Config: frame_system::Config {}
29
30 #[pallet::pallet]
31 pub struct Pallet<T>(_);
32}
33
34#[frame::pallet(dev_mode)]
35pub mod pallet {
36 use frame::prelude::*;
37
38 #[docify::export]
39 pub type Balance = u128;
40
41 #[pallet::config]
42 pub trait Config: frame_system::Config {}
43
44 #[pallet::pallet]
45 pub struct Pallet<T>(_);
46
47 #[docify::export]
48 #[pallet::storage]
50 pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
51
52 #[docify::export]
53 #[pallet::storage]
55 pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
56
57 #[docify::export(impl_pallet)]
58 #[pallet::call]
59 impl<T: Config> Pallet<T> {
60 pub fn mint_unsafe(
62 origin: T::RuntimeOrigin,
63 dest: T::AccountId,
64 amount: Balance,
65 ) -> DispatchResult {
66 let _anyone = ensure_signed(origin)?;
68
69 Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
71 TotalIssuance::<T>::mutate(|t| *t = Some(t.unwrap_or(0) + amount));
73
74 Ok(())
75 }
76
77 pub fn transfer(
79 origin: T::RuntimeOrigin,
80 dest: T::AccountId,
81 amount: Balance,
82 ) -> DispatchResult {
83 let sender = ensure_signed(origin)?;
84
85 let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
87 if sender_balance < amount {
88 return Err("InsufficientBalance".into())
89 }
90 let remainder = sender_balance - amount;
91
92 Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
94 Balances::<T>::insert(&sender, remainder);
95
96 Ok(())
97 }
98 }
99
100 #[allow(unused)]
101 impl<T: Config> Pallet<T> {
102 #[docify::export]
103 pub fn transfer_better(
104 origin: T::RuntimeOrigin,
105 dest: T::AccountId,
106 amount: Balance,
107 ) -> DispatchResult {
108 let sender = ensure_signed(origin)?;
109
110 let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
111 ensure!(sender_balance >= amount, "InsufficientBalance");
112 let remainder = sender_balance - amount;
113
114 Ok(())
116 }
117
118 #[docify::export]
119 pub fn transfer_better_checked(
121 origin: T::RuntimeOrigin,
122 dest: T::AccountId,
123 amount: Balance,
124 ) -> DispatchResult {
125 let sender = ensure_signed(origin)?;
126
127 let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
128 let remainder = sender_balance.checked_sub(amount).ok_or("InsufficientBalance")?;
129
130 Ok(())
132 }
133 }
134
135 #[cfg(any(test, doc))]
136 pub(crate) mod tests {
137 use crate::pallet::*;
138
139 #[docify::export(testing_prelude)]
140 use frame::testing_prelude::*;
141
142 pub(crate) const ALICE: u64 = 1;
143 pub(crate) const BOB: u64 = 2;
144 pub(crate) const CHARLIE: u64 = 3;
145
146 #[docify::export]
147 mod runtime {
150 use super::*;
151 use crate::pallet as pallet_currency;
155
156 construct_runtime!(
157 pub enum Runtime {
158 System: frame_system,
160 Currency: pallet_currency,
161 }
162 );
163
164 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
165 impl frame_system::Config for Runtime {
166 type Block = MockBlock<Runtime>;
167 type AccountId = u64;
170 }
171
172 impl pallet_currency::Config for Runtime {}
174 }
175
176 pub(crate) use runtime::*;
177
178 #[allow(unused)]
179 #[docify::export]
180 fn new_test_state_basic() -> TestState {
181 let mut state = TestState::new_empty();
182 let accounts = vec![(ALICE, 100), (BOB, 100)];
183 state.execute_with(|| {
184 for (who, amount) in &accounts {
185 Balances::<Runtime>::insert(who, amount);
186 TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
187 }
188 });
189
190 state
191 }
192
193 #[docify::export]
194 pub(crate) struct StateBuilder {
195 balances: Vec<(<Runtime as frame_system::Config>::AccountId, Balance)>,
196 }
197
198 #[docify::export(default_state_builder)]
199 impl Default for StateBuilder {
200 fn default() -> Self {
201 Self { balances: vec![(ALICE, 100), (BOB, 100)] }
202 }
203 }
204
205 #[docify::export(impl_state_builder_add)]
206 impl StateBuilder {
207 fn add_balance(
208 mut self,
209 who: <Runtime as frame_system::Config>::AccountId,
210 amount: Balance,
211 ) -> Self {
212 self.balances.push((who, amount));
213 self
214 }
215 }
216
217 #[docify::export(impl_state_builder_build)]
218 impl StateBuilder {
219 pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) {
220 let mut ext = TestState::new_empty();
221 ext.execute_with(|| {
222 for (who, amount) in &self.balances {
223 Balances::<Runtime>::insert(who, amount);
224 TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
225 }
226 });
227
228 ext.execute_with(test);
229
230 ext.execute_with(|| {
232 assert_eq!(
233 Balances::<Runtime>::iter().map(|(_, x)| x).sum::<u128>(),
234 TotalIssuance::<Runtime>::get().unwrap_or_default()
235 );
236 })
237 }
238 }
239
240 #[docify::export]
241 #[test]
242 fn first_test() {
243 TestState::new_empty().execute_with(|| {
244 assert_eq!(Balances::<Runtime>::get(&ALICE), None);
246 assert_eq!(TotalIssuance::<Runtime>::get(), None);
247
248 assert_ok!(Pallet::<Runtime>::mint_unsafe(
250 RuntimeOrigin::signed(ALICE),
251 ALICE,
252 100
253 ));
254
255 assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
257 assert_eq!(TotalIssuance::<Runtime>::get(), Some(100));
258 })
259 }
260
261 #[docify::export]
262 #[test]
263 fn state_builder_works() {
264 StateBuilder::default().build_and_execute(|| {
265 assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
266 assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
267 assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
268 assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
269 });
270 }
271
272 #[docify::export]
273 #[test]
274 fn state_builder_add_balance() {
275 StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| {
276 assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(42));
277 assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
278 })
279 }
280
281 #[test]
282 #[should_panic]
283 fn state_builder_duplicate_genesis_fails() {
284 StateBuilder::default()
285 .add_balance(CHARLIE, 42)
286 .add_balance(CHARLIE, 43)
287 .build_and_execute(|| {
288 assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
289 assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
290 })
291 }
292
293 #[docify::export]
294 #[test]
295 fn mint_works() {
296 StateBuilder::default().build_and_execute(|| {
297 assert_ok!(Pallet::<Runtime>::mint_unsafe(RuntimeOrigin::signed(ALICE), BOB, 100));
299
300 assert_eq!(Balances::<Runtime>::get(&BOB), Some(200));
302 assert_eq!(TotalIssuance::<Runtime>::get(), Some(300));
303
304 assert_ok!(Pallet::<Runtime>::mint_unsafe(
306 RuntimeOrigin::signed(ALICE),
307 CHARLIE,
308 100
309 ));
310
311 assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(100));
313 assert_eq!(TotalIssuance::<Runtime>::get(), Some(400));
314 });
315 }
316
317 #[docify::export]
318 #[test]
319 fn transfer_works() {
320 StateBuilder::default().build_and_execute(|| {
321 assert_ok!(Pallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
323
324 assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
326 assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
327 assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
328
329 assert_ok!(Pallet::<Runtime>::transfer(RuntimeOrigin::signed(BOB), ALICE, 50));
331
332 assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
334 assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
335 assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
336 });
337 }
338
339 #[docify::export]
340 #[test]
341 fn transfer_from_non_existent_fails() {
342 StateBuilder::default().build_and_execute(|| {
343 assert_err!(
345 Pallet::<Runtime>::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10),
346 "NonExistentAccount"
347 );
348
349 assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
351 assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
352 assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
353 assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
354 });
355 }
356 }
357}
358
359#[frame::pallet(dev_mode)]
360pub mod pallet_v2 {
361 use super::pallet::Balance;
362 use frame::prelude::*;
363
364 #[docify::export(config_v2)]
365 #[pallet::config]
366 pub trait Config: frame_system::Config {
367 #[allow(deprecated)]
369 type RuntimeEvent: From<Event<Self>>
370 + IsType<<Self as frame_system::Config>::RuntimeEvent>
371 + TryInto<Event<Self>>;
372 }
373
374 #[pallet::pallet]
375 pub struct Pallet<T>(_);
376
377 #[pallet::storage]
378 pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
379
380 #[pallet::storage]
381 pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
382
383 #[docify::export]
384 #[pallet::error]
385 pub enum Error<T> {
386 NonExistentAccount,
388 InsufficientBalance,
390 }
391
392 #[docify::export]
393 #[pallet::event]
394 #[pallet::generate_deposit(pub(super) fn deposit_event)]
395 pub enum Event<T: Config> {
396 Transferred { from: T::AccountId, to: T::AccountId, amount: Balance },
398 }
399
400 #[pallet::call]
401 impl<T: Config> Pallet<T> {
402 #[docify::export(transfer_v2)]
403 pub fn transfer(
404 origin: T::RuntimeOrigin,
405 dest: T::AccountId,
406 amount: Balance,
407 ) -> DispatchResult {
408 let sender = ensure_signed(origin)?;
409
410 let sender_balance =
412 Balances::<T>::get(&sender).ok_or(Error::<T>::NonExistentAccount)?;
413 let remainder =
414 sender_balance.checked_sub(amount).ok_or(Error::<T>::InsufficientBalance)?;
415
416 Balances::<T>::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount));
417 Balances::<T>::insert(&sender, remainder);
418
419 Self::deposit_event(Event::<T>::Transferred { from: sender, to: dest, amount });
420
421 Ok(())
422 }
423 }
424
425 #[cfg(any(test, doc))]
426 pub mod tests {
427 use super::{super::pallet::tests::StateBuilder, *};
428 use frame::testing_prelude::*;
429 const ALICE: u64 = 1;
430 const BOB: u64 = 2;
431
432 #[docify::export]
433 pub mod runtime_v2 {
434 use super::*;
435 use crate::pallet_v2 as pallet_currency;
436
437 construct_runtime!(
438 pub enum Runtime {
439 System: frame_system,
440 Currency: pallet_currency,
441 }
442 );
443
444 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
445 impl frame_system::Config for Runtime {
446 type Block = MockBlock<Runtime>;
447 type AccountId = u64;
448 }
449
450 impl pallet_currency::Config for Runtime {
451 type RuntimeEvent = RuntimeEvent;
452 }
453 }
454
455 pub(crate) use runtime_v2::*;
456
457 #[docify::export(transfer_works_v2)]
458 #[test]
459 fn transfer_works() {
460 StateBuilder::default().build_and_execute(|| {
461 System::set_block_number(ALICE);
464
465 assert_ok!(Pallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
467
468 assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
470 assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
471 assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
472
473 assert_eq!(
475 System::read_events_for_pallet::<Event<Runtime>>(),
476 vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }]
477 );
478 });
479 }
480 }
481}