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