Skip to main content

pop_chains/
new_pallet.rs

1// SPDX-License-Identifier: GPL-3.0
2
3use std::{
4	fs::{File, create_dir, create_dir_all},
5	path::{Path, PathBuf},
6};
7
8pub mod new_pallet_options;
9
10use crate::{
11	TemplatePalletConfigCommonTypes, TemplatePalletStorageTypes,
12	errors::Error,
13	generator::pallet::{
14		PalletAdvancedBenchmarking, PalletAdvancedLib, PalletAdvancedMock, PalletAdvancedTests,
15		PalletCargoToml, PalletConfigPreludes, PalletItem, PalletLogic, PalletOrigin,
16		PalletSimpleBenchmarking, PalletSimpleLib, PalletSimpleMock, PalletSimpleTests,
17		PalletTestsUtils, PalletTryState, PalletTypes, PalletWeights,
18	},
19	utils::helpers::sanitize,
20};
21
22/// Metadata for the Template Pallet.
23#[derive(Debug)]
24pub struct TemplatePalletConfig {
25	/// The authors of the pallet
26	pub authors: String,
27	/// The pallet description
28	pub description: String,
29	/// Indicate if the pallet is contained in a workspace
30	pub pallet_in_workspace: bool,
31	/// Indicate if the user wanna use the advanced mode
32	pub pallet_advanced_mode: bool,
33	/// Indicate if the template must include a default config for the pallet.
34	pub pallet_default_config: bool,
35	/// Types defined in `TemplatePalletConfigCommonTypes` that should be included in the template.
36	pub pallet_common_types: Vec<TemplatePalletConfigCommonTypes>,
37	/// Types defined in `TemplatePalletStorageTypes` that should be included in the template.
38	pub pallet_storage: Vec<TemplatePalletStorageTypes>,
39	/// Indicate if the template should include a genesis config
40	pub pallet_genesis: bool,
41	/// Indicate if the template should include a custom origin
42	pub pallet_custom_origin: bool,
43}
44/// Create a new pallet from a template.
45///
46/// # Arguments
47///
48/// * `path` - location where the pallet will be created.
49/// * `config` - customization values to include in the new pallet.
50pub fn create_pallet_template(path: PathBuf, config: TemplatePalletConfig) -> Result<(), Error> {
51	sanitize(&path)?;
52	generate_pallet_structure(&path, &config)?;
53	render_pallet(config, &path)?;
54	Ok(())
55}
56
57/// Generate a pallet folder and file structure
58fn generate_pallet_structure(path: &PathBuf, config: &TemplatePalletConfig) -> Result<(), Error> {
59	create_dir_all(path)?;
60	let (src, pallet_logic, tests) =
61		(path.join("src"), path.join("src/pallet_logic"), path.join("src/tests"));
62	create_dir(&src)?;
63	File::create(format!("{}/Cargo.toml", path.display()))?;
64	File::create(format!("{}/lib.rs", src.display()))?;
65	File::create(format!("{}/benchmarking.rs", src.display()))?;
66	File::create(format!("{}/tests.rs", src.display()))?;
67	File::create(format!("{}/mock.rs", src.display()))?;
68	if config.pallet_advanced_mode {
69		create_dir(&pallet_logic)?;
70		create_dir(&tests)?;
71		File::create(format!("{}/pallet_logic.rs", src.display()))?;
72		File::create(format!("{}/try_state.rs", pallet_logic.display()))?;
73		File::create(format!("{}/types.rs", src.display()))?;
74		File::create(format!("{}/utils.rs", tests.display()))?;
75		if config.pallet_default_config {
76			File::create(format!("{}/config_preludes.rs", src.display()))?;
77		}
78		if config.pallet_custom_origin {
79			File::create(format!("{}/origin.rs", pallet_logic.display()))?;
80		}
81	} else {
82		File::create(format!("{}/weights.rs", src.display()))?;
83	}
84	Ok(())
85}
86
87fn render_pallet(config: TemplatePalletConfig, pallet_path: &Path) -> Result<(), Error> {
88	// Extract the pallet name from the path.
89	let pallet_name = pallet_path
90		.file_name()
91		.and_then(|name| name.to_str())
92		.ok_or(Error::PathError)?
93		.replace('-', "_");
94	let mut pallet: Vec<Box<dyn PalletItem>> = vec![Box::new(PalletCargoToml {
95		name: pallet_name.clone(),
96		authors: config.authors,
97		description: config.description,
98		pallet_in_workspace: config.pallet_in_workspace,
99		pallet_common_types: config.pallet_common_types.clone(),
100	})];
101	let mut pallet_contents: Vec<Box<dyn PalletItem>>;
102	if config.pallet_advanced_mode {
103		pallet_contents = vec![
104			Box::new(PalletAdvancedLib {
105				name: pallet_name.clone(),
106				pallet_default_config: config.pallet_default_config,
107				pallet_common_types: config.pallet_common_types.clone(),
108				pallet_storage: config.pallet_storage.clone(),
109				pallet_genesis: config.pallet_genesis,
110				pallet_custom_origin: config.pallet_custom_origin,
111			}),
112			Box::new(PalletAdvancedTests {}),
113			Box::new(PalletAdvancedMock {
114				name: pallet_name.clone(),
115				pallet_default_config: config.pallet_default_config,
116				pallet_common_types: config.pallet_common_types.clone(),
117				pallet_custom_origin: config.pallet_custom_origin,
118			}),
119			Box::new(PalletAdvancedBenchmarking {}),
120			Box::new(PalletLogic { pallet_custom_origin: config.pallet_custom_origin }),
121			Box::new(PalletTryState {}),
122			Box::new(PalletTestsUtils { name: pallet_name.clone() }),
123			Box::new(PalletTypes {
124				pallet_common_types: config.pallet_common_types.clone(),
125				pallet_storage: config.pallet_storage,
126				pallet_custom_origin: config.pallet_custom_origin,
127			}),
128		];
129		if config.pallet_default_config {
130			pallet_contents.push(Box::new(PalletConfigPreludes {
131				pallet_common_types: config.pallet_common_types,
132				pallet_custom_origin: config.pallet_custom_origin,
133			}));
134		}
135
136		if config.pallet_custom_origin {
137			pallet_contents.push(Box::new(PalletOrigin {}));
138		}
139	} else {
140		pallet_contents = vec![
141			Box::new(PalletSimpleLib { name: pallet_name.clone() }),
142			Box::new(PalletSimpleTests { name: pallet_name.clone() }),
143			Box::new(PalletSimpleMock { name: pallet_name.clone() }),
144			Box::new(PalletSimpleBenchmarking {}),
145			Box::new(PalletWeights {}),
146		];
147	}
148
149	pallet.extend(pallet_contents);
150
151	for item in pallet {
152		item.execute(pallet_path)?;
153	}
154
155	Ok(())
156}
157
158#[cfg(test)]
159mod tests {
160	use super::*;
161	use std::fs::read_to_string;
162
163	#[test]
164	fn test_pallet_create_advanced_template() -> Result<(), Error> {
165		let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
166		let pallet_name = "MyPallet";
167		let pallet_path = temp_dir.path().join(pallet_name);
168		let config = TemplatePalletConfig {
169			authors: "Alice".to_string(),
170			description: "A sample pallet".to_string(),
171			pallet_in_workspace: false,
172			pallet_advanced_mode: true,
173			pallet_default_config: true,
174			pallet_common_types: Vec::new(),
175			pallet_storage: Vec::new(),
176			pallet_genesis: false,
177			pallet_custom_origin: true,
178		};
179
180		// Call the function being tested
181		create_pallet_template(pallet_path.clone(), config)?;
182
183		// Assert that the pallet structure is generated
184		assert!(pallet_path.exists(), "Pallet folder should be created");
185		assert!(pallet_path.join("src").exists(), "src folder should be created");
186		assert!(
187			pallet_path.join("src").join("pallet_logic").exists(),
188			"pallet_logic folder should be created"
189		);
190		assert!(
191			pallet_path.join("src").join("pallet_logic").join("try_state.rs").exists(),
192			"try_state.rs should be created"
193		);
194		assert!(
195			pallet_path.join("src").join("pallet_logic").join("origin.rs").exists(),
196			"origin.rs should be created"
197		);
198		assert!(pallet_path.join("src").join("tests").exists(), "tests folder should be created");
199		assert!(
200			pallet_path.join("src").join("tests").join("utils.rs").exists(),
201			"utils.rs folder should be created"
202		);
203		assert!(pallet_path.join("Cargo.toml").exists(), "Cargo.toml should be created");
204		assert!(pallet_path.join("src").join("lib.rs").exists(), "lib.rs should be created");
205		assert!(
206			pallet_path.join("src").join("benchmarking.rs").exists(),
207			"benchmarking.rs should be created"
208		);
209		assert!(pallet_path.join("src").join("tests.rs").exists(), "tests.rs should be created");
210		assert!(
211			!pallet_path.join("src").join("weights.rs").exists(),
212			"weights.rs shouldn't be created"
213		);
214		assert!(pallet_path.join("src").join("mock.rs").exists(), "mock.rs should be created");
215		assert!(
216			pallet_path.join("src").join("pallet_logic.rs").exists(),
217			"pallet_logic.rs should be created"
218		);
219		assert!(
220			pallet_path.join("src").join("config_preludes.rs").exists(),
221			"config_preludes.rs should be created"
222		);
223
224		let lib_rs_content =
225			read_to_string(pallet_path.join("src").join("lib.rs")).expect("Failed to read lib.rs");
226		assert!(lib_rs_content.contains("pub mod pallet"), "lib.rs should contain pub mod pallet");
227		assert!(
228			lib_rs_content.contains("pub mod config_preludes"),
229			"lib.rs should contain pub mod config_preludes"
230		);
231		Ok(())
232	}
233
234	#[test]
235	fn test_pallet_create_advanced_template_no_default_config() -> Result<(), Error> {
236		let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
237		let pallet_name = "MyPallet";
238		let pallet_path = temp_dir.path().join(pallet_name);
239		let config = TemplatePalletConfig {
240			authors: "Alice".to_string(),
241			description: "A sample pallet".to_string(),
242			pallet_in_workspace: false,
243			pallet_advanced_mode: true,
244			pallet_default_config: false,
245			pallet_common_types: Vec::new(),
246			pallet_storage: Vec::new(),
247			pallet_genesis: false,
248			pallet_custom_origin: true,
249		};
250
251		// Call the function being tested
252		create_pallet_template(pallet_path.clone(), config)?;
253
254		// Assert that the pallet structure is generated
255		assert!(pallet_path.exists(), "Pallet folder should be created");
256		assert!(pallet_path.join("src").exists(), "src folder should be created");
257		assert!(
258			pallet_path.join("src").join("pallet_logic").exists(),
259			"pallet_logic folder should be created"
260		);
261		assert!(
262			pallet_path.join("src").join("pallet_logic").join("try_state.rs").exists(),
263			"try_state.rs should be created"
264		);
265		assert!(
266			pallet_path.join("src").join("pallet_logic").join("origin.rs").exists(),
267			"origin.rs should be created"
268		);
269		assert!(pallet_path.join("src").join("tests").exists(), "tests folder should be created");
270		assert!(
271			pallet_path.join("src").join("tests").join("utils.rs").exists(),
272			"utils.rs folder should be created"
273		);
274		assert!(pallet_path.join("Cargo.toml").exists(), "Cargo.toml should be created");
275		assert!(pallet_path.join("src").join("lib.rs").exists(), "lib.rs should be created");
276		assert!(
277			pallet_path.join("src").join("benchmarking.rs").exists(),
278			"benchmarking.rs should be created"
279		);
280		assert!(
281			!pallet_path.join("src").join("weights.rs").exists(),
282			"weights.rs shouldn't be created"
283		);
284		assert!(pallet_path.join("src").join("tests.rs").exists(), "tests.rs should be created");
285		assert!(pallet_path.join("src").join("mock.rs").exists(), "mock.rs should be created");
286		assert!(
287			pallet_path.join("src").join("pallet_logic.rs").exists(),
288			"pallet_logic.rs should be created"
289		);
290		assert!(
291			!pallet_path.join("src").join("config_preludes.rs").exists(),
292			"config_preludes.rs should be created"
293		);
294
295		let lib_rs_content =
296			read_to_string(pallet_path.join("src").join("lib.rs")).expect("Failed to read lib.rs");
297		assert!(lib_rs_content.contains("pub mod pallet"), "lib.rs should contain pub mod pallet");
298		assert!(
299			!lib_rs_content.contains("pub mod config_preludes"),
300			"lib.rs should contain pub mod config_preludes"
301		);
302		Ok(())
303	}
304
305	#[test]
306	fn test_pallet_create_advanced_template_no_custom_origin() -> Result<(), Error> {
307		let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
308		let pallet_name = "MyPallet";
309		let pallet_path = temp_dir.path().join(pallet_name);
310		let config = TemplatePalletConfig {
311			authors: "Alice".to_string(),
312			description: "A sample pallet".to_string(),
313			pallet_in_workspace: true,
314			pallet_advanced_mode: true,
315			pallet_default_config: true,
316			pallet_common_types: Vec::new(),
317			pallet_storage: Vec::new(),
318			pallet_genesis: false,
319			pallet_custom_origin: false,
320		};
321
322		// Call the function being tested
323		create_pallet_template(pallet_path.clone(), config)?;
324
325		// Assert that the pallet structure is generated
326		assert!(pallet_path.exists(), "Pallet folder should be created");
327		assert!(pallet_path.join("src").exists(), "src folder should be created");
328		assert!(
329			pallet_path.join("src").join("pallet_logic").exists(),
330			"pallet_logic folder should be created"
331		);
332		assert!(
333			pallet_path.join("src").join("pallet_logic").join("try_state.rs").exists(),
334			"try_state.rs should be created"
335		);
336		assert!(
337			!pallet_path.join("src").join("pallet_logic").join("origin.rs").exists(),
338			"origin.rs should be created"
339		);
340		assert!(pallet_path.join("src").join("tests").exists(), "tests folder should be created");
341		assert!(
342			pallet_path.join("src").join("tests").join("utils.rs").exists(),
343			"utils.rs folder should be created"
344		);
345		assert!(pallet_path.join("Cargo.toml").exists(), "Cargo.toml should be created");
346		assert!(pallet_path.join("src").join("lib.rs").exists(), "lib.rs should be created");
347		assert!(
348			pallet_path.join("src").join("benchmarking.rs").exists(),
349			"benchmarking.rs should be created"
350		);
351		assert!(
352			!pallet_path.join("src").join("weights.rs").exists(),
353			"weights.rs shouldn't be created"
354		);
355		assert!(pallet_path.join("src").join("tests.rs").exists(), "tests.rs should be created");
356		assert!(pallet_path.join("src").join("mock.rs").exists(), "mock.rs should be created");
357		assert!(
358			pallet_path.join("src").join("pallet_logic.rs").exists(),
359			"pallet_logic.rs should be created"
360		);
361		assert!(
362			pallet_path.join("src").join("config_preludes.rs").exists(),
363			"config_preludes.rs should be created"
364		);
365
366		let lib_rs_content =
367			read_to_string(pallet_path.join("src").join("lib.rs")).expect("Failed to read lib.rs");
368		assert!(lib_rs_content.contains("pub mod pallet"), "lib.rs should contain pub mod pallet");
369		assert!(
370			lib_rs_content.contains("pub mod config_preludes"),
371			"lib.rs should contain pub mod config_preludes"
372		);
373		Ok(())
374	}
375
376	#[test]
377	fn test_pallet_create_simple_template() -> Result<(), Error> {
378		let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
379		let pallet_name = "MyPallet";
380		let pallet_path = temp_dir.path().join(pallet_name);
381		let config = TemplatePalletConfig {
382			authors: "Alice".to_string(),
383			description: "A sample pallet".to_string(),
384			pallet_in_workspace: false,
385			pallet_advanced_mode: false,
386			pallet_default_config: false,
387			pallet_common_types: Vec::new(),
388			pallet_storage: Vec::new(),
389			pallet_genesis: false,
390			pallet_custom_origin: false,
391		};
392
393		// Call the function being tested
394		create_pallet_template(pallet_path.clone(), config)?;
395
396		// Assert that the pallet structure is generated
397		let pallet_path = temp_dir.path().join(pallet_name);
398		assert!(pallet_path.exists(), "Pallet folder should be created");
399		assert!(pallet_path.join("src").exists(), "src folder should be created");
400		assert!(
401			!pallet_path.join("src").join("pallet_logic").exists(),
402			"pallet_logic folder shouldn't be created"
403		);
404		assert!(
405			!pallet_path.join("src").join("pallet_logic").join("try_state.rs").exists(),
406			"try_state.rs shouldn't be created"
407		);
408		assert!(
409			!pallet_path.join("src").join("pallet_logic").join("origin.rs").exists(),
410			"origin.rs shouldn't be created"
411		);
412		assert!(!pallet_path.join("src").join("tests").exists(), "tests folder should be created");
413		assert!(
414			!pallet_path.join("src").join("tests").join("utils.rs").exists(),
415			"utils.rs folder shouldn't be created"
416		);
417		assert!(pallet_path.join("Cargo.toml").exists(), "Cargo.toml should be created");
418		assert!(pallet_path.join("src").join("lib.rs").exists(), "lib.rs should be created");
419		assert!(
420			pallet_path.join("src").join("benchmarking.rs").exists(),
421			"benchmarking.rs should be created"
422		);
423		assert!(
424			pallet_path.join("src").join("weights.rs").exists(),
425			"weights.rs should be created"
426		);
427		assert!(pallet_path.join("src").join("tests.rs").exists(), "tests.rs should be created");
428		assert!(pallet_path.join("src").join("mock.rs").exists(), "mock.rs should be created");
429		assert!(
430			!pallet_path.join("src").join("pallet_logic.rs").exists(),
431			"pallet_logic.rs shouldn't be created"
432		);
433		assert!(
434			!pallet_path.join("src").join("config_preludes.rs").exists(),
435			"config_preludes.rs shouldn't be created"
436		);
437
438		let lib_rs_content =
439			read_to_string(pallet_path.join("src").join("lib.rs")).expect("Failed to read lib.rs");
440		assert!(lib_rs_content.contains("pub mod pallet"), "lib.rs should contain pub mod pallet");
441		assert!(
442			!lib_rs_content.contains("pub mod config_preludes"),
443			"lib.rs shouldn't contain pub mod config_preludes"
444		);
445		Ok(())
446	}
447
448	#[test]
449	fn test_pallet_create_template_invalid_path() {
450		// Use invalid characters that are not allowed in paths on any OS
451		let invalid_path = "\0/\0"; // NULL byte is invalid in all filesystems
452		let config = TemplatePalletConfig {
453			authors: "Alice".to_string(),
454			description: "A sample pallet".to_string(),
455			pallet_in_workspace: false,
456			pallet_advanced_mode: true,
457			pallet_default_config: false,
458			pallet_common_types: Vec::new(),
459			pallet_storage: Vec::new(),
460			pallet_genesis: false,
461			pallet_custom_origin: false,
462		};
463
464		// Call the function being tested with an invalid path
465		let result = create_pallet_template(PathBuf::from(invalid_path), config);
466
467		// Assert that the result is an error
468		assert!(result.is_err(), "Result should be an error");
469	}
470}