Skip to main content

pop_chains/
templates.rs

1// SPDX-License-Identifier: GPL-3.0
2
3//! Chain template definitions and configurations.
4//!
5//! This module provides template definitions for different parachain configurations,
6//! including providers like Pop, OpenZeppelin and Parity. It includes template metadata,
7//! configuration options and utility functions for template management.
8
9use pop_common::templates::{Template, Type};
10use serde::Serialize;
11use strum::{EnumProperty as _, VariantArray};
12use strum_macros::{AsRefStr, Display, EnumMessage, EnumProperty, EnumString};
13
14/// Supported template providers.
15#[derive(
16	AsRefStr,
17	Clone,
18	Default,
19	Debug,
20	Display,
21	EnumMessage,
22	EnumString,
23	Eq,
24	PartialEq,
25	VariantArray,
26	Serialize,
27)]
28pub enum Provider {
29	/// Pop: An all-in-one tool for Polkadot development.
30	#[default]
31	#[strum(
32		ascii_case_insensitive,
33		serialize = "pop",
34		message = "Pop",
35		detailed_message = "An all-in-one tool for Polkadot development."
36	)]
37	Pop,
38	/// OpenZeppelin: The standard for secure blockchain applications.
39	#[strum(
40		ascii_case_insensitive,
41		serialize = "openzeppelin",
42		message = "OpenZeppelin",
43		detailed_message = "The standard for secure blockchain applications."
44	)]
45	OpenZeppelin,
46	/// Parity: Solutions for a trust-free world.
47	#[strum(
48		ascii_case_insensitive,
49		serialize = "parity",
50		message = "Parity",
51		detailed_message = "Solutions for a trust-free world."
52	)]
53	Parity,
54}
55
56impl Type<ChainTemplate> for Provider {
57	fn default_template(&self) -> Option<ChainTemplate> {
58		match &self {
59			Provider::Pop => Some(ChainTemplate::Standard),
60			Provider::OpenZeppelin => Some(ChainTemplate::OpenZeppelinGeneric),
61			Provider::Parity => Some(ChainTemplate::ParityGeneric),
62		}
63	}
64}
65
66/// Configurable settings for parachain generation.
67#[derive(Debug, Clone, PartialEq)]
68pub struct Config {
69	/// The token symbol.
70	pub symbol: String,
71	/// The number of decimals used for the token.
72	pub decimals: u8,
73	/// The initial endowment amount.
74	pub initial_endowment: String,
75}
76
77/// Templates supported.
78#[derive(
79	AsRefStr,
80	Clone,
81	Debug,
82	Default,
83	Display,
84	EnumMessage,
85	EnumProperty,
86	EnumString,
87	Serialize,
88	Eq,
89	Hash,
90	PartialEq,
91	VariantArray,
92)]
93pub enum ChainTemplate {
94	/// Pop Standard Template: Minimalist parachain template.
95	#[default]
96	#[strum(
97		serialize = "r0gue-io/base-parachain",
98		message = "Standard",
99		detailed_message = "A standard parachain",
100		props(
101			Provider = "Pop",
102			Repository = "https://github.com/r0gue-io/base-parachain",
103			Network = "./network.toml",
104			License = "Unlicense",
105			DeploymentName = "POP_STANDARD"
106		)
107	)]
108	Standard,
109	/// Pop Assets Template: Parachain configured with fungible and non-fungible asset
110	/// functionalities.
111	#[strum(
112		serialize = "r0gue-io/assets-parachain",
113		message = "Assets",
114		detailed_message = "Parachain configured with fungible and non-fungible asset functionalities.",
115		props(
116			Provider = "Pop",
117			Repository = "https://github.com/r0gue-io/assets-parachain",
118			Network = "./network.toml",
119			License = "Unlicense",
120			DeploymentName = "POP_ASSETS"
121		)
122	)]
123	Assets,
124	/// Pop Contracts Template: Parachain configured to support WebAssembly smart contracts.
125	#[strum(
126		serialize = "r0gue-io/contracts-parachain",
127		message = "Contracts",
128		detailed_message = "Parachain configured to support WebAssembly smart contracts.",
129		props(
130			Provider = "Pop",
131			Repository = "https://github.com/r0gue-io/contracts-parachain",
132			Network = "./network.toml",
133			License = "Unlicense",
134			DeploymentName = "POP_CONTRACTS"
135		)
136	)]
137	Contracts,
138	/// OpenZeppelin Generic Runtime Template: A generic template for Substrate Runtime.
139	#[strum(
140		serialize = "openzeppelin/generic-template",
141		message = "Generic Runtime Template",
142		detailed_message = "A generic template for Substrate Runtime.",
143		props(
144			Provider = "OpenZeppelin",
145			Repository = "https://github.com/OpenZeppelin/polkadot-runtime-templates",
146			Network = "./zombienet-config/devnet.toml",
147			SupportedVersions = "v1.0.0,v2.0.1,v2.0.3,v3.0.0,v4.0.0",
148			IsAudited = "true",
149			License = "GPL-3.0",
150			DeploymentName = "OZ_GENERIC"
151		)
152	)]
153	OpenZeppelinGeneric,
154	/// OpenZeppelin EVM Template: Parachain with EVM compatibility out of the box.
155	#[strum(
156		serialize = "openzeppelin/evm-template",
157		message = "EVM Template",
158		detailed_message = "Parachain with EVM compatibility out of the box.",
159		props(
160			Provider = "OpenZeppelin",
161			Repository = "https://github.com/OpenZeppelin/polkadot-runtime-templates",
162			Network = "./zombienet-config/devnet.toml",
163			SupportedVersions = "v2.0.3,v3.0.0,v4.0.0",
164			IsAudited = "true",
165			License = "GPL-3.0",
166			DeploymentName = "OZ_EVM"
167		)
168	)]
169	OpenZeppelinEVM,
170	/// Parity Generic Template: The Parachain-Ready Template From Polkadot SDK.
171	#[strum(
172		serialize = "paritytech/polkadot-sdk-parachain-template",
173		message = "Polkadot SDK's Parachain Template",
174		detailed_message = "The Parachain-Ready Template From Polkadot SDK.",
175		props(
176			Provider = "Parity",
177			Repository = "https://github.com/paritytech/polkadot-sdk-parachain-template",
178			Network = "./zombienet.toml",
179			License = "Unlicense",
180			DeploymentName = "PARITY_GENERIC"
181		)
182	)]
183	ParityGeneric,
184	/// Test template 01 used for unit testing.
185	#[cfg(test)]
186	#[strum(
187		serialize = "test_01",
188		message = "Test_01",
189		detailed_message = "Test template only compiled in test mode.",
190		props(
191			Provider = "Test",
192			Repository = "",
193			Network = "",
194			SupportedVersions = "v1.0.0,v2.0.0",
195			IsAudited = "true",
196			IsDeprecated = "true",
197			DeprecatedMessage = "This template is deprecated. Please use test_02 in the future.",
198			License = "Unlicense",
199		)
200	)]
201	TestTemplate01,
202	/// Test template 02 used for unit testing.
203	#[cfg(test)]
204	#[strum(
205		serialize = "test_02",
206		message = "Test_02",
207		detailed_message = "Test template only compiled in test mode.",
208		props(Provider = "Test", Repository = "", Network = "", License = "GPL-3.0")
209	)]
210	TestTemplate02,
211}
212
213impl Template for ChainTemplate {
214	const PROPERTY: &'static str = "Provider";
215}
216
217impl ChainTemplate {
218	/// Returns the relative path to the default network configuration file to be used, if defined.
219	pub fn network_config(&self) -> Option<&str> {
220		self.get_str("Network")
221	}
222
223	/// The supported versions of the template.
224	pub fn supported_versions(&self) -> Option<Vec<&str>> {
225		self.get_str("SupportedVersions").map(|s| s.split(',').collect())
226	}
227
228	/// Whether the specified version is supported.
229	///
230	/// # Arguments
231	/// * `version`: The version to be checked.
232	pub fn is_supported_version(&self, version: &str) -> bool {
233		// if `SupportedVersion` is None, then all versions are supported. Otherwise, ensure version
234		// is present.
235		self.supported_versions().is_none_or(|versions| versions.contains(&version))
236	}
237
238	/// Whether the template has been audited.
239	pub fn is_audited(&self) -> bool {
240		self.get_str("IsAudited") == Some("true")
241	}
242
243	/// The license used.
244	pub fn license(&self) -> Option<&str> {
245		self.get_str("License")
246	}
247
248	/// Returns the deployment name for the parachain if defined.
249	pub fn deployment_name(&self) -> Option<&str> {
250		self.get_str("DeploymentName")
251	}
252
253	/// Retrieves the deployment name from the `based_on` value.
254	pub fn deployment_name_from_based_on(based_on: &str) -> Option<String> {
255		// OpenZeppelin special cases first (https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/406)
256		let mapped_based_on = match based_on {
257			"OpenZeppelin EVM Template" => Some(ChainTemplate::OpenZeppelinEVM),
258			"OpenZeppelin Generic Template" => Some(ChainTemplate::OpenZeppelinGeneric),
259			_ => None,
260		};
261		if let Some(variant) = mapped_based_on {
262			return variant.deployment_name().map(String::from);
263		}
264		ChainTemplate::VARIANTS
265			.iter()
266			.find(|variant| variant.as_ref() == based_on)
267			.and_then(|variant| variant.deployment_name().map(String::from))
268	}
269
270	/// Gets the template name, removing the provider if present.
271	pub fn template_name_without_provider(&self) -> &str {
272		let name = self.as_ref();
273		name.split_once('/').map_or(name, |(_, template)| template)
274	}
275}
276
277#[cfg(test)]
278mod tests {
279	use super::*;
280	use ChainTemplate::*;
281	use std::{collections::HashMap, str::FromStr};
282
283	fn templates_names() -> HashMap<String, ChainTemplate> {
284		HashMap::from([
285			("r0gue-io/base-parachain".to_string(), Standard),
286			("r0gue-io/assets-parachain".to_string(), Assets),
287			("r0gue-io/contracts-parachain".to_string(), Contracts),
288			// openzeppelin
289			("openzeppelin/generic-template".to_string(), OpenZeppelinGeneric),
290			("openzeppelin/evm-template".to_string(), OpenZeppelinEVM),
291			// pàrity
292			("paritytech/polkadot-sdk-parachain-template".to_string(), ParityGeneric),
293			("test_01".to_string(), TestTemplate01),
294			("test_02".to_string(), TestTemplate02),
295		])
296	}
297
298	fn templates_names_without_providers() -> HashMap<ChainTemplate, String> {
299		HashMap::from([
300			(Standard, "base-parachain".to_string()),
301			(Assets, "assets-parachain".to_string()),
302			(Contracts, "contracts-parachain".to_string()),
303			(OpenZeppelinGeneric, "generic-template".to_string()),
304			(OpenZeppelinEVM, "evm-template".to_string()),
305			(ParityGeneric, "polkadot-sdk-parachain-template".to_string()),
306			(TestTemplate01, "test_01".to_string()),
307			(TestTemplate02, "test_02".to_string()),
308		])
309	}
310
311	fn templates_urls() -> HashMap<String, &'static str> {
312		HashMap::from([
313			("r0gue-io/base-parachain".to_string(), "https://github.com/r0gue-io/base-parachain"),
314			(
315				"r0gue-io/assets-parachain".to_string(),
316				"https://github.com/r0gue-io/assets-parachain",
317			),
318			(
319				"r0gue-io/contracts-parachain".to_string(),
320				"https://github.com/r0gue-io/contracts-parachain",
321			),
322			("r0gue-io/evm-parachain".to_string(), "https://github.com/r0gue-io/evm-parachain"),
323			// openzeppelin
324			(
325				"openzeppelin/generic-template".to_string(),
326				"https://github.com/OpenZeppelin/polkadot-runtime-templates",
327			),
328			(
329				"openzeppelin/evm-template".to_string(),
330				"https://github.com/OpenZeppelin/polkadot-runtime-templates",
331			),
332			(
333				"polkadot-generic-runtime-template".to_string(),
334				"https://github.com/OpenZeppelin/polkadot-runtime-templates",
335			),
336			(
337				"paritytech/polkadot-sdk-parachain-template".to_string(),
338				"https://github.com/paritytech/polkadot-sdk-parachain-template",
339			),
340			(
341				"paritytech/substrate-contracts-node".to_string(),
342				"https://github.com/paritytech/substrate-contracts-node",
343			),
344			("cpt".to_string(), "https://github.com/paritytech/substrate-contracts-node"),
345			("test_01".to_string(), ""),
346			("test_02".to_string(), ""),
347		])
348	}
349
350	fn template_network_configs() -> HashMap<ChainTemplate, Option<&'static str>> {
351		[
352			(Standard, Some("./network.toml")),
353			(Assets, Some("./network.toml")),
354			(Contracts, Some("./network.toml")),
355			(OpenZeppelinGeneric, Some("./zombienet-config/devnet.toml")),
356			(OpenZeppelinEVM, Some("./zombienet-config/devnet.toml")),
357			(ParityGeneric, Some("./zombienet.toml")),
358			(TestTemplate01, Some("")),
359			(TestTemplate02, Some("")),
360		]
361		.into()
362	}
363
364	fn template_license() -> HashMap<ChainTemplate, Option<&'static str>> {
365		[
366			(Standard, Some("Unlicense")),
367			(Assets, Some("Unlicense")),
368			(Contracts, Some("Unlicense")),
369			(OpenZeppelinGeneric, Some("GPL-3.0")),
370			(OpenZeppelinEVM, Some("GPL-3.0")),
371			(ParityGeneric, Some("Unlicense")),
372			(TestTemplate01, Some("Unlicense")),
373			(TestTemplate02, Some("GPL-3.0")),
374		]
375		.into()
376	}
377
378	fn template_deployment_name() -> HashMap<ChainTemplate, Option<&'static str>> {
379		[
380			(Standard, Some("POP_STANDARD")),
381			(Assets, Some("POP_ASSETS")),
382			(Contracts, Some("POP_CONTRACTS")),
383			(OpenZeppelinGeneric, Some("OZ_GENERIC")),
384			(OpenZeppelinEVM, Some("OZ_EVM")),
385			(ParityGeneric, Some("PARITY_GENERIC")),
386			(TestTemplate01, None),
387			(TestTemplate02, None),
388		]
389		.into()
390	}
391
392	#[test]
393	fn test_is_template_correct() {
394		for template in ChainTemplate::VARIANTS {
395			if matches!(template, Standard | Assets | Contracts) {
396				assert!(Provider::Pop.provides(template));
397				assert!(!Provider::Parity.provides(template));
398			}
399			if matches!(template, ParityGeneric) {
400				assert!(!Provider::Pop.provides(template));
401				assert!(Provider::Parity.provides(template))
402			}
403		}
404	}
405
406	#[test]
407	fn test_convert_string_to_template() {
408		let template_names = templates_names();
409		// Test the default
410		assert_eq!(ChainTemplate::from_str("").unwrap_or_default(), Standard);
411		// Test the rest
412		for template in ChainTemplate::VARIANTS {
413			assert_eq!(
414				&ChainTemplate::from_str(template.as_ref()).unwrap(),
415				template_names.get(&template.to_string()).unwrap()
416			);
417		}
418	}
419
420	#[test]
421	fn test_repository_url() {
422		let template_urls = templates_urls();
423		for template in ChainTemplate::VARIANTS {
424			assert_eq!(
425				&template.repository_url().unwrap(),
426				template_urls.get(&template.to_string()).unwrap()
427			);
428		}
429	}
430
431	#[test]
432	fn test_network_config() {
433		let network_configs = template_network_configs();
434		for template in ChainTemplate::VARIANTS {
435			println!("{:?}", template.name());
436			assert_eq!(template.network_config(), network_configs[template]);
437		}
438	}
439
440	#[test]
441	fn test_license() {
442		let licenses = template_license();
443		for template in ChainTemplate::VARIANTS {
444			assert_eq!(template.license(), licenses[template]);
445		}
446	}
447
448	#[test]
449	fn deployment_name_works() {
450		let deployment_name = template_deployment_name();
451		for template in ChainTemplate::VARIANTS {
452			assert_eq!(template.deployment_name(), deployment_name[template]);
453		}
454	}
455
456	#[test]
457	fn deployment_name_from_based_on_works() {
458		for template in ChainTemplate::VARIANTS {
459			assert_eq!(
460				ChainTemplate::deployment_name_from_based_on(template.as_ref()),
461				template.deployment_name().map(String::from),
462			);
463		}
464		// test special cases
465		assert_eq!(
466			ChainTemplate::deployment_name_from_based_on("OpenZeppelin EVM Template"),
467			Some(OpenZeppelinEVM.deployment_name().unwrap().to_string())
468		);
469		assert_eq!(
470			ChainTemplate::deployment_name_from_based_on("OpenZeppelin Generic Template"),
471			Some(OpenZeppelinGeneric.deployment_name().unwrap().to_string())
472		);
473	}
474
475	#[test]
476	fn test_default_template_of_provider() {
477		let mut provider = Provider::Pop;
478		assert_eq!(provider.default_template(), Some(Standard));
479		provider = Provider::Parity;
480		assert_eq!(provider.default_template(), Some(ParityGeneric));
481	}
482
483	#[test]
484	fn test_templates_of_provider() {
485		let mut provider = Provider::Pop;
486		assert_eq!(provider.templates(), [&Standard, &Assets, &Contracts]);
487		provider = Provider::Parity;
488		assert_eq!(provider.templates(), [&ParityGeneric]);
489	}
490
491	#[test]
492	fn test_convert_string_to_provider() {
493		assert_eq!(Provider::from_str("Pop").unwrap(), Provider::Pop);
494		assert_eq!(Provider::from_str("").unwrap_or_default(), Provider::Pop);
495		assert_eq!(Provider::from_str("Parity").unwrap(), Provider::Parity);
496	}
497
498	#[test]
499	fn supported_versions_have_no_whitespace() {
500		for template in ChainTemplate::VARIANTS {
501			if let Some(versions) = template.supported_versions() {
502				for version in versions {
503					assert!(!version.contains(' '));
504				}
505			}
506		}
507	}
508
509	#[test]
510	fn test_supported_versions_works() {
511		let template = TestTemplate01;
512		assert_eq!(template.supported_versions(), Some(vec!["v1.0.0", "v2.0.0"]));
513		assert!(template.is_supported_version("v1.0.0"));
514		assert!(template.is_supported_version("v2.0.0"));
515		assert!(!template.is_supported_version("v3.0.0"));
516
517		let template = TestTemplate02;
518		assert_eq!(template.supported_versions(), None);
519		// will be true because an empty SupportedVersions defaults to all
520		assert!(template.is_supported_version("v1.0.0"));
521	}
522
523	#[test]
524	fn test_is_audited() {
525		let template = TestTemplate01;
526		assert!(template.is_audited());
527
528		let template = TestTemplate02;
529		assert!(!template.is_audited());
530	}
531
532	#[test]
533	fn is_deprecated_works() {
534		let template = TestTemplate01;
535		assert!(template.is_deprecated());
536
537		let template = TestTemplate02;
538		assert!(!template.is_deprecated());
539	}
540
541	#[test]
542	fn deprecated_message_works() {
543		let template = TestTemplate01;
544		assert_eq!(
545			template.deprecated_message(),
546			"This template is deprecated. Please use test_02 in the future."
547		);
548
549		let template = TestTemplate02;
550		assert_eq!(template.deprecated_message(), "");
551	}
552
553	#[test]
554	fn template_name_without_provider() {
555		let template_names = templates_names_without_providers();
556		for template in ChainTemplate::VARIANTS {
557			assert_eq!(
558				template.template_name_without_provider(),
559				template_names.get(template).unwrap()
560			);
561		}
562	}
563}