Skip to main content

pocket_ic/
lib.rs

1#![allow(clippy::test_attr_in_doctest)]
2#![doc = include_str!("../README.md")]
3/// # PocketIC: A Canister Testing Platform
4///
5/// PocketIC is the local canister smart contract testing platform for the [Internet Computer](https://internetcomputer.org/).
6///
7/// It consists of the PocketIC server, which can run many independent IC instances, and a client library (this crate), which provides an interface to your IC instances.
8///
9/// With PocketIC, testing canisters is as simple as calling rust functions. Here is a minimal example:
10///
11/// ```rust
12/// use candid::{Principal, encode_one};
13/// use pocket_ic::PocketIc;
14///
15/// // 2T cycles
16/// const INIT_CYCLES: u128 = 2_000_000_000_000;
17///
18/// // Create a counter canister and charge it with 2T cycles.
19/// fn deploy_counter_canister(pic: &PocketIc) -> Principal {
20///     let canister_id = pic.create_canister();
21///     pic.add_cycles(canister_id, INIT_CYCLES);
22///     let counter_wasm = todo!();
23///     pic.install_canister(canister_id, counter_wasm, vec![], None);
24///     canister_id
25/// }
26///
27/// // Call a method on the counter canister as the anonymous principal.
28/// fn call_counter_canister(pic: &PocketIc, canister_id: Principal, method: &str) -> Vec<u8> {
29///     pic.update_call(
30///         canister_id,
31///         Principal::anonymous(),
32///         method,
33///         encode_one(()).unwrap(),
34///     )
35///     .expect("Failed to call counter canister")
36/// }
37///
38/// #[test]
39/// fn test_counter_canister() {
40///     let pic = PocketIc::new();
41///     let canister_id = deploy_counter_canister(&pic);
42///
43///     // Make some calls to the counter canister.
44///     let reply = call_counter_canister(&pic, canister_id, "read");
45///     assert_eq!(reply, vec![0, 0, 0, 0]);
46///     let reply = call_counter_canister(&pic, canister_id, "write");
47///     assert_eq!(reply, vec![1, 0, 0, 0]);
48///     let reply = call_counter_canister(&pic, canister_id, "write");
49///     assert_eq!(reply, vec![2, 0, 0, 0]);
50///     let reply = call_counter_canister(&pic, canister_id, "read");
51///     assert_eq!(reply, vec![2, 0, 0, 0]);
52/// }
53/// ```
54/// For more information, see the [README](https://crates.io/crates/pocket-ic).
55///
56use crate::{
57    common::rest::{
58        AutoProgressConfig, BlobCompression, BlobId, CanisterHttpRequest, ExtendedSubnetConfigSet,
59        HttpsConfig, IcpConfig, IcpFeatures, InitialTime, InstanceHttpGatewayConfig, InstanceId,
60        MockCanisterHttpResponse, RawEffectivePrincipal, RawMessageId, RawSubnetBlockmakers,
61        RawTickConfigs, RawTime, SubnetId, SubnetKind, SubnetSpec, Topology,
62    },
63    nonblocking::PocketIc as PocketIcAsync,
64};
65use candid::{
66    Principal, decode_args, encode_args,
67    utils::{ArgumentDecoder, ArgumentEncoder},
68};
69use flate2::read::GzDecoder;
70pub use ic_management_canister_types::{
71    CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
72    Snapshot,
73};
74pub use ic_transport_types::SubnetMetrics;
75use reqwest::Url;
76use schemars::JsonSchema;
77use semver::{Version, VersionReq};
78use serde::{Deserialize, Serialize};
79use slog::Level;
80#[cfg(unix)]
81use std::os::unix::fs::OpenOptionsExt;
82#[cfg(windows)]
83use std::sync::Once;
84use std::{
85    fs::OpenOptions,
86    net::{IpAddr, SocketAddr},
87    path::PathBuf,
88    process::{Child, Command},
89    sync::{Arc, mpsc::channel},
90    thread,
91    thread::JoinHandle,
92    time::{Duration, SystemTime, UNIX_EPOCH},
93};
94use strum_macros::EnumIter;
95use tempfile::{NamedTempFile, TempDir};
96use thiserror::Error;
97use tokio::runtime::Runtime;
98use tracing::{instrument, warn};
99#[cfg(windows)]
100use wslpath::windows_to_wsl;
101
102pub mod common;
103pub mod nonblocking;
104
105const POCKET_IC_SERVER_NAME: &str = "pocket-ic-server";
106
107const MIN_SERVER_VERSION: &str = "13.0.0";
108const MAX_SERVER_VERSION: &str = "14";
109
110/// Public to facilitate downloading the PocketIC server.
111pub const LATEST_SERVER_VERSION: &str = "13.0.0";
112
113// the default timeout of a PocketIC operation
114const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
115
116const LOCALHOST: &str = "127.0.0.1";
117
118enum PocketIcStateKind {
119    /// A persistent state dir managed by the user.
120    StateDir(PathBuf),
121    /// A fresh temporary directory used if the user does not provide
122    /// a persistent state directory managed by the user.
123    /// The temporary directory is deleted when `PocketIcState` is dropped
124    /// unless `PocketIcState` is turned into a persistent state
125    /// at the path given by `PocketIcState::into_path`.
126    TempDir(TempDir),
127}
128
129pub struct PocketIcState {
130    state: PocketIcStateKind,
131}
132
133impl PocketIcState {
134    #[allow(clippy::new_without_default)]
135    pub fn new() -> Self {
136        let temp_dir = TempDir::new().unwrap();
137        Self {
138            state: PocketIcStateKind::TempDir(temp_dir),
139        }
140    }
141
142    pub fn new_from_path(state_dir: PathBuf) -> Self {
143        Self {
144            state: PocketIcStateKind::StateDir(state_dir),
145        }
146    }
147
148    pub fn into_path(self) -> PathBuf {
149        match self.state {
150            PocketIcStateKind::StateDir(state_dir) => state_dir,
151            PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
152        }
153    }
154
155    pub(crate) fn state_dir(&self) -> PathBuf {
156        match &self.state {
157            PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
158            PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
159        }
160    }
161}
162
163pub struct PocketIcBuilder {
164    config: Option<ExtendedSubnetConfigSet>,
165    http_gateway_config: Option<InstanceHttpGatewayConfig>,
166    server_binary: Option<PathBuf>,
167    server_url: Option<Url>,
168    max_request_time_ms: Option<u64>,
169    read_only_state_dir: Option<PathBuf>,
170    state_dir: Option<PocketIcState>,
171    icp_config: IcpConfig,
172    log_level: Option<Level>,
173    bitcoind_addr: Option<Vec<SocketAddr>>,
174    dogecoind_addr: Option<Vec<SocketAddr>>,
175    icp_features: IcpFeatures,
176    initial_time: Option<InitialTime>,
177    mainnet_nns_subnet_id: Option<bool>,
178}
179
180#[allow(clippy::new_without_default)]
181impl PocketIcBuilder {
182    pub fn new() -> Self {
183        Self {
184            config: None,
185            http_gateway_config: None,
186            server_binary: None,
187            server_url: None,
188            max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
189            read_only_state_dir: None,
190            state_dir: None,
191            icp_config: IcpConfig::default(),
192            log_level: None,
193            bitcoind_addr: None,
194            dogecoind_addr: None,
195            icp_features: IcpFeatures::default(),
196            initial_time: None,
197            mainnet_nns_subnet_id: None,
198        }
199    }
200
201    pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
202        let mut builder = Self::new();
203        builder.config = Some(config.into());
204        builder
205    }
206
207    pub fn build(self) -> PocketIc {
208        PocketIc::from_components(
209            self.config.unwrap_or_default(),
210            self.server_url,
211            self.server_binary,
212            self.max_request_time_ms,
213            self.read_only_state_dir,
214            self.state_dir,
215            self.icp_config,
216            self.log_level,
217            self.bitcoind_addr,
218            self.dogecoind_addr,
219            self.icp_features,
220            self.initial_time,
221            self.http_gateway_config,
222            self.mainnet_nns_subnet_id,
223        )
224    }
225
226    pub async fn build_async(self) -> PocketIcAsync {
227        PocketIcAsync::from_components(
228            self.config.unwrap_or_default(),
229            self.server_url,
230            self.server_binary,
231            self.max_request_time_ms,
232            self.read_only_state_dir,
233            self.state_dir,
234            self.icp_config,
235            self.log_level,
236            self.bitcoind_addr,
237            self.dogecoind_addr,
238            self.icp_features,
239            self.initial_time,
240            self.http_gateway_config,
241            self.mainnet_nns_subnet_id,
242        )
243        .await
244    }
245
246    /// Provide the path to the PocketIC server binary used instead of the environment variable `POCKET_IC_BIN`.
247    pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
248        self.server_binary = Some(server_binary);
249        self
250    }
251
252    /// Use an already running PocketIC server.
253    pub fn with_server_url(mut self, server_url: Url) -> Self {
254        self.server_url = Some(server_url);
255        self
256    }
257
258    pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
259        self.max_request_time_ms = max_request_time_ms;
260        self
261    }
262
263    pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
264        self.state_dir = Some(PocketIcState::new_from_path(state_dir));
265        self
266    }
267
268    pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
269        self.state_dir = Some(state_dir);
270        self
271    }
272
273    pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
274        self.read_only_state_dir = Some(read_only_state_dir.state_dir());
275        self
276    }
277
278    pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
279        self.icp_config = icp_config;
280        self
281    }
282
283    pub fn with_log_level(mut self, log_level: Level) -> Self {
284        self.log_level = Some(log_level);
285        self
286    }
287
288    pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
289        self.with_bitcoind_addrs(vec![bitcoind_addr])
290    }
291
292    pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
293        Self {
294            bitcoind_addr: Some(bitcoind_addrs),
295            ..self
296        }
297    }
298
299    pub fn with_dogecoind_addrs(self, dogecoind_addrs: Vec<SocketAddr>) -> Self {
300        Self {
301            dogecoind_addr: Some(dogecoind_addrs),
302            ..self
303        }
304    }
305
306    /// Add an empty NNS subnet unless an NNS subnet has already been added.
307    pub fn with_nns_subnet(mut self) -> Self {
308        let mut config = self.config.unwrap_or_default();
309        config.nns = Some(config.nns.unwrap_or_default());
310        self.config = Some(config);
311        self
312    }
313
314    /// Add an NNS subnet with state loaded from the given state directory.
315    /// Note that the provided path must be accessible for the PocketIC server process.
316    ///
317    /// `path_to_state` should lead to a directory which is expected to have
318    /// the following structure:
319    ///
320    /// path_to_state/
321    ///  |-- backups
322    ///  |-- checkpoints
323    ///  |-- diverged_checkpoints
324    ///  |-- diverged_state_markers
325    ///  |-- fs_tmp
326    ///  |-- page_deltas
327    ///  |-- states_metadata.pbuf
328    ///  |-- tip
329    ///  `-- tmp
330    pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
331        self.with_subnet_state(SubnetKind::NNS, path_to_state)
332    }
333
334    /// Add a subnet with state loaded from the given state directory.
335    /// Note that the provided path must be accessible for the PocketIC server process.
336    ///
337    /// `path_to_state` should point to a directory which is expected to have
338    /// the following structure:
339    ///
340    /// path_to_state/
341    ///  |-- backups
342    ///  |-- checkpoints
343    ///  |-- diverged_checkpoints
344    ///  |-- diverged_state_markers
345    ///  |-- fs_tmp
346    ///  |-- page_deltas
347    ///  |-- states_metadata.pbuf
348    ///  |-- tip
349    ///  `-- tmp
350    pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
351        let mut config = self.config.unwrap_or_default();
352        #[cfg(not(windows))]
353        let state_dir = path_to_state;
354        #[cfg(windows)]
355        let state_dir = wsl_path(&path_to_state, "subnet state").into();
356        let subnet_spec = SubnetSpec::default().with_state_dir(state_dir);
357        match subnet_kind {
358            SubnetKind::NNS => config.nns = Some(subnet_spec),
359            SubnetKind::SNS => config.sns = Some(subnet_spec),
360            SubnetKind::II => config.ii = Some(subnet_spec),
361            SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
362            SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
363            SubnetKind::Application => config.application.push(subnet_spec),
364            SubnetKind::CloudEngine => config.cloud_engine.push(subnet_spec),
365            SubnetKind::System => config.system.push(subnet_spec),
366            SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
367        };
368        self.config = Some(config);
369        self
370    }
371
372    /// Add an empty sns subnet unless an SNS subnet has already been added.
373    pub fn with_sns_subnet(mut self) -> Self {
374        let mut config = self.config.unwrap_or_default();
375        config.sns = Some(config.sns.unwrap_or_default());
376        self.config = Some(config);
377        self
378    }
379
380    /// Add an empty II subnet unless an II subnet has already been added.
381    pub fn with_ii_subnet(mut self) -> Self {
382        let mut config = self.config.unwrap_or_default();
383        config.ii = Some(config.ii.unwrap_or_default());
384        self.config = Some(config);
385        self
386    }
387
388    /// Add an empty fiduciary subnet unless a fiduciary subnet has already been added.
389    pub fn with_fiduciary_subnet(mut self) -> Self {
390        let mut config = self.config.unwrap_or_default();
391        config.fiduciary = Some(config.fiduciary.unwrap_or_default());
392        self.config = Some(config);
393        self
394    }
395
396    /// Add an empty bitcoin subnet unless a bitcoin subnet has already been added.
397    pub fn with_bitcoin_subnet(mut self) -> Self {
398        let mut config = self.config.unwrap_or_default();
399        config.bitcoin = Some(config.bitcoin.unwrap_or_default());
400        self.config = Some(config);
401        self
402    }
403
404    /// Add an empty generic system subnet.
405    pub fn with_system_subnet(mut self) -> Self {
406        let mut config = self.config.unwrap_or_default();
407        config.system.push(SubnetSpec::default());
408        self.config = Some(config);
409        self
410    }
411
412    /// Add an empty generic application subnet.
413    pub fn with_application_subnet(mut self) -> Self {
414        let mut config = self.config.unwrap_or_default();
415        config.application.push(SubnetSpec::default());
416        self.config = Some(config);
417        self
418    }
419
420    /// Add an empty generic verified application subnet.
421    pub fn with_verified_application_subnet(mut self) -> Self {
422        let mut config = self.config.unwrap_or_default();
423        config.verified_application.push(SubnetSpec::default());
424        self.config = Some(config);
425        self
426    }
427
428    /// Add an empty generic application subnet with benchmarking instruction configuration.
429    pub fn with_benchmarking_application_subnet(mut self) -> Self {
430        let mut config = self.config.unwrap_or_default();
431        config
432            .application
433            .push(SubnetSpec::default().with_benchmarking_instruction_config());
434        self.config = Some(config);
435        self
436    }
437
438    /// Add an empty generic system subnet with benchmarking instruction configuration.
439    pub fn with_benchmarking_system_subnet(mut self) -> Self {
440        let mut config = self.config.unwrap_or_default();
441        config
442            .system
443            .push(SubnetSpec::default().with_benchmarking_instruction_config());
444        self.config = Some(config);
445        self
446    }
447
448    /// Enables selected ICP features supported by PocketIC and implemented by system canisters
449    /// (deployed to the PocketIC instance automatically when creating a new PocketIC instance).
450    /// Subnets to which the system canisters are deployed are automatically declared as empty subnets,
451    /// e.g., `PocketIcBuilder::with_nns_subnet` is implicitly implied by specifying the `icp_token` feature.
452    pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
453        self.icp_features = icp_features;
454        self
455    }
456
457    /// Sets the initial timestamp of the new instance to the provided value which must be at least
458    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
459    /// - 06 May 2021 21:17:10 CEST otherwise.
460    #[deprecated(note = "Use `with_initial_time` instead")]
461    pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
462        self.initial_time = Some(InitialTime::Timestamp(RawTime {
463            nanos_since_epoch: initial_timestamp_nanos,
464        }));
465        self
466    }
467
468    /// Sets the initial time of the new instance to the provided value which must be at least
469    /// - 10 May 2021 10:00:01 AM CEST if the `cycles_minting` feature is enabled in `icp_features`;
470    /// - 06 May 2021 21:17:10 CEST otherwise.
471    pub fn with_initial_time(mut self, initial_time: Time) -> Self {
472        self.initial_time = Some(InitialTime::Timestamp(RawTime {
473            nanos_since_epoch: initial_time.as_nanos_since_unix_epoch(),
474        }));
475        self
476    }
477
478    /// Configures the new instance to make progress automatically,
479    /// i.e., periodically update the time of the IC instance
480    /// to the real time and execute rounds on the subnets.
481    pub fn with_auto_progress(mut self) -> Self {
482        let config = AutoProgressConfig {
483            artificial_delay_ms: None,
484        };
485        self.initial_time = Some(InitialTime::AutoProgress(config));
486        self
487    }
488
489    pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
490        self.http_gateway_config = Some(http_gateway_config);
491        self
492    }
493
494    pub fn with_mainnet_nns_subnet_id(mut self) -> Self {
495        self.mainnet_nns_subnet_id = Some(true);
496        self
497    }
498}
499
500/// Representation of system time as duration since UNIX epoch
501/// with cross-platform nanosecond precision.
502#[derive(Copy, Clone, PartialEq, PartialOrd)]
503pub struct Time(Duration);
504
505impl Time {
506    /// Number of nanoseconds since UNIX EPOCH.
507    pub fn as_nanos_since_unix_epoch(&self) -> u64 {
508        self.0.as_nanos().try_into().unwrap()
509    }
510
511    pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
512        Time(Duration::from_nanos(nanos))
513    }
514}
515
516impl std::fmt::Debug for Time {
517    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518        let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
519        write!(f, "{nanos_since_unix_epoch}")
520    }
521}
522
523impl std::ops::Add<Duration> for Time {
524    type Output = Time;
525    fn add(self, dur: Duration) -> Time {
526        Time(self.0 + dur)
527    }
528}
529
530impl From<SystemTime> for Time {
531    fn from(time: SystemTime) -> Self {
532        Self::from_nanos_since_unix_epoch(
533            time.duration_since(UNIX_EPOCH)
534                .unwrap()
535                .as_nanos()
536                .try_into()
537                .unwrap(),
538        )
539    }
540}
541
542impl TryFrom<Time> for SystemTime {
543    type Error = String;
544
545    fn try_from(time: Time) -> Result<SystemTime, String> {
546        let nanos = time.as_nanos_since_unix_epoch();
547        let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
548        let roundtrip: Time = system_time.into();
549        if roundtrip.as_nanos_since_unix_epoch() == nanos {
550            Ok(system_time)
551        } else {
552            Err(format!(
553                "Converting UNIX timestamp {nanos} in nanoseconds to SystemTime failed due to losing precision"
554            ))
555        }
556    }
557}
558
559/// Main entry point for interacting with PocketIC.
560pub struct PocketIc {
561    pocket_ic: PocketIcAsync,
562    runtime: Arc<tokio::runtime::Runtime>,
563    thread: Option<JoinHandle<()>>,
564}
565
566impl PocketIc {
567    /// Creates a new PocketIC instance with a single application subnet on the server.
568    /// The server is started if it's not already running.
569    pub fn new() -> Self {
570        PocketIcBuilder::new().with_application_subnet().build()
571    }
572
573    /// Creates a PocketIC handle to an existing instance on a running server.
574    /// Note that this handle does not extend the lifetime of the existing instance,
575    /// i.e., the existing instance is deleted and this handle stops working
576    /// when the PocketIC handle that created the existing instance is dropped.
577    pub fn new_from_existing_instance(
578        server_url: Url,
579        instance_id: InstanceId,
580        max_request_time_ms: Option<u64>,
581    ) -> Self {
582        let (tx, rx) = channel();
583        let thread = thread::spawn(move || {
584            let rt = tokio::runtime::Builder::new_current_thread()
585                .enable_all()
586                .build()
587                .unwrap();
588            tx.send(rt).unwrap();
589        });
590        let runtime = rx.recv().unwrap();
591
592        let pocket_ic =
593            PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
594
595        Self {
596            pocket_ic,
597            runtime: Arc::new(runtime),
598            thread: Some(thread),
599        }
600    }
601
602    #[allow(clippy::too_many_arguments)]
603    pub(crate) fn from_components(
604        subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
605        server_url: Option<Url>,
606        server_binary: Option<PathBuf>,
607        max_request_time_ms: Option<u64>,
608        read_only_state_dir: Option<PathBuf>,
609        state_dir: Option<PocketIcState>,
610        icp_config: IcpConfig,
611        log_level: Option<Level>,
612        bitcoind_addr: Option<Vec<SocketAddr>>,
613        dogecoind_addr: Option<Vec<SocketAddr>>,
614        icp_features: IcpFeatures,
615        initial_time: Option<InitialTime>,
616        http_gateway_config: Option<InstanceHttpGatewayConfig>,
617        mainnet_nns_subnet_id: Option<bool>,
618    ) -> Self {
619        let (tx, rx) = channel();
620        let thread = thread::spawn(move || {
621            let rt = tokio::runtime::Builder::new_current_thread()
622                .enable_all()
623                .build()
624                .unwrap();
625            tx.send(rt).unwrap();
626        });
627        let runtime = rx.recv().unwrap();
628
629        let pocket_ic = runtime.block_on(async {
630            PocketIcAsync::from_components(
631                subnet_config_set,
632                server_url,
633                server_binary,
634                max_request_time_ms,
635                read_only_state_dir,
636                state_dir,
637                icp_config,
638                log_level,
639                bitcoind_addr,
640                dogecoind_addr,
641                icp_features,
642                initial_time,
643                http_gateway_config,
644                mainnet_nns_subnet_id,
645            )
646            .await
647        });
648
649        Self {
650            pocket_ic,
651            runtime: Arc::new(runtime),
652            thread: Some(thread),
653        }
654    }
655
656    pub fn drop_and_take_state(mut self) -> Option<PocketIcState> {
657        self.pocket_ic.take_state_internal()
658    }
659
660    /// Returns the URL of the PocketIC server on which this PocketIC instance is running.
661    pub fn get_server_url(&self) -> Url {
662        self.pocket_ic.get_server_url()
663    }
664
665    /// Returns the instance ID.
666    pub fn instance_id(&self) -> InstanceId {
667        self.pocket_ic.instance_id
668    }
669
670    /// Returns the topology of the different subnets of this PocketIC instance.
671    pub fn topology(&self) -> Topology {
672        let runtime = self.runtime.clone();
673        runtime.block_on(async { self.pocket_ic.topology().await })
674    }
675
676    /// Upload and store a binary blob to the PocketIC server.
677    #[instrument(ret(Display), skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
678    pub fn upload_blob(&self, blob: Vec<u8>, compression: BlobCompression) -> BlobId {
679        let runtime = self.runtime.clone();
680        runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
681    }
682
683    /// Set stable memory of a canister. Optional GZIP compression can be used for reduced
684    /// data traffic.
685    #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
686    pub fn set_stable_memory(
687        &self,
688        canister_id: CanisterId,
689        data: Vec<u8>,
690        compression: BlobCompression,
691    ) {
692        let runtime = self.runtime.clone();
693        runtime.block_on(async {
694            self.pocket_ic
695                .set_stable_memory(canister_id, data, compression)
696                .await
697        })
698    }
699
700    /// Get stable memory of a canister.
701    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
702    pub fn get_stable_memory(&self, canister_id: CanisterId) -> Vec<u8> {
703        let runtime = self.runtime.clone();
704        runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
705    }
706
707    /// List all instances and their status.
708    #[instrument(ret)]
709    pub fn list_instances() -> Vec<String> {
710        let runtime = tokio::runtime::Builder::new_current_thread()
711            .build()
712            .unwrap();
713        let url = runtime.block_on(async {
714            let (_, server_url) = start_server(StartServerParams {
715                reuse: true,
716                ..Default::default()
717            })
718            .await;
719            server_url.join("instances").unwrap()
720        });
721        let instances: Vec<String> = reqwest::blocking::Client::new()
722            .get(url)
723            .send()
724            .expect("Failed to get result")
725            .json()
726            .expect("Failed to get json");
727        instances
728    }
729
730    /// Verify a canister signature.
731    #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
732    pub fn verify_canister_signature(
733        &self,
734        msg: Vec<u8>,
735        sig: Vec<u8>,
736        pubkey: Vec<u8>,
737        root_pubkey: Vec<u8>,
738    ) -> Result<(), String> {
739        let runtime = self.runtime.clone();
740        runtime.block_on(async {
741            self.pocket_ic
742                .verify_canister_signature(msg, sig, pubkey, root_pubkey)
743                .await
744        })
745    }
746
747    /// Make the IC produce and progress by one block.
748    /// Note that multiple ticks might be necessary to observe
749    /// an expected effect, e.g., if the effect depends on
750    /// inter-canister calls or heartbeats.
751    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
752    pub fn tick(&self) {
753        let runtime = self.runtime.clone();
754        runtime.block_on(async { self.pocket_ic.tick().await })
755    }
756
757    /// Make the IC produce and progress by one block with custom
758    /// configs for the round.
759    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
760    pub fn tick_with_configs(&self, configs: TickConfigs) {
761        let runtime = self.runtime.clone();
762        runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
763    }
764
765    /// Configures the IC to make progress automatically,
766    /// i.e., periodically update the time of the IC
767    /// to the real time and execute rounds on the subnets.
768    /// Returns the URL at which `/api` requests
769    /// for this instance can be made.
770    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
771    pub fn auto_progress(&self) -> Url {
772        let runtime = self.runtime.clone();
773        runtime.block_on(async { self.pocket_ic.auto_progress().await })
774    }
775
776    /// Returns whether automatic progress is enabled on the PocketIC instance.
777    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
778    pub fn auto_progress_enabled(&self) -> bool {
779        let runtime = self.runtime.clone();
780        runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
781    }
782
783    /// Stops automatic progress (see `auto_progress`) on the IC.
784    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
785    pub fn stop_progress(&self) {
786        let runtime = self.runtime.clone();
787        runtime.block_on(async { self.pocket_ic.stop_progress().await })
788    }
789
790    /// Returns the URL at which `/api` requests
791    /// for this instance can be made if the HTTP
792    /// gateway has been started.
793    pub fn url(&self) -> Option<Url> {
794        self.pocket_ic.url()
795    }
796
797    /// Creates an HTTP gateway for this PocketIC instance binding to `127.0.0.1`
798    /// and an optionally specified port (defaults to choosing an arbitrary unassigned port);
799    /// listening on `localhost`;
800    /// and configures the PocketIC instance to make progress automatically, i.e.,
801    /// periodically update the time of the PocketIC instance to the real time
802    /// and process messages on the PocketIC instance.
803    /// Returns the URL at which `/api` requests
804    /// for this instance can be made.
805    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
806    pub fn make_live(&mut self, listen_at: Option<u16>) -> Url {
807        let runtime = self.runtime.clone();
808        runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
809    }
810
811    /// Creates an HTTP gateway for this PocketIC instance binding
812    /// to an optionally specified IP address (defaults to `127.0.0.1`)
813    /// and port (defaults to choosing an arbitrary unassigned port);
814    /// listening on optionally specified domains (default to `localhost`);
815    /// and using an optionally specified TLS certificate (if provided, an HTTPS gateway is created)
816    /// and configures the PocketIC instance to make progress automatically, i.e.,
817    /// periodically update the time of the PocketIC instance to the real time
818    /// and process messages on the PocketIC instance.
819    /// Returns the URL at which `/api` requests
820    /// for this instance can be made.
821    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
822    pub fn make_live_with_params(
823        &mut self,
824        ip_addr: Option<IpAddr>,
825        listen_at: Option<u16>,
826        domains: Option<Vec<String>>,
827        https_config: Option<HttpsConfig>,
828    ) -> Url {
829        let runtime = self.runtime.clone();
830        runtime.block_on(async {
831            self.pocket_ic
832                .make_live_with_params(ip_addr, listen_at, domains, https_config)
833                .await
834        })
835    }
836
837    /// Stops auto progress (automatic time updates and round executions)
838    /// and the HTTP gateway for this IC instance.
839    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
840    pub fn stop_live(&mut self) {
841        let runtime = self.runtime.clone();
842        runtime.block_on(async { self.pocket_ic.stop_live().await })
843    }
844
845    /// Get the root key of this IC instance. Returns `None` if the IC has no NNS subnet.
846    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
847    pub fn root_key(&self) -> Option<Vec<u8>> {
848        let runtime = self.runtime.clone();
849        runtime.block_on(async { self.pocket_ic.root_key().await })
850    }
851
852    /// Get the current time of the IC.
853    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
854    pub fn get_time(&self) -> Time {
855        let runtime = self.runtime.clone();
856        runtime.block_on(async { self.pocket_ic.get_time().await })
857    }
858
859    /// Set the current time of the IC, on all subnets.
860    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
861    pub fn set_time(&self, time: Time) {
862        let runtime = self.runtime.clone();
863        runtime.block_on(async { self.pocket_ic.set_time(time).await })
864    }
865
866    /// Set the current certified time of the IC, on all subnets.
867    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
868    pub fn set_certified_time(&self, time: Time) {
869        let runtime = self.runtime.clone();
870        runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
871    }
872
873    /// Advance the time on the IC on all subnets by some nanoseconds.
874    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
875    pub fn advance_time(&self, duration: Duration) {
876        let runtime = self.runtime.clone();
877        runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
878    }
879
880    /// Get the controllers of a canister.
881    /// Panics if the canister does not exist.
882    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
883    pub fn get_controllers(&self, canister_id: CanisterId) -> Vec<Principal> {
884        let runtime = self.runtime.clone();
885        runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
886    }
887
888    /// Get the current cycles balance of a canister.
889    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
890    pub fn cycle_balance(&self, canister_id: CanisterId) -> u128 {
891        let runtime = self.runtime.clone();
892        runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
893    }
894
895    /// Add cycles to a canister. Returns the new balance.
896    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
897    pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> u128 {
898        let runtime = self.runtime.clone();
899        runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
900    }
901
902    /// Submit an update call (without executing it immediately).
903    pub fn submit_call(
904        &self,
905        canister_id: CanisterId,
906        sender: Principal,
907        method: &str,
908        payload: Vec<u8>,
909    ) -> Result<RawMessageId, RejectResponse> {
910        let runtime = self.runtime.clone();
911        runtime.block_on(async {
912            self.pocket_ic
913                .submit_call(canister_id, sender, method, payload)
914                .await
915        })
916    }
917
918    /// Submit an update call with a provided effective principal (without executing it immediately).
919    pub fn submit_call_with_effective_principal(
920        &self,
921        canister_id: CanisterId,
922        effective_principal: RawEffectivePrincipal,
923        sender: Principal,
924        method: &str,
925        payload: Vec<u8>,
926    ) -> Result<RawMessageId, RejectResponse> {
927        let runtime = self.runtime.clone();
928        runtime.block_on(async {
929            self.pocket_ic
930                .submit_call_with_effective_principal(
931                    canister_id,
932                    effective_principal,
933                    sender,
934                    method,
935                    payload,
936                )
937                .await
938        })
939    }
940
941    /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
942    pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
943        let runtime = self.runtime.clone();
944        runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
945    }
946
947    /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
948    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
949    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
950    pub fn ingress_status(
951        &self,
952        message_id: RawMessageId,
953    ) -> Option<Result<Vec<u8>, RejectResponse>> {
954        let runtime = self.runtime.clone();
955        runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
956    }
957
958    /// Fetch the status of an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
959    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
960    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
961    /// If the status of the update call is known, but the update call was submitted by a different caller, then an error is returned.
962    pub fn ingress_status_as(
963        &self,
964        message_id: RawMessageId,
965        caller: Principal,
966    ) -> IngressStatusResult {
967        let runtime = self.runtime.clone();
968        runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
969    }
970
971    /// Await an update call submitted previously by `submit_call` or `submit_call_with_effective_principal`.
972    /// Note that the status of the update call can only change if the PocketIC instance is in live mode
973    /// or a round has been executed due to a separate PocketIC library call, e.g., `PocketIc::tick()`.
974    pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
975        let runtime = self.runtime.clone();
976        runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
977    }
978
979    /// Execute an update call on a canister.
980    #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
981    pub fn update_call(
982        &self,
983        canister_id: CanisterId,
984        sender: Principal,
985        method: &str,
986        payload: Vec<u8>,
987    ) -> Result<Vec<u8>, RejectResponse> {
988        let runtime = self.runtime.clone();
989        runtime.block_on(async {
990            self.pocket_ic
991                .update_call(canister_id, sender, method, payload)
992                .await
993        })
994    }
995
996    /// Execute a query call on a canister.
997    #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
998    pub fn query_call(
999        &self,
1000        canister_id: CanisterId,
1001        sender: Principal,
1002        method: &str,
1003        payload: Vec<u8>,
1004    ) -> Result<Vec<u8>, RejectResponse> {
1005        let runtime = self.runtime.clone();
1006        runtime.block_on(async {
1007            self.pocket_ic
1008                .query_call(canister_id, sender, method, payload)
1009                .await
1010        })
1011    }
1012
1013    /// Fetch canister logs via a query call to the management canister.
1014    pub fn fetch_canister_logs(
1015        &self,
1016        canister_id: CanisterId,
1017        sender: Principal,
1018    ) -> Result<Vec<CanisterLogRecord>, RejectResponse> {
1019        let runtime = self.runtime.clone();
1020        runtime.block_on(async {
1021            self.pocket_ic
1022                .fetch_canister_logs(canister_id, sender)
1023                .await
1024        })
1025    }
1026
1027    /// Request a canister's status.
1028    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1029    pub fn canister_status(
1030        &self,
1031        canister_id: CanisterId,
1032        sender: Option<Principal>,
1033    ) -> Result<CanisterStatusResult, RejectResponse> {
1034        let runtime = self.runtime.clone();
1035        runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
1036    }
1037
1038    /// Create a canister with default settings as the anonymous principal.
1039    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1040    pub fn create_canister(&self) -> CanisterId {
1041        let runtime = self.runtime.clone();
1042        runtime.block_on(async { self.pocket_ic.create_canister().await })
1043    }
1044
1045    /// Create a canister with optional custom settings and a sender.
1046    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1047    pub fn create_canister_with_settings(
1048        &self,
1049        sender: Option<Principal>,
1050        settings: Option<CanisterSettings>,
1051    ) -> CanisterId {
1052        let runtime = self.runtime.clone();
1053        runtime.block_on(async {
1054            self.pocket_ic
1055                .create_canister_with_settings(sender, settings)
1056                .await
1057        })
1058    }
1059
1060    /// Creates a canister with a specific canister ID and optional custom settings.
1061    /// Returns an error if the canister ID is already in use.
1062    /// Creates a new subnet if the canister ID is not contained in any of the subnets.
1063    ///
1064    /// The canister ID must be an IC mainnet canister ID that does not belong to the NNS or II subnet,
1065    /// otherwise the function might panic (for NNS and II canister IDs,
1066    /// the PocketIC instance should already be created with those subnets).
1067    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, canister_id = %canister_id.to_string()))]
1068    pub fn create_canister_with_id(
1069        &self,
1070        sender: Option<Principal>,
1071        settings: Option<CanisterSettings>,
1072        canister_id: CanisterId,
1073    ) -> Result<CanisterId, String> {
1074        let runtime = self.runtime.clone();
1075        runtime.block_on(async {
1076            self.pocket_ic
1077                .create_canister_with_id(sender, settings, canister_id)
1078                .await
1079        })
1080    }
1081
1082    /// Create a canister on a specific subnet with optional custom settings.
1083    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string(), settings = ?settings, subnet_id = %subnet_id.to_string()))]
1084    pub fn create_canister_on_subnet(
1085        &self,
1086        sender: Option<Principal>,
1087        settings: Option<CanisterSettings>,
1088        subnet_id: SubnetId,
1089    ) -> CanisterId {
1090        let runtime = self.runtime.clone();
1091        runtime.block_on(async {
1092            self.pocket_ic
1093                .create_canister_on_subnet(sender, settings, subnet_id)
1094                .await
1095        })
1096    }
1097
1098    /// Upload a WASM chunk to the WASM chunk store of a canister.
1099    /// Returns the WASM chunk hash.
1100    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1101    pub fn upload_chunk(
1102        &self,
1103        canister_id: CanisterId,
1104        sender: Option<Principal>,
1105        chunk: Vec<u8>,
1106    ) -> Result<Vec<u8>, RejectResponse> {
1107        let runtime = self.runtime.clone();
1108        runtime.block_on(async {
1109            self.pocket_ic
1110                .upload_chunk(canister_id, sender, chunk)
1111                .await
1112        })
1113    }
1114
1115    /// List WASM chunk hashes in the WASM chunk store of a canister.
1116    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1117    pub fn stored_chunks(
1118        &self,
1119        canister_id: CanisterId,
1120        sender: Option<Principal>,
1121    ) -> Result<Vec<Vec<u8>>, RejectResponse> {
1122        let runtime = self.runtime.clone();
1123        runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1124    }
1125
1126    /// Clear the WASM chunk store of a canister.
1127    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1128    pub fn clear_chunk_store(
1129        &self,
1130        canister_id: CanisterId,
1131        sender: Option<Principal>,
1132    ) -> Result<(), RejectResponse> {
1133        let runtime = self.runtime.clone();
1134        runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1135    }
1136
1137    /// Install a WASM module assembled from chunks on an existing canister.
1138    #[instrument(skip(self, mode, chunk_hashes_list, wasm_module_hash, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string(), store_canister_id = %store_canister_id.to_string(), arg_len = %arg.len()))]
1139    pub fn install_chunked_canister(
1140        &self,
1141        canister_id: CanisterId,
1142        sender: Option<Principal>,
1143        mode: CanisterInstallMode,
1144        store_canister_id: CanisterId,
1145        chunk_hashes_list: Vec<Vec<u8>>,
1146        wasm_module_hash: Vec<u8>,
1147        arg: Vec<u8>,
1148    ) -> Result<(), RejectResponse> {
1149        let runtime = self.runtime.clone();
1150        runtime.block_on(async {
1151            self.pocket_ic
1152                .install_chunked_canister(
1153                    canister_id,
1154                    sender,
1155                    mode,
1156                    store_canister_id,
1157                    chunk_hashes_list,
1158                    wasm_module_hash,
1159                    arg,
1160                )
1161                .await
1162        })
1163    }
1164
1165    /// Install a WASM module on an existing canister.
1166    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1167    pub fn install_canister(
1168        &self,
1169        canister_id: CanisterId,
1170        wasm_module: Vec<u8>,
1171        arg: Vec<u8>,
1172        sender: Option<Principal>,
1173    ) {
1174        let runtime = self.runtime.clone();
1175        runtime.block_on(async {
1176            self.pocket_ic
1177                .install_canister(canister_id, wasm_module, arg, sender)
1178                .await
1179        })
1180    }
1181
1182    /// Upgrade a canister with a new WASM module.
1183    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1184    pub fn upgrade_canister(
1185        &self,
1186        canister_id: CanisterId,
1187        wasm_module: Vec<u8>,
1188        arg: Vec<u8>,
1189        sender: Option<Principal>,
1190    ) -> Result<(), RejectResponse> {
1191        let runtime = self.runtime.clone();
1192        runtime.block_on(async {
1193            self.pocket_ic
1194                .upgrade_canister(canister_id, wasm_module, arg, sender)
1195                .await
1196        })
1197    }
1198
1199    /// Upgrade a Motoko EOP canister with a new WASM module.
1200    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1201    pub fn upgrade_eop_canister(
1202        &self,
1203        canister_id: CanisterId,
1204        wasm_module: Vec<u8>,
1205        arg: Vec<u8>,
1206        sender: Option<Principal>,
1207    ) -> Result<(), RejectResponse> {
1208        let runtime = self.runtime.clone();
1209        runtime.block_on(async {
1210            self.pocket_ic
1211                .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1212                .await
1213        })
1214    }
1215
1216    /// Reinstall a canister WASM module.
1217    #[instrument(skip(self, wasm_module, arg), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), wasm_module_len = %wasm_module.len(), arg_len = %arg.len(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1218    pub fn reinstall_canister(
1219        &self,
1220        canister_id: CanisterId,
1221        wasm_module: Vec<u8>,
1222        arg: Vec<u8>,
1223        sender: Option<Principal>,
1224    ) -> Result<(), RejectResponse> {
1225        let runtime = self.runtime.clone();
1226        runtime.block_on(async {
1227            self.pocket_ic
1228                .reinstall_canister(canister_id, wasm_module, arg, sender)
1229                .await
1230        })
1231    }
1232
1233    /// Uninstall a canister.
1234    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1235    pub fn uninstall_canister(
1236        &self,
1237        canister_id: CanisterId,
1238        sender: Option<Principal>,
1239    ) -> Result<(), RejectResponse> {
1240        let runtime = self.runtime.clone();
1241        runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1242    }
1243
1244    /// Take canister snapshot.
1245    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1246    pub fn take_canister_snapshot(
1247        &self,
1248        canister_id: CanisterId,
1249        sender: Option<Principal>,
1250        replace_snapshot: Option<Vec<u8>>,
1251    ) -> Result<Snapshot, RejectResponse> {
1252        let runtime = self.runtime.clone();
1253        runtime.block_on(async {
1254            self.pocket_ic
1255                .take_canister_snapshot(canister_id, sender, replace_snapshot)
1256                .await
1257        })
1258    }
1259
1260    /// Load canister snapshot.
1261    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1262    pub fn load_canister_snapshot(
1263        &self,
1264        canister_id: CanisterId,
1265        sender: Option<Principal>,
1266        snapshot_id: Vec<u8>,
1267    ) -> Result<(), RejectResponse> {
1268        let runtime = self.runtime.clone();
1269        runtime.block_on(async {
1270            self.pocket_ic
1271                .load_canister_snapshot(canister_id, sender, snapshot_id)
1272                .await
1273        })
1274    }
1275
1276    /// List canister snapshots.
1277    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1278    pub fn list_canister_snapshots(
1279        &self,
1280        canister_id: CanisterId,
1281        sender: Option<Principal>,
1282    ) -> Result<Vec<Snapshot>, RejectResponse> {
1283        let runtime = self.runtime.clone();
1284        runtime.block_on(async {
1285            self.pocket_ic
1286                .list_canister_snapshots(canister_id, sender)
1287                .await
1288        })
1289    }
1290
1291    /// Delete canister snapshot.
1292    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1293    pub fn delete_canister_snapshot(
1294        &self,
1295        canister_id: CanisterId,
1296        sender: Option<Principal>,
1297        snapshot_id: Vec<u8>,
1298    ) -> Result<(), RejectResponse> {
1299        let runtime = self.runtime.clone();
1300        runtime.block_on(async {
1301            self.pocket_ic
1302                .delete_canister_snapshot(canister_id, sender, snapshot_id)
1303                .await
1304        })
1305    }
1306
1307    /// Update canister settings.
1308    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1309    pub fn update_canister_settings(
1310        &self,
1311        canister_id: CanisterId,
1312        sender: Option<Principal>,
1313        settings: CanisterSettings,
1314    ) -> Result<(), RejectResponse> {
1315        let runtime = self.runtime.clone();
1316        runtime.block_on(async {
1317            self.pocket_ic
1318                .update_canister_settings(canister_id, sender, settings)
1319                .await
1320        })
1321    }
1322
1323    /// Set canister's controllers.
1324    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1325    pub fn set_controllers(
1326        &self,
1327        canister_id: CanisterId,
1328        sender: Option<Principal>,
1329        new_controllers: Vec<Principal>,
1330    ) -> Result<(), RejectResponse> {
1331        let runtime = self.runtime.clone();
1332        runtime.block_on(async {
1333            self.pocket_ic
1334                .set_controllers(canister_id, sender, new_controllers)
1335                .await
1336        })
1337    }
1338
1339    /// Start a canister.
1340    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1341    pub fn start_canister(
1342        &self,
1343        canister_id: CanisterId,
1344        sender: Option<Principal>,
1345    ) -> Result<(), RejectResponse> {
1346        let runtime = self.runtime.clone();
1347        runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await })
1348    }
1349
1350    /// Stop a canister.
1351    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1352    pub fn stop_canister(
1353        &self,
1354        canister_id: CanisterId,
1355        sender: Option<Principal>,
1356    ) -> Result<(), RejectResponse> {
1357        let runtime = self.runtime.clone();
1358        runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1359    }
1360
1361    /// Delete a canister.
1362    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1363    pub fn delete_canister(
1364        &self,
1365        canister_id: CanisterId,
1366        sender: Option<Principal>,
1367    ) -> Result<(), RejectResponse> {
1368        let runtime = self.runtime.clone();
1369        runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1370    }
1371
1372    /// Checks whether the provided canister exists.
1373    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1374    pub fn canister_exists(&self, canister_id: CanisterId) -> bool {
1375        let runtime = self.runtime.clone();
1376        runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1377    }
1378
1379    /// Returns the subnet ID of the canister if the canister exists.
1380    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1381    pub fn get_subnet(&self, canister_id: CanisterId) -> Option<SubnetId> {
1382        let runtime = self.runtime.clone();
1383        runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1384    }
1385
1386    /// Returns subnet metrics for a given subnet.
1387    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1388    pub fn get_subnet_metrics(&self, subnet_id: Principal) -> Option<SubnetMetrics> {
1389        let runtime = self.runtime.clone();
1390        runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1391    }
1392
1393    pub fn update_call_with_effective_principal(
1394        &self,
1395        canister_id: CanisterId,
1396        effective_principal: RawEffectivePrincipal,
1397        sender: Principal,
1398        method: &str,
1399        payload: Vec<u8>,
1400    ) -> Result<Vec<u8>, RejectResponse> {
1401        let runtime = self.runtime.clone();
1402        runtime.block_on(async {
1403            self.pocket_ic
1404                .update_call_with_effective_principal(
1405                    canister_id,
1406                    effective_principal,
1407                    sender,
1408                    method,
1409                    payload,
1410                )
1411                .await
1412        })
1413    }
1414
1415    /// Execute a query call on a canister explicitly specifying an effective principal to route the request:
1416    /// this API is useful for making generic query calls (including management canister query calls) without using dedicated functions from this library
1417    /// (e.g., making generic query calls in dfx to a PocketIC instance).
1418    #[instrument(skip(self, payload), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), effective_principal = %effective_principal.to_string(), sender = %sender.to_string(), method = %method, payload_len = %payload.len()))]
1419    pub fn query_call_with_effective_principal(
1420        &self,
1421        canister_id: CanisterId,
1422        effective_principal: RawEffectivePrincipal,
1423        sender: Principal,
1424        method: &str,
1425        payload: Vec<u8>,
1426    ) -> Result<Vec<u8>, RejectResponse> {
1427        let runtime = self.runtime.clone();
1428        runtime.block_on(async {
1429            self.pocket_ic
1430                .query_call_with_effective_principal(
1431                    canister_id,
1432                    effective_principal,
1433                    sender,
1434                    method,
1435                    payload,
1436                )
1437                .await
1438        })
1439    }
1440
1441    /// Get the pending canister HTTP outcalls.
1442    /// Note that an additional `PocketIc::tick` is necessary after a canister
1443    /// executes a message making a canister HTTP outcall for the HTTP outcall
1444    /// to be retrievable here.
1445    /// Note that, unless a PocketIC instance is in auto progress mode,
1446    /// a response to the pending canister HTTP outcalls
1447    /// must be produced by the test driver and passed on to the PocketIC instace
1448    /// using `PocketIc::mock_canister_http_response`.
1449    /// In auto progress mode, the PocketIC server produces a response for every
1450    /// pending canister HTTP outcall by actually making an HTTP request
1451    /// to the specified URL.
1452    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1453    pub fn get_canister_http(&self) -> Vec<CanisterHttpRequest> {
1454        let runtime = self.runtime.clone();
1455        runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1456    }
1457
1458    /// Mock a response to a pending canister HTTP outcall.
1459    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1460    pub fn mock_canister_http_response(
1461        &self,
1462        mock_canister_http_response: MockCanisterHttpResponse,
1463    ) {
1464        let runtime = self.runtime.clone();
1465        runtime.block_on(async {
1466            self.pocket_ic
1467                .mock_canister_http_response(mock_canister_http_response)
1468                .await
1469        })
1470    }
1471
1472    /// Download a canister snapshot to a given snapshot directory.
1473    /// The sender must be a controller of the canister.
1474    /// The snapshot directory must be empty if it exists.
1475    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1476    pub fn canister_snapshot_download(
1477        &self,
1478        canister_id: CanisterId,
1479        sender: Principal,
1480        snapshot_id: Vec<u8>,
1481        snapshot_dir: PathBuf,
1482    ) {
1483        let runtime = self.runtime.clone();
1484        runtime.block_on(async {
1485            self.pocket_ic
1486                .canister_snapshot_download(canister_id, sender, snapshot_id, snapshot_dir)
1487                .await
1488        })
1489    }
1490
1491    /// Upload a canister snapshot from a given snapshot directory.
1492    /// The sender must be a controller of the canister.
1493    /// Returns the snapshot ID of the uploaded snapshot.
1494    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1495    pub fn canister_snapshot_upload(
1496        &self,
1497        canister_id: CanisterId,
1498        sender: Principal,
1499        replace_snapshot: Option<Vec<u8>>,
1500        snapshot_dir: PathBuf,
1501    ) -> Vec<u8> {
1502        let runtime = self.runtime.clone();
1503        runtime.block_on(async {
1504            self.pocket_ic
1505                .canister_snapshot_upload(canister_id, sender, replace_snapshot, snapshot_dir)
1506                .await
1507        })
1508    }
1509}
1510
1511impl Default for PocketIc {
1512    fn default() -> Self {
1513        Self::new()
1514    }
1515}
1516
1517impl Drop for PocketIc {
1518    fn drop(&mut self) {
1519        self.runtime.block_on(async {
1520            self.pocket_ic.do_drop().await;
1521        });
1522        if let Some(thread) = self.thread.take() {
1523            thread.join().unwrap();
1524        }
1525    }
1526}
1527
1528/// Call a canister candid method, authenticated. The sender can be impersonated (i.e., the
1529/// signature is not verified).
1530/// PocketIC executes update calls synchronously, so there is no need to poll for the result.
1531pub fn call_candid_as<Input, Output>(
1532    env: &PocketIc,
1533    canister_id: CanisterId,
1534    effective_principal: RawEffectivePrincipal,
1535    sender: Principal,
1536    method: &str,
1537    input: Input,
1538) -> Result<Output, RejectResponse>
1539where
1540    Input: ArgumentEncoder,
1541    Output: for<'a> ArgumentDecoder<'a>,
1542{
1543    with_candid(input, |payload| {
1544        env.update_call_with_effective_principal(
1545            canister_id,
1546            effective_principal,
1547            sender,
1548            method,
1549            payload,
1550        )
1551    })
1552}
1553
1554/// Call a canister candid method, anonymous.
1555/// PocketIC executes update calls synchronously, so there is no need to poll for the result.
1556pub fn call_candid<Input, Output>(
1557    env: &PocketIc,
1558    canister_id: CanisterId,
1559    effective_principal: RawEffectivePrincipal,
1560    method: &str,
1561    input: Input,
1562) -> Result<Output, RejectResponse>
1563where
1564    Input: ArgumentEncoder,
1565    Output: for<'a> ArgumentDecoder<'a>,
1566{
1567    call_candid_as(
1568        env,
1569        canister_id,
1570        effective_principal,
1571        Principal::anonymous(),
1572        method,
1573        input,
1574    )
1575}
1576
1577/// Call a canister candid query method, anonymous.
1578pub fn query_candid<Input, Output>(
1579    env: &PocketIc,
1580    canister_id: CanisterId,
1581    method: &str,
1582    input: Input,
1583) -> Result<Output, RejectResponse>
1584where
1585    Input: ArgumentEncoder,
1586    Output: for<'a> ArgumentDecoder<'a>,
1587{
1588    query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1589}
1590
1591/// Call a canister candid query method, authenticated. The sender can be impersonated (i.e., the
1592/// signature is not verified).
1593pub fn query_candid_as<Input, Output>(
1594    env: &PocketIc,
1595    canister_id: CanisterId,
1596    sender: Principal,
1597    method: &str,
1598    input: Input,
1599) -> Result<Output, RejectResponse>
1600where
1601    Input: ArgumentEncoder,
1602    Output: for<'a> ArgumentDecoder<'a>,
1603{
1604    with_candid(input, |bytes| {
1605        env.query_call(canister_id, sender, method, bytes)
1606    })
1607}
1608
1609/// Call a canister candid update method, anonymous.
1610pub fn update_candid<Input, Output>(
1611    env: &PocketIc,
1612    canister_id: CanisterId,
1613    method: &str,
1614    input: Input,
1615) -> Result<Output, RejectResponse>
1616where
1617    Input: ArgumentEncoder,
1618    Output: for<'a> ArgumentDecoder<'a>,
1619{
1620    update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1621}
1622
1623/// Call a canister candid update method, authenticated. The sender can be impersonated (i.e., the
1624/// signature is not verified).
1625pub fn update_candid_as<Input, Output>(
1626    env: &PocketIc,
1627    canister_id: CanisterId,
1628    sender: Principal,
1629    method: &str,
1630    input: Input,
1631) -> Result<Output, RejectResponse>
1632where
1633    Input: ArgumentEncoder,
1634    Output: for<'a> ArgumentDecoder<'a>,
1635{
1636    with_candid(input, |bytes| {
1637        env.update_call(canister_id, sender, method, bytes)
1638    })
1639}
1640
1641/// A helper function that we use to implement both [`call_candid`] and
1642/// [`query_candid`].
1643pub fn with_candid<Input, Output>(
1644    input: Input,
1645    f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, RejectResponse>,
1646) -> Result<Output, RejectResponse>
1647where
1648    Input: ArgumentEncoder,
1649    Output: for<'a> ArgumentDecoder<'a>,
1650{
1651    let in_bytes = encode_args(input).expect("failed to encode args");
1652    f(in_bytes).map(|out_bytes| {
1653        decode_args(&out_bytes).unwrap_or_else(|e| {
1654            panic!(
1655                "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}",
1656                std::any::type_name::<Output>(),
1657                e,
1658                out_bytes,
1659                String::from_utf8_lossy(&out_bytes),
1660            )
1661        })
1662    })
1663}
1664
1665/// Error type for [`TryFrom<u64>`].
1666#[derive(Clone, Copy, Debug)]
1667pub enum TryFromError {
1668    ValueOutOfRange(u64),
1669}
1670
1671/// User-facing error codes.
1672///
1673/// The error codes are currently assigned using an HTTP-like
1674/// convention: the most significant digit is the corresponding reject
1675/// code and the rest is just a sequentially assigned two-digit
1676/// number.
1677#[derive(
1678    PartialOrd,
1679    Ord,
1680    Clone,
1681    Copy,
1682    Debug,
1683    PartialEq,
1684    Eq,
1685    Hash,
1686    Serialize,
1687    Deserialize,
1688    JsonSchema,
1689    EnumIter,
1690)]
1691pub enum ErrorCode {
1692    // 1xx -- `RejectCode::SysFatal`
1693    SubnetOversubscribed = 101,
1694    MaxNumberOfCanistersReached = 102,
1695    // 2xx -- `RejectCode::SysTransient`
1696    CanisterQueueFull = 201,
1697    IngressMessageTimeout = 202,
1698    CanisterQueueNotEmpty = 203,
1699    IngressHistoryFull = 204,
1700    CanisterIdAlreadyExists = 205,
1701    StopCanisterRequestTimeout = 206,
1702    CanisterOutOfCycles = 207,
1703    CertifiedStateUnavailable = 208,
1704    CanisterInstallCodeRateLimited = 209,
1705    CanisterHeapDeltaRateLimited = 210,
1706    // 3xx -- `RejectCode::DestinationInvalid`
1707    CanisterNotFound = 301,
1708    CanisterSnapshotNotFound = 305,
1709    // 4xx -- `RejectCode::CanisterReject`
1710    InsufficientMemoryAllocation = 402,
1711    InsufficientCyclesForCreateCanister = 403,
1712    SubnetNotFound = 404,
1713    CanisterNotHostedBySubnet = 405,
1714    CanisterRejectedMessage = 406,
1715    UnknownManagementMessage = 407,
1716    InvalidManagementPayload = 408,
1717    CanisterSnapshotImmutable = 409,
1718    InvalidSubnetAdmin = 410,
1719    // 5xx -- `RejectCode::CanisterError`
1720    CanisterTrapped = 502,
1721    CanisterCalledTrap = 503,
1722    CanisterContractViolation = 504,
1723    CanisterInvalidWasm = 505,
1724    CanisterDidNotReply = 506,
1725    CanisterOutOfMemory = 507,
1726    CanisterStopped = 508,
1727    CanisterStopping = 509,
1728    CanisterNotStopped = 510,
1729    CanisterStoppingCancelled = 511,
1730    CanisterInvalidController = 512,
1731    CanisterFunctionNotFound = 513,
1732    CanisterNonEmpty = 514,
1733    QueryCallGraphLoopDetected = 517,
1734    InsufficientCyclesInCall = 520,
1735    CanisterWasmEngineError = 521,
1736    CanisterInstructionLimitExceeded = 522,
1737    CanisterMemoryAccessLimitExceeded = 524,
1738    QueryCallGraphTooDeep = 525,
1739    QueryCallGraphTotalInstructionLimitExceeded = 526,
1740    CompositeQueryCalledInReplicatedMode = 527,
1741    QueryTimeLimitExceeded = 528,
1742    QueryCallGraphInternal = 529,
1743    InsufficientCyclesInComputeAllocation = 530,
1744    InsufficientCyclesInMemoryAllocation = 531,
1745    InsufficientCyclesInMemoryGrow = 532,
1746    ReservedCyclesLimitExceededInMemoryAllocation = 533,
1747    ReservedCyclesLimitExceededInMemoryGrow = 534,
1748    InsufficientCyclesInMessageMemoryGrow = 535,
1749    CanisterMethodNotFound = 536,
1750    CanisterWasmModuleNotFound = 537,
1751    CanisterAlreadyInstalled = 538,
1752    CanisterWasmMemoryLimitExceeded = 539,
1753    ReservedCyclesLimitIsTooLow = 540,
1754    CanisterInvalidControllerOrSubnetAdmin = 541,
1755    // 6xx -- `RejectCode::SysUnknown`
1756    DeadlineExpired = 601,
1757    ResponseDropped = 602,
1758}
1759
1760impl TryFrom<u64> for ErrorCode {
1761    type Error = TryFromError;
1762    fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
1763        match err {
1764            // 1xx -- `RejectCode::SysFatal`
1765            101 => Ok(ErrorCode::SubnetOversubscribed),
1766            102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
1767            // 2xx -- `RejectCode::SysTransient`
1768            201 => Ok(ErrorCode::CanisterQueueFull),
1769            202 => Ok(ErrorCode::IngressMessageTimeout),
1770            203 => Ok(ErrorCode::CanisterQueueNotEmpty),
1771            204 => Ok(ErrorCode::IngressHistoryFull),
1772            205 => Ok(ErrorCode::CanisterIdAlreadyExists),
1773            206 => Ok(ErrorCode::StopCanisterRequestTimeout),
1774            207 => Ok(ErrorCode::CanisterOutOfCycles),
1775            208 => Ok(ErrorCode::CertifiedStateUnavailable),
1776            209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
1777            210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
1778            // 3xx -- `RejectCode::DestinationInvalid`
1779            301 => Ok(ErrorCode::CanisterNotFound),
1780            305 => Ok(ErrorCode::CanisterSnapshotNotFound),
1781            // 4xx -- `RejectCode::CanisterReject`
1782            402 => Ok(ErrorCode::InsufficientMemoryAllocation),
1783            403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
1784            404 => Ok(ErrorCode::SubnetNotFound),
1785            405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
1786            406 => Ok(ErrorCode::CanisterRejectedMessage),
1787            407 => Ok(ErrorCode::UnknownManagementMessage),
1788            408 => Ok(ErrorCode::InvalidManagementPayload),
1789            409 => Ok(ErrorCode::CanisterSnapshotImmutable),
1790            410 => Ok(ErrorCode::InvalidSubnetAdmin),
1791            // 5xx -- `RejectCode::CanisterError`
1792            502 => Ok(ErrorCode::CanisterTrapped),
1793            503 => Ok(ErrorCode::CanisterCalledTrap),
1794            504 => Ok(ErrorCode::CanisterContractViolation),
1795            505 => Ok(ErrorCode::CanisterInvalidWasm),
1796            506 => Ok(ErrorCode::CanisterDidNotReply),
1797            507 => Ok(ErrorCode::CanisterOutOfMemory),
1798            508 => Ok(ErrorCode::CanisterStopped),
1799            509 => Ok(ErrorCode::CanisterStopping),
1800            510 => Ok(ErrorCode::CanisterNotStopped),
1801            511 => Ok(ErrorCode::CanisterStoppingCancelled),
1802            512 => Ok(ErrorCode::CanisterInvalidController),
1803            513 => Ok(ErrorCode::CanisterFunctionNotFound),
1804            514 => Ok(ErrorCode::CanisterNonEmpty),
1805            517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
1806            520 => Ok(ErrorCode::InsufficientCyclesInCall),
1807            521 => Ok(ErrorCode::CanisterWasmEngineError),
1808            522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
1809            524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
1810            525 => Ok(ErrorCode::QueryCallGraphTooDeep),
1811            526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
1812            527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
1813            528 => Ok(ErrorCode::QueryTimeLimitExceeded),
1814            529 => Ok(ErrorCode::QueryCallGraphInternal),
1815            530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
1816            531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
1817            532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
1818            533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
1819            534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
1820            535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
1821            536 => Ok(ErrorCode::CanisterMethodNotFound),
1822            537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
1823            538 => Ok(ErrorCode::CanisterAlreadyInstalled),
1824            539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
1825            540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
1826            541 => Ok(ErrorCode::CanisterInvalidControllerOrSubnetAdmin),
1827            // 6xx -- `RejectCode::SysUnknown`
1828            601 => Ok(ErrorCode::DeadlineExpired),
1829            602 => Ok(ErrorCode::ResponseDropped),
1830            _ => Err(TryFromError::ValueOutOfRange(err)),
1831        }
1832    }
1833}
1834
1835impl std::fmt::Display for ErrorCode {
1836    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1837        // E.g. "IC0301"
1838        write!(f, "IC{:04}", *self as i32)
1839    }
1840}
1841
1842/// User-facing reject codes.
1843///
1844/// They can be derived from the most significant digit of the
1845/// corresponding error code.
1846#[derive(
1847    PartialOrd,
1848    Ord,
1849    Clone,
1850    Copy,
1851    Debug,
1852    PartialEq,
1853    Eq,
1854    Hash,
1855    Serialize,
1856    Deserialize,
1857    JsonSchema,
1858    EnumIter,
1859)]
1860pub enum RejectCode {
1861    SysFatal = 1,
1862    SysTransient = 2,
1863    DestinationInvalid = 3,
1864    CanisterReject = 4,
1865    CanisterError = 5,
1866    SysUnknown = 6,
1867}
1868
1869impl TryFrom<u64> for RejectCode {
1870    type Error = TryFromError;
1871    fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
1872        match err {
1873            1 => Ok(RejectCode::SysFatal),
1874            2 => Ok(RejectCode::SysTransient),
1875            3 => Ok(RejectCode::DestinationInvalid),
1876            4 => Ok(RejectCode::CanisterReject),
1877            5 => Ok(RejectCode::CanisterError),
1878            6 => Ok(RejectCode::SysUnknown),
1879            _ => Err(TryFromError::ValueOutOfRange(err)),
1880        }
1881    }
1882}
1883
1884/// User-facing type describing an unsuccessful (also called reject) call response.
1885#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1886pub struct RejectResponse {
1887    pub reject_code: RejectCode,
1888    pub reject_message: String,
1889    pub error_code: ErrorCode,
1890    pub certified: bool,
1891}
1892
1893impl std::fmt::Display for RejectResponse {
1894    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1895        // Follows [agent-rs](https://github.com/dfinity/agent-rs/blob/a651dbbe69e61d4e8508c144cd60cfa3118eeb3a/ic-agent/src/agent/agent_error.rs#L54)
1896        write!(
1897            f,
1898            "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}",
1899            self.reject_code, self.reject_message, self.error_code
1900        )
1901    }
1902}
1903
1904/// This enum describes the result of retrieving ingress status.
1905/// The `IngressStatusResult::Forbidden` variant is produced
1906/// if an optional caller is provided and a corresponding read state request
1907/// for the status of the same update call signed by that specified caller
1908/// was rejected because the update call was submitted by a different caller.
1909#[derive(Debug, Serialize, Deserialize)]
1910pub enum IngressStatusResult {
1911    NotAvailable,
1912    Forbidden(String),
1913    Success(Result<Vec<u8>, RejectResponse>),
1914}
1915
1916#[derive(Clone, Debug, Default)]
1917pub struct TickConfigs {
1918    pub blockmakers: Option<Vec<SubnetBlockmakers>>,
1919}
1920
1921impl From<TickConfigs> for RawTickConfigs {
1922    fn from(tick_configs: TickConfigs) -> Self {
1923        Self {
1924            blockmakers: tick_configs.blockmakers.map(|blockmakers| {
1925                blockmakers
1926                    .into_iter()
1927                    .map(|blockmaker| blockmaker.into())
1928                    .collect()
1929            }),
1930        }
1931    }
1932}
1933
1934#[derive(Clone, Debug)]
1935pub struct SubnetBlockmakers {
1936    pub subnet: Principal,
1937    pub blockmaker: Principal,
1938    pub failed_blockmakers: Vec<Principal>,
1939}
1940
1941impl From<SubnetBlockmakers> for RawSubnetBlockmakers {
1942    fn from(blockmaker: SubnetBlockmakers) -> Self {
1943        Self {
1944            subnet: blockmaker.subnet.into(),
1945            blockmaker: blockmaker.blockmaker.into(),
1946            failed_blockmakers: blockmaker
1947                .failed_blockmakers
1948                .into_iter()
1949                .map(|p| p.into())
1950                .collect(),
1951        }
1952    }
1953}
1954
1955#[cfg(windows)]
1956fn wsl_path(path: &PathBuf, desc: &str) -> String {
1957    windows_to_wsl(
1958        path.as_os_str()
1959            .to_str()
1960            .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
1961    )
1962    .unwrap_or_else(|e| {
1963        panic!(
1964            "Could not convert {} path ({:?}) to WSL path: {:?}",
1965            desc, path, e
1966        )
1967    })
1968}
1969
1970#[cfg(windows)]
1971static WSL_WARM_UP: Once = Once::new();
1972
1973#[cfg(windows)]
1974fn warm_up_wsl() {
1975    WSL_WARM_UP.call_once(|| {
1976        let output = Command::new("wsl")
1977            .arg("bash")
1978            .arg("-c")
1979            .arg("true")
1980            .output()
1981            .expect("Failed to warm up WSL");
1982        if !output.status.success() {
1983            panic!(
1984                "Failed to warm up WSL.\nStatus: {}\nStdout: {}\nStderr: {}",
1985                output.status,
1986                String::from_utf8_lossy(&output.stdout),
1987                String::from_utf8_lossy(&output.stderr),
1988            );
1989        }
1990    });
1991}
1992
1993#[cfg(windows)]
1994fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
1995    warm_up_wsl();
1996    let mut cmd = Command::new("wsl");
1997    cmd.arg(wsl_path(bin_path, "PocketIC binary"));
1998    cmd
1999}
2000
2001#[cfg(not(windows))]
2002fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
2003    Command::new(bin_path)
2004}
2005
2006fn check_pocketic_server_version(version_line: &str) -> Result<(), String> {
2007    let unexpected_version = format!(
2008        "Unexpected PocketIC server version: got `{version_line}`; expected `{POCKET_IC_SERVER_NAME} x.y.z`."
2009    );
2010    let Some((pocket_ic_server, version)) = version_line.split_once(' ') else {
2011        return Err(unexpected_version);
2012    };
2013    if pocket_ic_server != POCKET_IC_SERVER_NAME {
2014        return Err(unexpected_version);
2015    }
2016    let req = VersionReq::parse(&format!(">={MIN_SERVER_VERSION},<{MAX_SERVER_VERSION}")).unwrap();
2017    let version = Version::parse(version)
2018        .map_err(|e| format!("Failed to parse PocketIC server version: {e}"))?;
2019    if !req.matches(&version) {
2020        return Err(format!(
2021            "Incompatible PocketIC server version: got {version}; expected {req}."
2022        ));
2023    }
2024
2025    Ok(())
2026}
2027
2028fn get_and_check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
2029    let mut cmd = pocket_ic_server_cmd(server_binary);
2030    cmd.arg("--version");
2031    let output = cmd.output().map_err(|e| e.to_string())?;
2032    if !output.status.success() {
2033        return Err(format!(
2034            "PocketIC server failed to print its version.\nStatus: {}\nStdout: {}\nStderr: {}",
2035            output.status,
2036            String::from_utf8_lossy(&output.stdout),
2037            String::from_utf8_lossy(&output.stderr),
2038        ));
2039    }
2040    let version_str = String::from_utf8(output.stdout)
2041        .map_err(|e| format!("Failed to parse PocketIC server version: {e}."))?;
2042    let version_line = version_str.trim_end_matches('\n');
2043    check_pocketic_server_version(version_line)
2044}
2045
2046async fn download_pocketic_server(
2047    server_url: String,
2048    mut out: std::fs::File,
2049) -> Result<(), String> {
2050    let binary = reqwest::get(server_url)
2051        .await
2052        .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
2053        .bytes()
2054        .await
2055        .map_err(|e| format!("Failed to download PocketIC server: {e}"))?
2056        .to_vec();
2057    let mut gz = GzDecoder::new(&binary[..]);
2058    let _ = std::io::copy(&mut gz, &mut out)
2059        .map_err(|e| format!("Failed to write PocketIC server binary: {e}"));
2060    Ok(())
2061}
2062
2063#[derive(Default)]
2064pub struct StartServerParams {
2065    pub server_binary: Option<PathBuf>,
2066    /// Reuse an existing PocketIC server spawned by this process.
2067    pub reuse: bool,
2068    /// TTL for the PocketIC server.
2069    /// The server stops gracefully if no request has been received for the duration of its TTL
2070    /// after the last request finished and if there are no more pending requests.
2071    /// A default value of TTL is used if no `ttl` is specified here.
2072    /// Note: The TTL might not be overriden if the same test process sets `reuse` to `true`
2073    /// and passes different values of `ttl`.
2074    pub ttl: Option<Duration>,
2075    /// Hard TTL for the PocketIC server.
2076    /// The server stops with a hard exit after the duration of its hard TTL
2077    /// since its launch.
2078    /// If no `hard_ttl` is specified here, then the PocketIC server
2079    /// does not use any default hard TTL.
2080    /// Note: The hard TTL might not be overriden if the same test process sets `reuse` to `true`
2081    /// and passes different values of `hard_ttl`.
2082    pub hard_ttl: Option<Duration>,
2083}
2084
2085/// Attempt to start a new PocketIC server.
2086pub async fn start_server(params: StartServerParams) -> (Child, Url) {
2087    let default_bin_dir =
2088        std::env::temp_dir().join(format!("{POCKET_IC_SERVER_NAME}-{LATEST_SERVER_VERSION}"));
2089    let default_bin_path = default_bin_dir.join("pocket-ic");
2090    let bin_path_provided =
2091        params.server_binary.is_some() || std::env::var_os("POCKET_IC_BIN").is_some();
2092    let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
2093        std::env::var_os("POCKET_IC_BIN")
2094            .unwrap_or_else(|| default_bin_path.clone().into())
2095            .into()
2096    });
2097
2098    if let Err(e) = get_and_check_pocketic_server_version(&bin_path) {
2099        if bin_path_provided {
2100            panic!(
2101                "Failed to validate PocketIC server binary `{}`: `{}`.",
2102                bin_path.display(),
2103                e
2104            );
2105        }
2106        bin_path = default_bin_path.clone();
2107        std::fs::create_dir_all(&default_bin_dir)
2108            .expect("Failed to create PocketIC server directory");
2109        let mut options = OpenOptions::new();
2110        options.write(true).create_new(true);
2111        #[cfg(unix)]
2112        options.mode(0o777);
2113        match options.open(&default_bin_path) {
2114            Ok(out) => {
2115                #[cfg(target_os = "macos")]
2116                let os = "darwin";
2117                #[cfg(not(target_os = "macos"))]
2118                let os = "linux";
2119                #[cfg(target_arch = "aarch64")]
2120                let arch = "arm64";
2121                #[cfg(not(target_arch = "aarch64"))]
2122                let arch = "x86_64";
2123                let server_url = format!(
2124                    "https://github.com/dfinity/pocketic/releases/download/{LATEST_SERVER_VERSION}/pocket-ic-{arch}-{os}.gz"
2125                );
2126                println!(
2127                    "Failed to validate PocketIC server binary `{}`: `{}`. Going to download PocketIC server {} from {} to the local path {}. To avoid downloads during test execution, please specify the path to the (ungzipped and executable) PocketIC server {} using the function `PocketIcBuilder::with_server_binary` or using the `POCKET_IC_BIN` environment variable.",
2128                    bin_path.display(),
2129                    e,
2130                    LATEST_SERVER_VERSION,
2131                    server_url,
2132                    default_bin_path.display(),
2133                    LATEST_SERVER_VERSION
2134                );
2135                if let Err(e) = download_pocketic_server(server_url, out).await {
2136                    let _ = std::fs::remove_file(default_bin_path);
2137                    panic!("{}", e);
2138                }
2139            }
2140            _ => {
2141                // PocketIC server has already been created by another test: wait until it's fully downloaded.
2142                let start = std::time::Instant::now();
2143                loop {
2144                    if get_and_check_pocketic_server_version(&default_bin_path).is_ok() {
2145                        break;
2146                    }
2147                    if start.elapsed() > std::time::Duration::from_secs(60) {
2148                        let _ = std::fs::remove_file(&default_bin_path);
2149                        panic!(
2150                            "Timed out waiting for PocketIC server being available at the local path {}.",
2151                            default_bin_path.display()
2152                        );
2153                    }
2154                    std::thread::sleep(std::time::Duration::from_millis(100));
2155                }
2156            }
2157        }
2158    }
2159
2160    let port_file_path = if params.reuse {
2161        // We use the test driver's process ID to share the PocketIC server between multiple tests
2162        // launched by the same test driver.
2163        let test_driver_pid = std::process::id();
2164        std::env::temp_dir().join(format!("pocket_ic_{test_driver_pid}.port"))
2165    } else {
2166        NamedTempFile::new().unwrap().into_temp_path().to_path_buf()
2167    };
2168    let mut cmd = pocket_ic_server_cmd(&bin_path);
2169    if let Some(ttl) = params.ttl {
2170        cmd.arg("--ttl").arg(ttl.as_secs().to_string());
2171    }
2172    if let Some(hard_ttl) = params.hard_ttl {
2173        cmd.arg("--hard-ttl").arg(hard_ttl.as_secs().to_string());
2174    }
2175    cmd.arg("--port-file");
2176    #[cfg(windows)]
2177    cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
2178    #[cfg(not(windows))]
2179    cmd.arg(port_file_path.clone());
2180    if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER")
2181        && !mute_server.is_empty()
2182    {
2183        cmd.stdout(std::process::Stdio::null());
2184        cmd.stderr(std::process::Stdio::null());
2185    }
2186
2187    // Start the server in the background so that it doesn't receive signals such as CTRL^C
2188    // from the foreground terminal.
2189    #[cfg(unix)]
2190    {
2191        use std::os::unix::process::CommandExt;
2192        cmd.process_group(0);
2193    }
2194
2195    // TODO: SDK-1936
2196    #[allow(clippy::zombie_processes)]
2197    let child = cmd
2198        .spawn()
2199        .unwrap_or_else(|_| panic!("Failed to start PocketIC binary ({})", bin_path.display()));
2200
2201    loop {
2202        if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone())
2203            && port_string.contains("\n")
2204        {
2205            let port: u16 = port_string
2206                .trim_end()
2207                .parse()
2208                .expect("Failed to parse port to number");
2209            break (
2210                child,
2211                Url::parse(&format!("http://{LOCALHOST}:{port}/")).unwrap(),
2212            );
2213        }
2214        std::thread::sleep(Duration::from_millis(20));
2215    }
2216}
2217
2218#[derive(Error, Debug)]
2219pub enum DefaultEffectiveCanisterIdError {
2220    ReqwestError(#[from] reqwest::Error),
2221    JsonError(#[from] serde_json::Error),
2222    Utf8Error(#[from] std::string::FromUtf8Error),
2223}
2224
2225impl std::fmt::Display for DefaultEffectiveCanisterIdError {
2226    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2227        match self {
2228            DefaultEffectiveCanisterIdError::ReqwestError(err) => {
2229                write!(f, "ReqwestError({err})")
2230            }
2231            DefaultEffectiveCanisterIdError::JsonError(err) => write!(f, "JsonError({err})"),
2232            DefaultEffectiveCanisterIdError::Utf8Error(err) => write!(f, "Utf8Error({err})"),
2233        }
2234    }
2235}
2236
2237/// Retrieves a default effective canister id for canister creation on a PocketIC instance
2238/// characterized by:
2239///  - a PocketIC instance URL of the form http://<ip>:<port>/instances/<instance_id>;
2240///  - a PocketIC HTTP gateway URL of the form http://<ip>:port for a PocketIC instance.
2241///
2242/// Returns an error if the PocketIC instance topology could not be fetched or parsed, e.g.,
2243/// because the given URL points to a replica (i.e., does not meet any of the above two properties).
2244pub fn get_default_effective_canister_id(
2245    pocket_ic_url: String,
2246) -> Result<Principal, DefaultEffectiveCanisterIdError> {
2247    let runtime = Runtime::new().expect("Unable to create a runtime");
2248    runtime.block_on(crate::nonblocking::get_default_effective_canister_id(
2249        pocket_ic_url,
2250    ))
2251}
2252
2253pub fn copy_dir(
2254    src: impl AsRef<std::path::Path>,
2255    dst: impl AsRef<std::path::Path>,
2256) -> std::io::Result<()> {
2257    std::fs::create_dir_all(&dst)?;
2258    for entry in std::fs::read_dir(src)? {
2259        let entry = entry?;
2260        let ty = entry.file_type()?;
2261        if ty.is_dir() {
2262            copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2263        } else {
2264            std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2265        }
2266    }
2267    Ok(())
2268}
2269
2270#[cfg(test)]
2271mod test {
2272    use crate::{ErrorCode, RejectCode, check_pocketic_server_version};
2273    use strum::IntoEnumIterator;
2274
2275    #[test]
2276    fn reject_code_round_trip() {
2277        for initial in RejectCode::iter() {
2278            let round_trip = RejectCode::try_from(initial as u64).unwrap();
2279
2280            assert_eq!(initial, round_trip);
2281        }
2282    }
2283
2284    #[test]
2285    fn error_code_round_trip() {
2286        for initial in ErrorCode::iter() {
2287            let round_trip = ErrorCode::try_from(initial as u64).unwrap();
2288
2289            assert_eq!(initial, round_trip);
2290        }
2291    }
2292
2293    #[test]
2294    fn reject_code_matches_ic_error_code() {
2295        assert_eq!(
2296            RejectCode::iter().len(),
2297            ic_error_types::RejectCode::iter().len()
2298        );
2299        for ic_reject_code in ic_error_types::RejectCode::iter() {
2300            let reject_code: RejectCode = (ic_reject_code as u64).try_into().unwrap();
2301            assert_eq!(format!("{reject_code:?}"), format!("{:?}", ic_reject_code));
2302        }
2303    }
2304
2305    #[test]
2306    fn error_code_matches_ic_error_code() {
2307        assert_eq!(
2308            ErrorCode::iter().len(),
2309            ic_error_types::ErrorCode::iter().len()
2310        );
2311        for ic_error_code in ic_error_types::ErrorCode::iter() {
2312            let error_code: ErrorCode = (ic_error_code as u64).try_into().unwrap();
2313            assert_eq!(format!("{error_code:?}"), format!("{:?}", ic_error_code));
2314        }
2315    }
2316
2317    #[test]
2318    fn test_check_pocketic_server_version() {
2319        assert!(
2320            check_pocketic_server_version("pocket-ic-server")
2321                .unwrap_err()
2322                .contains("Unexpected PocketIC server version")
2323        );
2324        assert!(
2325            check_pocketic_server_version("pocket-ic 13.0.0")
2326                .unwrap_err()
2327                .contains("Unexpected PocketIC server version")
2328        );
2329        assert!(
2330            check_pocketic_server_version("pocket-ic-server 13 0 0")
2331                .unwrap_err()
2332                .contains("Failed to parse PocketIC server version")
2333        );
2334        assert!(
2335            check_pocketic_server_version("pocket-ic-server 12.0.0")
2336                .unwrap_err()
2337                .contains("Incompatible PocketIC server version")
2338        );
2339        check_pocketic_server_version("pocket-ic-server 13.0.0").unwrap();
2340        check_pocketic_server_version("pocket-ic-server 13.0.1").unwrap();
2341        check_pocketic_server_version("pocket-ic-server 13.1.0").unwrap();
2342        assert!(
2343            check_pocketic_server_version("pocket-ic-server 14.0.0")
2344                .unwrap_err()
2345                .contains("Incompatible PocketIC server version")
2346        );
2347    }
2348}