1use crate::errors::{Error, handle_command_error};
4use anyhow::{Result, anyhow};
5use duct::cmd;
6use pop_common::{Profile, account_id::convert_to_evm_accounts, manifest::from_path};
7use sc_chain_spec::{GenericChainSpec, NoExtension};
8use serde_json::{Value, json};
9use sp_core::bytes::to_hex;
10use std::{
11 fs,
12 io::Write,
13 path::{Path, PathBuf},
14 str::FromStr,
15};
16
17pub mod runtime;
19
20pub enum ChainSpecBuilder {
26 Node {
28 node_path: PathBuf,
30 default_bootnode: bool,
32 profile: Profile,
34 },
35 Runtime {
37 runtime_path: PathBuf,
39 profile: Profile,
41 },
42}
43
44impl ChainSpecBuilder {
45 pub fn build(&self, features: &[String], redirect_output_to_stderr: bool) -> Result<PathBuf> {
53 build_project(
54 &self.path(),
55 None,
56 &self.profile(),
57 features,
58 None,
59 redirect_output_to_stderr,
60 )?;
61 self.artifact_path()
63 }
64
65 pub fn path(&self) -> PathBuf {
70 match self {
71 ChainSpecBuilder::Node { node_path, .. } => node_path,
72 ChainSpecBuilder::Runtime { runtime_path, .. } => runtime_path,
73 }
74 .clone()
75 }
76
77 pub fn profile(&self) -> Profile {
82 *match self {
83 ChainSpecBuilder::Node { profile, .. } => profile,
84 ChainSpecBuilder::Runtime { profile, .. } => profile,
85 }
86 }
87
88 pub fn artifact_path(&self) -> Result<PathBuf> {
93 let manifest = from_path(&self.path())?;
94 let package = manifest.package().name();
95 let root_folder = rustilities::manifest::find_workspace_manifest(self.path())
96 .ok_or(anyhow::anyhow!("Not inside a workspace"))?
97 .parent()
98 .expect("Path to Cargo.toml workspace root folder must exist")
99 .to_path_buf();
100 let path = match self {
101 ChainSpecBuilder::Node { profile, .. } =>
102 profile.target_directory(&root_folder).join(package),
103 ChainSpecBuilder::Runtime { profile, .. } => {
104 let base = profile.target_directory(&root_folder).join("wbuild").join(package);
105 let wasm_file = package.replace("-", "_");
106 let compact_compressed = base.join(format!("{wasm_file}.compact.compressed.wasm"));
107 let raw = base.join(format!("{wasm_file}.wasm"));
108 if compact_compressed.is_file() {
109 compact_compressed
110 } else if raw.is_file() {
111 raw
112 } else {
113 return Err(anyhow::anyhow!("No runtime found"));
114 }
115 },
116 };
117 Ok(path.canonicalize()?)
118 }
119
120 pub fn generate_plain_chain_spec(
128 &self,
129 chain_or_preset: &str,
130 output_file: &Path,
131 name: Option<&str>,
132 id: Option<&str>,
133 ) -> Result<(), Error> {
134 match self {
135 ChainSpecBuilder::Node { default_bootnode, .. } => generate_plain_chain_spec_with_node(
136 &self.artifact_path()?,
137 output_file,
138 *default_bootnode,
139 chain_or_preset,
140 ),
141 ChainSpecBuilder::Runtime { .. } => generate_plain_chain_spec_with_runtime(
142 fs::read(self.artifact_path()?)?,
143 output_file,
144 chain_or_preset,
145 name,
146 id,
147 ),
148 }
149 }
150
151 pub fn generate_raw_chain_spec(
160 &self,
161 plain_chain_spec: &Path,
162 raw_chain_spec_name: &str,
163 ) -> Result<PathBuf, Error> {
164 match self {
165 ChainSpecBuilder::Node { .. } => generate_raw_chain_spec_with_node(
166 &self.artifact_path()?,
167 plain_chain_spec,
168 raw_chain_spec_name,
169 ),
170 ChainSpecBuilder::Runtime { .. } =>
171 generate_raw_chain_spec_with_runtime(plain_chain_spec, raw_chain_spec_name),
172 }
173 }
174
175 pub fn export_wasm_file(
190 &self,
191 raw_chain_spec: &Path,
192 wasm_file_name: &str,
193 ) -> Result<PathBuf, Error> {
194 match self {
195 ChainSpecBuilder::Node { .. } =>
196 export_wasm_file_with_node(&self.artifact_path()?, raw_chain_spec, wasm_file_name),
197 ChainSpecBuilder::Runtime { .. } =>
198 export_wasm_file_with_runtime(raw_chain_spec, wasm_file_name),
199 }
200 }
201}
202
203pub fn build_chain(
213 path: &Path,
214 package: Option<String>,
215 profile: &Profile,
216 node_path: Option<&Path>,
217 features: &[String],
218 redirect_output_to_stderr: bool,
219) -> Result<PathBuf, Error> {
220 build_project(path, package, profile, features, None, redirect_output_to_stderr)?;
221 binary_path(&profile.target_directory(path), node_path.unwrap_or(&path.join("node")))
222}
223
224fn fetch_dependencies(path: &Path) -> Result<(), Error> {
226 cmd("cargo", ["fetch"]).dir(path).stdout_null().run()?;
227 Ok(())
228}
229
230pub fn build_project(
240 path: &Path,
241 package: Option<String>,
242 profile: &Profile,
243 features: &[String],
244 target: Option<&str>,
245 redirect_output_to_stderr: bool,
246) -> Result<(), Error> {
247 fetch_dependencies(path)?;
248 let mut args = vec!["build"];
249 if let Some(package) = package.as_deref() {
250 args.push("--package");
251 args.push(package)
252 }
253 if profile == &Profile::Release {
254 args.push("--release");
255 } else if profile == &Profile::Production {
256 args.push("--profile=production");
257 }
258
259 let feature_args = features.join(",");
260 if !features.is_empty() {
261 args.push("--features");
262 args.push(&feature_args);
263 }
264
265 if let Some(target) = target {
266 args.push("--target");
267 args.push(target);
268 }
269
270 if redirect_output_to_stderr {
271 let output = cmd("cargo", args)
272 .dir(path)
273 .stdout_capture()
274 .stderr_capture()
275 .unchecked()
276 .run()?;
277 let combined = combine_streams_to_string(&output);
278 if !combined.is_empty() {
279 let _ = std::io::stderr().write_all(combined.as_bytes());
280 if !combined.ends_with('\n') {
281 let _ = std::io::stderr().write_all(b"\n");
282 }
283 }
284 if !output.status.success() {
285 let details =
286 if combined.is_empty() { "cargo build failed".to_string() } else { combined };
287 return Err(Error::AnyhowError(anyhow!("cargo build failed:\n{details}")));
288 }
289 } else {
290 cmd("cargo", args).dir(path).run()?;
291 }
292 Ok(())
293}
294
295fn combine_streams_to_string(output: &std::process::Output) -> String {
296 let mut combined = String::new();
297 let stdout = String::from_utf8_lossy(&output.stdout);
298 let stderr = String::from_utf8_lossy(&output.stderr);
299 if !stdout.is_empty() {
300 combined.push_str(&stdout);
301 }
302 if !stderr.is_empty() {
303 combined.push_str(&stderr);
304 }
305 combined
306}
307
308pub fn is_supported(path: &Path) -> bool {
314 let manifest = match from_path(path) {
315 Ok(m) => m,
316 Err(_) => return false,
317 };
318 const DEPENDENCIES: [&str; 4] =
320 ["cumulus-client-collator", "cumulus-primitives-core", "parachains-common", "polkadot-sdk"];
321 DEPENDENCIES.into_iter().any(|d| {
322 manifest.dependencies.contains_key(d) ||
323 manifest.workspace.as_ref().is_some_and(|w| w.dependencies.contains_key(d))
324 })
325}
326
327pub fn binary_path(target_path: &Path, node_path: &Path) -> Result<PathBuf, Error> {
333 build_binary_path(node_path, |node_name| target_path.join(node_name))
334}
335
336pub fn runtime_binary_path(target_path: &Path, runtime_path: &Path) -> Result<PathBuf, Error> {
342 build_binary_path(runtime_path, |runtime_name| {
343 target_path.join(format!("{runtime_name}/{}.wasm", runtime_name.replace("-", "_")))
344 })
345}
346
347fn build_binary_path<F>(project_path: &Path, path_builder: F) -> Result<PathBuf, Error>
348where
349 F: Fn(&str) -> PathBuf,
350{
351 let manifest = from_path(project_path)?;
352 let project_name = manifest.package().name();
353 let release = path_builder(project_name);
354 if !release.exists() {
355 return Err(Error::MissingBinary(project_name.to_string()));
356 }
357 Ok(release)
358}
359
360pub fn generate_raw_chain_spec_with_runtime(
369 plain_chain_spec: &Path,
370 raw_chain_spec_name: &str,
371) -> Result<PathBuf, Error> {
372 let chain_spec = GenericChainSpec::<Option<()>>::from_json_file(plain_chain_spec.to_path_buf())
373 .map_err(|e| anyhow::anyhow!(e))?;
374 let raw_chain_spec = chain_spec.as_json(true).map_err(|e| anyhow::anyhow!(e))?;
375 let raw_chain_spec_file = plain_chain_spec.with_file_name(raw_chain_spec_name);
376 fs::write(&raw_chain_spec_file, raw_chain_spec)?;
377 Ok(raw_chain_spec_file)
378}
379
380pub fn generate_plain_chain_spec_with_runtime(
389 wasm: Vec<u8>,
390 plain_chain_spec: &Path,
391 preset: &str,
392 name: Option<&str>,
393 id: Option<&str>,
394) -> Result<(), Error> {
395 let mut chain_spec = GenericChainSpec::<NoExtension>::builder(&wasm[..], None)
396 .with_genesis_config_preset_name(preset.trim());
397
398 if let Some(name) = name {
399 chain_spec = chain_spec.with_name(name);
400 }
401
402 if let Some(id) = id {
403 chain_spec = chain_spec.with_id(id);
404 }
405
406 let chain_spec = chain_spec.build().as_json(false).map_err(|e| anyhow::anyhow!(e))?;
407 fs::write(plain_chain_spec, chain_spec)?;
408
409 Ok(())
410}
411
412pub fn export_wasm_file_with_runtime(
427 raw_chain_spec: &Path,
428 wasm_file_name: &str,
429) -> Result<PathBuf, Error> {
430 let chain_spec = GenericChainSpec::<Option<()>>::from_json_file(raw_chain_spec.to_path_buf())
431 .map_err(|e| anyhow::anyhow!(e))?;
432 let raw_wasm_blob =
433 cumulus_client_cli::extract_genesis_wasm(&chain_spec).map_err(|e| anyhow::anyhow!(e))?;
434 let wasm_file = raw_chain_spec.parent().unwrap_or(Path::new("./")).join(wasm_file_name);
435 fs::write(&wasm_file, raw_wasm_blob)?;
436 Ok(wasm_file)
437}
438
439pub fn generate_plain_chain_spec_with_node(
448 binary_path: &Path,
449 plain_chain_spec: &Path,
450 default_bootnode: bool,
451 chain: &str,
452) -> Result<(), Error> {
453 check_command_exists(binary_path, "build-spec")?;
454 let mut args = vec!["build-spec", "--chain", chain];
455 if !default_bootnode {
456 args.push("--disable-default-bootnode");
457 }
458 let temp_file = tempfile::NamedTempFile::new_in(std::env::temp_dir())?;
460 let output = cmd(binary_path, args)
462 .stdout_path(temp_file.path())
463 .stderr_capture()
464 .unchecked()
465 .run()?;
466 handle_command_error(&output, Error::BuildSpecError)?;
468 temp_file.persist(plain_chain_spec).map_err(|e| {
470 Error::AnyhowError(anyhow!(
471 "Failed to replace the chain spec file with the temporary file: {e}"
472 ))
473 })?;
474 Ok(())
475}
476
477pub fn generate_raw_chain_spec_with_node(
484 binary_path: &Path,
485 plain_chain_spec: &Path,
486 chain_spec_file_name: &str,
487) -> Result<PathBuf, Error> {
488 if !plain_chain_spec.exists() {
489 return Err(Error::MissingChainSpec(plain_chain_spec.display().to_string()));
490 }
491 check_command_exists(binary_path, "build-spec")?;
492 let raw_chain_spec = plain_chain_spec.with_file_name(chain_spec_file_name);
493 let output = cmd(
494 binary_path,
495 vec![
496 "build-spec",
497 "--chain",
498 &plain_chain_spec.display().to_string(),
499 "--disable-default-bootnode",
500 "--raw",
501 ],
502 )
503 .stdout_path(&raw_chain_spec)
504 .stderr_capture()
505 .unchecked()
506 .run()?;
507 handle_command_error(&output, Error::BuildSpecError)?;
508 Ok(raw_chain_spec)
509}
510
511pub fn export_wasm_file_with_node(
519 binary_path: &Path,
520 raw_chain_spec: &Path,
521 wasm_file_name: &str,
522) -> Result<PathBuf, Error> {
523 if !raw_chain_spec.exists() {
524 return Err(Error::MissingChainSpec(raw_chain_spec.display().to_string()));
525 }
526 check_command_exists(binary_path, "export-genesis-wasm")?;
527 let wasm_file = raw_chain_spec.parent().unwrap_or(Path::new("./")).join(wasm_file_name);
528 let output = cmd(
529 binary_path,
530 vec![
531 "export-genesis-wasm",
532 "--chain",
533 &raw_chain_spec.display().to_string(),
534 &wasm_file.display().to_string(),
535 ],
536 )
537 .stdout_null()
538 .stderr_capture()
539 .unchecked()
540 .run()?;
541 handle_command_error(&output, Error::BuildSpecError)?;
542 Ok(wasm_file)
543}
544
545pub fn generate_genesis_state_file_with_node(
553 binary_path: &Path,
554 raw_chain_spec: &Path,
555 genesis_file_name: &str,
556) -> Result<PathBuf, Error> {
557 if !raw_chain_spec.exists() {
558 return Err(Error::MissingChainSpec(raw_chain_spec.display().to_string()));
559 }
560 check_command_exists(binary_path, "export-genesis-state")?;
561 let genesis_file = raw_chain_spec.parent().unwrap_or(Path::new("./")).join(genesis_file_name);
562 let output = cmd(
563 binary_path,
564 vec![
565 "export-genesis-state",
566 "--chain",
567 &raw_chain_spec.display().to_string(),
568 &genesis_file.display().to_string(),
569 ],
570 )
571 .stdout_null()
572 .stderr_capture()
573 .unchecked()
574 .run()?;
575 handle_command_error(&output, Error::BuildSpecError)?;
576 Ok(genesis_file)
577}
578
579fn check_command_exists(binary_path: &Path, command: &str) -> Result<(), Error> {
581 cmd(binary_path, vec![command, "--help"]).stdout_null().run().map_err(|_err| {
582 Error::MissingCommand {
583 command: command.to_string(),
584 binary: binary_path.display().to_string(),
585 }
586 })?;
587 Ok(())
588}
589
590pub struct ChainSpec(Value);
592impl ChainSpec {
593 pub fn from(path: &Path) -> Result<ChainSpec> {
598 Ok(ChainSpec(Value::from_str(&fs::read_to_string(path)?)?))
599 }
600
601 pub fn get_chain_type(&self) -> Option<&str> {
603 self.0.get("chainType").and_then(|v| v.as_str())
604 }
605
606 pub fn get_name(&self) -> Option<&str> {
608 self.0.get("name").and_then(|v| v.as_str())
609 }
610
611 pub fn get_chain_id(&self) -> Option<u64> {
613 self.0.get("para_id").and_then(|v| v.as_u64())
614 }
615
616 pub fn get_property_based_on(&self) -> Option<&str> {
618 self.0.get("properties").and_then(|v| v.get("basedOn")).and_then(|v| v.as_str())
619 }
620
621 pub fn get_protocol_id(&self) -> Option<&str> {
623 self.0.get("protocolId").and_then(|v| v.as_str())
624 }
625
626 pub fn get_relay_chain(&self) -> Option<&str> {
628 self.0.get("relay_chain").and_then(|v| v.as_str())
629 }
630
631 pub fn get_sudo_key(&self) -> Option<&str> {
633 self.0
634 .get("genesis")
635 .and_then(|genesis| genesis.get("runtimeGenesis"))
636 .and_then(|runtime_genesis| runtime_genesis.get("patch"))
637 .and_then(|patch| patch.get("sudo"))
638 .and_then(|sudo| sudo.get("key"))
639 .and_then(|key| key.as_str())
640 }
641
642 pub fn replace_para_id(&mut self, para_id: u32) -> Result<(), Error> {
647 let root = self
649 .0
650 .as_object_mut()
651 .ok_or_else(|| Error::Config("expected root object".into()))?;
652 root.insert("para_id".to_string(), json!(para_id));
653
654 let replace = self.0.pointer_mut("/genesis/runtimeGenesis/patch/parachainInfo/parachainId");
656 if let Some(replace) = replace {
658 *replace = json!(para_id);
659 }
660 Ok(())
661 }
662
663 pub fn replace_relay_chain(&mut self, relay_name: &str) -> Result<(), Error> {
668 let root = self
670 .0
671 .as_object_mut()
672 .ok_or_else(|| Error::Config("expected root object".into()))?;
673 root.insert("relay_chain".to_string(), json!(relay_name));
674 Ok(())
675 }
676
677 pub fn replace_chain_type(&mut self, chain_type: &str) -> Result<(), Error> {
682 let replace = self
684 .0
685 .get_mut("chainType")
686 .ok_or_else(|| Error::Config("expected `chainType`".into()))?;
687 *replace = json!(chain_type);
688 Ok(())
689 }
690
691 pub fn replace_protocol_id(&mut self, protocol_id: &str) -> Result<(), Error> {
696 let replace = self
698 .0
699 .get_mut("protocolId")
700 .ok_or_else(|| Error::Config("expected `protocolId`".into()))?;
701 *replace = json!(protocol_id);
702 Ok(())
703 }
704
705 pub fn replace_properties(&mut self, raw_properties: &str) -> Result<(), Error> {
710 let replace = self
712 .0
713 .get_mut("properties")
714 .ok_or_else(|| Error::Config("expected `properties`".into()))?;
715 let mut properties = serde_json::Map::new();
716 let mut iter = raw_properties
717 .split(',')
718 .flat_map(|s| s.split('=').map(|p| p.trim()).collect::<Vec<_>>())
719 .collect::<Vec<_>>()
720 .into_iter();
721 while let Some(key) = iter.next() {
722 let value = iter.next().expect("Property value expected but not found");
723 properties.insert(key.to_string(), Value::String(value.to_string()));
724 }
725 *replace = Value::Object(properties);
726 Ok(())
727 }
728
729 pub fn replace_collator_keys(&mut self, collator_keys: Vec<String>) -> Result<(), Error> {
735 let uses_evm_keys = self
736 .0
737 .get("properties")
738 .and_then(|p| p.get("isEthereum"))
739 .and_then(|v| v.as_bool())
740 .unwrap_or(false);
741
742 let keys = if uses_evm_keys {
743 convert_to_evm_accounts(collator_keys.clone())?
744 } else {
745 collator_keys.clone()
746 };
747
748 let invulnerables = self
749 .0
750 .get_mut("genesis")
751 .ok_or_else(|| Error::Config("expected `genesis`".into()))?
752 .get_mut("runtimeGenesis")
753 .ok_or_else(|| Error::Config("expected `runtimeGenesis`".into()))?
754 .get_mut("patch")
755 .ok_or_else(|| Error::Config("expected `patch`".into()))?
756 .get_mut("collatorSelection")
757 .ok_or_else(|| Error::Config("expected `collatorSelection`".into()))?
758 .get_mut("invulnerables")
759 .ok_or_else(|| Error::Config("expected `invulnerables`".into()))?;
760
761 *invulnerables = json!(keys);
762
763 let session_keys = keys
764 .iter()
765 .zip(collator_keys.iter())
766 .map(|(address, original_address)| {
767 json!([
768 address,
769 address,
770 { "aura": original_address } ])
772 })
773 .collect::<Vec<_>>();
774
775 let session_keys_field = self
776 .0
777 .get_mut("genesis")
778 .ok_or_else(|| Error::Config("expected `genesis`".into()))?
779 .get_mut("runtimeGenesis")
780 .ok_or_else(|| Error::Config("expected `runtimeGenesis`".into()))?
781 .get_mut("patch")
782 .ok_or_else(|| Error::Config("expected `patch`".into()))?
783 .get_mut("session")
784 .ok_or_else(|| Error::Config("expected `session`".into()))?
785 .get_mut("keys")
786 .ok_or_else(|| Error::Config("expected `session.keys`".into()))?;
787
788 *session_keys_field = json!(session_keys);
789
790 Ok(())
791 }
792
793 pub fn to_string(&self) -> Result<String> {
795 Ok(serde_json::to_string_pretty(&self.0)?)
796 }
797
798 pub fn to_file(&self, path: &Path) -> Result<()> {
803 fs::write(path, self.to_string()?)?;
804 Ok(())
805 }
806
807 pub fn update_runtime_code(&mut self, bytes: &[u8]) -> Result<(), Error> {
812 let code = self
814 .0
815 .get_mut("genesis")
816 .ok_or_else(|| Error::Config("expected `genesis`".into()))?
817 .get_mut("runtimeGenesis")
818 .ok_or_else(|| Error::Config("expected `runtimeGenesis`".into()))?
819 .get_mut("code")
820 .ok_or_else(|| Error::Config("expected `runtimeGenesis.code`".into()))?;
821 let hex = to_hex(bytes, true);
822 *code = json!(hex);
823 Ok(())
824 }
825}
826
827#[cfg(test)]
828mod tests {
829 use super::*;
830 use crate::{
831 Config, Error, new_chain::instantiate_standard_template, templates::ChainTemplate,
832 up::Zombienet,
833 };
834 use anyhow::Result;
835 use pop_common::{
836 manifest::{Dependency, add_feature},
837 set_executable_permission,
838 };
839 use sp_core::bytes::from_hex;
840 use std::{
841 fs::{self, write},
842 io::Write,
843 path::Path,
844 };
845 use strum::VariantArray;
846 use tempfile::{Builder, TempDir, tempdir};
847
848 static MOCK_WASM: &[u8] = include_bytes!("../../../../tests/runtimes/base_parachain.wasm");
849
850 fn setup_template_and_instantiate() -> Result<TempDir> {
851 let temp_dir = tempdir().expect("Failed to create temp dir");
852 let config = Config {
853 symbol: "DOT".to_string(),
854 decimals: 18,
855 initial_endowment: "1000000".to_string(),
856 };
857 instantiate_standard_template(&ChainTemplate::Standard, temp_dir.path(), config, None)?;
858 Ok(temp_dir)
859 }
860
861 fn mock_build_process(temp_dir: &Path) -> Result<(), Error> {
863 let target_dir = temp_dir.join("target");
865 fs::create_dir(&target_dir)?;
866 fs::create_dir(target_dir.join("release"))?;
867 fs::File::create(target_dir.join("release/parachain-template-node"))?;
869 Ok(())
870 }
871
872 fn mock_node(temp_dir: &Path) -> Result<(), Error> {
874 let node_dir = temp_dir.join("node");
875 fs::create_dir(&node_dir)?;
876 fs::write(
877 node_dir.join("Cargo.toml"),
878 r#"[package]
879name = "parachain-template-node"
880version = "0.1.0"
881edition = "2021"
882"#,
883 )?;
884 Ok(())
885 }
886
887 fn mock_build_runtime_process(temp_dir: &Path) -> Result<(), Error> {
889 let runtime = "parachain-template-runtime";
890 let target_dir = temp_dir.join("target");
892 fs::create_dir(&target_dir)?;
893 fs::create_dir(target_dir.join("release"))?;
894 fs::create_dir(target_dir.join("release/wbuild"))?;
895 fs::create_dir(target_dir.join(format!("release/wbuild/{runtime}")))?;
896 fs::File::create(
898 target_dir.join(format!("release/wbuild/{runtime}/{}.wasm", runtime.replace("-", "_"))),
899 )?;
900 Ok(())
901 }
902
903 fn generate_mock_node(temp_dir: &Path, name: Option<&str>) -> Result<PathBuf, Error> {
905 let target_dir = temp_dir.join(name.unwrap_or("node"));
907 fs::create_dir(&target_dir)?;
908 let mut toml_file = fs::File::create(target_dir.join("Cargo.toml"))?;
910 writeln!(
911 toml_file,
912 r#"
913 [package]
914 name = "parachain_template_node"
915 version = "0.1.0"
916
917 [dependencies]
918
919 "#
920 )?;
921 Ok(target_dir)
922 }
923
924 async fn fetch_binary(cache: &Path) -> Result<String, Error> {
926 let config = Builder::new().suffix(".toml").tempfile()?;
927 writeln!(
928 config.as_file(),
929 r#"
930 [relaychain]
931 chain = "paseo-local"
932
933 [[parachains]]
934 id = 4385
935 default_command = "pop-node"
936 "#
937 )?;
938 let mut zombienet = Zombienet::new(
939 cache,
940 config.path().try_into()?,
941 None,
942 None,
943 None,
944 None,
945 Some(&vec!["https://github.com/r0gue-io/pop-node#node-v0.3.0".to_string()]),
946 )
947 .await?;
948 let mut binary_name: String = "".to_string();
949 for binary in zombienet.binaries().filter(|b| !b.exists() && b.name() == "pop-node") {
950 binary_name = format!("{}-{}", binary.name(), binary.version().unwrap());
951 binary.source(true, &(), true).await?;
952 }
953 Ok(binary_name)
954 }
955
956 fn replace_mock_with_binary(temp_dir: &Path, binary_name: String) -> Result<PathBuf, Error> {
958 let binary_path = temp_dir.join(binary_name);
959 let content = fs::read(&binary_path)?;
960 write(temp_dir.join("target/release/parachain-template-node"), content)?;
961 set_executable_permission(temp_dir.join("target/release/parachain-template-node"))?;
963 Ok(binary_path)
964 }
965
966 fn add_production_profile(project: &Path) -> Result<()> {
967 let root_toml_path = project.join("Cargo.toml");
968 let mut root_toml_content = fs::read_to_string(&root_toml_path)?;
969 root_toml_content.push_str(
970 r#"
971 [profile.production]
972 codegen-units = 1
973 inherits = "release"
974 lto = true
975 "#,
976 );
977 write(&root_toml_path, root_toml_content)?;
979 Ok(())
980 }
981
982 #[test]
983 fn build_chain_works() -> Result<()> {
984 let name = "parachain_template_node";
985 let temp_dir = tempdir()?;
986 cmd("cargo", ["new", name, "--bin"]).dir(temp_dir.path()).run()?;
987 let project = temp_dir.path().join(name);
988 add_production_profile(&project)?;
989 add_feature(&project, ("dummy-feature".to_string(), vec![]))?;
990 for node in [None, Some("custom_node")] {
991 let node_path = generate_mock_node(&project, node)?;
992 for package in [None, Some(String::from("parachain_template_node"))] {
993 for profile in Profile::VARIANTS {
994 let node_path = node.map(|_| node_path.as_path());
995 let binary = build_chain(
996 &project,
997 package.clone(),
998 profile,
999 node_path,
1000 &["dummy-feature".to_string()],
1001 false,
1002 )?;
1003 let target_directory = profile.target_directory(&project);
1004 assert!(target_directory.exists());
1005 assert!(target_directory.join("parachain_template_node").exists());
1006 assert_eq!(
1007 binary.display().to_string(),
1008 target_directory.join("parachain_template_node").display().to_string()
1009 );
1010 }
1011 }
1012 }
1013 Ok(())
1014 }
1015
1016 #[test]
1017 fn build_project_works() -> Result<()> {
1018 let name = "example_project";
1019 let temp_dir = tempdir()?;
1020 cmd("cargo", ["new", name, "--bin"]).dir(temp_dir.path()).run()?;
1021 let project = temp_dir.path().join(name);
1022 add_production_profile(&project)?;
1023 add_feature(&project, ("dummy-feature".to_string(), vec![]))?;
1024 for package in [None, Some(String::from(name))] {
1025 for profile in Profile::VARIANTS {
1026 build_project(
1027 &project,
1028 package.clone(),
1029 profile,
1030 &["dummy-feature".to_string()],
1031 None,
1032 false,
1033 )?;
1034 let target_directory = profile.target_directory(&project);
1035 let binary = build_binary_path(&project, |runtime_name| {
1036 target_directory.join(runtime_name)
1037 })?;
1038 assert!(target_directory.exists());
1039 assert!(target_directory.join(name).exists());
1040 assert_eq!(
1041 binary.display().to_string(),
1042 target_directory.join(name).display().to_string()
1043 );
1044 }
1045 }
1046 Ok(())
1047 }
1048
1049 #[test]
1050 fn binary_path_of_node_works() -> Result<()> {
1051 let temp_dir =
1052 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1053 mock_build_process(temp_dir.path())?;
1054 mock_node(temp_dir.path())?;
1055 let release_path =
1056 binary_path(&temp_dir.path().join("target/release"), &temp_dir.path().join("node"))?;
1057 assert_eq!(
1058 release_path.display().to_string(),
1059 format!("{}/target/release/parachain-template-node", temp_dir.path().display())
1060 );
1061 Ok(())
1062 }
1063
1064 #[test]
1065 fn binary_path_of_runtime_works() -> Result<()> {
1066 let temp_dir =
1067 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1068 let runtime = "parachain-template-runtime";
1070 mock_build_runtime_process(temp_dir.path())?;
1071 let release_path = runtime_binary_path(
1072 &temp_dir.path().join("target/release/wbuild"),
1073 &temp_dir.path().join("runtime"),
1074 )?;
1075 assert_eq!(
1076 release_path.display().to_string(),
1077 format!(
1078 "{}/target/release/wbuild/{runtime}/{}.wasm",
1079 temp_dir.path().display(),
1080 runtime.replace("-", "_")
1081 )
1082 );
1083
1084 Ok(())
1085 }
1086
1087 #[test]
1088 fn binary_path_fails_missing_binary() -> Result<()> {
1089 let temp_dir =
1090 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1091 mock_node(temp_dir.path())?;
1092 assert!(matches!(
1093 binary_path(&temp_dir.path().join("target/release"), &temp_dir.path().join("node")),
1094 Err(Error::MissingBinary(error)) if error == "parachain-template-node"
1095 ));
1096 Ok(())
1097 }
1098
1099 #[tokio::test]
1100 async fn generate_files_works() -> Result<()> {
1101 let temp_dir =
1102 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1103 mock_build_process(temp_dir.path())?;
1104 let binary_name = fetch_binary(temp_dir.path()).await?;
1105 let binary_path = replace_mock_with_binary(temp_dir.path(), binary_name)?;
1106 let plain_chain_spec = &temp_dir.path().join("plain-parachain-chainspec.json");
1108 generate_plain_chain_spec_with_node(
1109 &binary_path,
1110 &temp_dir.path().join("plain-parachain-chainspec.json"),
1111 false,
1112 "local",
1113 )?;
1114 assert!(plain_chain_spec.exists());
1115 {
1116 let mut chain_spec = ChainSpec::from(plain_chain_spec)?;
1117 chain_spec.replace_para_id(2001)?;
1118 chain_spec.to_file(plain_chain_spec)?;
1119 }
1120 let raw_chain_spec = generate_raw_chain_spec_with_node(
1121 &binary_path,
1122 plain_chain_spec,
1123 "raw-parachain-chainspec.json",
1124 )?;
1125 assert!(raw_chain_spec.exists());
1126 let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file");
1127 assert!(content.contains("\"para_id\": 2001"));
1128 assert!(content.contains("\"bootNodes\": []"));
1129 let wasm_file =
1131 export_wasm_file_with_node(&binary_path, &raw_chain_spec, "para-2001-wasm")?;
1132 assert!(wasm_file.exists());
1133 let genesis_file = generate_genesis_state_file_with_node(
1135 &binary_path,
1136 &raw_chain_spec,
1137 "para-2001-genesis-state",
1138 )?;
1139 assert!(genesis_file.exists());
1140 Ok(())
1141 }
1142
1143 #[test]
1144 fn generate_plain_chain_spec_with_runtime_works_with_name_and_id_override() -> Result<()> {
1145 let temp_dir = tempdir()?;
1146 let plain_chain_spec = &temp_dir.path().join("plain-parachain-chainspec.json");
1148 generate_plain_chain_spec_with_runtime(
1149 Vec::from(MOCK_WASM),
1150 plain_chain_spec,
1151 "local_testnet",
1152 Some("POP Chain Spec"),
1153 Some("pop-chain-spec"),
1154 )?;
1155 assert!(plain_chain_spec.exists());
1156 let raw_chain_spec =
1157 generate_raw_chain_spec_with_runtime(plain_chain_spec, "raw-parachain-chainspec.json")?;
1158 assert!(raw_chain_spec.exists());
1159 let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file");
1160 assert!(content.contains("\"name\": \"POP Chain Spec\""));
1161 assert!(content.contains("\"id\": \"pop-chain-spec\""));
1162 assert!(content.contains("\"bootNodes\": []"));
1163 Ok(())
1164 }
1165
1166 #[test]
1167 fn generate_plain_chain_spec_with_runtime_works_without_name_and_id_override() -> Result<()> {
1168 let temp_dir = tempdir()?;
1169 let plain_chain_spec = &temp_dir.path().join("plain-parachain-chainspec.json");
1171 generate_plain_chain_spec_with_runtime(
1172 Vec::from(MOCK_WASM),
1173 plain_chain_spec,
1174 "local_testnet",
1175 None,
1176 None,
1177 )?;
1178 assert!(plain_chain_spec.exists());
1179 let raw_chain_spec =
1180 generate_raw_chain_spec_with_runtime(plain_chain_spec, "raw-parachain-chainspec.json")?;
1181 assert!(raw_chain_spec.exists());
1182 let content = fs::read_to_string(raw_chain_spec.clone()).expect("Could not read file");
1183 assert!(content.contains("\"name\": \"Development\""));
1184 assert!(content.contains("\"id\": \"dev\""));
1185 assert!(content.contains("\"bootNodes\": []"));
1186 Ok(())
1187 }
1188
1189 #[tokio::test]
1190 async fn fails_to_generate_plain_chain_spec_when_file_missing() -> Result<()> {
1191 let temp_dir =
1192 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1193 mock_build_process(temp_dir.path())?;
1194 let binary_name = fetch_binary(temp_dir.path()).await?;
1195 let binary_path = replace_mock_with_binary(temp_dir.path(), binary_name)?;
1196 assert!(matches!(
1197 generate_plain_chain_spec_with_node(
1198 &binary_path,
1199 &temp_dir.path().join("plain-parachain-chainspec.json"),
1200 false,
1201 &temp_dir.path().join("plain-parachain-chainspec.json").display().to_string(),
1202 ),
1203 Err(Error::BuildSpecError(message)) if message.contains("No such file or directory")
1204 ));
1205 assert!(!temp_dir.path().join("plain-parachain-chainspec.json").exists());
1206 Ok(())
1207 }
1208
1209 #[test]
1210 fn raw_chain_spec_fails_wrong_chain_spec() -> Result<()> {
1211 assert!(matches!(
1212 generate_raw_chain_spec_with_node(
1213 Path::new("./binary"),
1214 Path::new("./plain-parachain-chainspec.json"),
1215 "plain-parachain-chainspec.json"
1216 ),
1217 Err(Error::MissingChainSpec(..))
1218 ));
1219 Ok(())
1220 }
1221
1222 #[test]
1223 fn export_wasm_file_fails_wrong_chain_spec() -> Result<()> {
1224 assert!(matches!(
1225 export_wasm_file_with_node(
1226 Path::new("./binary"),
1227 Path::new("./raw-parachain-chainspec"),
1228 "para-2001-wasm"
1229 ),
1230 Err(Error::MissingChainSpec(..))
1231 ));
1232 Ok(())
1233 }
1234
1235 #[test]
1236 fn generate_genesis_state_file_wrong_chain_spec() -> Result<()> {
1237 assert!(matches!(
1238 generate_genesis_state_file_with_node(
1239 Path::new("./binary"),
1240 Path::new("./raw-parachain-chainspec"),
1241 "para-2001-genesis-state",
1242 ),
1243 Err(Error::MissingChainSpec(..))
1244 ));
1245 Ok(())
1246 }
1247
1248 #[test]
1249 fn get_chain_type_works() -> Result<()> {
1250 let chain_spec = ChainSpec(json!({
1251 "chainType": "test",
1252 }));
1253 assert_eq!(chain_spec.get_chain_type(), Some("test"));
1254 Ok(())
1255 }
1256
1257 #[test]
1258 fn get_chain_name_works() -> Result<()> {
1259 assert_eq!(ChainSpec(json!({})).get_name(), None);
1260 let chain_spec = ChainSpec(json!({
1261 "name": "test",
1262 }));
1263 assert_eq!(chain_spec.get_name(), Some("test"));
1264 Ok(())
1265 }
1266
1267 #[test]
1268 fn get_chain_id_works() -> Result<()> {
1269 let chain_spec = ChainSpec(json!({
1270 "para_id": 2002,
1271 }));
1272 assert_eq!(chain_spec.get_chain_id(), Some(2002));
1273 Ok(())
1274 }
1275
1276 #[test]
1277 fn get_property_based_on_works() -> Result<()> {
1278 assert_eq!(ChainSpec(json!({})).get_property_based_on(), None);
1279 let chain_spec = ChainSpec(json!({
1280 "properties": {
1281 "basedOn": "test",
1282 }
1283 }));
1284 assert_eq!(chain_spec.get_property_based_on(), Some("test"));
1285 Ok(())
1286 }
1287
1288 #[test]
1289 fn get_protocol_id_works() -> Result<()> {
1290 let chain_spec = ChainSpec(json!({
1291 "protocolId": "test",
1292 }));
1293 assert_eq!(chain_spec.get_protocol_id(), Some("test"));
1294 Ok(())
1295 }
1296
1297 #[test]
1298 fn get_relay_chain_works() -> Result<()> {
1299 let chain_spec = ChainSpec(json!({
1300 "relay_chain": "test",
1301 }));
1302 assert_eq!(chain_spec.get_relay_chain(), Some("test"));
1303 Ok(())
1304 }
1305
1306 #[test]
1307 fn get_sudo_key_works() -> Result<()> {
1308 assert_eq!(ChainSpec(json!({})).get_sudo_key(), None);
1309 let chain_spec = ChainSpec(json!({
1310 "para_id": 1000,
1311 "genesis": {
1312 "runtimeGenesis": {
1313 "patch": {
1314 "sudo": {
1315 "key": "sudo-key"
1316 }
1317 }
1318 }
1319 },
1320 }));
1321 assert_eq!(chain_spec.get_sudo_key(), Some("sudo-key"));
1322 Ok(())
1323 }
1324
1325 #[test]
1326 fn replace_para_id_works() -> Result<()> {
1327 let mut chain_spec = ChainSpec(json!({
1328 "para_id": 1000,
1329 "genesis": {
1330 "runtimeGenesis": {
1331 "patch": {
1332 "parachainInfo": {
1333 "parachainId": 1000
1334 }
1335 }
1336 }
1337 },
1338 }));
1339 chain_spec.replace_para_id(2001)?;
1340 assert_eq!(
1341 chain_spec.0,
1342 json!({
1343 "para_id": 2001,
1344 "genesis": {
1345 "runtimeGenesis": {
1346 "patch": {
1347 "parachainInfo": {
1348 "parachainId": 2001
1349 }
1350 }
1351 }
1352 },
1353 })
1354 );
1355 Ok(())
1356 }
1357
1358 #[test]
1359 fn replace_para_id_fails() -> Result<()> {
1360 let mut chain_spec = ChainSpec(json!({
1361 "para_id": 2001,
1362 "": {
1363 "runtimeGenesis": {
1364 "patch": {
1365 "parachainInfo": {
1366 "parachainId": 1000
1367 }
1368 }
1369 }
1370 },
1371 }));
1372 assert!(chain_spec.replace_para_id(2001).is_ok());
1373 chain_spec = ChainSpec(json!({
1374 "para_id": 2001,
1375 "genesis": {
1376 "": {
1377 "patch": {
1378 "parachainInfo": {
1379 "parachainId": 1000
1380 }
1381 }
1382 }
1383 },
1384 }));
1385 assert!(chain_spec.replace_para_id(2001).is_ok());
1386 chain_spec = ChainSpec(json!({
1387 "para_id": 2001,
1388 "genesis": {
1389 "runtimeGenesis": {
1390 "": {
1391 "parachainInfo": {
1392 "parachainId": 1000
1393 }
1394 }
1395 }
1396 },
1397 }));
1398 assert!(chain_spec.replace_para_id(2001).is_ok());
1399 chain_spec = ChainSpec(json!({
1400 "para_id": 2001,
1401 "genesis": {
1402 "runtimeGenesis": {
1403 "patch": {
1404 "": {
1405 "parachainId": 1000
1406 }
1407 }
1408 }
1409 },
1410 }));
1411 assert!(chain_spec.replace_para_id(2001).is_ok());
1412 chain_spec = ChainSpec(json!({
1413 "para_id": 2001,
1414 "genesis": {
1415 "runtimeGenesis": {
1416 "patch": {
1417 "parachainInfo": {
1418 }
1419 }
1420 }
1421 },
1422 }));
1423 assert!(chain_spec.replace_para_id(2001).is_ok());
1424 Ok(())
1425 }
1426
1427 #[test]
1428 fn replace_relay_chain_works() -> Result<()> {
1429 let mut chain_spec = ChainSpec(json!({"relay_chain": "old-relay"}));
1430 chain_spec.replace_relay_chain("new-relay")?;
1431 assert_eq!(chain_spec.0, json!({"relay_chain": "new-relay"}));
1432 Ok(())
1433 }
1434
1435 #[test]
1436 fn replace_chain_type_works() -> Result<()> {
1437 let mut chain_spec = ChainSpec(json!({"chainType": "old-chainType"}));
1438 chain_spec.replace_chain_type("new-chainType")?;
1439 assert_eq!(chain_spec.0, json!({"chainType": "new-chainType"}));
1440 Ok(())
1441 }
1442
1443 #[test]
1444 fn replace_chain_type_fails() -> Result<()> {
1445 let mut chain_spec = ChainSpec(json!({"": "old-chainType"}));
1446 assert!(
1447 matches!(chain_spec.replace_chain_type("new-chainType"), Err(Error::Config(error)) if error == "expected `chainType`")
1448 );
1449 Ok(())
1450 }
1451
1452 #[test]
1453 fn replace_protocol_id_works() -> Result<()> {
1454 let mut chain_spec = ChainSpec(json!({"protocolId": "old-protocolId"}));
1455 chain_spec.replace_protocol_id("new-protocolId")?;
1456 assert_eq!(chain_spec.0, json!({"protocolId": "new-protocolId"}));
1457 Ok(())
1458 }
1459
1460 #[test]
1461 fn replace_protocol_id_fails() -> Result<()> {
1462 let mut chain_spec = ChainSpec(json!({"": "old-protocolId"}));
1463 assert!(
1464 matches!(chain_spec.replace_protocol_id("new-protocolId"), Err(Error::Config(error)) if error == "expected `protocolId`")
1465 );
1466 Ok(())
1467 }
1468
1469 #[test]
1470 fn replace_collator_keys_works() -> Result<()> {
1471 let mut chain_spec = ChainSpec(json!({
1472 "para_id": 1000,
1473 "genesis": {
1474 "runtimeGenesis": {
1475 "patch": {
1476 "collatorSelection": {
1477 "invulnerables": [
1478 "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
1479 "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
1480 ]
1481 },
1482 "session": {
1483 "keys": [
1484 [
1485 "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
1486 "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
1487 {
1488 "aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
1489 }
1490 ],
1491 [
1492 "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
1493 "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
1494 {
1495 "aura": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
1496 }
1497 ]
1498 ]
1499 },
1500 }
1501 }
1502 },
1503 }));
1504 chain_spec.replace_collator_keys(vec![
1505 "5Gw3s7q4QLkSWwknsi8jj5P1K79e5N4b6pfsNUzS97H1DXYF".to_string(),
1506 ])?;
1507 assert_eq!(
1508 chain_spec.0,
1509 json!({
1510 "para_id": 1000,
1511 "genesis": {
1512 "runtimeGenesis": {
1513 "patch": {
1514 "collatorSelection": {
1515 "invulnerables": [
1516 "5Gw3s7q4QLkSWwknsi8jj5P1K79e5N4b6pfsNUzS97H1DXYF",
1517 ]
1518 },
1519 "session": {
1520 "keys": [
1521 [
1522 "5Gw3s7q4QLkSWwknsi8jj5P1K79e5N4b6pfsNUzS97H1DXYF",
1523 "5Gw3s7q4QLkSWwknsi8jj5P1K79e5N4b6pfsNUzS97H1DXYF",
1524 {
1525 "aura": "5Gw3s7q4QLkSWwknsi8jj5P1K79e5N4b6pfsNUzS97H1DXYF"
1526 }
1527 ],
1528 ]
1529 },
1530 }
1531 }
1532 },
1533 })
1534 );
1535 Ok(())
1536 }
1537
1538 #[test]
1539 fn replace_use_evm_collator_keys_works() -> Result<()> {
1540 let mut chain_spec = ChainSpec(json!({
1541 "para_id": 1000,
1542 "properties": {
1543 "isEthereum": true
1544 },
1545 "genesis": {
1546 "runtimeGenesis": {
1547 "patch": {
1548 "collatorSelection": {
1549 "invulnerables": [
1550 "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
1551 ]
1552 },
1553 "session": {
1554 "keys": [
1555 [
1556 "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
1557 "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
1558 {
1559 "aura": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
1560 }
1561 ]
1562 ]
1563 },
1564 }
1565 }
1566 },
1567 }));
1568 chain_spec.replace_collator_keys(vec![
1569 "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".to_string(),
1570 ])?;
1571 assert_eq!(
1572 chain_spec.0,
1573 json!({
1574 "para_id": 1000,
1575 "properties": {
1576 "isEthereum": true
1577 },
1578 "genesis": {
1579 "runtimeGenesis": {
1580 "patch": {
1581 "collatorSelection": {
1582 "invulnerables": [
1583 "0x9621dde636de098b43efb0fa9b61facfe328f99d",
1584 ]
1585 },
1586 "session": {
1587 "keys": [
1588 [
1589 "0x9621dde636de098b43efb0fa9b61facfe328f99d",
1590 "0x9621dde636de098b43efb0fa9b61facfe328f99d",
1591 {
1592 "aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
1593 }
1594 ],
1595 ]
1596 },
1597 }
1598 }
1599 },
1600 })
1601 );
1602 Ok(())
1603 }
1604
1605 #[test]
1606 fn update_runtime_code_works() -> Result<()> {
1607 let mut chain_spec =
1608 ChainSpec(json!({"genesis": {"runtimeGenesis" : { "code": "0x00" }}}));
1609
1610 chain_spec.update_runtime_code(&from_hex("0x1234")?)?;
1611 assert_eq!(chain_spec.0, json!({"genesis": {"runtimeGenesis" : { "code": "0x1234" }}}));
1612 Ok(())
1613 }
1614
1615 #[test]
1616 fn update_runtime_code_fails() -> Result<()> {
1617 let mut chain_spec =
1618 ChainSpec(json!({"invalidKey": {"runtimeGenesis" : { "code": "0x00" }}}));
1619 assert!(
1620 matches!(chain_spec.update_runtime_code(&from_hex("0x1234")?), Err(Error::Config(error)) if error == "expected `genesis`")
1621 );
1622
1623 chain_spec = ChainSpec(json!({"genesis": {"invalidKey" : { "code": "0x00" }}}));
1624 assert!(
1625 matches!(chain_spec.update_runtime_code(&from_hex("0x1234")?), Err(Error::Config(error)) if error == "expected `runtimeGenesis`")
1626 );
1627
1628 chain_spec = ChainSpec(json!({"genesis": {"runtimeGenesis" : { "invalidKey": "0x00" }}}));
1629 assert!(
1630 matches!(chain_spec.update_runtime_code(&from_hex("0x1234")?), Err(Error::Config(error)) if error == "expected `runtimeGenesis.code`")
1631 );
1632 Ok(())
1633 }
1634
1635 #[test]
1636 fn check_command_exists_fails() -> Result<()> {
1637 let binary_path = PathBuf::from("/bin");
1638 let cmd = "nonexistent_command";
1639 assert!(matches!(
1640 check_command_exists(&binary_path, cmd),
1641 Err(Error::MissingCommand {command, binary })
1642 if command == cmd && binary == binary_path.display().to_string()
1643 ));
1644 Ok(())
1645 }
1646
1647 #[test]
1648 fn is_supported_works() -> Result<()> {
1649 let temp_dir = tempdir()?;
1650 let path = temp_dir.path();
1651
1652 let name = "hello_world";
1654 cmd("cargo", ["new", name]).dir(path).run()?;
1655 assert!(!is_supported(&path.join(name)));
1656
1657 let mut manifest = from_path(&path.join(name))?;
1659 manifest
1660 .dependencies
1661 .insert("cumulus-client-collator".into(), Dependency::Simple("^0.14.0".into()));
1662 let manifest = toml_edit::ser::to_string_pretty(&manifest)?;
1663 write(path.join(name).join("Cargo.toml"), manifest)?;
1664 assert!(is_supported(&path.join(name)));
1665 Ok(())
1666 }
1667
1668 #[test]
1669 fn chain_spec_builder_node_path_works() -> Result<()> {
1670 let node_path = PathBuf::from("/test/node");
1671 let builder = ChainSpecBuilder::Node {
1672 node_path: node_path.clone(),
1673 default_bootnode: true,
1674 profile: Profile::Release,
1675 };
1676 assert_eq!(builder.path(), node_path);
1677 Ok(())
1678 }
1679
1680 #[test]
1681 fn chain_spec_builder_runtime_path_works() -> Result<()> {
1682 let runtime_path = PathBuf::from("/test/runtime");
1683 let builder = ChainSpecBuilder::Runtime {
1684 runtime_path: runtime_path.clone(),
1685 profile: Profile::Release,
1686 };
1687 assert_eq!(builder.path(), runtime_path);
1688 Ok(())
1689 }
1690
1691 #[test]
1692 fn chain_spec_builder_node_profile_works() -> Result<()> {
1693 for profile in Profile::VARIANTS {
1694 let builder = ChainSpecBuilder::Node {
1695 node_path: PathBuf::from("/test/node"),
1696 default_bootnode: true,
1697 profile: *profile,
1698 };
1699 assert_eq!(builder.profile(), *profile);
1700 }
1701 Ok(())
1702 }
1703
1704 #[test]
1705 fn chain_spec_builder_runtime_profile_works() -> Result<()> {
1706 for profile in Profile::VARIANTS {
1707 let builder = ChainSpecBuilder::Runtime {
1708 runtime_path: PathBuf::from("/test/runtime"),
1709 profile: *profile,
1710 };
1711 assert_eq!(builder.profile(), *profile);
1712 }
1713 Ok(())
1714 }
1715
1716 #[test]
1717 fn chain_spec_builder_node_artifact_path_works() -> Result<()> {
1718 let temp_dir =
1719 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1720 mock_build_process(temp_dir.path())?;
1721 mock_node(temp_dir.path())?;
1722 let builder = ChainSpecBuilder::Node {
1723 node_path: temp_dir.path().join("node"),
1724 default_bootnode: true,
1725 profile: Profile::Release,
1726 };
1727 let artifact_path = builder.artifact_path()?;
1728 assert!(artifact_path.exists());
1729 assert!(artifact_path.ends_with("parachain-template-node"));
1730 Ok(())
1731 }
1732
1733 #[test]
1734 fn chain_spec_builder_runtime_artifact_path_works() -> Result<()> {
1735 let temp_dir =
1736 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1737 mock_build_runtime_process(temp_dir.path())?;
1738
1739 let builder = ChainSpecBuilder::Runtime {
1740 runtime_path: temp_dir.path().join("runtime"),
1741 profile: Profile::Release,
1742 };
1743 let artifact_path = builder.artifact_path()?;
1744 assert!(artifact_path.is_file());
1745 assert!(artifact_path.ends_with("parachain_template_runtime.wasm"));
1746 Ok(())
1747 }
1748
1749 #[test]
1750 fn chain_spec_builder_node_artifact_path_fails() -> Result<()> {
1751 let temp_dir =
1752 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1753
1754 let builder = ChainSpecBuilder::Node {
1755 node_path: temp_dir.path().join("node"),
1756 default_bootnode: true,
1757 profile: Profile::Release,
1758 };
1759 assert!(builder.artifact_path().is_err());
1760 Ok(())
1761 }
1762
1763 #[test]
1764 fn chain_spec_builder_runtime_artifact_path_fails() -> Result<()> {
1765 let temp_dir =
1766 setup_template_and_instantiate().expect("Failed to setup template and instantiate");
1767
1768 let builder = ChainSpecBuilder::Runtime {
1769 runtime_path: temp_dir.path().join("runtime"),
1770 profile: Profile::Release,
1771 };
1772 let result = builder.artifact_path();
1773 assert!(result.is_err());
1774 assert!(matches!(result, Err(e) if e.to_string().contains("No runtime found")));
1775 Ok(())
1776 }
1777
1778 #[test]
1779 fn chain_spec_builder_generate_raw_chain_spec_works() -> Result<()> {
1780 let temp_dir = tempdir()?;
1781 let builder = ChainSpecBuilder::Runtime {
1782 runtime_path: temp_dir.path().join("runtime"),
1783 profile: Profile::Release,
1784 };
1785 let original_chain_spec_path =
1786 PathBuf::from("artifacts/passet-hub-spec.json").canonicalize()?;
1787 assert!(original_chain_spec_path.exists());
1788 let chain_spec_path = temp_dir.path().join(original_chain_spec_path.file_name().unwrap());
1789 fs::copy(&original_chain_spec_path, &chain_spec_path)?;
1790 let raw_chain_spec_path = temp_dir.path().join("raw.json");
1791 let final_raw_path = builder.generate_raw_chain_spec(
1792 &chain_spec_path,
1793 raw_chain_spec_path.file_name().unwrap().to_str().unwrap(),
1794 )?;
1795 assert!(final_raw_path.is_file());
1796 assert_eq!(final_raw_path, raw_chain_spec_path);
1797
1798 let raw_content = fs::read_to_string(&raw_chain_spec_path)?;
1800 let raw_json: Value = serde_json::from_str(&raw_content)?;
1801 assert!(raw_json.get("genesis").is_some());
1802 assert!(raw_json.get("genesis").unwrap().get("raw").is_some());
1803 assert!(raw_json.get("genesis").unwrap().get("raw").unwrap().get("top").is_some());
1804 Ok(())
1805 }
1806
1807 #[test]
1808 fn chain_spec_builder_export_wasm_works() -> Result<()> {
1809 let temp_dir = tempdir()?;
1810 let builder = ChainSpecBuilder::Runtime {
1811 runtime_path: temp_dir.path().join("runtime"),
1812 profile: Profile::Release,
1813 };
1814 let original_chain_spec_path =
1815 PathBuf::from("artifacts/passet-hub-spec.json").canonicalize()?;
1816 let chain_spec_path = temp_dir.path().join(original_chain_spec_path.file_name().unwrap());
1817 fs::copy(&original_chain_spec_path, &chain_spec_path)?;
1818 let final_wasm_path = temp_dir.path().join("runtime.wasm");
1819 let final_raw_path = builder.generate_raw_chain_spec(&chain_spec_path, "raw.json")?;
1820 let wasm_path = builder.export_wasm_file(
1821 &final_raw_path,
1822 final_wasm_path.file_name().unwrap().to_str().unwrap(),
1823 )?;
1824 assert!(wasm_path.is_file());
1825 assert_eq!(final_wasm_path, wasm_path);
1826 Ok(())
1827 }
1828
1829 #[test]
1830 fn fetch_dependencies_works() -> Result<()> {
1831 let name = "fetch_test";
1832 let temp_dir = tempdir()?;
1833 cmd("cargo", ["new", name, "--bin"]).dir(temp_dir.path()).run()?;
1834 let project = temp_dir.path().join(name);
1835 fetch_dependencies(&project)?;
1836 Ok(())
1837 }
1838
1839 #[test]
1840 fn fetch_dependencies_handles_invalid_path() {
1841 assert!(fetch_dependencies(Path::new("/nonexistent/path")).is_err());
1842 }
1843}