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