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