1use std::fs;
2use std::path::Path;
3use std::rc::Rc;
4use std::sync::Arc;
5
6use deno_ast::MediaType;
7use deno_ast::ParseParams;
8use deno_core::error::AnyError;
9use deno_core::serde_json;
10use deno_core::serde_json::json;
11use deno_core::serde_v8;
12use deno_core::url;
13use deno_core::v8;
14use deno_core::JsRuntime;
15
16mod perms;
17use perms::ZombiePermissions;
18
19mod ext;
24use ext::pjs_extension;
25
26type DynLoader = Rc<dyn deno_core::ModuleLoader + 'static>;
27
28#[derive(Debug, PartialEq)]
29pub enum ReturnValue {
32 Deserialized(serde_json::Value),
33 CantDeserialize(String),
34}
35
36pub fn create_runtime_with_loader(loader: Option<DynLoader>) -> JsRuntime {
40 JsRuntime::new(deno_core::RuntimeOptions {
41 module_loader: if let Some(loader) = loader {
42 Some(loader)
43 } else {
44 Some(Rc::new(deno_core::NoopModuleLoader))
45 },
46 startup_snapshot: None,
47 extensions: vec![
48 deno_telemetry::deno_telemetry::init_ops_and_esm(),
49 deno_console::deno_console::init_ops_and_esm(),
50 deno_webidl::deno_webidl::init_ops_and_esm(),
51 deno_url::deno_url::init_ops_and_esm(),
52 deno_web::deno_web::init_ops_and_esm::<ZombiePermissions>(
53 Arc::new(deno_web::BlobStore::default()),
54 None,
55 ),
56 deno_crypto::deno_crypto::init_ops_and_esm(None),
57 deno_fetch::deno_fetch::init_ops_and_esm::<ZombiePermissions>(deno_fetch::Options {
58 user_agent: "zombienet-agent".to_string(),
59 ..Default::default()
60 }),
61 deno_net::deno_net::init_ops_and_esm::<ZombiePermissions>(None, None),
62 deno_websocket::deno_websocket::init_ops_and_esm::<ZombiePermissions>(
63 "zombienet-agent".to_string(),
64 None,
65 None,
66 ),
67 pjs_extension::init_ops_and_esm(),
68 ],
69 extension_transpiler: Some(Rc::new(|specifier, source| {
70 deno_runtime::transpile::maybe_transpile_source(specifier, source)
71 })),
72 ..Default::default()
73 })
74}
75
76pub async fn run_file(
103 file_path: impl AsRef<Path>,
104 json_args: Option<Vec<serde_json::Value>>,
105) -> Result<ReturnValue, AnyError> {
106 let code_content = get_code(file_path).await?;
107 run_code(code_content, json_args, false).await
108}
109
110pub async fn run_file_with_wrap(
148 file_path: impl AsRef<Path>,
149 json_args: Option<Vec<serde_json::Value>>,
150) -> Result<ReturnValue, AnyError> {
151 let code_content = get_code(file_path).await?;
152 run_code(&code_content, json_args, true).await
153}
154
155pub async fn run_js_code(
156 code_content: impl Into<String>,
157 json_args: Option<Vec<serde_json::Value>>,
158) -> Result<ReturnValue, AnyError> {
159 run_code(code_content, json_args, false).await
160}
161
162pub async fn run_ts_code(
163 code_content: impl Into<String>,
164 json_args: Option<Vec<serde_json::Value>>,
165) -> Result<ReturnValue, AnyError> {
166 let transpiled = transpile(code_content)?;
167 run_code(transpiled, json_args, false).await
168}
169
170fn transpile(code: impl Into<String>) -> Result<String, AnyError> {
171 let parsed = deno_ast::parse_module(ParseParams {
172 specifier: url::Url::parse("file:///inner")?,
173 text: Arc::from(code.into()),
174 media_type: MediaType::TypeScript,
175 capture_tokens: false,
176 scope_analysis: false,
177 maybe_syntax: None,
178 })?;
179
180 let transpiled = parsed.transpile(
181 &Default::default(),
182 &Default::default(),
183 &Default::default(),
184 )?;
185 Ok(transpiled.into_source().text)
186}
187async fn get_code(file_path: impl AsRef<Path>) -> Result<String, AnyError> {
188 let content = fs::read_to_string(file_path.as_ref())?;
189
190 let code_content = if let MediaType::TypeScript = MediaType::from_path(file_path.as_ref()) {
192 transpile(&content)?
193 } else {
194 content
195 };
196
197 Ok(code_content)
198}
199
200async fn run_code(
201 code_content: impl Into<String>,
202 json_args: Option<Vec<serde_json::Value>>,
203 use_wrapper: bool,
204) -> Result<ReturnValue, AnyError> {
205 let code_content = code_content.into();
206 let bundle_util = include_str!("js/bundle-polkadot-util.js");
207 let bundle_util_crypto = include_str!("js/bundle-polkadot-util-crypto.js");
208 let bundle_keyring = include_str!("js/bundle-polkadot-keyring.js");
209 let bundle_types = include_str!("js/bundle-polkadot-types.js");
210 let bundle_api = include_str!("js/bundle-polkadot-api.js");
211 let code = if use_wrapper {
213 format!(
214 r#"
215 const {{ ApiPromise, WsProvider }} = pjs.api;
216 const {{ util, utilCrypto, keyring, types }} = pjs;
217 (async (arguments, ApiPromise, WsProvider, util, utilCrypto, keyring, types) => {{
218 {}
219 }})({}, ApiPromise, WsProvider, util, utilCrypto, keyring, types)"#,
220 &code_content,
221 json!(json_args.unwrap_or_default())
222 )
223 } else {
224 format!(
225 r#"
226 pjs.arguments = {};
227 {}
228 "#,
229 json!(json_args.unwrap_or_default()),
230 &code_content
231 )
232 };
233
234 log::trace!("code: \n{}", code);
235
236 let mut js_runtime = create_runtime_with_loader(None);
237 let with_bundle = format!(
238 "
239 {}
240 {}
241 {}
242 {}
243 {}
244
245 let pjs = {{
246 util: polkadotUtil,
247 utilCrypto: polkadotUtilCrypto,
248 keyring: polkadotKeyring,
249 types: polkadotTypes,
250 api: polkadotApi,
251 }};
252
253 {}
254 ",
255 bundle_util, bundle_util_crypto, bundle_keyring, bundle_types, bundle_api, code
256 );
257 log::trace!("full code: \n{}", with_bundle);
258 execute_script(&mut js_runtime, &with_bundle).await
259}
260async fn execute_script(
261 js_runtime: &mut JsRuntime,
262 code: impl Into<String>,
263) -> Result<ReturnValue, AnyError> {
264 let executed = js_runtime.execute_script("name", deno_core::FastString::from(code.into()))?;
266 let resolve = js_runtime.resolve(executed);
267 let resolved = js_runtime
268 .with_event_loop_promise(resolve, deno_core::PollEventLoopOptions::default())
269 .await;
270 match resolved {
271 Ok(global) => {
272 let scope = &mut js_runtime.handle_scope();
273 let local = v8::Local::new(scope, global);
274 let deserialized_value = serde_v8::from_v8::<serde_json::Value>(scope, local);
277
278 let resp = match deserialized_value {
279 Ok(value) => {
280 log::debug!("{:#?}", value);
281 ReturnValue::Deserialized(value)
282 }
283 Err(err) => {
284 log::warn!("{}", format!("Cannot deserialize value: {:?}", err));
285 ReturnValue::CantDeserialize(err.to_string())
286 }
287 };
288
289 Ok(resp)
290 }
291 Err(err) => {
292 log::error!("{}", format!("Evaling error: {:?}", err));
293 Err(err.into())
294 }
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[tokio::test]
303 async fn query_parachains_works() {
304 let resp = run_file_with_wrap("./testing/query_parachains.ts", None)
305 .await
306 .unwrap();
307 if let ReturnValue::Deserialized(value) = resp {
308 let first_para_id = value.as_array().unwrap().first().unwrap().as_u64().unwrap();
309 assert_eq!(first_para_id, 1000_u64);
310 }
311 }
312
313 #[tokio::test]
314 async fn consts_works() {
315 let resp = run_file("./testing/epoch_duration_rococo.js", None)
316 .await
317 .unwrap();
318
319 println!("{:#?}", resp);
320 assert!(matches!(resp, ReturnValue::Deserialized { .. }));
321 if let ReturnValue::Deserialized(value) = resp {
322 assert_eq!(value, json!(600));
323 }
324 }
325
326 #[tokio::test]
327 async fn query_parachains_without_wrap_works() {
328 let resp = run_file("./testing/query_parachains_no_wrap.ts", None)
329 .await
330 .unwrap();
331 assert!(matches!(resp, ReturnValue::Deserialized { .. }));
332 if let ReturnValue::Deserialized(value) = resp {
333 println!("{:?}", value);
334 let first_para_id = value.as_array().unwrap().first().unwrap().as_u64().unwrap();
335 assert_eq!(first_para_id, 1000_u64);
336 }
337 }
338
339 #[tokio::test]
340 async fn query_parachains_from_ts_code_works() {
341 let ts_code = r#"
342 (async () => {
343 const api = await pjs.api.ApiPromise.create({ provider: new pjs.api.WsProvider('wss://rpc.polkadot.io') });
344 const parachains: number[] = (await api.query.paras.parachains()) || [];
345
346 return parachains.toJSON();
347 })();
348 "#;
349 let resp = run_ts_code(ts_code, None).await.unwrap();
350 assert!(matches!(resp, ReturnValue::Deserialized { .. }));
351 if let ReturnValue::Deserialized(value) = resp {
352 let first_para_id = value.as_array().unwrap().first().unwrap().as_u64().unwrap();
353 assert_eq!(first_para_id, 1000_u64);
354 }
355 }
356
357 #[tokio::test]
358 async fn query_parachains_from_js_code_works() {
359 let ts_code = r#"
360 (async () => {
361 const api = await pjs.api.ApiPromise.create({ provider: new pjs.api.WsProvider('wss://rpc.polkadot.io') });
362 const parachains = (await api.query.paras.parachains()) || [];
363
364 return parachains.toJSON();
365 })();
366 "#;
367 let resp = run_ts_code(ts_code, None).await.unwrap();
368 assert!(matches!(resp, ReturnValue::Deserialized { .. }));
369 if let ReturnValue::Deserialized(value) = resp {
370 let first_para_id = value.as_array().unwrap().first().unwrap().as_u64().unwrap();
371 assert_eq!(first_para_id, 1000_u64);
372 }
373 }
374
375 #[tokio::test]
376 async fn query_historic_data_rococo_works() {
377 run_file_with_wrap("./testing/query_historic_data.js", None)
378 .await
379 .unwrap();
380 }
381
382 #[tokio::test]
383 async fn query_chain_state_info_rococo_works() {
384 run_file_with_wrap(
385 "./testing/get_chain_state_info.js",
386 Some(vec![json!("wss://paseo-rpc.dwellir.com")]),
387 )
388 .await
389 .unwrap();
390 }
391
392 #[tokio::test]
393 async fn listen_new_head_works() {
394 let resp = run_file_with_wrap(
395 "./testing/rpc_listen_new_head.js",
396 Some(vec![json!("wss://paseo-rpc.dwellir.com")]),
397 )
398 .await
399 .unwrap();
400 assert_eq!(resp, ReturnValue::Deserialized(json!(5)));
401 }
402
403 #[tokio::test]
404 async fn transfer_works() {
405 let args = vec![json!("wss://paseo-rpc.dwellir.com"), json!("//Alice")];
406 let resp = run_file_with_wrap("./testing/transfer.js", Some(args.clone()))
407 .await
408 .unwrap();
409
410 assert!(matches!(resp, ReturnValue::Deserialized { .. }));
411
412 if let ReturnValue::Deserialized(value) = resp {
413 let amount = value.as_u64().unwrap();
414 println!("Returning {amount:?} to Bob");
415 let args = [args, vec![json!(amount)]].concat();
416 run_file_with_wrap("./testing/transfer.js", Some(args))
417 .await
418 .unwrap();
419 }
420 }
421}