1use crate::{Error, registry::traits};
4use pop_common::{
5 git::GitHub,
6 polkadot_sdk::sort_by_latest_semantic_version,
7 sourcing::{
8 ArchiveFileSpec, Binary,
9 GitHub::*,
10 Source,
11 filters::prefix,
12 traits::{
13 Source as SourceT,
14 enums::{Source as _, *},
15 },
16 },
17 target,
18};
19use std::path::Path;
20use strum::{EnumProperty as _, VariantArray as _};
21use strum_macros::{AsRefStr, EnumProperty, VariantArray};
22
23#[repr(u8)]
25#[derive(AsRefStr, Clone, Debug, EnumProperty, Eq, Hash, PartialEq, VariantArray)]
26pub enum Runtime {
27 #[strum(props(
29 Repository = "https://github.com/r0gue-io/polkadot-runtimes",
30 Binary = "chain-spec-generator",
31 Chain = "kusama-local",
32 Fallback = "v1.4.1"
33 ))]
34 Kusama = 0,
35 #[strum(props(
37 Repository = "https://github.com/r0gue-io/paseo-runtimes",
38 Binary = "chain-spec-generator",
39 Chain = "paseo-local",
40 Fallback = "v1.4.1"
41 ))]
42 Paseo = 1,
43 #[strum(props(
45 Repository = "https://github.com/r0gue-io/polkadot-runtimes",
46 Binary = "chain-spec-generator",
47 Chain = "polkadot-local",
48 Fallback = "v1.4.1"
49 ))]
50 Polkadot = 2,
51 #[strum(props(Repository = "https://github.com/r0gue-io/polkadot", Chain = "westend-local",))]
53 Westend = 3,
54}
55
56impl SourceT for Runtime {
57 type Error = Error;
58 fn source(&self) -> Result<Source, Error> {
60 let repo = GitHub::parse(self.repository())?;
62 let name = self.name().to_lowercase();
63 let binary = self.binary();
64 Ok(Source::GitHub(ReleaseArchive {
65 owner: repo.org,
66 repository: repo.name,
67 tag: None,
68 tag_pattern: self.tag_pattern().map(|t| t.into()),
69 prerelease: false,
70 version_comparator: sort_by_latest_semantic_version,
71 fallback: self.fallback().into(),
72 archive: format!("{binary}-{}.tar.gz", target()?),
73 contents: vec![ArchiveFileSpec::new(
74 binary.into(),
75 Some(format!("{name}-{binary}").into()),
76 true,
77 )],
78 latest: None,
79 }))
80 }
81}
82
83impl Runtime {
84 pub fn from(value: u8) -> Option<Self> {
89 match value {
90 0 => Some(Self::Kusama),
91 1 => Some(Self::Paseo),
92 2 => Some(Self::Polkadot),
93 3 => Some(Self::Westend),
94 _ => None,
95 }
96 }
97
98 pub fn from_chain(chain: &str) -> Option<Self> {
103 Runtime::VARIANTS
104 .iter()
105 .find(|r| chain.to_lowercase().ends_with(r.chain()))
106 .cloned()
107 }
108
109 pub(crate) fn chain(&self) -> &'static str {
111 self.get_str("Chain").expect("expected specification of `Chain`")
112 }
113
114 pub fn name(&self) -> &str {
116 self.as_ref()
117 }
118
119 pub fn chains(&self) -> &'static [Box<dyn traits::Chain>] {
121 crate::registry::chains(self)
122 }
123}
124
125pub(super) async fn chain_spec_generator(
126 chain: &str,
127 version: Option<&str>,
128 cache: &Path,
129) -> Result<Option<Binary>, Error> {
130 if let Some(runtime) = Runtime::from_chain(chain) {
131 if runtime == Runtime::Westend {
132 return Ok(None);
134 }
135 let name = format!("{}-{}", runtime.name().to_lowercase(), runtime.binary());
136 let source = runtime
137 .source()?
138 .resolve(&name, version, cache, |f| prefix(f, &name))
139 .await
140 .into();
141 let binary = Binary::Source { name, source, cache: cache.to_path_buf() };
142 return Ok(Some(binary));
143 }
144 Ok(None)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use Runtime::*;
151 use tempfile::tempdir;
152
153 #[tokio::test]
154 async fn kusama_works() -> anyhow::Result<()> {
155 let expected = Runtime::Kusama;
156 let version = "v1.4.1";
157 let temp_dir = tempdir()?;
158 let binary = chain_spec_generator("kusama-local", Some(version), temp_dir.path())
159 .await?
160 .unwrap();
161 assert!(matches!(binary, Binary::Source { name, source, cache }
162 if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) &&
163 source == Source::GitHub(ReleaseArchive {
164 owner: "r0gue-io".to_string(),
165 repository: "polkadot-runtimes".to_string(),
166 tag: Some(version.to_string()),
167 tag_pattern: None,
168 prerelease: false,
169 version_comparator: sort_by_latest_semantic_version,
170 fallback: expected.fallback().to_string(),
171 archive: format!("chain-spec-generator-{}.tar.gz", target()?),
172 contents: ["chain-spec-generator"].map(|b| ArchiveFileSpec::new(b.into(), Some(format!("kusama-{b}").into()), true)).to_vec(),
173 latest: binary.latest().map(|l| l.to_string()),
174 }).into() &&
175 cache == temp_dir.path()
176 ));
177 Ok(())
178 }
179
180 #[tokio::test]
181 async fn paseo_works() -> anyhow::Result<()> {
182 let expected = Runtime::Paseo;
183 let version = "v1.4.1";
184 let temp_dir = tempdir()?;
185 let binary = chain_spec_generator("paseo-local", Some(version), temp_dir.path())
186 .await?
187 .unwrap();
188 assert!(matches!(binary, Binary::Source { name, source, cache }
189 if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) &&
190 source == Source::GitHub(ReleaseArchive {
191 owner: "r0gue-io".to_string(),
192 repository: "paseo-runtimes".to_string(),
193 tag: Some(version.to_string()),
194 tag_pattern: None,
195 prerelease: false,
196 version_comparator: sort_by_latest_semantic_version,
197 fallback: expected.fallback().to_string(),
198 archive: format!("chain-spec-generator-{}.tar.gz", target()?),
199 contents: ["chain-spec-generator"].map(|b| ArchiveFileSpec::new(b.into(), Some(format!("paseo-{b}").into()), true)).to_vec(),
200 latest: binary.latest().map(|l| l.to_string()),
201 }).into() &&
202 cache == temp_dir.path()
203 ));
204 Ok(())
205 }
206
207 #[tokio::test]
208 async fn polkadot_works() -> anyhow::Result<()> {
209 let expected = Runtime::Polkadot;
210 let version = "v1.4.1";
211 let temp_dir = tempdir()?;
212 let binary = chain_spec_generator("polkadot-local", Some(version), temp_dir.path())
213 .await?
214 .unwrap();
215 assert!(matches!(binary, Binary::Source { name, source, cache }
216 if name == format!("{}-{}", expected.name().to_lowercase(), expected.binary()) &&
217 source == Source::GitHub(ReleaseArchive {
218 owner: "r0gue-io".to_string(),
219 repository: "polkadot-runtimes".to_string(),
220 tag: Some(version.to_string()),
221 tag_pattern: None,
222 prerelease: false,
223 version_comparator: sort_by_latest_semantic_version,
224 fallback: expected.fallback().to_string(),
225 archive: format!("chain-spec-generator-{}.tar.gz", target()?),
226 contents: ["chain-spec-generator"].map(|b| ArchiveFileSpec::new(b.into(), Some(format!("polkadot-{b}").into()), true)).to_vec(),
227 latest: binary.latest().map(|l| l.to_string()),
228 }).into() &&
229 cache == temp_dir.path()
230 ));
231 Ok(())
232 }
233
234 #[tokio::test]
235 async fn chain_spec_generator_returns_none_when_no_match() -> anyhow::Result<()> {
236 let temp_dir = tempdir()?;
237 assert_eq!(chain_spec_generator("rococo-local", None, temp_dir.path()).await?, None);
238 Ok(())
239 }
240
241 #[tokio::test]
242 async fn chain_spec_generator_returns_none_when_westend() -> anyhow::Result<()> {
243 let temp_dir = tempdir()?;
244 assert_eq!(chain_spec_generator("westend-local", None, temp_dir.path()).await?, None);
245 Ok(())
246 }
247
248 #[test]
249 fn from_u8_works() {
250 for i in 0u8..4 {
251 assert_eq!(Runtime::from(i).unwrap() as u8, i);
252 }
253 assert_eq!(Runtime::from(4), None);
254 }
255
256 #[test]
257 fn from_chain_works() {
258 for (chain, expected) in [
259 ("kusama-local", Kusama),
260 ("paseo-local", Paseo),
261 ("polkadot-local", Polkadot),
262 ("westend-local", Westend),
263 ] {
264 assert_eq!(Runtime::from_chain(chain).unwrap(), expected);
265 }
266 assert_eq!(Runtime::from_chain("pop"), None);
267 }
268
269 #[test]
270 fn chains_works() {
271 let comparator = |chains: &[Box<dyn traits::Chain>]| {
272 chains
273 .iter()
274 .map(|r| (r.id(), r.chain().to_string(), r.name().to_string()))
275 .collect::<Vec<_>>()
276 };
277 {};
278 for runtime in Runtime::VARIANTS {
279 assert_eq!(comparator(runtime.chains()), comparator(crate::registry::chains(runtime)));
280 }
281 }
282}