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