Skip to main content

ic_testkit/pocket_ic/
mod.rs

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