Skip to main content

pop_chains/up/
mod.rs

1// SPDX-License-Identifier: GPL-3.0
2
3use crate::{
4	errors::Error, omni_node::PolkadotOmniNodeCli::PolkadotOmniNode,
5	registry::traits::Chain as ChainT, up::chain_specs::Runtime,
6};
7pub use chain_specs::Runtime as Relay;
8use glob::glob;
9use indexmap::IndexMap;
10use pop_common::sourcing::traits::{Source as _, enums::Source as _};
11pub use pop_common::{
12	Profile,
13	git::{GitHub, Repository},
14	sourcing::{Binary, GitHub::*, Source, Source::*},
15};
16use std::{
17	collections::BTreeSet,
18	fmt::Debug,
19	iter::once,
20	path::{Path, PathBuf},
21};
22use strum::VariantArray;
23use symlink::{remove_symlink_file, symlink_file};
24use toml_edit::DocumentMut;
25use zombienet_configuration::{
26	NodeConfig,
27	shared::node::{Buildable, Initial, NodeConfigBuilder},
28};
29pub use zombienet_sdk::NetworkConfigBuilder;
30use zombienet_sdk::{
31	AttachToLive, AttachToLiveNetwork, LocalFileSystem, Network, NetworkConfig, NetworkConfigExt,
32};
33
34mod chain_specs;
35/// Configuration for supported parachains.
36pub mod chains;
37mod relay;
38
39const VALIDATORS: [&str; 6] = ["alice", "bob", "charlie", "dave", "eve", "ferdie"];
40
41/// Configuration to launch a local network.
42pub struct Zombienet {
43	/// The config to be used to launch a network.
44	network_config: NetworkConfiguration,
45	/// The configuration required to launch the relay chain.
46	relay_chain: RelayChain,
47	/// The configuration required to launch parachains.
48	parachains: IndexMap<u32, Chain>,
49	/// Whether any HRMP channels are to be pre-opened.
50	hrmp_channels: bool,
51}
52
53/// Attaches to a running network via zombie.json and tears it down.
54pub async fn destroy_network(zombie_json: &Path) -> Result<(), Error> {
55	let network = AttachToLiveNetwork::attach_native(zombie_json.to_path_buf()).await?;
56	network.destroy().await.map_err(anyhow::Error::from)?;
57	Ok(())
58}
59
60impl Zombienet {
61	/// Initializes the configuration for launching a local network.
62	///
63	/// # Arguments
64	/// * `cache` - The location used for caching binaries.
65	/// * `network_config` - The configuration to be used to launch a network.
66	/// * `relay_chain_version` - The specific binary version used for the relay chain (`None` will
67	///   use the latest available version).
68	/// * `relay_chain_runtime_version` - The specific runtime version used for the relay chain
69	///   runtime (`None` will use the latest available version).
70	/// * `system_parachain_version` - The specific binary version used for system parachains
71	///   (`None` will use the latest available version).
72	/// * `system_parachain_runtime_version` - The specific runtime version used for system
73	///   parachains (`None` will use the latest available version).
74	/// * `parachains` - The parachain(s) specified.
75	pub async fn new(
76		cache: &Path,
77		network_config: NetworkConfiguration,
78		relay_chain_version: Option<&str>,
79		relay_chain_runtime_version: Option<&str>,
80		system_parachain_version: Option<&str>,
81		system_parachain_runtime_version: Option<&str>,
82		parachains: Option<&Vec<String>>,
83	) -> Result<Self, Error> {
84		// Determine relay and parachain requirements based on arguments and config
85		let relay_chain = Self::init_relay_chain(
86			relay_chain_version,
87			relay_chain_runtime_version,
88			&network_config,
89			cache,
90		)
91		.await?;
92		let parachains = match parachains {
93			Some(parachains) => {
94				let mut repos = Vec::new();
95				for parachain in parachains {
96					if parachain.contains("://") {
97						repos.push(Repository::parse(parachain)?);
98					}
99				}
100				(!repos.is_empty()).then_some(repos)
101			},
102			None => None,
103		};
104		let parachains = Self::parachains(
105			&relay_chain,
106			system_parachain_version,
107			system_parachain_runtime_version.or(relay_chain_runtime_version),
108			parachains,
109			&network_config,
110			cache,
111		)
112		.await?;
113		let hrmp_channels = !network_config.0.hrmp_channels().is_empty();
114		Ok(Self { network_config, relay_chain, parachains, hrmp_channels })
115	}
116
117	/// The binaries required to launch the network.
118	pub fn binaries(&mut self) -> impl Iterator<Item = &mut Binary> {
119		once([Some(&mut self.relay_chain.binary), self.relay_chain.chain_spec_generator.as_mut()])
120			.chain(
121				self.parachains
122					.values_mut()
123					.map(|p| [Some(&mut p.binary), p.chain_spec_generator.as_mut()]),
124			)
125			.flatten()
126			.flatten()
127	}
128
129	/// Determine parachain configuration based on specified version and network configuration.
130	///
131	/// # Arguments
132	/// * `relay_chain` - The configuration required to launch the relay chain.
133	/// * `system_parachain_version` - The specific binary version used for system parachains
134	///   (`None` will use the latest available version).
135	/// * `system_parachain_runtime_version` - The specific runtime version used for system
136	///   parachains (`None` will use the latest available version).
137	/// * `parachains` - The parachain repositories specified.
138	/// * `network_config` - The network configuration to be used to launch a network.
139	/// * `cache` - The location used for caching binaries.
140	async fn parachains(
141		relay_chain: &RelayChain,
142		system_parachain_version: Option<&str>,
143		system_parachain_runtime_version: Option<&str>,
144		parachains: Option<Vec<Repository>>,
145		network_config: &NetworkConfiguration,
146		cache: &Path,
147	) -> Result<IndexMap<u32, Chain>, Error> {
148		let mut paras: IndexMap<u32, Chain> = IndexMap::new();
149		'outer: for parachain in network_config.0.parachains() {
150			let id = parachain.id();
151			let chain = parachain.chain().map(|c| c.as_str());
152
153			let command = parachain
154				.default_command()
155				.map(|c| c.as_str())
156				.or_else(|| {
157					// Check if any collators define command
158					for collator in parachain.collators() {
159						if let Some(command) = collator.command().map(|i| i.as_str()) {
160							return Some(command);
161						}
162					}
163
164					// Otherwise default to polkadot-parachain
165					Some("polkadot-parachain")
166				})
167				.expect("missing default_command set above")
168				.to_lowercase();
169
170			// Check if system parachain
171			if let Some(parachain) = chains::system(
172				id,
173				&command,
174				system_parachain_version,
175				system_parachain_runtime_version,
176				relay_chain.binary.version().expect("expected relay chain to have version"),
177				chain,
178				cache,
179			)
180			.await?
181			{
182				paras.insert(id, parachain);
183				continue;
184			}
185
186			// Check if known parachain
187			let version = parachains.as_ref().and_then(|r| {
188				r.iter()
189					.filter_map(|r| {
190						(r.package == command).then_some(r.reference.as_ref()).flatten()
191					})
192					.nth(0)
193					.map(|v| v.as_str())
194			});
195			if let Some(parachain) =
196				chains::from(&relay_chain.runtime, id, &command, version, chain, cache).await?
197			{
198				paras.insert(id, parachain);
199				continue;
200			}
201
202			// Check if parachain binary source specified as an argument
203			if let Some(parachains) = parachains.as_ref() &&
204				let Some(repo) = parachains.iter().find(|r| command == r.package)
205			{
206				paras.insert(id, Chain::from_repository(id, repo, chain, cache)?);
207				continue 'outer;
208			}
209
210			// Check if command references a local binary
211			if ["./", "../", "/"].iter().any(|p| command.starts_with(p)) {
212				paras.insert(id, Chain::from_local(id, command.into(), chain)?);
213				continue;
214			}
215
216			// Check if command references a parachain template binary without a specified path
217			// (e.g. Polkadot SDK parachain template)
218			if ["parachain-template-node", "substrate-contracts-node"].contains(&command.as_str()) {
219				for profile in Profile::VARIANTS {
220					let binary_path = profile.target_directory(Path::new("./")).join(&command);
221					if binary_path.exists() {
222						paras.insert(id, Chain::from_local(id, binary_path, chain)?);
223						continue 'outer;
224					}
225				}
226				return Err(Error::MissingBinary(command));
227			}
228
229			if command.starts_with(PolkadotOmniNode.binary()) {
230				paras.insert(id, Chain::from_omni_node(id, cache)?);
231				continue 'outer;
232			}
233
234			return Err(Error::MissingBinary(command));
235		}
236		Ok(paras)
237	}
238
239	/// Determines relay chain configuration based on specified version and network configuration.
240	///
241	/// # Arguments
242	/// * `version` - The specific binary version used for the relay chain (`None` will use the
243	///   latest available version).
244	/// * `runtime_version` - The specific runtime version used for the relay chain runtime (`None`
245	///   will use the latest available version).
246	/// * `network_config` - The network configuration to be used to launch a network.
247	/// * `cache` - The location used for caching binaries.
248	async fn init_relay_chain(
249		version: Option<&str>,
250		runtime_version: Option<&str>,
251		network_config: &NetworkConfiguration,
252		cache: &Path,
253	) -> Result<RelayChain, Error> {
254		// Attempt to determine relay from configuration
255		let relay_chain = network_config.0.relaychain();
256		let chain = relay_chain.chain().as_str();
257		if let Some(default_command) = relay_chain.default_command().map(|c| c.as_str()) {
258			let relay =
259				relay::from(default_command, version, runtime_version, chain, cache).await?;
260			// Validate any node config is supported
261			for node in relay_chain.nodes() {
262				if let Some(command) = node.command().map(|c| c.as_str()) &&
263					command.to_lowercase() != relay.binary.name()
264				{
265					return Err(Error::UnsupportedCommand(format!(
266						"the relay chain command is unsupported: {command}",
267					)));
268				}
269			}
270			return Ok(relay);
271		}
272		// Attempt to determine from nodes
273		let mut relay: Option<RelayChain> = None;
274		for node in relay_chain.nodes() {
275			if let Some(command) = node.command().map(|c| c.as_str()) {
276				match &relay {
277					Some(relay) =>
278						if command.to_lowercase() != relay.binary.name() {
279							return Err(Error::UnsupportedCommand(format!(
280								"the relay chain command is unsupported: {command}",
281							)));
282						},
283					None => {
284						relay = Some(
285							relay::from(command, version, runtime_version, chain, cache).await?,
286						);
287					},
288				}
289			}
290		}
291		if let Some(relay) = relay {
292			return Ok(relay);
293		}
294		// Otherwise use default
295		relay::default(version, runtime_version, chain, cache).await
296	}
297
298	/// The name of the relay chain.
299	pub fn relay_chain(&self) -> &str {
300		&self.relay_chain.chain
301	}
302
303	/// Whether any HRMP channels are to be pre-opened.
304	pub fn hrmp_channels(&self) -> bool {
305		self.hrmp_channels
306	}
307
308	/// Launches the local network.
309	pub async fn spawn(&mut self) -> Result<Network<LocalFileSystem>, Error> {
310		// Symlink polkadot workers
311		let relay_chain_binary_path = self.relay_chain.binary.path();
312		if !relay_chain_binary_path.exists() {
313			return Err(Error::MissingBinary(self.relay_chain.binary.name().to_string()));
314		}
315		let cache = relay_chain_binary_path
316			.parent()
317			.expect("expected relay chain binary path to exist");
318		let version = self.relay_chain.binary.version().ok_or_else(|| {
319			Error::MissingBinary(format!(
320				"Could not determine version for `{}` binary",
321				self.relay_chain.binary.name()
322			))
323		})?;
324		for worker in &self.relay_chain.workers {
325			let dest = cache.join(worker);
326			if dest.exists() {
327				remove_symlink_file(&dest)?;
328			}
329			symlink_file(cache.join(format!("{worker}-{version}")), dest)?;
330		}
331
332		// Load from config and spawn network
333		let network_config = self.network_config.adapt(&self.relay_chain, &self.parachains)?;
334		Ok(network_config.spawn_native().await?)
335	}
336}
337
338/// The network configuration.
339///
340/// Network configuration can be provided via [Path] or by using the [NetworkConfigBuilder].
341#[derive(Debug, PartialEq)]
342pub struct NetworkConfiguration(NetworkConfig, BTreeSet<u32>);
343
344impl NetworkConfiguration {
345	/// Build a network configuration for the specified relay chain and chains.
346	///
347	/// # Arguments
348	/// * `relay_chain` - The relay chain runtime to be used.
349	/// * `port` - The port to be used for the first relay chain validator.
350	/// * `chains` - The optional chains to be included.
351	pub fn build(
352		relay_chain: Relay,
353		port: Option<u16>,
354		chains: Option<&[Box<dyn ChainT>]>,
355	) -> Result<Self, Error> {
356		let validators: Vec<_> = VALIDATORS
357			.into_iter()
358			.take(chains.as_ref().map(|v| v.len()).unwrap_or_default().max(2))
359			.map(String::from)
360			.collect();
361
362		let mut builder = NetworkConfigBuilder::new().with_relaychain(|builder| {
363			let mut builder = builder.with_chain(relay_chain.chain()).with_validator(|builder| {
364				let mut builder = builder
365					.with_name(validators.first().expect("at least two validators defined above"));
366				if let Some(port) = port {
367					builder = builder.with_rpc_port(port)
368				}
369				builder
370			});
371
372			for validator in validators.iter().skip(1) {
373				builder = builder.with_validator(|builder| builder.with_name(validator))
374			}
375			builder
376		});
377
378		if let Some(chains) = chains {
379			let mut dependencies =
380				chains.iter().filter_map(|p| p.requires()).flatten().collect::<Vec<_>>();
381
382			for chain in chains {
383				builder = builder.with_parachain(|builder| {
384					let mut builder = builder
385						.with_id(chain.id())
386						.with_chain(chain.chain())
387						.with_default_command(chain.binary());
388
389					// Apply any genesis overrides
390					let mut genesis_overrides = serde_json::Map::new();
391					if let Some(mut r#override) = chain.genesis_overrides() {
392						r#override(&mut genesis_overrides);
393					}
394					for (_, r#override) in
395						dependencies.iter_mut().filter(|(t, _)| t == &chain.as_any().type_id())
396					{
397						r#override(&mut genesis_overrides);
398					}
399					if !genesis_overrides.is_empty() {
400						builder = builder.with_genesis_overrides(genesis_overrides);
401					}
402
403					builder.with_collator(|builder| {
404						let mut builder =
405							builder.with_name(&format!("{}-collator", chain.name())).with_args(
406								chain
407									.args()
408									.map(|args| args.into_iter().map(|arg| arg.into()).collect())
409									.unwrap_or_default(),
410							);
411						if let Some(port) = chain.port() {
412							builder = builder.with_rpc_port(*port)
413						}
414						builder
415					})
416				})
417			}
418
419			// Open HRMP channels between all chains
420			let chains = || chains.iter().map(|p| p.id());
421			for (sender, recipient) in
422				chains().flat_map(|s| chains().filter(move |r| s != *r).map(move |r| (s, r)))
423			{
424				builder = builder.with_hrmp_channel(|channel| {
425					channel
426						.with_sender(sender)
427						.with_recipient(recipient)
428						.with_max_capacity(1_000)
429						.with_max_message_size(8_000)
430				})
431			}
432		}
433
434		Ok(NetworkConfiguration(
435			builder.build().map_err(Error::NetworkConfigurationError)?,
436			Default::default(),
437		))
438	}
439
440	/// Adapts user-provided configuration to one with resolved binary paths and which is compatible
441	/// with [zombienet-sdk](zombienet_sdk) requirements.
442	///
443	/// # Arguments
444	/// * `relay_chain` - The configuration required to launch the relay chain.
445	/// * `parachains` - The configuration required to launch the parachain(s).
446	fn adapt(
447		&self,
448		relay_chain: &RelayChain,
449		parachains: &IndexMap<u32, Chain>,
450	) -> Result<NetworkConfig, Error> {
451		// Resolve paths to relay binary and chain spec generator
452		let binary_path = NetworkConfiguration::resolve_path(&relay_chain.binary.path())?;
453		let chain_spec_generator = match &relay_chain.chain_spec_generator {
454			None => None,
455			Some(path) => Some(format!(
456				"{} {}",
457				NetworkConfiguration::resolve_path(&path.path())?,
458				"{{chainName}}"
459			)),
460		};
461
462		// Use builder to clone network config, adapting binary paths as necessary
463		let mut builder = NetworkConfigBuilder::new()
464			.with_relaychain(|relay| {
465				let source = self.0.relaychain();
466				let nodes = source.nodes();
467
468				let mut builder = relay
469					.with_chain(source.chain().as_str())
470					.with_default_args(source.default_args().into_iter().cloned().collect())
471					// Replace default command with resolved binary path
472					.with_default_command(binary_path.as_str());
473
474				// Chain spec
475				if let Some(command) = source.chain_spec_command() {
476					builder = builder.with_chain_spec_command(command);
477				}
478				if source.chain_spec_command_is_local() {
479					builder = builder.chain_spec_command_is_local(true);
480				}
481				if let Some(location) = source.chain_spec_path() {
482					builder = builder.with_chain_spec_path(location.clone());
483				}
484				if let Some(chain_spec_command_output_path) =
485					source.chain_spec_command_output_path()
486				{
487					builder =
488						builder.with_chain_spec_command_output_path(chain_spec_command_output_path);
489				}
490				// Configure chain spec generator
491				if let Some(command) = chain_spec_generator {
492					builder = builder.with_chain_spec_command(command);
493				}
494				// Overrides: genesis/wasm
495				if let Some(genesis) = source.runtime_genesis_patch() {
496					builder = builder.with_genesis_overrides(genesis.clone());
497				}
498				if let Some(location) = source.wasm_override() {
499					builder = builder.with_wasm_override(location.clone());
500				}
501
502				// Add nodes from source
503				let mut builder = builder.with_validator(|builder| {
504					let source = nodes.first().expect("expected at least one node");
505					Self::build_node_from_source(builder, source, binary_path.as_str())
506				});
507				for source in nodes.iter().skip(1) {
508					builder = builder.with_validator(|builder| {
509						Self::build_node_from_source(builder, source, binary_path.as_str())
510					});
511				}
512
513				builder
514			})
515			// Add global settings
516			.with_global_settings(|settings| {
517				settings.with_network_spawn_timeout(1_000).with_node_spawn_timeout(300)
518			});
519
520		// Process parachains
521		let parachains = &parachains;
522		for source in self.0.parachains() {
523			let id = source.id();
524			let collators = source.collators();
525			let para =
526				parachains.get(&id).expect("expected parachain existence due to preprocessing");
527
528			// Resolve paths to parachain binary and chain spec generator
529			let binary_path = NetworkConfiguration::resolve_path(&para.binary.path())?;
530			let mut chain_spec_generator = match &para.chain_spec_generator {
531				None => None,
532				Some(path) => Some(format!(
533					"{} {}",
534					NetworkConfiguration::resolve_path(&path.path())?,
535					"{{chainName}}"
536				)),
537			};
538
539			builder = builder.with_parachain(|builder| {
540				let mut builder = builder
541					.with_id(id)
542					.with_default_args(source.default_args().into_iter().cloned().collect())
543					// Replace default command with resolved binary path
544					.with_default_command(binary_path.as_str());
545
546				// Chain spec
547				if let Some(chain) = source.chain() {
548					builder = builder.with_chain(chain.as_str());
549					// TODO: Just a temporary fix, once Paseo chain-spec-generator supports
550					// passet-hub just remove this.
551					if chain.as_str().contains("passet-hub") {
552						let chain_spec = crate::get_passet_hub_spec_content();
553						let temp_dir = std::env::temp_dir();
554						let spec_path = temp_dir.join("passet-hub-spec.json");
555						std::fs::write(&spec_path, chain_spec)
556							.expect("Failed to write passet-hub chain spec");
557						builder = builder.with_chain_spec_path(spec_path);
558						chain_spec_generator = None;
559					}
560				}
561				if let Some(command) = source.chain_spec_command() {
562					builder = builder.with_chain_spec_command(command);
563				}
564				if source.chain_spec_command_is_local() {
565					builder = builder.chain_spec_command_is_local(true);
566				}
567				if let Some(chain_spec_command_output_path) =
568					source.chain_spec_command_output_path()
569				{
570					builder =
571						builder.with_chain_spec_command_output_path(chain_spec_command_output_path)
572				}
573				if let Some(location) = source.chain_spec_path() {
574					builder = builder.with_chain_spec_path(location.clone());
575				}
576				// Configure chain spec generator
577				if let Some(command) = chain_spec_generator {
578					builder = builder.with_chain_spec_command(command);
579				}
580				// Overrides: genesis/wasm
581				if let Some(genesis) = source.genesis_overrides() {
582					builder = builder.with_genesis_overrides(genesis.clone());
583				}
584				if let Some(location) = source.wasm_override() {
585					builder = builder.with_wasm_override(location.clone());
586				}
587				// Configure whether EVM based
588				builder = builder.evm_based(self.1.contains(&id) || source.is_evm_based());
589
590				// Add collators from source
591				let mut builder = builder.with_collator(|builder| {
592					let source = collators.first().expect("expected at least one collator");
593					Self::build_node_from_source(builder, source, binary_path.as_str())
594				});
595				for source in collators.iter().skip(1) {
596					builder = builder.with_collator(|builder| {
597						Self::build_node_from_source(builder, source, binary_path.as_str())
598					});
599				}
600
601				builder
602			});
603		}
604
605		// Process HRMP channels
606		for source in self.0.hrmp_channels() {
607			builder = builder.with_hrmp_channel(|channel| {
608				channel
609					.with_sender(source.sender())
610					.with_recipient(source.recipient())
611					.with_max_capacity(source.max_capacity())
612					.with_max_message_size(source.max_message_size())
613			})
614		}
615
616		builder
617			.build()
618			.map_err(|e| Error::Config(format!("could not configure network {:?}", e)))
619	}
620
621	// Build a node using the provided builder and source config.
622	fn build_node_from_source(
623		builder: NodeConfigBuilder<Initial>,
624		source: &NodeConfig,
625		binary_path: &str,
626	) -> NodeConfigBuilder<Buildable> {
627		let mut builder = builder
628			.with_name(source.name())
629			.bootnode(source.is_bootnode())
630			.invulnerable(source.is_invulnerable())
631			.validator(source.is_validator())
632			.with_args(source.args().into_iter().cloned().collect())
633			.with_command(binary_path)
634			.with_env(source.env().into_iter().cloned().collect());
635		if let Some(command) = source.subcommand() {
636			builder = builder.with_subcommand(command.clone())
637		}
638		if let Some(port) = source.rpc_port() {
639			builder = builder.with_rpc_port(port)
640		}
641		if let Some(port) = source.ws_port() {
642			builder = builder.with_ws_port(port)
643		}
644		builder
645	}
646
647	/// Resolves the canonical path of a command specified within a network configuration file.
648	///
649	/// # Arguments
650	/// * `path` - The path to be resolved.
651	fn resolve_path(path: &Path) -> Result<String, Error> {
652		path.canonicalize()
653			.map_err(|_| {
654				Error::Config(format!("the canonical path of {:?} could not be resolved", path))
655			})
656			.map(|p| p.to_str().map(|p| p.to_string()))?
657			.ok_or_else(|| Error::Config("the path is invalid".into()))
658	}
659}
660
661impl TryFrom<&Path> for NetworkConfiguration {
662	type Error = Error;
663
664	fn try_from(file: &Path) -> Result<Self, Self::Error> {
665		if !file.exists() {
666			return Err(Error::Config(format!("The {file:?} configuration file was not found")));
667		}
668
669		// Parse the file to determine if there are any parachains using `force_decorator`
670		let contents = std::fs::read_to_string(file)?;
671		let config = contents.parse::<DocumentMut>().map_err(|err| Error::TomlError(err.into()))?;
672		let evm_based = config
673			.get("parachains")
674			.and_then(|p| p.as_array_of_tables())
675			.map(|tables| {
676				tables
677					.iter()
678					.filter_map(|table| {
679						table
680							.get("force_decorator")
681							.and_then(|i| i.as_str())
682							.filter(|v| *v == "generic-evm")
683							.and_then(|_| table.get("id"))
684							.and_then(|i| i.as_integer())
685							.map(|id| id as u32)
686					})
687					.collect()
688			})
689			.unwrap_or_default();
690
691		Ok(NetworkConfiguration(
692			NetworkConfig::load_from_toml(
693				file.to_str().expect("expected file path to be convertible to string"),
694			)
695			.map_err(|e| Error::Config(e.to_string()))?,
696			evm_based,
697		))
698	}
699}
700
701impl TryFrom<NetworkConfig> for NetworkConfiguration {
702	type Error = ();
703
704	fn try_from(value: NetworkConfig) -> Result<Self, Self::Error> {
705		Ok(NetworkConfiguration(value, Default::default()))
706	}
707}
708
709/// The configuration required to launch the relay chain.
710struct RelayChain {
711	// The runtime used.
712	runtime: Runtime,
713	/// The binary used to launch a relay chain node.
714	binary: Binary,
715	/// The additional workers required by the relay chain node.
716	workers: [&'static str; 2],
717	/// The name of the chain.
718	#[allow(dead_code)]
719	chain: String,
720	/// If applicable, the binary used to generate a chain specification.
721	chain_spec_generator: Option<Binary>,
722}
723
724/// The configuration required to launch a parachain.
725#[derive(Debug, PartialEq)]
726struct Chain {
727	/// The parachain identifier on the local network.
728	id: u32,
729	/// The binary used to launch a parachain node.
730	binary: Binary,
731	/// The name of the chain.
732	chain: Option<String>,
733	/// If applicable, the binary used to generate a chain specification.
734	chain_spec_generator: Option<Binary>,
735}
736
737impl Chain {
738	/// Initializes the configuration required to launch a parachain using a local binary.
739	///
740	/// # Arguments
741	/// * `id` - The parachain identifier on the local network.
742	/// * `path` - The path to the local binary.
743	/// * `chain` - The chain specified.
744	fn from_local(id: u32, path: PathBuf, chain: Option<&str>) -> Result<Chain, Error> {
745		let name = path
746			.file_name()
747			.and_then(|f| f.to_str())
748			.ok_or_else(|| Error::Config(format!("unable to determine file name for {path:?}")))?
749			.to_string();
750		// Check if package manifest can be found within path
751		let manifest = resolve_manifest(&name, &path)?;
752		Ok(Chain {
753			id,
754			binary: Binary::Local { name, path, manifest },
755			chain: chain.map(|c| c.to_string()),
756			chain_spec_generator: None,
757		})
758	}
759
760	/// Initializes the configuration required to launch a parachain using a binary sourced from the
761	/// specified repository.
762	///
763	/// # Arguments
764	/// * `id` - The parachain identifier on the local network.
765	/// * `repo` - The repository to be used to source the binary.
766	/// * `chain` - The chain specified.
767	/// * `cache` - The location used for caching binaries.
768	fn from_repository(
769		id: u32,
770		repo: &Repository,
771		chain: Option<&str>,
772		cache: &Path,
773	) -> Result<Chain, Error> {
774		// Check for GitHub repository to be able to download source as an archive
775		if repo.url.host_str().is_some_and(|h| h.to_lowercase() == "github.com") {
776			let github = GitHub::parse(repo.url.as_str())?;
777			let source = Source::GitHub(SourceCodeArchive {
778				owner: github.org,
779				repository: github.name,
780				reference: repo.reference.clone(),
781				manifest: None,
782				package: repo.package.clone(),
783				artifacts: vec![repo.package.clone()],
784			})
785			.into();
786			Ok(Chain {
787				id,
788				binary: Binary::Source {
789					name: repo.package.clone(),
790					source,
791					cache: cache.to_path_buf(),
792				},
793				chain: chain.map(|c| c.to_string()),
794				chain_spec_generator: None,
795			})
796		} else {
797			Ok(Chain {
798				id,
799				binary: Binary::Source {
800					name: repo.package.clone(),
801					source: Git {
802						url: repo.url.clone(),
803						reference: repo.reference.clone(),
804						manifest: None,
805						package: repo.package.clone(),
806						artifacts: vec![repo.package.clone()],
807					}
808					.into(),
809					cache: cache.to_path_buf(),
810				},
811				chain: chain.map(|c| c.to_string()),
812				chain_spec_generator: None,
813			})
814		}
815	}
816
817	fn from_omni_node(id: u32, cache: &Path) -> Result<Chain, Error> {
818		Ok(Chain {
819			id,
820			binary: Binary::Source {
821				name: PolkadotOmniNode.binary().to_string(),
822				source: Box::new(PolkadotOmniNode.source()?),
823				cache: cache.to_path_buf(),
824			},
825			chain: None,
826			chain_spec_generator: None,
827		})
828	}
829}
830
831/// Attempts to resolve the package manifest from the specified path.
832///
833/// # Arguments
834/// * `package` - The name of the package.
835/// * `path` - The path to start searching.
836fn resolve_manifest(package: &str, path: &Path) -> Result<Option<PathBuf>, Error> {
837	let matches_package = |config: &DocumentMut| {
838		config
839			.get("package")
840			.and_then(|i| i.as_table())
841			.and_then(|t| t.get("name"))
842			.and_then(|i| i.as_str()) ==
843			Some(package)
844	};
845
846	let mut manifest = Some(path);
847	'outer: while let Some(path) = manifest {
848		let manifest_path = path.join("Cargo.toml");
849		if !manifest_path.exists() {
850			manifest = path.parent();
851			continue;
852		}
853		let contents = std::fs::read_to_string(&manifest_path)?;
854		let config = contents.parse::<DocumentMut>().map_err(|err| Error::TomlError(err.into()))?;
855		// Check if package manifest
856		if matches_package(&config) {
857			break 'outer;
858		}
859		// Check if package defined as a workspace member
860		if let Some(members) = config
861			.get("workspace")
862			.and_then(|i| i.as_table())
863			.and_then(|t| t.get("members"))
864			.and_then(|m| m.as_array())
865			.map(|a| a.iter().filter_map(|v| v.as_str()))
866		{
867			// Check manifest of each member
868			for member in members {
869				let member_path = path.join(member);
870				for entry in glob(member_path.to_string_lossy().as_ref())
871					.expect("expected valid glob for workspace member")
872					.filter_map(Result::ok)
873				{
874					let manifest_path = entry.join("Cargo.toml");
875					if manifest_path.exists() {
876						let contents = std::fs::read_to_string(&manifest_path)?;
877						let config = contents
878							.parse::<DocumentMut>()
879							.map_err(|err| Error::TomlError(err.into()))?;
880						if matches_package(&config) {
881							break 'outer;
882						}
883					}
884				}
885			}
886		};
887		manifest = path.parent();
888	}
889	Ok(manifest.map(|p| p.join("Cargo.toml")))
890}
891
892#[cfg(test)]
893mod tests {
894	use super::*;
895	use anyhow::Result;
896	use std::{
897		env::current_dir,
898		fs::{File, create_dir_all, remove_dir, remove_file},
899		io::Write,
900	};
901	use tempfile::{Builder, tempdir};
902
903	pub(crate) const FALLBACK: &str = "stable2512";
904	pub(crate) const RELAY_BINARY_VERSION: &str = "stable2512";
905	pub(crate) const SYSTEM_PARA_BINARY_VERSION: &str = "stable2512";
906	const SYSTEM_PARA_RUNTIME_VERSION: &str = "v1.4.1";
907
908	mod zombienet {
909		use super::*;
910		use pop_common::{Status, helpers::with_current_dir_async};
911
912		pub(crate) struct Output;
913		impl Status for Output {
914			fn update(&self, status: &str) {
915				println!("{status}")
916			}
917		}
918
919		#[tokio::test]
920		async fn new_with_relay_only_works() -> Result<()> {
921			let temp_dir = tempdir()?;
922			let cache = PathBuf::from(temp_dir.path());
923			let config = Builder::new().suffix(".toml").tempfile()?;
924			writeln!(
925				config.as_file(),
926				r#"
927[relaychain]
928chain = "paseo-local"
929"#
930			)?;
931
932			let zombienet = Zombienet::new(
933				&cache,
934				config.path().try_into()?,
935				Some(RELAY_BINARY_VERSION),
936				None,
937				None,
938				None,
939				None,
940			)
941			.await?;
942
943			let relay_chain = &zombienet.relay_chain.binary;
944			assert_eq!(relay_chain.name(), "polkadot");
945			assert_eq!(
946				relay_chain.path(),
947				temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
948			);
949			assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
950			assert!(matches!(
951				relay_chain,
952				Binary::Source { source, .. }
953					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
954						if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
955					)
956			));
957			assert!(zombienet.parachains.is_empty());
958			assert_eq!(zombienet.relay_chain(), "paseo-local");
959			assert!(!zombienet.hrmp_channels());
960			Ok(())
961		}
962
963		#[tokio::test]
964		async fn new_with_relay_only_from_network_config_works() -> Result<()> {
965			let temp_dir = tempdir()?;
966			let cache = PathBuf::from(temp_dir.path());
967			let config = NetworkConfigBuilder::new()
968				.with_relaychain(|b| {
969					b.with_chain("paseo-local").with_validator(|b| b.with_name("alice"))
970				})
971				.build()
972				.unwrap();
973
974			let zombienet = Zombienet::new(
975				&cache,
976				config.try_into().unwrap(),
977				Some(RELAY_BINARY_VERSION),
978				None,
979				None,
980				None,
981				None,
982			)
983			.await?;
984
985			let relay_chain = &zombienet.relay_chain.binary;
986			assert_eq!(relay_chain.name(), "polkadot");
987			assert_eq!(
988				relay_chain.path(),
989				temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
990			);
991			assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
992			assert!(matches!(
993				relay_chain,
994				Binary::Source { source, .. }
995					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
996						if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
997					)
998			));
999			assert!(zombienet.parachains.is_empty());
1000			assert_eq!(zombienet.relay_chain(), "paseo-local");
1001			assert!(!zombienet.hrmp_channels());
1002			Ok(())
1003		}
1004
1005		#[tokio::test]
1006		async fn new_with_relay_chain_spec_generator_works() -> Result<()> {
1007			let temp_dir = tempdir()?;
1008			let cache = PathBuf::from(temp_dir.path());
1009			let config = Builder::new().suffix(".toml").tempfile()?;
1010			writeln!(
1011				config.as_file(),
1012				r#"
1013[relaychain]
1014chain = "paseo-local"
1015"#
1016			)?;
1017			let version = "v1.3.3";
1018
1019			let zombienet = Zombienet::new(
1020				&cache,
1021				config.path().try_into()?,
1022				None,
1023				Some(version),
1024				None,
1025				None,
1026				None,
1027			)
1028			.await?;
1029
1030			assert_eq!(zombienet.relay_chain.chain, "paseo-local");
1031			let chain_spec_generator = &zombienet.relay_chain.chain_spec_generator.unwrap();
1032			assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator");
1033			assert_eq!(
1034				chain_spec_generator.path(),
1035				temp_dir.path().join(format!("paseo-chain-spec-generator-{version}"))
1036			);
1037			assert_eq!(chain_spec_generator.version().unwrap(), version);
1038			assert!(matches!(
1039				chain_spec_generator,
1040				Binary::Source { source, .. }
1041					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1042						if *tag == Some(version.to_string())
1043					)
1044			));
1045			assert!(zombienet.parachains.is_empty());
1046			Ok(())
1047		}
1048
1049		#[tokio::test]
1050		async fn new_with_default_command_works() -> Result<()> {
1051			let temp_dir = tempdir()?;
1052			let cache = PathBuf::from(temp_dir.path());
1053			let config = Builder::new().suffix(".toml").tempfile()?;
1054			writeln!(
1055				config.as_file(),
1056				r#"
1057[relaychain]
1058chain = "paseo-local"
1059default_command = "./bin-stable2512/polkadot"
1060"#
1061			)?;
1062
1063			let zombienet = Zombienet::new(
1064				&cache,
1065				config.path().try_into()?,
1066				Some(RELAY_BINARY_VERSION),
1067				None,
1068				None,
1069				None,
1070				None,
1071			)
1072			.await?;
1073
1074			let relay_chain = &zombienet.relay_chain.binary;
1075			assert_eq!(relay_chain.name(), "polkadot");
1076			assert_eq!(
1077				relay_chain.path(),
1078				temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1079			);
1080			assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1081			assert!(matches!(
1082				relay_chain,
1083				Binary::Source { source, ..}
1084					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1085						if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1086					)
1087			));
1088			assert!(zombienet.parachains.is_empty());
1089			Ok(())
1090		}
1091
1092		#[tokio::test]
1093		async fn new_accepts_parachain_names_without_repo_urls() -> Result<()> {
1094			let temp_dir = tempdir()?;
1095			let cache = PathBuf::from(temp_dir.path());
1096			let config = Builder::new().suffix(".toml").tempfile()?;
1097			writeln!(
1098				config.as_file(),
1099				r#"
1100[relaychain]
1101chain = "paseo-local"
1102"#
1103			)?;
1104
1105			let zombienet = Zombienet::new(
1106				&cache,
1107				config.path().try_into()?,
1108				Some(RELAY_BINARY_VERSION),
1109				None,
1110				None,
1111				None,
1112				Some(&vec!["asset-hub".to_string(), "coretime".to_string()]),
1113			)
1114			.await?;
1115
1116			assert!(zombienet.parachains.is_empty());
1117			Ok(())
1118		}
1119
1120		#[tokio::test]
1121		async fn new_with_node_command_works() -> Result<()> {
1122			let temp_dir = tempdir()?;
1123			let cache = PathBuf::from(temp_dir.path());
1124			let config = Builder::new().suffix(".toml").tempfile()?;
1125			writeln!(
1126				config.as_file(),
1127				r#"
1128[relaychain]
1129chain = "paseo-local"
1130
1131[[relaychain.nodes]]
1132name = "alice"
1133validator = true
1134command = "polkadot"
1135"#
1136			)?;
1137
1138			let zombienet = Zombienet::new(
1139				&cache,
1140				config.path().try_into()?,
1141				Some(RELAY_BINARY_VERSION),
1142				None,
1143				None,
1144				None,
1145				None,
1146			)
1147			.await?;
1148
1149			let relay_chain = &zombienet.relay_chain.binary;
1150			assert_eq!(relay_chain.name(), "polkadot");
1151			assert_eq!(
1152				relay_chain.path(),
1153				temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1154			);
1155			assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1156			assert!(matches!(
1157				relay_chain,
1158				Binary::Source { source, .. }
1159					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1160						if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1161					)
1162			));
1163			assert!(zombienet.parachains.is_empty());
1164			Ok(())
1165		}
1166
1167		#[tokio::test]
1168		async fn new_with_node_command_from_network_config_works() -> Result<()> {
1169			let temp_dir = tempdir()?;
1170			let cache = PathBuf::from(temp_dir.path());
1171			let config = NetworkConfigBuilder::new()
1172				.with_relaychain(|b| {
1173					b.with_chain("paseo-local")
1174						.with_validator(|b| b.with_name("alice").with_command("polkadot"))
1175				})
1176				.build()
1177				.unwrap();
1178
1179			let zombienet = Zombienet::new(
1180				&cache,
1181				config.try_into().unwrap(),
1182				Some(RELAY_BINARY_VERSION),
1183				None,
1184				None,
1185				None,
1186				None,
1187			)
1188			.await?;
1189
1190			let relay_chain = &zombienet.relay_chain.binary;
1191			assert_eq!(relay_chain.name(), "polkadot");
1192			assert_eq!(
1193				relay_chain.path(),
1194				temp_dir.path().join(format!("polkadot-{RELAY_BINARY_VERSION}"))
1195			);
1196			assert_eq!(relay_chain.version().unwrap(), RELAY_BINARY_VERSION);
1197			assert!(matches!(
1198				relay_chain,
1199				Binary::Source { source, .. }
1200					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1201						if *tag == Some(format!("polkadot-{RELAY_BINARY_VERSION}"))
1202					)
1203			));
1204			assert!(zombienet.parachains.is_empty());
1205			Ok(())
1206		}
1207
1208		#[tokio::test]
1209		async fn new_ensures_node_commands_valid() -> Result<()> {
1210			let temp_dir = tempdir()?;
1211			let cache = PathBuf::from(temp_dir.path());
1212			let config = Builder::new().suffix(".toml").tempfile()?;
1213			writeln!(
1214				config.as_file(),
1215				r#"
1216[relaychain]
1217chain = "paseo-local"
1218
1219[[relaychain.nodes]]
1220name = "alice"
1221validator = true
1222command = "polkadot"
1223
1224[[relaychain.nodes]]
1225name = "bob"
1226validator = true
1227command = "polkadot-stable2512"
1228"#
1229			)?;
1230
1231			assert!(matches!(
1232				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1233				Err(Error::UnsupportedCommand(error))
1234				if error == "the relay chain command is unsupported: polkadot-stable2512"
1235			));
1236			Ok(())
1237		}
1238
1239		#[tokio::test]
1240		async fn new_ensures_node_command_valid() -> Result<()> {
1241			let temp_dir = tempdir()?;
1242			let cache = PathBuf::from(temp_dir.path());
1243			let config = Builder::new().suffix(".toml").tempfile()?;
1244			writeln!(
1245				config.as_file(),
1246				r#"
1247[relaychain]
1248chain = "paseo-local"
1249default_command = "polkadot"
1250
1251[[relaychain.nodes]]
1252name = "alice"
1253validator = true
1254command = "polkadot-stable2512"
1255"#
1256			)?;
1257
1258			assert!(matches!(
1259				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1260				Err(Error::UnsupportedCommand(error))
1261				if error == "the relay chain command is unsupported: polkadot-stable2512"
1262			));
1263			Ok(())
1264		}
1265
1266		#[tokio::test]
1267		async fn new_ensures_node_command_from_network_config_valid() -> Result<()> {
1268			let temp_dir = tempdir()?;
1269			let cache = PathBuf::from(temp_dir.path());
1270			let config = NetworkConfigBuilder::new()
1271				.with_relaychain(|b| {
1272					b.with_chain("paseo-local")
1273						.with_validator(|b| b.with_name("alice").with_command("polkadot"))
1274						.with_validator(|b| b.with_name("bob").with_command("polkadot"))
1275						.with_validator(|b| b.with_name("charlie").with_command("p0lk4d0t"))
1276				})
1277				.build()
1278				.unwrap();
1279
1280			assert!(matches!(
1281				Zombienet::new(&cache, config.try_into().unwrap(), None, None, None, None,None).await,
1282				Err(Error::UnsupportedCommand(error))
1283					if error == "the relay chain command is unsupported: p0lk4d0t"
1284			));
1285			Ok(())
1286		}
1287
1288		#[tokio::test]
1289		async fn new_with_system_chain_works() -> Result<()> {
1290			let temp_dir = tempdir()?;
1291			let cache = PathBuf::from(temp_dir.path());
1292			let config = Builder::new().suffix(".toml").tempfile()?;
1293			writeln!(
1294				config.as_file(),
1295				r#"
1296[relaychain]
1297chain = "paseo-local"
1298
1299[[parachains]]
1300id = 1000
1301chain = "asset-hub-paseo-local"
1302"#
1303			)?;
1304
1305			let zombienet = Zombienet::new(
1306				&cache,
1307				config.path().try_into()?,
1308				Some(RELAY_BINARY_VERSION),
1309				None,
1310				Some(SYSTEM_PARA_BINARY_VERSION),
1311				None,
1312				None,
1313			)
1314			.await?;
1315
1316			assert_eq!(zombienet.parachains.len(), 1);
1317			let system_parachain = &zombienet.parachains.get(&1000).unwrap().binary;
1318			assert_eq!(system_parachain.name(), "polkadot-parachain");
1319			assert_eq!(
1320				system_parachain.path(),
1321				temp_dir.path().join(format!("polkadot-parachain-{SYSTEM_PARA_BINARY_VERSION}"))
1322			);
1323			assert_eq!(system_parachain.version().unwrap(), SYSTEM_PARA_BINARY_VERSION);
1324			assert!(matches!(
1325				system_parachain,
1326				Binary::Source { source, .. }
1327					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1328						if *tag == Some(format!("polkadot-{SYSTEM_PARA_BINARY_VERSION}"))
1329					)
1330			));
1331			Ok(())
1332		}
1333
1334		#[tokio::test]
1335		async fn new_with_system_chain_spec_generator_works() -> Result<()> {
1336			let temp_dir = tempdir()?;
1337			let cache = PathBuf::from(temp_dir.path());
1338			let config = Builder::new().suffix(".toml").tempfile()?;
1339			writeln!(
1340				config.as_file(),
1341				r#"
1342[relaychain]
1343chain = "paseo-local"
1344
1345[[parachains]]
1346id = 1000
1347chain = "asset-hub-paseo-local"
1348"#
1349			)?;
1350
1351			let zombienet = Zombienet::new(
1352				&cache,
1353				config.path().try_into()?,
1354				None,
1355				None,
1356				None,
1357				Some(SYSTEM_PARA_RUNTIME_VERSION),
1358				None,
1359			)
1360			.await?;
1361
1362			assert_eq!(zombienet.parachains.len(), 1);
1363			let system_parachain = &zombienet.parachains.get(&1000).unwrap();
1364			assert_eq!(system_parachain.chain.as_ref().unwrap(), "asset-hub-paseo-local");
1365			let chain_spec_generator = system_parachain.chain_spec_generator.as_ref().unwrap();
1366			assert_eq!(chain_spec_generator.name(), "paseo-chain-spec-generator");
1367			assert_eq!(
1368				chain_spec_generator.path(),
1369				temp_dir
1370					.path()
1371					.join(format!("paseo-chain-spec-generator-{SYSTEM_PARA_RUNTIME_VERSION}"))
1372			);
1373			assert_eq!(chain_spec_generator.version().unwrap(), SYSTEM_PARA_RUNTIME_VERSION);
1374			assert!(matches!(
1375				chain_spec_generator,
1376				Binary::Source { source, .. }
1377					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1378						if *tag == Some(SYSTEM_PARA_RUNTIME_VERSION.to_string())
1379					)
1380			));
1381			Ok(())
1382		}
1383
1384		#[tokio::test]
1385		async fn new_with_pop_works() -> Result<()> {
1386			let temp_dir = tempdir()?;
1387			let cache = PathBuf::from(temp_dir.path());
1388			let config = Builder::new().suffix(".toml").tempfile()?;
1389			writeln!(
1390				config.as_file(),
1391				r#"
1392[relaychain]
1393chain = "paseo-local"
1394
1395[[parachains]]
1396id = 4385
1397default_command = "pop-node"
1398"#
1399			)?;
1400
1401			let zombienet =
1402				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1403					.await?;
1404
1405			assert_eq!(zombienet.parachains.len(), 1);
1406			let pop = &zombienet.parachains.get(&4385).unwrap().binary;
1407			let version = pop.latest().unwrap();
1408			assert_eq!(pop.name(), "pop-node");
1409			assert_eq!(pop.path(), temp_dir.path().join(format!("pop-node-{version}")));
1410			assert_eq!(pop.version().unwrap(), version);
1411			assert!(matches!(
1412				pop,
1413				Binary::Source { source, .. }
1414					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1415						if *tag == Some(format!("node-{version}"))
1416					)
1417			));
1418			Ok(())
1419		}
1420
1421		#[tokio::test]
1422		async fn new_with_pop_version_works() -> Result<()> {
1423			let temp_dir = tempdir()?;
1424			let cache = PathBuf::from(temp_dir.path());
1425			let config = Builder::new().suffix(".toml").tempfile()?;
1426			writeln!(
1427				config.as_file(),
1428				r#"
1429[relaychain]
1430chain = "paseo-local"
1431
1432[[parachains]]
1433id = 4385
1434default_command = "pop-node"
1435"#
1436			)?;
1437			let version = "v1.0";
1438
1439			let zombienet = Zombienet::new(
1440				&cache,
1441				config.path().try_into()?,
1442				None,
1443				None,
1444				None,
1445				None,
1446				Some(&vec![format!("https://github.com/r0gue-io/pop-node#{version}")]),
1447			)
1448			.await?;
1449
1450			assert_eq!(zombienet.parachains.len(), 1);
1451			let pop = &zombienet.parachains.get(&4385).unwrap().binary;
1452			assert_eq!(pop.name(), "pop-node");
1453			assert_eq!(pop.path(), temp_dir.path().join(format!("pop-node-{version}")));
1454			assert_eq!(pop.version().unwrap(), version);
1455			assert!(matches!(
1456				pop,
1457				Binary::Source { source, .. }
1458					if matches!(source.as_ref(), Source::GitHub(ReleaseArchive { tag, .. })
1459						if *tag == Some(format!("node-{version}"))
1460					)
1461			));
1462			Ok(())
1463		}
1464
1465		#[tokio::test]
1466		async fn new_with_local_parachain_works() -> Result<()> {
1467			let temp_dir = tempdir()?;
1468			let cache = PathBuf::from(temp_dir.path());
1469			let config = Builder::new().suffix(".toml").tempfile()?;
1470			writeln!(
1471				config.as_file(),
1472				r#"
1473[relaychain]
1474chain = "paseo-local"
1475
1476[[parachains]]
1477id = 2000
1478default_command = "./target/release/parachain-template-node"
1479"#
1480			)?;
1481
1482			let zombienet =
1483				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1484					.await?;
1485
1486			assert_eq!(zombienet.parachains.len(), 1);
1487			let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1488			assert_eq!(pop.name(), "parachain-template-node");
1489			assert_eq!(pop.path(), Path::new("./target/release/parachain-template-node"));
1490			assert_eq!(pop.version(), None);
1491			assert!(matches!(pop, Binary::Local { .. }));
1492			Ok(())
1493		}
1494
1495		#[tokio::test]
1496		async fn new_with_local_parachain_without_path_works() -> Result<()> {
1497			let temp_dir = tempdir()?;
1498			let cache = PathBuf::from(temp_dir.path());
1499			let config = Builder::new().suffix(".toml").tempfile()?;
1500			writeln!(
1501				config.as_file(),
1502				r#"
1503[relaychain]
1504chain = "paseo-local"
1505
1506[[parachains]]
1507id = 1000
1508
1509[parachains.collator]
1510name = "collator"
1511command = "parachain-template-node"
1512
1513[[parachains]]
1514id = 2000
1515
1516[parachains.collator]
1517name = "collator"
1518command = "substrate-contracts-node"
1519"#
1520			)?;
1521			let temp_workspace = tempdir()?;
1522			with_current_dir_async(temp_workspace.path(), async || {
1523				// Expecting failure since no custom path is provided and binaries don't exist in
1524				// the default build directory.
1525				assert!(matches!(
1526					Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1527					Err(Error::MissingBinary(command))
1528					if command == "parachain-template-node"
1529				));
1530				// Create the binaries in the default build directory.
1531				let parachain_template = PathBuf::from("target/release/parachain-template-node");
1532				create_dir_all(parachain_template.parent().unwrap())?;
1533				File::create(&parachain_template)?;
1534				// Ensure the the binary is detected in the debug profile too.
1535				let parachain_contracts_template =
1536					PathBuf::from("target/debug/substrate-contracts-node");
1537				create_dir_all(parachain_contracts_template.parent().unwrap())?;
1538				File::create(&parachain_contracts_template)?;
1539
1540				let zombienet =
1541					Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1542						.await?;
1543				// Remove the binaries created above after Zombienet initialization, as they are no
1544				// longer needed.
1545				remove_file(&parachain_template)?;
1546				remove_file(&parachain_contracts_template)?;
1547				remove_dir(parachain_template.parent().unwrap())?;
1548				remove_dir(parachain_contracts_template.parent().unwrap())?;
1549
1550				assert_eq!(zombienet.parachains.len(), 2);
1551				let parachain = &zombienet.parachains.get(&1000).unwrap().binary;
1552				assert_eq!(parachain.name(), "parachain-template-node");
1553				assert_eq!(parachain.path(), Path::new("./target/release/parachain-template-node"));
1554				assert_eq!(parachain.version(), None);
1555				assert!(matches!(parachain, Binary::Local { .. }));
1556				let contract_parachain = &zombienet.parachains.get(&2000).unwrap().binary;
1557				assert_eq!(contract_parachain.name(), "substrate-contracts-node");
1558				assert_eq!(
1559					contract_parachain.path(),
1560					Path::new("./target/debug/substrate-contracts-node")
1561				);
1562				assert_eq!(contract_parachain.version(), None);
1563				assert!(matches!(contract_parachain, Binary::Local { .. }));
1564				Ok(())
1565			})
1566			.await
1567		}
1568
1569		#[tokio::test]
1570		async fn new_with_collator_command_works() -> Result<()> {
1571			let temp_dir = tempdir()?;
1572			let cache = PathBuf::from(temp_dir.path());
1573			let config = Builder::new().suffix(".toml").tempfile()?;
1574			writeln!(
1575				config.as_file(),
1576				r#"
1577[relaychain]
1578chain = "paseo-local"
1579
1580[[parachains]]
1581id = 2000
1582
1583[[parachains.collators]]
1584name = "collator-01"
1585command = "./target/release/parachain-template-node"
1586"#
1587			)?;
1588
1589			let zombienet =
1590				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1591					.await?;
1592
1593			assert_eq!(zombienet.parachains.len(), 1);
1594			let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1595			assert_eq!(pop.name(), "parachain-template-node");
1596			assert_eq!(pop.path(), Path::new("./target/release/parachain-template-node"));
1597			assert_eq!(pop.version(), None);
1598			assert!(matches!(pop, Binary::Local { .. }));
1599			Ok(())
1600		}
1601
1602		#[tokio::test]
1603		async fn new_with_moonbeam_works() -> Result<()> {
1604			let temp_dir = tempdir()?;
1605			let cache = PathBuf::from(temp_dir.path());
1606			let config = Builder::new().suffix(".toml").tempfile()?;
1607			writeln!(
1608				config.as_file(),
1609				r#"
1610[relaychain]
1611chain = "paseo-local"
1612
1613[[parachains]]
1614id = 2000
1615default_command = "moonbeam"
1616"#
1617			)?;
1618			let version = "v0.38.0";
1619
1620			let zombienet = Zombienet::new(
1621				&cache,
1622				config.path().try_into()?,
1623				None,
1624				None,
1625				None,
1626				None,
1627				Some(&vec![format!("https://github.com/moonbeam-foundation/moonbeam#{version}")]),
1628			)
1629			.await?;
1630
1631			assert_eq!(zombienet.parachains.len(), 1);
1632			let pop = &zombienet.parachains.get(&2000).unwrap().binary;
1633			assert_eq!(pop.name(), "moonbeam");
1634			assert_eq!(pop.path(), temp_dir.path().join(format!("moonbeam-{version}")));
1635			assert_eq!(pop.version().unwrap(), version);
1636			assert!(matches!(
1637				pop,
1638				Binary::Source { source, .. }
1639					if matches!(source.as_ref(), Source::GitHub(SourceCodeArchive { reference, .. })
1640						if *reference == Some(version.to_string())
1641					)
1642			));
1643			Ok(())
1644		}
1645
1646		#[tokio::test]
1647		async fn new_with_hrmp_channels_works() -> Result<()> {
1648			let temp_dir = tempdir()?;
1649			let cache = PathBuf::from(temp_dir.path());
1650			let config = Builder::new().suffix(".toml").tempfile()?;
1651			writeln!(
1652				config.as_file(),
1653				r#"
1654[relaychain]
1655chain = "paseo-local"
1656
1657[[parachains]]
1658id = 1000
1659chain = "asset-hub-paseo-local"
1660
1661[[parachains]]
1662id = 4385
1663default_command = "pop-node"
1664
1665[[hrmp_channels]]
1666sender = 4385
1667recipient = 1000
1668max_capacity = 1000
1669max_message_size = 8000
1670"#
1671			)?;
1672
1673			let zombienet =
1674				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1675					.await?;
1676
1677			assert!(zombienet.hrmp_channels());
1678			Ok(())
1679		}
1680
1681		#[tokio::test]
1682		async fn new_handles_missing_binary() -> Result<()> {
1683			let temp_dir = tempdir()?;
1684			let cache = PathBuf::from(temp_dir.path());
1685			let config = Builder::new().suffix(".toml").tempfile()?;
1686			writeln!(
1687				config.as_file(),
1688				r#"
1689[relaychain]
1690chain = "paseo-local"
1691
1692[[parachains]]
1693id = 404
1694default_command = "missing-binary"
1695"#
1696			)?;
1697
1698			assert!(matches!(
1699				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None).await,
1700				Err(Error::MissingBinary(command))
1701				if command == "missing-binary"
1702			));
1703			Ok(())
1704		}
1705
1706		#[tokio::test]
1707		async fn binaries_works() -> Result<()> {
1708			let temp_dir = tempdir()?;
1709			let cache = PathBuf::from(temp_dir.path());
1710			let config = Builder::new().suffix(".toml").tempfile()?;
1711			writeln!(
1712				config.as_file(),
1713				r#"
1714[relaychain]
1715chain = "paseo-local"
1716
1717[[parachains]]
1718id = 1000
1719chain = "asset-hub-paseo-local"
1720
1721[[parachains]]
1722id = 2000
1723default_command = "./target/release/parachain-template-node"
1724
1725[[parachains]]
1726id = 4385
1727default_command = "pop-node"
1728"#
1729			)?;
1730
1731			let mut zombienet =
1732				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1733					.await?;
1734			assert_eq!(zombienet.binaries().count(), 6);
1735			Ok(())
1736		}
1737
1738		#[tokio::test]
1739		async fn binaries_includes_chain_spec_generators() -> Result<()> {
1740			let temp_dir = tempdir()?;
1741			let cache = PathBuf::from(temp_dir.path());
1742			let config = Builder::new().suffix(".toml").tempfile()?;
1743			writeln!(
1744				config.as_file(),
1745				r#"
1746[relaychain]
1747chain = "paseo-local"
1748
1749[[parachains]]
1750id = 1000
1751chain = "asset-hub-paseo-local"
1752"#
1753			)?;
1754
1755			let mut zombienet =
1756				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1757					.await?;
1758			assert_eq!(zombienet.binaries().count(), 4);
1759			Ok(())
1760		}
1761
1762		#[tokio::test]
1763		async fn spawn_ensures_relay_chain_binary_exists() -> Result<()> {
1764			let temp_dir = tempdir()?;
1765			let cache = PathBuf::from(temp_dir.path());
1766			let config = Builder::new().suffix(".toml").tempfile()?;
1767			writeln!(
1768				config.as_file(),
1769				r#"
1770[relaychain]
1771chain = "paseo-local"
1772"#
1773			)?;
1774
1775			let mut zombienet =
1776				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1777					.await?;
1778			assert!(matches!(
1779				zombienet.spawn().await,
1780				Err(Error::MissingBinary(error))
1781				if error == "polkadot"
1782			));
1783			Ok(())
1784		}
1785
1786		#[tokio::test]
1787		async fn spawn_ensures_relay_chain_version_set() -> Result<()> {
1788			let temp_dir = tempdir()?;
1789			let cache = PathBuf::from(temp_dir.path());
1790			let config = Builder::new().suffix(".toml").tempfile()?;
1791			writeln!(
1792				config.as_file(),
1793				r#"
1794[relaychain]
1795chain = "paseo-local"
1796"#
1797			)?;
1798			File::create(cache.join("polkadot"))?;
1799
1800			let mut zombienet =
1801				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1802					.await?;
1803			let Binary::Source { source, .. } = &mut zombienet.relay_chain.binary else {
1804				panic!("expected binary which needs to be sourced")
1805			};
1806			if let Source::GitHub(ReleaseArchive { tag, .. }) = source.as_mut() {
1807				*tag = None
1808			}
1809			assert!(matches!(
1810				zombienet.spawn().await,
1811				Err(Error::MissingBinary(error))
1812				if error == "Could not determine version for `polkadot` binary",
1813			));
1814			Ok(())
1815		}
1816
1817		#[tokio::test]
1818		async fn spawn_symlinks_workers() -> Result<()> {
1819			let temp_dir = tempdir()?;
1820			let cache = PathBuf::from(temp_dir.path());
1821			let config = Builder::new().suffix(".toml").tempfile()?;
1822			writeln!(
1823				config.as_file(),
1824				r#"
1825[relaychain]
1826chain = "paseo-local"
1827"#
1828			)?;
1829			File::create(cache.join(format!("polkadot-{RELAY_BINARY_VERSION}")))?;
1830			File::create(cache.join(format!("polkadot-execute-worker-{RELAY_BINARY_VERSION}")))?;
1831			File::create(cache.join(format!("polkadot-prepare-worker-{RELAY_BINARY_VERSION}")))?;
1832
1833			let mut zombienet = Zombienet::new(
1834				&cache,
1835				config.path().try_into()?,
1836				Some(RELAY_BINARY_VERSION),
1837				None,
1838				None,
1839				None,
1840				None,
1841			)
1842			.await?;
1843			assert!(!cache.join("polkadot-execute-worker").exists());
1844			assert!(!cache.join("polkadot-prepare-worker").exists());
1845			let _ = zombienet.spawn().await;
1846			assert!(cache.join("polkadot-execute-worker").exists());
1847			assert!(cache.join("polkadot-prepare-worker").exists());
1848			let _ = zombienet.spawn().await;
1849			Ok(())
1850		}
1851
1852		#[tokio::test]
1853		async fn spawn_works() -> Result<()> {
1854			let temp_dir = tempdir()?;
1855			let cache = PathBuf::from(temp_dir.path());
1856			let config = Builder::new().suffix(".toml").tempfile()?;
1857			writeln!(
1858				config.as_file(),
1859				r#"
1860[relaychain]
1861chain = "paseo-local"
1862
1863[[relaychain.nodes]]
1864name = "alice"
1865validator = true
1866"#
1867			)?;
1868
1869			let mut zombienet =
1870				Zombienet::new(&cache, config.path().try_into()?, None, None, None, None, None)
1871					.await?;
1872			for b in zombienet.binaries() {
1873				b.source(true, &Output, true).await?;
1874			}
1875
1876			zombienet.spawn().await?;
1877			Ok(())
1878		}
1879	}
1880
1881	mod network_config {
1882		use super::{Relay::*, *};
1883		use crate::registry::chains;
1884		use std::{
1885			fs::{File, create_dir_all},
1886			io::Write,
1887			path::PathBuf,
1888		};
1889		use tempfile::{Builder, tempdir};
1890
1891		#[test]
1892		fn initializing_from_file_fails_when_missing() {
1893			assert!(NetworkConfiguration::try_from(PathBuf::new().as_path()).is_err());
1894		}
1895
1896		#[test]
1897		fn initializing_from_file_fails_when_malformed() -> Result<(), Error> {
1898			let config = Builder::new().suffix(".toml").tempfile()?;
1899			writeln!(config.as_file(), "[")?;
1900			assert!(matches!(
1901				NetworkConfiguration::try_from(config.path()),
1902				Err(Error::TomlError(..))
1903			));
1904			Ok(())
1905		}
1906
1907		#[test]
1908		fn initializing_from_file_fails_when_relaychain_missing() -> Result<(), Error> {
1909			let config = Builder::new().suffix(".toml").tempfile()?;
1910			assert!(matches!(
1911				NetworkConfiguration::try_from(config.path()),
1912				Err(Error::Config(error)) if error == "Relay chain does not exist."
1913			));
1914			Ok(())
1915		}
1916
1917		#[tokio::test]
1918		async fn initializing_from_file_fails_when_parachain_id_missing() -> Result<()> {
1919			let config = Builder::new().suffix(".toml").tempfile()?;
1920			writeln!(
1921				config.as_file(),
1922				r#"
1923[relaychain]
1924chain = "paseo-local"
1925
1926[[parachains]]
1927"#
1928			)?;
1929
1930			assert!(matches!(
1931				<&Path as TryInto<NetworkConfiguration>>::try_into(config.path()),
1932				Err(Error::Config(error))
1933				if error == "TOML parse error at line 5, column 1\n  |\n5 | [[parachains]]\n  | ^^^^^^^^^^^^^^\nmissing field `id`\n"
1934			));
1935			Ok(())
1936		}
1937
1938		#[test]
1939		fn initializes_relay_from_file() -> Result<(), Error> {
1940			let config = Builder::new().suffix(".toml").tempfile()?;
1941			writeln!(
1942				config.as_file(),
1943				r#"
1944				[relaychain]
1945				chain = "paseo-local"
1946				default_command = "polkadot"
1947				[[relaychain.nodes]]
1948				name = "alice"
1949			"#
1950			)?;
1951			let network_config = NetworkConfiguration::try_from(config.path())?;
1952			let relay_chain = network_config.0.relaychain();
1953			assert_eq!("paseo-local", relay_chain.chain().as_str());
1954			assert_eq!(Some("polkadot"), relay_chain.default_command().map(|c| c.as_str()));
1955			let nodes = relay_chain.nodes();
1956			assert_eq!("alice", nodes.first().unwrap().name());
1957			assert!(network_config.0.parachains().is_empty());
1958			Ok(())
1959		}
1960
1961		#[test]
1962		fn initializes_parachains_from_file() -> Result<(), Error> {
1963			let config = Builder::new().suffix(".toml").tempfile()?;
1964			writeln!(
1965				config.as_file(),
1966				r#"
1967				[relaychain]
1968				chain = "paseo-local"
1969				[[parachains]]
1970				id = 2000
1971				default_command = "node"
1972			"#
1973			)?;
1974			let network_config = NetworkConfiguration::try_from(config.path())?;
1975			let parachains = network_config.0.parachains();
1976			let para_2000 = parachains.first().unwrap();
1977			assert_eq!(2000, para_2000.id());
1978			assert_eq!(Some("node"), para_2000.default_command().map(|c| c.as_str()));
1979			Ok(())
1980		}
1981
1982		#[test]
1983		fn initializing_from_network_config_works() -> Result<(), Error> {
1984			let network_config = NetworkConfigBuilder::new()
1985				.with_relaychain(|b| {
1986					b.with_chain("paseo-local").with_validator(|b| b.with_name("alice"))
1987				})
1988				.build()
1989				.unwrap();
1990			let config = NetworkConfiguration::try_from(network_config.clone()).unwrap();
1991			assert_eq!(config, NetworkConfiguration(network_config, Default::default()));
1992			Ok(())
1993		}
1994
1995		#[test]
1996		fn build_works() -> Result<(), Error> {
1997			let port = 9944;
1998			for relay in [Paseo, Kusama, Polkadot, Westend] {
1999				let mut chains: Vec<_> = chains(&relay).to_vec();
2000				chains
2001					.iter_mut()
2002					.enumerate()
2003					.for_each(|(i, chain)| chain.set_port(port + i as u16 + 1));
2004				let relay_chain = relay.chain();
2005
2006				let config =
2007					NetworkConfiguration::build(relay, Some(port), Some(chains.as_slice()))?;
2008
2009				let relay_config = config.0.relaychain();
2010				assert_eq!(relay_config.chain().as_str(), relay_chain);
2011				// TODO: Just a temporary removal, once Paseo chain-spec-generator supports
2012				// passet-hub just remove the comment.
2013				//assert_eq!(relay_config.nodes().len(), chains.len().max(2));
2014				assert_eq!(
2015					relay_config.nodes().iter().map(|n| n.name()).collect::<Vec<_>>(),
2016					VALIDATORS.into_iter().take(relay_config.nodes().len()).collect::<Vec<_>>()
2017				);
2018				assert_eq!(relay_config.nodes().first().unwrap().rpc_port().unwrap(), port);
2019
2020				let parachains = config.0.parachains();
2021				assert_eq!(parachains.len(), chains.len());
2022				for (i, chain) in chains.iter().enumerate() {
2023					let parachain = parachains.iter().find(|p| p.id() == chain.id()).unwrap();
2024					assert_eq!(parachain.chain().unwrap().as_str(), chain.chain());
2025					assert_eq!(parachain.default_command().unwrap().as_str(), chain.binary());
2026					println!("{} {}", relay_chain, chain.name());
2027					assert_eq!(
2028						parachain.genesis_overrides().is_some(),
2029						chain.genesis_overrides().is_some() ||
2030							chains.iter().any(|r| r
2031								.requires()
2032								.map(|r| r.contains_key(&chain.as_any().type_id()))
2033								.unwrap_or_default())
2034					);
2035					let collators = parachain.collators();
2036					assert_eq!(collators.len(), 1);
2037					let collator = collators.first().unwrap();
2038					assert_eq!(collator.name(), &format!("{}-collator", chain.name()));
2039					assert_eq!(
2040						collator.args().len(),
2041						chain.args().map(|a| a.len()).unwrap_or_default()
2042					);
2043					assert_eq!(collator.rpc_port(), Some(port + i as u16 + 1));
2044				}
2045
2046				// Ensure channels open between all chains
2047				let channels = config.0.hrmp_channels();
2048				assert_eq!(channels.len(), chains.len() * (chains.len() - 1));
2049				for chain in chains.iter() {
2050					for other in chains.iter().filter(|r| r.id() != chain.id()) {
2051						assert!(
2052							channels
2053								.iter()
2054								.any(|c| c.sender() == chain.id() && c.recipient() == other.id())
2055						);
2056						assert!(
2057							channels
2058								.iter()
2059								.any(|c| c.sender() == other.id() && c.recipient() == chain.id())
2060						);
2061					}
2062				}
2063				assert!(
2064					channels
2065						.iter()
2066						.all(|c| c.max_capacity() == 1000 && c.max_message_size() == 8000)
2067				);
2068			}
2069			Ok(())
2070		}
2071
2072		#[test]
2073		fn adapt_works() -> Result<(), Error> {
2074			let config = Builder::new().suffix(".toml").tempfile()?;
2075			writeln!(
2076				config.as_file(),
2077				r#"
2078[relaychain]
2079chain = "paseo-local"
2080
2081[[relaychain.nodes]]
2082name = "alice"
2083command = "polkadot"
2084
2085[[relaychain.nodes]]
2086name = "bob"
2087
2088[[parachains]]
2089id = 1000
2090chain = "asset-hub-paseo-local"
2091
2092[[parachains.collators]]
2093name = "asset-hub-1"
2094command = "polkadot-parachain"
2095
2096[[parachains.collators]]
2097name = "asset-hub-2"
2098
2099[[parachains]]
2100id = 2000
2101default_command = "pop-node"
2102
2103[[parachains.collators]]
2104name = "pop"
2105command = "pop-node"
2106
2107[[parachains]]
2108id = 2001
2109default_command = "./target/release/parachain-template-node"
2110
2111[[parachains.collators]]
2112name = "collator-2001"
2113command = "./target/release/parachain-template-node"
2114
2115[[parachains]]
2116id = 2002
2117default_command = "./target/release/parachain-template-node"
2118
2119[parachains.collator]
2120name = "collator-2002"
2121command = "./target/release/parachain-template-node"
2122subcommand = "test"
2123ws_port = 9945
2124rpc_port = 9944
2125"#
2126			)?;
2127			let network_config = NetworkConfiguration::try_from(config.path())?;
2128
2129			let relay_chain_binary = Builder::new().tempfile()?;
2130			let relay_chain = relay_chain_binary.path();
2131			File::create(relay_chain)?;
2132			let system_chain_binary = Builder::new().tempfile()?;
2133			let system_chain = system_chain_binary.path();
2134			File::create(system_chain)?;
2135			let pop_binary = Builder::new().tempfile()?;
2136			let pop = pop_binary.path();
2137			File::create(pop)?;
2138			let parachain_template_node = Builder::new().tempfile()?;
2139			let parachain_template = parachain_template_node.path();
2140			create_dir_all(parachain_template.parent().unwrap())?;
2141			File::create(parachain_template)?;
2142
2143			let adapted = network_config.adapt(
2144				&RelayChain {
2145					runtime: Paseo,
2146					binary: Binary::Local {
2147						name: "polkadot".to_string(),
2148						path: relay_chain.to_path_buf(),
2149						manifest: None,
2150					},
2151					workers: ["polkadot-execute-worker", ""],
2152					chain: "paseo-local".to_string(),
2153					chain_spec_generator: None,
2154				},
2155				&[
2156					(
2157						1000,
2158						Chain {
2159							id: 1000,
2160							binary: Binary::Local {
2161								name: "polkadot-parachain".to_string(),
2162								path: system_chain.to_path_buf(),
2163								manifest: None,
2164							},
2165							chain: None,
2166							chain_spec_generator: None,
2167						},
2168					),
2169					(
2170						2000,
2171						Chain {
2172							id: 2000,
2173							binary: Binary::Local {
2174								name: "pop-node".to_string(),
2175								path: pop.to_path_buf(),
2176								manifest: None,
2177							},
2178							chain: None,
2179							chain_spec_generator: None,
2180						},
2181					),
2182					(
2183						2001,
2184						Chain {
2185							id: 2001,
2186							binary: Binary::Local {
2187								name: "parachain-template-node".to_string(),
2188								path: parachain_template.to_path_buf(),
2189								manifest: None,
2190							},
2191							chain: None,
2192							chain_spec_generator: None,
2193						},
2194					),
2195					(
2196						2002,
2197						Chain {
2198							id: 2002,
2199							binary: Binary::Local {
2200								name: "parachain-template-node".to_string(),
2201								path: parachain_template.to_path_buf(),
2202								manifest: None,
2203							},
2204							chain: None,
2205							chain_spec_generator: None,
2206						},
2207					),
2208				]
2209				.into(),
2210			)?;
2211
2212			let contents = adapted.dump_to_toml().unwrap();
2213			assert_eq!(
2214				contents,
2215				format!(
2216					r#"[settings]
2217timeout = 1000
2218node_spawn_timeout = 300
2219tear_down_on_failure = true
2220
2221[relaychain]
2222chain = "paseo-local"
2223default_command = "{0}"
2224
2225[[relaychain.nodes]]
2226name = "alice"
2227validator = true
2228invulnerable = true
2229bootnode = false
2230balance = 2000000000000
2231
2232[[relaychain.nodes]]
2233name = "bob"
2234validator = true
2235invulnerable = true
2236bootnode = false
2237balance = 2000000000000
2238
2239[[parachains]]
2240id = 1000
2241chain = "asset-hub-paseo-local"
2242add_to_genesis = true
2243balance = 2000000000000
2244default_command = "{1}"
2245cumulus_based = true
2246evm_based = false
2247
2248[[parachains.collators]]
2249name = "asset-hub-1"
2250validator = true
2251invulnerable = true
2252bootnode = false
2253balance = 2000000000000
2254
2255[[parachains.collators]]
2256name = "asset-hub-2"
2257validator = true
2258invulnerable = true
2259bootnode = false
2260balance = 2000000000000
2261
2262[[parachains]]
2263id = 2000
2264add_to_genesis = true
2265balance = 2000000000000
2266default_command = "{2}"
2267cumulus_based = true
2268evm_based = false
2269
2270[[parachains.collators]]
2271name = "pop"
2272validator = true
2273invulnerable = true
2274bootnode = false
2275balance = 2000000000000
2276
2277[[parachains]]
2278id = 2001
2279add_to_genesis = true
2280balance = 2000000000000
2281default_command = "{3}"
2282cumulus_based = true
2283evm_based = false
2284
2285[[parachains.collators]]
2286name = "collator-2001"
2287validator = true
2288invulnerable = true
2289bootnode = false
2290balance = 2000000000000
2291
2292[[parachains]]
2293id = 2002
2294add_to_genesis = true
2295balance = 2000000000000
2296default_command = "{3}"
2297cumulus_based = true
2298evm_based = false
2299
2300[[parachains.collators]]
2301name = "collator-2002"
2302subcommand = "test"
2303validator = true
2304invulnerable = true
2305bootnode = false
2306balance = 2000000000000
2307ws_port = 9945
2308rpc_port = 9944
2309"#,
2310					relay_chain.canonicalize()?.to_str().unwrap(),
2311					system_chain.canonicalize()?.to_str().unwrap(),
2312					pop.canonicalize()?.to_str().unwrap(),
2313					parachain_template.canonicalize()?.to_str().unwrap()
2314				)
2315			);
2316			Ok(())
2317		}
2318
2319		#[test]
2320		fn adapt_with_chain_spec_generator_works() -> Result<(), Error> {
2321			let config = Builder::new().suffix(".toml").tempfile()?;
2322			writeln!(
2323				config.as_file(),
2324				r#"
2325[relaychain]
2326chain = "paseo-local"
2327
2328[[relaychain.nodes]]
2329name = "alice"
2330command = "polkadot"
2331
2332[[parachains]]
2333id = 1000
2334chain = "asset-hub-paseo-local"
2335
2336[[parachains.collators]]
2337name = "asset-hub"
2338command = "polkadot-parachain"
2339
2340"#
2341			)?;
2342			let network_config = NetworkConfiguration::try_from(config.path())?;
2343
2344			let relay_chain_binary = Builder::new().tempfile()?;
2345			let relay_chain = relay_chain_binary.path();
2346			File::create(relay_chain)?;
2347			let relay_chain_spec_generator = Builder::new().tempfile()?;
2348			let relay_chain_spec_generator = relay_chain_spec_generator.path();
2349			File::create(relay_chain_spec_generator)?;
2350			let system_chain_binary = Builder::new().tempfile()?;
2351			let system_chain = system_chain_binary.path();
2352			File::create(system_chain)?;
2353			let system_chain_spec_generator = Builder::new().tempfile()?;
2354			let system_chain_spec_generator = system_chain_spec_generator.path();
2355			File::create(system_chain_spec_generator)?;
2356
2357			let adapted = network_config.adapt(
2358				&RelayChain {
2359					runtime: Paseo,
2360					binary: Binary::Local {
2361						name: "polkadot".to_string(),
2362						path: relay_chain.to_path_buf(),
2363						manifest: None,
2364					},
2365					workers: ["polkadot-execute-worker", ""],
2366					chain: "paseo-local".to_string(),
2367					chain_spec_generator: Some(Binary::Local {
2368						name: "paseo-chain-spec-generator".to_string(),
2369						path: relay_chain_spec_generator.to_path_buf(),
2370						manifest: None,
2371					}),
2372				},
2373				&[(
2374					1000,
2375					Chain {
2376						id: 1000,
2377						binary: Binary::Local {
2378							name: "polkadot-parachain".to_string(),
2379							path: system_chain.to_path_buf(),
2380							manifest: None,
2381						},
2382						chain: Some("asset-hub-paseo-local".to_string()),
2383						chain_spec_generator: Some(Binary::Local {
2384							name: "paseo-chain-spec-generator".to_string(),
2385							path: system_chain_spec_generator.to_path_buf(),
2386							manifest: None,
2387						}),
2388					},
2389				)]
2390				.into(),
2391			)?;
2392
2393			let contents = adapted.dump_to_toml().unwrap();
2394			assert_eq!(
2395				contents,
2396				format!(
2397					r#"[settings]
2398timeout = 1000
2399node_spawn_timeout = 300
2400tear_down_on_failure = true
2401
2402[relaychain]
2403chain = "paseo-local"
2404default_command = "{0}"
2405chain_spec_command = "{1} {2}"
2406
2407[[relaychain.nodes]]
2408name = "alice"
2409validator = true
2410invulnerable = true
2411bootnode = false
2412balance = 2000000000000
2413
2414[[parachains]]
2415id = 1000
2416chain = "asset-hub-paseo-local"
2417add_to_genesis = true
2418balance = 2000000000000
2419default_command = "{3}"
2420chain_spec_command = "{4} {2}"
2421cumulus_based = true
2422evm_based = false
2423
2424[[parachains.collators]]
2425name = "asset-hub"
2426validator = true
2427invulnerable = true
2428bootnode = false
2429balance = 2000000000000
2430"#,
2431					relay_chain.canonicalize()?.to_str().unwrap(),
2432					relay_chain_spec_generator.canonicalize()?.to_str().unwrap(),
2433					"{{chainName}}",
2434					system_chain.canonicalize()?.to_str().unwrap(),
2435					system_chain_spec_generator.canonicalize()?.to_str().unwrap(),
2436				)
2437			);
2438			Ok(())
2439		}
2440
2441		#[test]
2442		fn adapt_with_hrmp_channels_works() -> Result<(), Error> {
2443			let config = Builder::new().suffix(".toml").tempfile()?;
2444			writeln!(
2445				config.as_file(),
2446				r#"
2447[relaychain]
2448chain = "paseo-local"
2449
2450[[relaychain.nodes]]
2451name = "alice"
2452
2453[[parachains]]
2454id = 1000
2455chain = "asset-hub-paseo-local"
2456
2457[[parachains.collators]]
2458name = "asset-hub"
2459
2460[[parachains]]
2461id = 2000
2462default_command = "pop-node"
2463
2464[[parachains.collators]]
2465name = "pop"
2466
2467[[hrmp_channels]]
2468sender = 1000
2469recipient = 2000
2470max_capacity = 1000
2471max_message_size = 5000
2472
2473[[hrmp_channels]]
2474sender = 2000
2475recipient = 1000
2476max_capacity = 1000
2477max_message_size = 8000
2478
2479"#
2480			)?;
2481			let network_config = NetworkConfiguration::try_from(config.path())?;
2482
2483			let relay_chain_binary = Builder::new().tempfile()?;
2484			let relay_chain = relay_chain_binary.path();
2485			File::create(relay_chain)?;
2486			let system_chain_binary = Builder::new().tempfile()?;
2487			let system_chain = system_chain_binary.path();
2488			File::create(system_chain)?;
2489			let pop_binary = Builder::new().tempfile()?;
2490			let pop = pop_binary.path();
2491			File::create(pop)?;
2492
2493			let adapted = network_config.adapt(
2494				&RelayChain {
2495					runtime: Paseo,
2496					binary: Binary::Local {
2497						name: "polkadot".to_string(),
2498						path: relay_chain.to_path_buf(),
2499						manifest: None,
2500					},
2501					workers: ["polkadot-execute-worker", ""],
2502					chain: "paseo-local".to_string(),
2503					chain_spec_generator: None,
2504				},
2505				&[
2506					(
2507						1000,
2508						Chain {
2509							id: 1000,
2510							binary: Binary::Local {
2511								name: "polkadot-parachain".to_string(),
2512								path: system_chain.to_path_buf(),
2513								manifest: None,
2514							},
2515							chain: Some("asset-hub-paseo-local".to_string()),
2516							chain_spec_generator: None,
2517						},
2518					),
2519					(
2520						2000,
2521						Chain {
2522							id: 2000,
2523							binary: Binary::Local {
2524								name: "pop-node".to_string(),
2525								path: pop.to_path_buf(),
2526								manifest: None,
2527							},
2528							chain: None,
2529							chain_spec_generator: None,
2530						},
2531					),
2532				]
2533				.into(),
2534			)?;
2535
2536			let contents = adapted.dump_to_toml().unwrap();
2537			assert_eq!(
2538				contents,
2539				format!(
2540					r#"[settings]
2541timeout = 1000
2542node_spawn_timeout = 300
2543tear_down_on_failure = true
2544
2545[relaychain]
2546chain = "paseo-local"
2547default_command = "{0}"
2548
2549[[relaychain.nodes]]
2550name = "alice"
2551validator = true
2552invulnerable = true
2553bootnode = false
2554balance = 2000000000000
2555
2556[[parachains]]
2557id = 1000
2558chain = "asset-hub-paseo-local"
2559add_to_genesis = true
2560balance = 2000000000000
2561default_command = "{1}"
2562cumulus_based = true
2563evm_based = false
2564
2565[[parachains.collators]]
2566name = "asset-hub"
2567validator = true
2568invulnerable = true
2569bootnode = false
2570balance = 2000000000000
2571
2572[[parachains]]
2573id = 2000
2574add_to_genesis = true
2575balance = 2000000000000
2576default_command = "{2}"
2577cumulus_based = true
2578evm_based = false
2579
2580[[parachains.collators]]
2581name = "pop"
2582validator = true
2583invulnerable = true
2584bootnode = false
2585balance = 2000000000000
2586
2587[[hrmp_channels]]
2588sender = 1000
2589recipient = 2000
2590max_capacity = 1000
2591max_message_size = 5000
2592
2593[[hrmp_channels]]
2594sender = 2000
2595recipient = 1000
2596max_capacity = 1000
2597max_message_size = 8000
2598"#,
2599					relay_chain.canonicalize()?.to_str().unwrap(),
2600					system_chain.canonicalize()?.to_str().unwrap(),
2601					pop.canonicalize()?.to_str().unwrap(),
2602				)
2603			);
2604			Ok(())
2605		}
2606
2607		#[test]
2608		fn adapt_with_chain_spec_works() -> Result<(), Error> {
2609			let config = Builder::new().suffix(".toml").tempfile()?;
2610			writeln!(
2611				config.as_file(),
2612				r#"
2613[relaychain]
2614chain = "paseo-local"
2615chain_spec_command = "cmd_template"
2616chain_spec_command_is_local = true
2617chain_spec_path = "./path/to/paseo-local.spec.json"
2618
2619[[relaychain.nodes]]
2620name = "alice"
2621
2622[[parachains]]
2623id = 1000
2624chain = "asset-hub-paseo-local"
2625chain_spec_command = "cmd_template"
2626chain_spec_command_is_local = true
2627chain_spec_path = "./path/to/asset-hub-paseo-local.spec.json"
2628
2629[[parachains.collators]]
2630name = "asset-hub"
2631"#
2632			)?;
2633			let network_config = NetworkConfiguration::try_from(config.path())?;
2634
2635			let relay_chain_binary = Builder::new().tempfile()?;
2636			let relay_chain = relay_chain_binary.path();
2637			File::create(relay_chain)?;
2638			let system_chain_binary = Builder::new().tempfile()?;
2639			let system_chain = system_chain_binary.path();
2640			File::create(system_chain)?;
2641			let pop_binary = Builder::new().tempfile()?;
2642			let pop = pop_binary.path();
2643			File::create(pop)?;
2644
2645			let adapted = network_config.adapt(
2646				&RelayChain {
2647					runtime: Paseo,
2648					binary: Binary::Local {
2649						name: "polkadot".to_string(),
2650						path: relay_chain.to_path_buf(),
2651						manifest: None,
2652					},
2653					workers: ["polkadot-execute-worker", ""],
2654					chain: "paseo-local".to_string(),
2655					chain_spec_generator: None,
2656				},
2657				&[(
2658					1000,
2659					Chain {
2660						id: 1000,
2661						binary: Binary::Local {
2662							name: "polkadot-parachain".to_string(),
2663							path: system_chain.to_path_buf(),
2664							manifest: None,
2665						},
2666						chain: Some("asset-hub-paseo-local".to_string()),
2667						chain_spec_generator: None,
2668					},
2669				)]
2670				.into(),
2671			)?;
2672
2673			let contents = adapted.dump_to_toml().unwrap();
2674			assert_eq!(
2675				contents,
2676				format!(
2677					r#"[settings]
2678timeout = 1000
2679node_spawn_timeout = 300
2680tear_down_on_failure = true
2681
2682[relaychain]
2683chain = "paseo-local"
2684default_command = "{0}"
2685chain_spec_path = "./path/to/paseo-local.spec.json"
2686chain_spec_command = "cmd_template"
2687chain_spec_command_is_local = true
2688
2689[[relaychain.nodes]]
2690name = "alice"
2691validator = true
2692invulnerable = true
2693bootnode = false
2694balance = 2000000000000
2695
2696[[parachains]]
2697id = 1000
2698chain = "asset-hub-paseo-local"
2699add_to_genesis = true
2700balance = 2000000000000
2701default_command = "{1}"
2702chain_spec_path = "./path/to/asset-hub-paseo-local.spec.json"
2703chain_spec_command = "cmd_template"
2704chain_spec_command_is_local = true
2705cumulus_based = true
2706evm_based = false
2707
2708[[parachains.collators]]
2709name = "asset-hub"
2710validator = true
2711invulnerable = true
2712bootnode = false
2713balance = 2000000000000
2714"#,
2715					relay_chain.canonicalize()?.to_str().unwrap(),
2716					system_chain.canonicalize()?.to_str().unwrap(),
2717				)
2718			);
2719			Ok(())
2720		}
2721
2722		#[test]
2723		fn adapt_with_overrides_works() -> Result<(), Error> {
2724			let config = Builder::new().suffix(".toml").tempfile()?;
2725			writeln!(
2726				config.as_file(),
2727				r#"
2728[relaychain]
2729chain = "paseo-local"
2730wasm_override = "./path/to/paseo-local.wasm"
2731
2732[[relaychain.nodes]]
2733name = "alice"
2734
2735[relaychain.genesis.balances]
2736balances = [["5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ", 420000000000]]
2737
2738[[parachains]]
2739id = 1000
2740chain = "asset-hub-paseo-local"
2741wasm_override = "./path/to/asset-hub-paseo-local.wasm"
2742
2743[[parachains.collators]]
2744name = "asset-hub"
2745
2746[parachains.genesis.balances]
2747balances = [["5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ", 420000000000]]
2748
2749"#
2750			)?;
2751			let network_config = NetworkConfiguration::try_from(config.path())?;
2752
2753			let relay_chain_binary = Builder::new().tempfile()?;
2754			let relay_chain = relay_chain_binary.path();
2755			File::create(relay_chain)?;
2756			let system_chain_binary = Builder::new().tempfile()?;
2757			let system_chain = system_chain_binary.path();
2758			File::create(system_chain)?;
2759			let pop_binary = Builder::new().tempfile()?;
2760			let pop = pop_binary.path();
2761			File::create(pop)?;
2762
2763			let adapted = network_config.adapt(
2764				&RelayChain {
2765					runtime: Paseo,
2766					binary: Binary::Local {
2767						name: "polkadot".to_string(),
2768						path: relay_chain.to_path_buf(),
2769						manifest: None,
2770					},
2771					workers: ["polkadot-execute-worker", ""],
2772					chain: "paseo-local".to_string(),
2773					chain_spec_generator: None,
2774				},
2775				&[(
2776					1000,
2777					Chain {
2778						id: 1000,
2779						binary: Binary::Local {
2780							name: "polkadot-parachain".to_string(),
2781							path: system_chain.to_path_buf(),
2782							manifest: None,
2783						},
2784						chain: Some("asset-hub-paseo-local".to_string()),
2785						chain_spec_generator: None,
2786					},
2787				)]
2788				.into(),
2789			)?;
2790
2791			let contents = adapted.dump_to_toml().unwrap();
2792			assert_eq!(
2793				contents,
2794				format!(
2795					r#"[settings]
2796timeout = 1000
2797node_spawn_timeout = 300
2798tear_down_on_failure = true
2799
2800[relaychain]
2801chain = "paseo-local"
2802default_command = "{0}"
2803wasm_override = "./path/to/paseo-local.wasm"
2804
2805[[relaychain.nodes]]
2806name = "alice"
2807validator = true
2808invulnerable = true
2809bootnode = false
2810balance = 2000000000000
2811
2812[relaychain.genesis.balances]
2813balances = [[
2814    "5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ",
2815    {{ "$serde_json::private::Number" = "420000000000" }},
2816]]
2817
2818[[parachains]]
2819id = 1000
2820chain = "asset-hub-paseo-local"
2821add_to_genesis = true
2822balance = 2000000000000
2823default_command = "{1}"
2824wasm_override = "./path/to/asset-hub-paseo-local.wasm"
2825cumulus_based = true
2826evm_based = false
2827
2828[parachains.genesis.balances]
2829balances = [[
2830    "5Ec4AhPKXY9B4ayGshkz2wFMh7N8gP7XKfAvtt1cigpG9FkJ",
2831    {{ "$serde_json::private::Number" = "420000000000" }},
2832]]
2833
2834[[parachains.collators]]
2835name = "asset-hub"
2836validator = true
2837invulnerable = true
2838bootnode = false
2839balance = 2000000000000
2840"#,
2841					relay_chain.canonicalize()?.to_str().unwrap(),
2842					system_chain.canonicalize()?.to_str().unwrap(),
2843				)
2844			);
2845			Ok(())
2846		}
2847
2848		#[test]
2849		fn resolves_path() -> Result<(), Error> {
2850			let working_dir = tempdir()?;
2851			let path = working_dir.path().join("./target/release/node");
2852			assert!(
2853				matches!(NetworkConfiguration::resolve_path(&path), Err(Error::Config(message))
2854						if message == format!("the canonical path of {:?} could not be resolved", path)
2855				)
2856			);
2857
2858			create_dir_all(path.parent().unwrap())?;
2859			File::create(&path)?;
2860			assert_eq!(
2861				NetworkConfiguration::resolve_path(&path)?,
2862				path.canonicalize()?.to_str().unwrap().to_string()
2863			);
2864			Ok(())
2865		}
2866	}
2867
2868	mod parachain {
2869		use super::*;
2870		use pop_common::sourcing::GitHub::SourceCodeArchive;
2871		use std::path::PathBuf;
2872
2873		#[test]
2874		fn initializes_from_local_binary() -> Result<(), Error> {
2875			let name = "parachain-template-node";
2876			let command = PathBuf::from("./target/release").join(name);
2877			assert_eq!(
2878				Chain::from_local(2000, command.clone(), Some("dev"))?,
2879				Chain {
2880					id: 2000,
2881					binary: Binary::Local { name: name.to_string(), path: command, manifest: None },
2882					chain: Some("dev".to_string()),
2883					chain_spec_generator: None,
2884				}
2885			);
2886			Ok(())
2887		}
2888
2889		#[test]
2890		fn initializes_from_local_package() -> Result<(), Error> {
2891			let name = "pop-chains";
2892			let command = PathBuf::from("./target/release").join(name);
2893			assert_eq!(
2894				Chain::from_local(2000, command.clone(), Some("dev"))?,
2895				Chain {
2896					id: 2000,
2897					binary: Binary::Local {
2898						name: name.to_string(),
2899						path: command,
2900						manifest: Some(PathBuf::from("./Cargo.toml"))
2901					},
2902					chain: Some("dev".to_string()),
2903					chain_spec_generator: None,
2904				}
2905			);
2906			Ok(())
2907		}
2908
2909		#[test]
2910		fn initializes_from_git() -> Result<(), Error> {
2911			let repo = Repository::parse("https://git.com/r0gue-io/pop-node#v1.0")?;
2912			let cache = tempdir()?;
2913			assert_eq!(
2914				Chain::from_repository(2000, &repo, Some("dev"), cache.path())?,
2915				Chain {
2916					id: 2000,
2917					binary: Binary::Source {
2918						name: "pop-node".to_string(),
2919						source: Git {
2920							url: repo.url,
2921							reference: repo.reference,
2922							manifest: None,
2923							package: "pop-node".to_string(),
2924							artifacts: vec!["pop-node".to_string()],
2925						}
2926						.into(),
2927						cache: cache.path().to_path_buf(),
2928					},
2929					chain: Some("dev".to_string()),
2930					chain_spec_generator: None,
2931				}
2932			);
2933			Ok(())
2934		}
2935
2936		#[test]
2937		fn initializes_from_github() -> Result<(), Error> {
2938			let repo = Repository::parse("https://github.com/r0gue-io/pop-node#v1.0")?;
2939			let cache = tempdir()?;
2940			assert_eq!(
2941				Chain::from_repository(2000, &repo, Some("dev"), cache.path())?,
2942				Chain {
2943					id: 2000,
2944					binary: Binary::Source {
2945						name: "pop-node".to_string(),
2946						source: Source::GitHub(SourceCodeArchive {
2947							owner: "r0gue-io".to_string(),
2948							repository: "pop-node".to_string(),
2949							reference: Some("v1.0".to_string()),
2950							manifest: None,
2951							package: "pop-node".to_string(),
2952							artifacts: vec!["pop-node".to_string()],
2953						})
2954						.into(),
2955						cache: cache.path().to_path_buf(),
2956					},
2957					chain: Some("dev".to_string()),
2958					chain_spec_generator: None,
2959				},
2960			);
2961			Ok(())
2962		}
2963	}
2964
2965	#[test]
2966	fn resolve_manifest_works() -> Result<()> {
2967		let current_dir = current_dir()?;
2968		// Crate
2969		assert_eq!(
2970			current_dir.join("Cargo.toml"),
2971			resolve_manifest("pop-chains", &current_dir)?.unwrap()
2972		);
2973		// Workspace
2974		assert_eq!(
2975			current_dir.join("../../Cargo.toml").canonicalize()?,
2976			resolve_manifest("pop-cli", &current_dir)?.unwrap()
2977		);
2978		Ok(())
2979	}
2980}