1#![allow(clippy::test_attr_in_doctest)]
2#![doc = include_str!("../README.md")]
3use 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    decode_args, encode_args,
67    utils::{ArgumentDecoder, ArgumentEncoder},
68    Principal,
69};
70use flate2::read::GzDecoder;
71use ic_management_canister_types::{
72    CanisterId, CanisterInstallMode, CanisterLogRecord, CanisterSettings, CanisterStatusResult,
73    Snapshot,
74};
75use ic_transport_types::SubnetMetrics;
76use reqwest::Url;
77use schemars::JsonSchema;
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::{mpsc::channel, Arc},
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
103pub const EXPECTED_SERVER_VERSION: &str = "10.0.0";
104
105const DEFAULT_MAX_REQUEST_TIME_MS: u64 = 300_000;
107
108const LOCALHOST: &str = "127.0.0.1";
109
110enum PocketIcStateKind {
111    StateDir(PathBuf),
113    TempDir(TempDir),
119}
120
121pub struct PocketIcState {
122    state: PocketIcStateKind,
123}
124
125impl PocketIcState {
126    #[allow(clippy::new_without_default)]
127    pub fn new() -> Self {
128        let temp_dir = TempDir::new().unwrap();
129        Self {
130            state: PocketIcStateKind::TempDir(temp_dir),
131        }
132    }
133
134    pub fn new_from_path(state_dir: PathBuf) -> Self {
135        Self {
136            state: PocketIcStateKind::StateDir(state_dir),
137        }
138    }
139
140    pub fn into_path(self) -> PathBuf {
141        match self.state {
142            PocketIcStateKind::StateDir(state_dir) => state_dir,
143            PocketIcStateKind::TempDir(temp_dir) => temp_dir.keep(),
144        }
145    }
146
147    pub(crate) fn state_dir(&self) -> PathBuf {
148        match &self.state {
149            PocketIcStateKind::StateDir(state_dir) => state_dir.clone(),
150            PocketIcStateKind::TempDir(temp_dir) => temp_dir.path().to_path_buf(),
151        }
152    }
153}
154
155pub struct PocketIcBuilder {
156    config: Option<ExtendedSubnetConfigSet>,
157    http_gateway_config: Option<InstanceHttpGatewayConfig>,
158    server_binary: Option<PathBuf>,
159    server_url: Option<Url>,
160    max_request_time_ms: Option<u64>,
161    read_only_state_dir: Option<PathBuf>,
162    state_dir: Option<PocketIcState>,
163    icp_config: IcpConfig,
164    log_level: Option<Level>,
165    bitcoind_addr: Option<Vec<SocketAddr>>,
166    icp_features: IcpFeatures,
167    initial_time: Option<InitialTime>,
168}
169
170#[allow(clippy::new_without_default)]
171impl PocketIcBuilder {
172    pub fn new() -> Self {
173        Self {
174            config: None,
175            http_gateway_config: None,
176            server_binary: None,
177            server_url: None,
178            max_request_time_ms: Some(DEFAULT_MAX_REQUEST_TIME_MS),
179            read_only_state_dir: None,
180            state_dir: None,
181            icp_config: IcpConfig::default(),
182            log_level: None,
183            bitcoind_addr: None,
184            icp_features: IcpFeatures::default(),
185            initial_time: None,
186        }
187    }
188
189    pub fn new_with_config(config: impl Into<ExtendedSubnetConfigSet>) -> Self {
190        let mut builder = Self::new();
191        builder.config = Some(config.into());
192        builder
193    }
194
195    pub fn build(self) -> PocketIc {
196        PocketIc::from_components(
197            self.config.unwrap_or_default(),
198            self.server_url,
199            self.server_binary,
200            self.max_request_time_ms,
201            self.read_only_state_dir,
202            self.state_dir,
203            self.icp_config,
204            self.log_level,
205            self.bitcoind_addr,
206            self.icp_features,
207            self.initial_time,
208            self.http_gateway_config,
209        )
210    }
211
212    pub async fn build_async(self) -> PocketIcAsync {
213        PocketIcAsync::from_components(
214            self.config.unwrap_or_default(),
215            self.server_url,
216            self.server_binary,
217            self.max_request_time_ms,
218            self.read_only_state_dir,
219            self.state_dir,
220            self.icp_config,
221            self.log_level,
222            self.bitcoind_addr,
223            self.icp_features,
224            self.initial_time,
225            self.http_gateway_config,
226        )
227        .await
228    }
229
230    pub fn with_server_binary(mut self, server_binary: PathBuf) -> Self {
232        self.server_binary = Some(server_binary);
233        self
234    }
235
236    pub fn with_server_url(mut self, server_url: Url) -> Self {
238        self.server_url = Some(server_url);
239        self
240    }
241
242    pub fn with_max_request_time_ms(mut self, max_request_time_ms: Option<u64>) -> Self {
243        self.max_request_time_ms = max_request_time_ms;
244        self
245    }
246
247    pub fn with_state_dir(mut self, state_dir: PathBuf) -> Self {
248        self.state_dir = Some(PocketIcState::new_from_path(state_dir));
249        self
250    }
251
252    pub fn with_state(mut self, state_dir: PocketIcState) -> Self {
253        self.state_dir = Some(state_dir);
254        self
255    }
256
257    pub fn with_read_only_state(mut self, read_only_state_dir: &PocketIcState) -> Self {
258        self.read_only_state_dir = Some(read_only_state_dir.state_dir());
259        self
260    }
261
262    pub fn with_icp_config(mut self, icp_config: IcpConfig) -> Self {
263        self.icp_config = icp_config;
264        self
265    }
266
267    pub fn with_log_level(mut self, log_level: Level) -> Self {
268        self.log_level = Some(log_level);
269        self
270    }
271
272    pub fn with_bitcoind_addr(self, bitcoind_addr: SocketAddr) -> Self {
273        self.with_bitcoind_addrs(vec![bitcoind_addr])
274    }
275
276    pub fn with_bitcoind_addrs(self, bitcoind_addrs: Vec<SocketAddr>) -> Self {
277        Self {
278            bitcoind_addr: Some(bitcoind_addrs),
279            ..self
280        }
281    }
282
283    pub fn with_nns_subnet(mut self) -> Self {
285        let mut config = self.config.unwrap_or_default();
286        config.nns = Some(config.nns.unwrap_or_default());
287        self.config = Some(config);
288        self
289    }
290
291    pub fn with_nns_state(self, path_to_state: PathBuf) -> Self {
308        self.with_subnet_state(SubnetKind::NNS, path_to_state)
309    }
310
311    pub fn with_subnet_state(mut self, subnet_kind: SubnetKind, path_to_state: PathBuf) -> Self {
328        let mut config = self.config.unwrap_or_default();
329        let subnet_spec = SubnetSpec::default().with_state_dir(path_to_state);
330        match subnet_kind {
331            SubnetKind::NNS => config.nns = Some(subnet_spec),
332            SubnetKind::SNS => config.sns = Some(subnet_spec),
333            SubnetKind::II => config.ii = Some(subnet_spec),
334            SubnetKind::Fiduciary => config.fiduciary = Some(subnet_spec),
335            SubnetKind::Bitcoin => config.bitcoin = Some(subnet_spec),
336            SubnetKind::Application => config.application.push(subnet_spec),
337            SubnetKind::System => config.system.push(subnet_spec),
338            SubnetKind::VerifiedApplication => config.verified_application.push(subnet_spec),
339        };
340        self.config = Some(config);
341        self
342    }
343
344    pub fn with_sns_subnet(mut self) -> Self {
346        let mut config = self.config.unwrap_or_default();
347        config.sns = Some(config.sns.unwrap_or_default());
348        self.config = Some(config);
349        self
350    }
351
352    pub fn with_ii_subnet(mut self) -> Self {
354        let mut config = self.config.unwrap_or_default();
355        config.ii = Some(config.ii.unwrap_or_default());
356        self.config = Some(config);
357        self
358    }
359
360    pub fn with_fiduciary_subnet(mut self) -> Self {
362        let mut config = self.config.unwrap_or_default();
363        config.fiduciary = Some(config.fiduciary.unwrap_or_default());
364        self.config = Some(config);
365        self
366    }
367
368    pub fn with_bitcoin_subnet(mut self) -> Self {
370        let mut config = self.config.unwrap_or_default();
371        config.bitcoin = Some(config.bitcoin.unwrap_or_default());
372        self.config = Some(config);
373        self
374    }
375
376    pub fn with_system_subnet(mut self) -> Self {
378        let mut config = self.config.unwrap_or_default();
379        config.system.push(SubnetSpec::default());
380        self.config = Some(config);
381        self
382    }
383
384    pub fn with_application_subnet(mut self) -> Self {
386        let mut config = self.config.unwrap_or_default();
387        config.application.push(SubnetSpec::default());
388        self.config = Some(config);
389        self
390    }
391
392    pub fn with_verified_application_subnet(mut self) -> Self {
394        let mut config = self.config.unwrap_or_default();
395        config.verified_application.push(SubnetSpec::default());
396        self.config = Some(config);
397        self
398    }
399
400    pub fn with_benchmarking_application_subnet(mut self) -> Self {
402        let mut config = self.config.unwrap_or_default();
403        config
404            .application
405            .push(SubnetSpec::default().with_benchmarking_instruction_config());
406        self.config = Some(config);
407        self
408    }
409
410    pub fn with_benchmarking_system_subnet(mut self) -> Self {
412        let mut config = self.config.unwrap_or_default();
413        config
414            .system
415            .push(SubnetSpec::default().with_benchmarking_instruction_config());
416        self.config = Some(config);
417        self
418    }
419
420    pub fn with_icp_features(mut self, icp_features: IcpFeatures) -> Self {
423        self.icp_features = icp_features;
424        self
425    }
426
427    pub fn with_initial_timestamp(mut self, initial_timestamp_nanos: u64) -> Self {
431        self.initial_time = Some(InitialTime::Timestamp(RawTime {
432            nanos_since_epoch: initial_timestamp_nanos,
433        }));
434        self
435    }
436
437    pub fn with_auto_progress(mut self) -> Self {
441        let config = AutoProgressConfig {
442            artificial_delay_ms: None,
443        };
444        self.initial_time = Some(InitialTime::AutoProgress(config));
445        self
446    }
447
448    pub fn with_http_gateway(mut self, http_gateway_config: InstanceHttpGatewayConfig) -> Self {
449        self.http_gateway_config = Some(http_gateway_config);
450        self
451    }
452}
453
454#[derive(Copy, Clone, PartialEq, PartialOrd)]
457pub struct Time(Duration);
458
459impl Time {
460    pub fn as_nanos_since_unix_epoch(&self) -> u64 {
462        self.0.as_nanos().try_into().unwrap()
463    }
464
465    pub const fn from_nanos_since_unix_epoch(nanos: u64) -> Self {
466        Time(Duration::from_nanos(nanos))
467    }
468}
469
470impl std::fmt::Debug for Time {
471    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472        let nanos_since_unix_epoch = self.as_nanos_since_unix_epoch();
473        write!(f, "{}", nanos_since_unix_epoch)
474    }
475}
476
477impl std::ops::Add<Duration> for Time {
478    type Output = Time;
479    fn add(self, dur: Duration) -> Time {
480        Time(self.0 + dur)
481    }
482}
483
484impl From<SystemTime> for Time {
485    fn from(time: SystemTime) -> Self {
486        Self::from_nanos_since_unix_epoch(
487            time.duration_since(UNIX_EPOCH)
488                .unwrap()
489                .as_nanos()
490                .try_into()
491                .unwrap(),
492        )
493    }
494}
495
496impl TryFrom<Time> for SystemTime {
497    type Error = String;
498
499    fn try_from(time: Time) -> Result<SystemTime, String> {
500        let nanos = time.as_nanos_since_unix_epoch();
501        let system_time = UNIX_EPOCH + Duration::from_nanos(nanos);
502        let roundtrip: Time = system_time.into();
503        if roundtrip.as_nanos_since_unix_epoch() == nanos {
504            Ok(system_time)
505        } else {
506            Err(format!("Converting UNIX timestamp {} in nanoseconds to SystemTime failed due to losing precision", nanos))
507        }
508    }
509}
510
511pub struct PocketIc {
513    pocket_ic: PocketIcAsync,
514    runtime: Arc<tokio::runtime::Runtime>,
515    thread: Option<JoinHandle<()>>,
516}
517
518impl PocketIc {
519    pub fn new() -> Self {
522        PocketIcBuilder::new().with_application_subnet().build()
523    }
524
525    pub fn new_from_existing_instance(
530        server_url: Url,
531        instance_id: InstanceId,
532        max_request_time_ms: Option<u64>,
533    ) -> Self {
534        let (tx, rx) = channel();
535        let thread = thread::spawn(move || {
536            let rt = tokio::runtime::Builder::new_current_thread()
537                .enable_all()
538                .build()
539                .unwrap();
540            tx.send(rt).unwrap();
541        });
542        let runtime = rx.recv().unwrap();
543
544        let pocket_ic =
545            PocketIcAsync::new_from_existing_instance(server_url, instance_id, max_request_time_ms);
546
547        Self {
548            pocket_ic,
549            runtime: Arc::new(runtime),
550            thread: Some(thread),
551        }
552    }
553
554    pub(crate) fn from_components(
555        subnet_config_set: impl Into<ExtendedSubnetConfigSet>,
556        server_url: Option<Url>,
557        server_binary: Option<PathBuf>,
558        max_request_time_ms: Option<u64>,
559        read_only_state_dir: Option<PathBuf>,
560        state_dir: Option<PocketIcState>,
561        icp_config: IcpConfig,
562        log_level: Option<Level>,
563        bitcoind_addr: Option<Vec<SocketAddr>>,
564        icp_features: IcpFeatures,
565        initial_time: Option<InitialTime>,
566        http_gateway_config: Option<InstanceHttpGatewayConfig>,
567    ) -> Self {
568        let (tx, rx) = channel();
569        let thread = thread::spawn(move || {
570            let rt = tokio::runtime::Builder::new_current_thread()
571                .enable_all()
572                .build()
573                .unwrap();
574            tx.send(rt).unwrap();
575        });
576        let runtime = rx.recv().unwrap();
577
578        let pocket_ic = runtime.block_on(async {
579            PocketIcAsync::from_components(
580                subnet_config_set,
581                server_url,
582                server_binary,
583                max_request_time_ms,
584                read_only_state_dir,
585                state_dir,
586                icp_config,
587                log_level,
588                bitcoind_addr,
589                icp_features,
590                initial_time,
591                http_gateway_config,
592            )
593            .await
594        });
595
596        Self {
597            pocket_ic,
598            runtime: Arc::new(runtime),
599            thread: Some(thread),
600        }
601    }
602
603    pub fn drop_and_take_state(mut self) -> Option<PocketIcState> {
604        self.pocket_ic.take_state_internal()
605    }
606
607    pub fn get_server_url(&self) -> Url {
609        self.pocket_ic.get_server_url()
610    }
611
612    pub fn instance_id(&self) -> InstanceId {
614        self.pocket_ic.instance_id
615    }
616
617    pub fn topology(&self) -> Topology {
619        let runtime = self.runtime.clone();
620        runtime.block_on(async { self.pocket_ic.topology().await })
621    }
622
623    #[instrument(ret(Display), skip(self, blob), fields(instance_id=self.pocket_ic.instance_id, blob_len = %blob.len(), compression = ?compression))]
625    pub fn upload_blob(&self, blob: Vec<u8>, compression: BlobCompression) -> BlobId {
626        let runtime = self.runtime.clone();
627        runtime.block_on(async { self.pocket_ic.upload_blob(blob, compression).await })
628    }
629
630    #[instrument(skip(self, data), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), data_len = %data.len(), compression = ?compression))]
633    pub fn set_stable_memory(
634        &self,
635        canister_id: CanisterId,
636        data: Vec<u8>,
637        compression: BlobCompression,
638    ) {
639        let runtime = self.runtime.clone();
640        runtime.block_on(async {
641            self.pocket_ic
642                .set_stable_memory(canister_id, data, compression)
643                .await
644        })
645    }
646
647    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
649    pub fn get_stable_memory(&self, canister_id: CanisterId) -> Vec<u8> {
650        let runtime = self.runtime.clone();
651        runtime.block_on(async { self.pocket_ic.get_stable_memory(canister_id).await })
652    }
653
654    #[instrument(ret)]
656    pub fn list_instances() -> Vec<String> {
657        let runtime = tokio::runtime::Builder::new_current_thread()
658            .build()
659            .unwrap();
660        let url = runtime.block_on(async {
661            let (_, server_url) = start_server(StartServerParams {
662                reuse: true,
663                ..Default::default()
664            })
665            .await;
666            server_url.join("instances").unwrap()
667        });
668        let instances: Vec<String> = reqwest::blocking::Client::new()
669            .get(url)
670            .send()
671            .expect("Failed to get result")
672            .json()
673            .expect("Failed to get json");
674        instances
675    }
676
677    #[instrument(skip_all, fields(instance_id=self.pocket_ic.instance_id))]
679    pub fn verify_canister_signature(
680        &self,
681        msg: Vec<u8>,
682        sig: Vec<u8>,
683        pubkey: Vec<u8>,
684        root_pubkey: Vec<u8>,
685    ) -> Result<(), String> {
686        let runtime = self.runtime.clone();
687        runtime.block_on(async {
688            self.pocket_ic
689                .verify_canister_signature(msg, sig, pubkey, root_pubkey)
690                .await
691        })
692    }
693
694    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
699    pub fn tick(&self) {
700        let runtime = self.runtime.clone();
701        runtime.block_on(async { self.pocket_ic.tick().await })
702    }
703
704    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
707    pub fn tick_with_configs(&self, configs: crate::common::rest::TickConfigs) {
708        let runtime = self.runtime.clone();
709        runtime.block_on(async { self.pocket_ic.tick_with_configs(configs).await })
710    }
711
712    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
718    pub fn auto_progress(&self) -> Url {
719        let runtime = self.runtime.clone();
720        runtime.block_on(async { self.pocket_ic.auto_progress().await })
721    }
722
723    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
725    pub fn auto_progress_enabled(&self) -> bool {
726        let runtime = self.runtime.clone();
727        runtime.block_on(async { self.pocket_ic.auto_progress_enabled().await })
728    }
729
730    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
732    pub fn stop_progress(&self) {
733        let runtime = self.runtime.clone();
734        runtime.block_on(async { self.pocket_ic.stop_progress().await })
735    }
736
737    pub fn url(&self) -> Option<Url> {
741        self.pocket_ic.url()
742    }
743
744    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
753    pub fn make_live(&mut self, listen_at: Option<u16>) -> Url {
754        let runtime = self.runtime.clone();
755        runtime.block_on(async { self.pocket_ic.make_live(listen_at).await })
756    }
757
758    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
769    pub async fn make_live_with_params(
770        &mut self,
771        ip_addr: Option<IpAddr>,
772        listen_at: Option<u16>,
773        domains: Option<Vec<String>>,
774        https_config: Option<HttpsConfig>,
775    ) -> Url {
776        let runtime = self.runtime.clone();
777        runtime.block_on(async {
778            self.pocket_ic
779                .make_live_with_params(ip_addr, listen_at, domains, https_config)
780                .await
781        })
782    }
783
784    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
787    pub fn stop_live(&mut self) {
788        let runtime = self.runtime.clone();
789        runtime.block_on(async { self.pocket_ic.stop_live().await })
790    }
791
792    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id))]
794    pub fn root_key(&self) -> Option<Vec<u8>> {
795        let runtime = self.runtime.clone();
796        runtime.block_on(async { self.pocket_ic.root_key().await })
797    }
798
799    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
801    pub fn get_time(&self) -> Time {
802        let runtime = self.runtime.clone();
803        runtime.block_on(async { self.pocket_ic.get_time().await })
804    }
805
806    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
808    pub fn set_time(&self, time: Time) {
809        let runtime = self.runtime.clone();
810        runtime.block_on(async { self.pocket_ic.set_time(time).await })
811    }
812
813    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, time = ?time))]
815    pub fn set_certified_time(&self, time: Time) {
816        let runtime = self.runtime.clone();
817        runtime.block_on(async { self.pocket_ic.set_certified_time(time).await })
818    }
819
820    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, duration = ?duration))]
822    pub fn advance_time(&self, duration: Duration) {
823        let runtime = self.runtime.clone();
824        runtime.block_on(async { self.pocket_ic.advance_time(duration).await })
825    }
826
827    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
830    pub fn get_controllers(&self, canister_id: CanisterId) -> Vec<Principal> {
831        let runtime = self.runtime.clone();
832        runtime.block_on(async { self.pocket_ic.get_controllers(canister_id).await })
833    }
834
835    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
837    pub fn cycle_balance(&self, canister_id: CanisterId) -> u128 {
838        let runtime = self.runtime.clone();
839        runtime.block_on(async { self.pocket_ic.cycle_balance(canister_id).await })
840    }
841
842    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), amount = %amount))]
844    pub fn add_cycles(&self, canister_id: CanisterId, amount: u128) -> u128 {
845        let runtime = self.runtime.clone();
846        runtime.block_on(async { self.pocket_ic.add_cycles(canister_id, amount).await })
847    }
848
849    pub fn submit_call(
851        &self,
852        canister_id: CanisterId,
853        sender: Principal,
854        method: &str,
855        payload: Vec<u8>,
856    ) -> Result<RawMessageId, RejectResponse> {
857        let runtime = self.runtime.clone();
858        runtime.block_on(async {
859            self.pocket_ic
860                .submit_call(canister_id, sender, method, payload)
861                .await
862        })
863    }
864
865    pub fn submit_call_with_effective_principal(
867        &self,
868        canister_id: CanisterId,
869        effective_principal: RawEffectivePrincipal,
870        sender: Principal,
871        method: &str,
872        payload: Vec<u8>,
873    ) -> Result<RawMessageId, RejectResponse> {
874        let runtime = self.runtime.clone();
875        runtime.block_on(async {
876            self.pocket_ic
877                .submit_call_with_effective_principal(
878                    canister_id,
879                    effective_principal,
880                    sender,
881                    method,
882                    payload,
883                )
884                .await
885        })
886    }
887
888    pub fn await_call(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
890        let runtime = self.runtime.clone();
891        runtime.block_on(async { self.pocket_ic.await_call(message_id).await })
892    }
893
894    pub fn ingress_status(
898        &self,
899        message_id: RawMessageId,
900    ) -> Option<Result<Vec<u8>, RejectResponse>> {
901        let runtime = self.runtime.clone();
902        runtime.block_on(async { self.pocket_ic.ingress_status(message_id).await })
903    }
904
905    pub fn ingress_status_as(
910        &self,
911        message_id: RawMessageId,
912        caller: Principal,
913    ) -> IngressStatusResult {
914        let runtime = self.runtime.clone();
915        runtime.block_on(async { self.pocket_ic.ingress_status_as(message_id, caller).await })
916    }
917
918    pub fn await_call_no_ticks(&self, message_id: RawMessageId) -> Result<Vec<u8>, RejectResponse> {
922        let runtime = self.runtime.clone();
923        runtime.block_on(async { self.pocket_ic.await_call_no_ticks(message_id).await })
924    }
925
926    #[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()))]
928    pub fn update_call(
929        &self,
930        canister_id: CanisterId,
931        sender: Principal,
932        method: &str,
933        payload: Vec<u8>,
934    ) -> Result<Vec<u8>, RejectResponse> {
935        let runtime = self.runtime.clone();
936        runtime.block_on(async {
937            self.pocket_ic
938                .update_call(canister_id, sender, method, payload)
939                .await
940        })
941    }
942
943    #[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()))]
945    pub fn query_call(
946        &self,
947        canister_id: CanisterId,
948        sender: Principal,
949        method: &str,
950        payload: Vec<u8>,
951    ) -> Result<Vec<u8>, RejectResponse> {
952        let runtime = self.runtime.clone();
953        runtime.block_on(async {
954            self.pocket_ic
955                .query_call(canister_id, sender, method, payload)
956                .await
957        })
958    }
959
960    pub fn fetch_canister_logs(
962        &self,
963        canister_id: CanisterId,
964        sender: Principal,
965    ) -> Result<Vec<CanisterLogRecord>, RejectResponse> {
966        let runtime = self.runtime.clone();
967        runtime.block_on(async {
968            self.pocket_ic
969                .fetch_canister_logs(canister_id, sender)
970                .await
971        })
972    }
973
974    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
976    pub fn canister_status(
977        &self,
978        canister_id: CanisterId,
979        sender: Option<Principal>,
980    ) -> Result<CanisterStatusResult, RejectResponse> {
981        let runtime = self.runtime.clone();
982        runtime.block_on(async { self.pocket_ic.canister_status(canister_id, sender).await })
983    }
984
985    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id))]
987    pub fn create_canister(&self) -> CanisterId {
988        let runtime = self.runtime.clone();
989        runtime.block_on(async { self.pocket_ic.create_canister().await })
990    }
991
992    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, settings = ?settings, sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
994    pub fn create_canister_with_settings(
995        &self,
996        sender: Option<Principal>,
997        settings: Option<CanisterSettings>,
998    ) -> CanisterId {
999        let runtime = self.runtime.clone();
1000        runtime.block_on(async {
1001            self.pocket_ic
1002                .create_canister_with_settings(sender, settings)
1003                .await
1004        })
1005    }
1006
1007    #[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()))]
1015    pub fn create_canister_with_id(
1016        &self,
1017        sender: Option<Principal>,
1018        settings: Option<CanisterSettings>,
1019        canister_id: CanisterId,
1020    ) -> Result<CanisterId, String> {
1021        let runtime = self.runtime.clone();
1022        runtime.block_on(async {
1023            self.pocket_ic
1024                .create_canister_with_id(sender, settings, canister_id)
1025                .await
1026        })
1027    }
1028
1029    #[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()))]
1031    pub fn create_canister_on_subnet(
1032        &self,
1033        sender: Option<Principal>,
1034        settings: Option<CanisterSettings>,
1035        subnet_id: SubnetId,
1036    ) -> CanisterId {
1037        let runtime = self.runtime.clone();
1038        runtime.block_on(async {
1039            self.pocket_ic
1040                .create_canister_on_subnet(sender, settings, subnet_id)
1041                .await
1042        })
1043    }
1044
1045    #[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()))]
1048    pub fn upload_chunk(
1049        &self,
1050        canister_id: CanisterId,
1051        sender: Option<Principal>,
1052        chunk: Vec<u8>,
1053    ) -> Result<Vec<u8>, RejectResponse> {
1054        let runtime = self.runtime.clone();
1055        runtime.block_on(async {
1056            self.pocket_ic
1057                .upload_chunk(canister_id, sender, chunk)
1058                .await
1059        })
1060    }
1061
1062    #[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()))]
1064    pub fn stored_chunks(
1065        &self,
1066        canister_id: CanisterId,
1067        sender: Option<Principal>,
1068    ) -> Result<Vec<Vec<u8>>, RejectResponse> {
1069        let runtime = self.runtime.clone();
1070        runtime.block_on(async { self.pocket_ic.stored_chunks(canister_id, sender).await })
1071    }
1072
1073    #[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()))]
1075    pub fn clear_chunk_store(
1076        &self,
1077        canister_id: CanisterId,
1078        sender: Option<Principal>,
1079    ) -> Result<(), RejectResponse> {
1080        let runtime = self.runtime.clone();
1081        runtime.block_on(async { self.pocket_ic.clear_chunk_store(canister_id, sender).await })
1082    }
1083
1084    #[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()))]
1086    pub fn install_chunked_canister(
1087        &self,
1088        canister_id: CanisterId,
1089        sender: Option<Principal>,
1090        mode: CanisterInstallMode,
1091        store_canister_id: CanisterId,
1092        chunk_hashes_list: Vec<Vec<u8>>,
1093        wasm_module_hash: Vec<u8>,
1094        arg: Vec<u8>,
1095    ) -> Result<(), RejectResponse> {
1096        let runtime = self.runtime.clone();
1097        runtime.block_on(async {
1098            self.pocket_ic
1099                .install_chunked_canister(
1100                    canister_id,
1101                    sender,
1102                    mode,
1103                    store_canister_id,
1104                    chunk_hashes_list,
1105                    wasm_module_hash,
1106                    arg,
1107                )
1108                .await
1109        })
1110    }
1111
1112    #[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()))]
1114    pub fn install_canister(
1115        &self,
1116        canister_id: CanisterId,
1117        wasm_module: Vec<u8>,
1118        arg: Vec<u8>,
1119        sender: Option<Principal>,
1120    ) {
1121        let runtime = self.runtime.clone();
1122        runtime.block_on(async {
1123            self.pocket_ic
1124                .install_canister(canister_id, wasm_module, arg, sender)
1125                .await
1126        })
1127    }
1128
1129    #[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()))]
1131    pub fn upgrade_canister(
1132        &self,
1133        canister_id: CanisterId,
1134        wasm_module: Vec<u8>,
1135        arg: Vec<u8>,
1136        sender: Option<Principal>,
1137    ) -> Result<(), RejectResponse> {
1138        let runtime = self.runtime.clone();
1139        runtime.block_on(async {
1140            self.pocket_ic
1141                .upgrade_canister(canister_id, wasm_module, arg, sender)
1142                .await
1143        })
1144    }
1145
1146    #[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()))]
1148    pub fn upgrade_eop_canister(
1149        &self,
1150        canister_id: CanisterId,
1151        wasm_module: Vec<u8>,
1152        arg: Vec<u8>,
1153        sender: Option<Principal>,
1154    ) -> Result<(), RejectResponse> {
1155        let runtime = self.runtime.clone();
1156        runtime.block_on(async {
1157            self.pocket_ic
1158                .upgrade_eop_canister(canister_id, wasm_module, arg, sender)
1159                .await
1160        })
1161    }
1162
1163    #[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()))]
1165    pub fn reinstall_canister(
1166        &self,
1167        canister_id: CanisterId,
1168        wasm_module: Vec<u8>,
1169        arg: Vec<u8>,
1170        sender: Option<Principal>,
1171    ) -> Result<(), RejectResponse> {
1172        let runtime = self.runtime.clone();
1173        runtime.block_on(async {
1174            self.pocket_ic
1175                .reinstall_canister(canister_id, wasm_module, arg, sender)
1176                .await
1177        })
1178    }
1179
1180    #[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()))]
1182    pub fn uninstall_canister(
1183        &self,
1184        canister_id: CanisterId,
1185        sender: Option<Principal>,
1186    ) -> Result<(), RejectResponse> {
1187        let runtime = self.runtime.clone();
1188        runtime.block_on(async { self.pocket_ic.uninstall_canister(canister_id, sender).await })
1189    }
1190
1191    #[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()))]
1193    pub fn take_canister_snapshot(
1194        &self,
1195        canister_id: CanisterId,
1196        sender: Option<Principal>,
1197        replace_snapshot: Option<Vec<u8>>,
1198    ) -> Result<Snapshot, RejectResponse> {
1199        let runtime = self.runtime.clone();
1200        runtime.block_on(async {
1201            self.pocket_ic
1202                .take_canister_snapshot(canister_id, sender, replace_snapshot)
1203                .await
1204        })
1205    }
1206
1207    #[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()))]
1209    pub fn load_canister_snapshot(
1210        &self,
1211        canister_id: CanisterId,
1212        sender: Option<Principal>,
1213        snapshot_id: Vec<u8>,
1214    ) -> Result<(), RejectResponse> {
1215        let runtime = self.runtime.clone();
1216        runtime.block_on(async {
1217            self.pocket_ic
1218                .load_canister_snapshot(canister_id, sender, snapshot_id)
1219                .await
1220        })
1221    }
1222
1223    #[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()))]
1225    pub fn list_canister_snapshots(
1226        &self,
1227        canister_id: CanisterId,
1228        sender: Option<Principal>,
1229    ) -> Result<Vec<Snapshot>, RejectResponse> {
1230        let runtime = self.runtime.clone();
1231        runtime.block_on(async {
1232            self.pocket_ic
1233                .list_canister_snapshots(canister_id, sender)
1234                .await
1235        })
1236    }
1237
1238    #[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()))]
1240    pub fn delete_canister_snapshot(
1241        &self,
1242        canister_id: CanisterId,
1243        sender: Option<Principal>,
1244        snapshot_id: Vec<u8>,
1245    ) -> Result<(), RejectResponse> {
1246        let runtime = self.runtime.clone();
1247        runtime.block_on(async {
1248            self.pocket_ic
1249                .delete_canister_snapshot(canister_id, sender, snapshot_id)
1250                .await
1251        })
1252    }
1253
1254    #[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()))]
1256    pub fn update_canister_settings(
1257        &self,
1258        canister_id: CanisterId,
1259        sender: Option<Principal>,
1260        settings: CanisterSettings,
1261    ) -> Result<(), RejectResponse> {
1262        let runtime = self.runtime.clone();
1263        runtime.block_on(async {
1264            self.pocket_ic
1265                .update_canister_settings(canister_id, sender, settings)
1266                .await
1267        })
1268    }
1269
1270    #[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()))]
1272    pub fn set_controllers(
1273        &self,
1274        canister_id: CanisterId,
1275        sender: Option<Principal>,
1276        new_controllers: Vec<Principal>,
1277    ) -> Result<(), RejectResponse> {
1278        let runtime = self.runtime.clone();
1279        runtime.block_on(async {
1280            self.pocket_ic
1281                .set_controllers(canister_id, sender, new_controllers)
1282                .await
1283        })
1284    }
1285
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()))]
1288    pub fn start_canister(
1289        &self,
1290        canister_id: CanisterId,
1291        sender: Option<Principal>,
1292    ) -> Result<(), RejectResponse> {
1293        let runtime = self.runtime.clone();
1294        runtime.block_on(async { self.pocket_ic.start_canister(canister_id, sender).await })
1295    }
1296
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()))]
1299    pub fn stop_canister(
1300        &self,
1301        canister_id: CanisterId,
1302        sender: Option<Principal>,
1303    ) -> Result<(), RejectResponse> {
1304        let runtime = self.runtime.clone();
1305        runtime.block_on(async { self.pocket_ic.stop_canister(canister_id, sender).await })
1306    }
1307
1308    #[instrument(skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string(), sender = %sender.unwrap_or(Principal::anonymous()).to_string()))]
1310    pub fn delete_canister(
1311        &self,
1312        canister_id: CanisterId,
1313        sender: Option<Principal>,
1314    ) -> Result<(), RejectResponse> {
1315        let runtime = self.runtime.clone();
1316        runtime.block_on(async { self.pocket_ic.delete_canister(canister_id, sender).await })
1317    }
1318
1319    #[instrument(ret(Display), skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1321    pub fn canister_exists(&self, canister_id: CanisterId) -> bool {
1322        let runtime = self.runtime.clone();
1323        runtime.block_on(async { self.pocket_ic.canister_exists(canister_id).await })
1324    }
1325
1326    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, canister_id = %canister_id.to_string()))]
1328    pub fn get_subnet(&self, canister_id: CanisterId) -> Option<SubnetId> {
1329        let runtime = self.runtime.clone();
1330        runtime.block_on(async { self.pocket_ic.get_subnet(canister_id).await })
1331    }
1332
1333    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id, subnet_id = %subnet_id.to_string()))]
1335    pub fn get_subnet_metrics(&self, subnet_id: Principal) -> Option<SubnetMetrics> {
1336        let runtime = self.runtime.clone();
1337        runtime.block_on(async { self.pocket_ic.get_subnet_metrics(subnet_id).await })
1338    }
1339
1340    pub fn update_call_with_effective_principal(
1341        &self,
1342        canister_id: CanisterId,
1343        effective_principal: RawEffectivePrincipal,
1344        sender: Principal,
1345        method: &str,
1346        payload: Vec<u8>,
1347    ) -> Result<Vec<u8>, RejectResponse> {
1348        let runtime = self.runtime.clone();
1349        runtime.block_on(async {
1350            self.pocket_ic
1351                .update_call_with_effective_principal(
1352                    canister_id,
1353                    effective_principal,
1354                    sender,
1355                    method,
1356                    payload,
1357                )
1358                .await
1359        })
1360    }
1361
1362    #[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()))]
1366    pub fn query_call_with_effective_principal(
1367        &self,
1368        canister_id: CanisterId,
1369        effective_principal: RawEffectivePrincipal,
1370        sender: Principal,
1371        method: &str,
1372        payload: Vec<u8>,
1373    ) -> Result<Vec<u8>, RejectResponse> {
1374        let runtime = self.runtime.clone();
1375        runtime.block_on(async {
1376            self.pocket_ic
1377                .query_call_with_effective_principal(
1378                    canister_id,
1379                    effective_principal,
1380                    sender,
1381                    method,
1382                    payload,
1383                )
1384                .await
1385        })
1386    }
1387
1388    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1400    pub fn get_canister_http(&self) -> Vec<CanisterHttpRequest> {
1401        let runtime = self.runtime.clone();
1402        runtime.block_on(async { self.pocket_ic.get_canister_http().await })
1403    }
1404
1405    #[instrument(ret, skip(self), fields(instance_id=self.pocket_ic.instance_id))]
1407    pub fn mock_canister_http_response(
1408        &self,
1409        mock_canister_http_response: MockCanisterHttpResponse,
1410    ) {
1411        let runtime = self.runtime.clone();
1412        runtime.block_on(async {
1413            self.pocket_ic
1414                .mock_canister_http_response(mock_canister_http_response)
1415                .await
1416        })
1417    }
1418}
1419
1420impl Default for PocketIc {
1421    fn default() -> Self {
1422        Self::new()
1423    }
1424}
1425
1426impl Drop for PocketIc {
1427    fn drop(&mut self) {
1428        self.runtime.block_on(async {
1429            self.pocket_ic.do_drop().await;
1430        });
1431        if let Some(thread) = self.thread.take() {
1432            thread.join().unwrap();
1433        }
1434    }
1435}
1436
1437pub fn call_candid_as<Input, Output>(
1441    env: &PocketIc,
1442    canister_id: CanisterId,
1443    effective_principal: RawEffectivePrincipal,
1444    sender: Principal,
1445    method: &str,
1446    input: Input,
1447) -> Result<Output, RejectResponse>
1448where
1449    Input: ArgumentEncoder,
1450    Output: for<'a> ArgumentDecoder<'a>,
1451{
1452    with_candid(input, |payload| {
1453        env.update_call_with_effective_principal(
1454            canister_id,
1455            effective_principal,
1456            sender,
1457            method,
1458            payload,
1459        )
1460    })
1461}
1462
1463pub fn call_candid<Input, Output>(
1466    env: &PocketIc,
1467    canister_id: CanisterId,
1468    effective_principal: RawEffectivePrincipal,
1469    method: &str,
1470    input: Input,
1471) -> Result<Output, RejectResponse>
1472where
1473    Input: ArgumentEncoder,
1474    Output: for<'a> ArgumentDecoder<'a>,
1475{
1476    call_candid_as(
1477        env,
1478        canister_id,
1479        effective_principal,
1480        Principal::anonymous(),
1481        method,
1482        input,
1483    )
1484}
1485
1486pub fn query_candid<Input, Output>(
1488    env: &PocketIc,
1489    canister_id: CanisterId,
1490    method: &str,
1491    input: Input,
1492) -> Result<Output, RejectResponse>
1493where
1494    Input: ArgumentEncoder,
1495    Output: for<'a> ArgumentDecoder<'a>,
1496{
1497    query_candid_as(env, canister_id, Principal::anonymous(), method, input)
1498}
1499
1500pub fn query_candid_as<Input, Output>(
1503    env: &PocketIc,
1504    canister_id: CanisterId,
1505    sender: Principal,
1506    method: &str,
1507    input: Input,
1508) -> Result<Output, RejectResponse>
1509where
1510    Input: ArgumentEncoder,
1511    Output: for<'a> ArgumentDecoder<'a>,
1512{
1513    with_candid(input, |bytes| {
1514        env.query_call(canister_id, sender, method, bytes)
1515    })
1516}
1517
1518pub fn update_candid<Input, Output>(
1520    env: &PocketIc,
1521    canister_id: CanisterId,
1522    method: &str,
1523    input: Input,
1524) -> Result<Output, RejectResponse>
1525where
1526    Input: ArgumentEncoder,
1527    Output: for<'a> ArgumentDecoder<'a>,
1528{
1529    update_candid_as(env, canister_id, Principal::anonymous(), method, input)
1530}
1531
1532pub fn update_candid_as<Input, Output>(
1535    env: &PocketIc,
1536    canister_id: CanisterId,
1537    sender: Principal,
1538    method: &str,
1539    input: Input,
1540) -> Result<Output, RejectResponse>
1541where
1542    Input: ArgumentEncoder,
1543    Output: for<'a> ArgumentDecoder<'a>,
1544{
1545    with_candid(input, |bytes| {
1546        env.update_call(canister_id, sender, method, bytes)
1547    })
1548}
1549
1550pub fn with_candid<Input, Output>(
1553    input: Input,
1554    f: impl FnOnce(Vec<u8>) -> Result<Vec<u8>, RejectResponse>,
1555) -> Result<Output, RejectResponse>
1556where
1557    Input: ArgumentEncoder,
1558    Output: for<'a> ArgumentDecoder<'a>,
1559{
1560    let in_bytes = encode_args(input).expect("failed to encode args");
1561    f(in_bytes).map(|out_bytes| {
1562        decode_args(&out_bytes).unwrap_or_else(|e| {
1563            panic!(
1564                "Failed to decode response as candid type {}:\nerror: {}\nbytes: {:?}\nutf8: {}",
1565                std::any::type_name::<Output>(),
1566                e,
1567                out_bytes,
1568                String::from_utf8_lossy(&out_bytes),
1569            )
1570        })
1571    })
1572}
1573
1574#[derive(Clone, Copy, Debug)]
1576pub enum TryFromError {
1577    ValueOutOfRange(u64),
1578}
1579
1580#[derive(
1587    PartialOrd,
1588    Ord,
1589    Clone,
1590    Copy,
1591    Debug,
1592    PartialEq,
1593    Eq,
1594    Hash,
1595    Serialize,
1596    Deserialize,
1597    JsonSchema,
1598    EnumIter,
1599)]
1600pub enum ErrorCode {
1601    SubnetOversubscribed = 101,
1603    MaxNumberOfCanistersReached = 102,
1604    CanisterQueueFull = 201,
1606    IngressMessageTimeout = 202,
1607    CanisterQueueNotEmpty = 203,
1608    IngressHistoryFull = 204,
1609    CanisterIdAlreadyExists = 205,
1610    StopCanisterRequestTimeout = 206,
1611    CanisterOutOfCycles = 207,
1612    CertifiedStateUnavailable = 208,
1613    CanisterInstallCodeRateLimited = 209,
1614    CanisterHeapDeltaRateLimited = 210,
1615    CanisterNotFound = 301,
1617    CanisterSnapshotNotFound = 305,
1618    InsufficientMemoryAllocation = 402,
1620    InsufficientCyclesForCreateCanister = 403,
1621    SubnetNotFound = 404,
1622    CanisterNotHostedBySubnet = 405,
1623    CanisterRejectedMessage = 406,
1624    UnknownManagementMessage = 407,
1625    InvalidManagementPayload = 408,
1626    CanisterSnapshotImmutable = 409,
1627    CanisterTrapped = 502,
1629    CanisterCalledTrap = 503,
1630    CanisterContractViolation = 504,
1631    CanisterInvalidWasm = 505,
1632    CanisterDidNotReply = 506,
1633    CanisterOutOfMemory = 507,
1634    CanisterStopped = 508,
1635    CanisterStopping = 509,
1636    CanisterNotStopped = 510,
1637    CanisterStoppingCancelled = 511,
1638    CanisterInvalidController = 512,
1639    CanisterFunctionNotFound = 513,
1640    CanisterNonEmpty = 514,
1641    QueryCallGraphLoopDetected = 517,
1642    InsufficientCyclesInCall = 520,
1643    CanisterWasmEngineError = 521,
1644    CanisterInstructionLimitExceeded = 522,
1645    CanisterMemoryAccessLimitExceeded = 524,
1646    QueryCallGraphTooDeep = 525,
1647    QueryCallGraphTotalInstructionLimitExceeded = 526,
1648    CompositeQueryCalledInReplicatedMode = 527,
1649    QueryTimeLimitExceeded = 528,
1650    QueryCallGraphInternal = 529,
1651    InsufficientCyclesInComputeAllocation = 530,
1652    InsufficientCyclesInMemoryAllocation = 531,
1653    InsufficientCyclesInMemoryGrow = 532,
1654    ReservedCyclesLimitExceededInMemoryAllocation = 533,
1655    ReservedCyclesLimitExceededInMemoryGrow = 534,
1656    InsufficientCyclesInMessageMemoryGrow = 535,
1657    CanisterMethodNotFound = 536,
1658    CanisterWasmModuleNotFound = 537,
1659    CanisterAlreadyInstalled = 538,
1660    CanisterWasmMemoryLimitExceeded = 539,
1661    ReservedCyclesLimitIsTooLow = 540,
1662    DeadlineExpired = 601,
1664    ResponseDropped = 602,
1665}
1666
1667impl TryFrom<u64> for ErrorCode {
1668    type Error = TryFromError;
1669    fn try_from(err: u64) -> Result<ErrorCode, Self::Error> {
1670        match err {
1671            101 => Ok(ErrorCode::SubnetOversubscribed),
1673            102 => Ok(ErrorCode::MaxNumberOfCanistersReached),
1674            201 => Ok(ErrorCode::CanisterQueueFull),
1676            202 => Ok(ErrorCode::IngressMessageTimeout),
1677            203 => Ok(ErrorCode::CanisterQueueNotEmpty),
1678            204 => Ok(ErrorCode::IngressHistoryFull),
1679            205 => Ok(ErrorCode::CanisterIdAlreadyExists),
1680            206 => Ok(ErrorCode::StopCanisterRequestTimeout),
1681            207 => Ok(ErrorCode::CanisterOutOfCycles),
1682            208 => Ok(ErrorCode::CertifiedStateUnavailable),
1683            209 => Ok(ErrorCode::CanisterInstallCodeRateLimited),
1684            210 => Ok(ErrorCode::CanisterHeapDeltaRateLimited),
1685            301 => Ok(ErrorCode::CanisterNotFound),
1687            305 => Ok(ErrorCode::CanisterSnapshotNotFound),
1688            402 => Ok(ErrorCode::InsufficientMemoryAllocation),
1690            403 => Ok(ErrorCode::InsufficientCyclesForCreateCanister),
1691            404 => Ok(ErrorCode::SubnetNotFound),
1692            405 => Ok(ErrorCode::CanisterNotHostedBySubnet),
1693            406 => Ok(ErrorCode::CanisterRejectedMessage),
1694            407 => Ok(ErrorCode::UnknownManagementMessage),
1695            408 => Ok(ErrorCode::InvalidManagementPayload),
1696            409 => Ok(ErrorCode::CanisterSnapshotImmutable),
1697            502 => Ok(ErrorCode::CanisterTrapped),
1699            503 => Ok(ErrorCode::CanisterCalledTrap),
1700            504 => Ok(ErrorCode::CanisterContractViolation),
1701            505 => Ok(ErrorCode::CanisterInvalidWasm),
1702            506 => Ok(ErrorCode::CanisterDidNotReply),
1703            507 => Ok(ErrorCode::CanisterOutOfMemory),
1704            508 => Ok(ErrorCode::CanisterStopped),
1705            509 => Ok(ErrorCode::CanisterStopping),
1706            510 => Ok(ErrorCode::CanisterNotStopped),
1707            511 => Ok(ErrorCode::CanisterStoppingCancelled),
1708            512 => Ok(ErrorCode::CanisterInvalidController),
1709            513 => Ok(ErrorCode::CanisterFunctionNotFound),
1710            514 => Ok(ErrorCode::CanisterNonEmpty),
1711            517 => Ok(ErrorCode::QueryCallGraphLoopDetected),
1712            520 => Ok(ErrorCode::InsufficientCyclesInCall),
1713            521 => Ok(ErrorCode::CanisterWasmEngineError),
1714            522 => Ok(ErrorCode::CanisterInstructionLimitExceeded),
1715            524 => Ok(ErrorCode::CanisterMemoryAccessLimitExceeded),
1716            525 => Ok(ErrorCode::QueryCallGraphTooDeep),
1717            526 => Ok(ErrorCode::QueryCallGraphTotalInstructionLimitExceeded),
1718            527 => Ok(ErrorCode::CompositeQueryCalledInReplicatedMode),
1719            528 => Ok(ErrorCode::QueryTimeLimitExceeded),
1720            529 => Ok(ErrorCode::QueryCallGraphInternal),
1721            530 => Ok(ErrorCode::InsufficientCyclesInComputeAllocation),
1722            531 => Ok(ErrorCode::InsufficientCyclesInMemoryAllocation),
1723            532 => Ok(ErrorCode::InsufficientCyclesInMemoryGrow),
1724            533 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryAllocation),
1725            534 => Ok(ErrorCode::ReservedCyclesLimitExceededInMemoryGrow),
1726            535 => Ok(ErrorCode::InsufficientCyclesInMessageMemoryGrow),
1727            536 => Ok(ErrorCode::CanisterMethodNotFound),
1728            537 => Ok(ErrorCode::CanisterWasmModuleNotFound),
1729            538 => Ok(ErrorCode::CanisterAlreadyInstalled),
1730            539 => Ok(ErrorCode::CanisterWasmMemoryLimitExceeded),
1731            540 => Ok(ErrorCode::ReservedCyclesLimitIsTooLow),
1732            601 => Ok(ErrorCode::DeadlineExpired),
1734            602 => Ok(ErrorCode::ResponseDropped),
1735            _ => Err(TryFromError::ValueOutOfRange(err)),
1736        }
1737    }
1738}
1739
1740impl std::fmt::Display for ErrorCode {
1741    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1742        write!(f, "IC{:04}", *self as i32)
1744    }
1745}
1746
1747#[derive(
1752    PartialOrd,
1753    Ord,
1754    Clone,
1755    Copy,
1756    Debug,
1757    PartialEq,
1758    Eq,
1759    Hash,
1760    Serialize,
1761    Deserialize,
1762    JsonSchema,
1763    EnumIter,
1764)]
1765pub enum RejectCode {
1766    SysFatal = 1,
1767    SysTransient = 2,
1768    DestinationInvalid = 3,
1769    CanisterReject = 4,
1770    CanisterError = 5,
1771    SysUnknown = 6,
1772}
1773
1774impl TryFrom<u64> for RejectCode {
1775    type Error = TryFromError;
1776    fn try_from(err: u64) -> Result<RejectCode, Self::Error> {
1777        match err {
1778            1 => Ok(RejectCode::SysFatal),
1779            2 => Ok(RejectCode::SysTransient),
1780            3 => Ok(RejectCode::DestinationInvalid),
1781            4 => Ok(RejectCode::CanisterReject),
1782            5 => Ok(RejectCode::CanisterError),
1783            6 => Ok(RejectCode::SysUnknown),
1784            _ => Err(TryFromError::ValueOutOfRange(err)),
1785        }
1786    }
1787}
1788
1789#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
1791pub struct RejectResponse {
1792    pub reject_code: RejectCode,
1793    pub reject_message: String,
1794    pub error_code: ErrorCode,
1795    pub certified: bool,
1796}
1797
1798impl std::fmt::Display for RejectResponse {
1799    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1800        write!(f, "PocketIC returned a rejection error: reject code {:?}, reject message {}, error code {:?}", self.reject_code, self.reject_message, self.error_code)
1802    }
1803}
1804
1805#[derive(Debug, Serialize, Deserialize)]
1811pub enum IngressStatusResult {
1812    NotAvailable,
1813    Forbidden(String),
1814    Success(Result<Vec<u8>, RejectResponse>),
1815}
1816
1817#[cfg(windows)]
1818fn wsl_path(path: &PathBuf, desc: &str) -> String {
1819    windows_to_wsl(
1820        path.as_os_str()
1821            .to_str()
1822            .unwrap_or_else(|| panic!("Could not convert {} path ({:?}) to String", desc, path)),
1823    )
1824    .unwrap_or_else(|e| {
1825        panic!(
1826            "Could not convert {} path ({:?}) to WSL path: {:?}",
1827            desc, path, e
1828        )
1829    })
1830}
1831
1832#[cfg(windows)]
1833fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
1834    let mut cmd = Command::new("wsl");
1835    cmd.arg(wsl_path(bin_path, "PocketIC binary"));
1836    cmd
1837}
1838
1839#[cfg(not(windows))]
1840fn pocket_ic_server_cmd(bin_path: &PathBuf) -> Command {
1841    Command::new(bin_path)
1842}
1843
1844fn check_pocketic_server_version(server_binary: &PathBuf) -> Result<(), String> {
1845    let mut cmd = pocket_ic_server_cmd(server_binary);
1846    cmd.arg("--version");
1847    let version = cmd.output().map_err(|e| e.to_string())?.stdout;
1848    let version_str = String::from_utf8(version)
1849        .map_err(|e| format!("Failed to parse PocketIC server version: {}.", e))?;
1850    let version_line = version_str.trim_end_matches('\n');
1851    let expected_version_line = format!("pocket-ic-server {}", EXPECTED_SERVER_VERSION);
1852    if version_line != expected_version_line {
1853        return Err(format!(
1854            "Incompatible PocketIC server version: got {}; expected {}.",
1855            version_line, expected_version_line
1856        ));
1857    }
1858    Ok(())
1859}
1860
1861async fn download_pocketic_server(
1862    server_url: String,
1863    mut out: std::fs::File,
1864) -> Result<(), String> {
1865    let binary = reqwest::get(server_url)
1866        .await
1867        .map_err(|e| format!("Failed to download PocketIC server: {}", e))?
1868        .bytes()
1869        .await
1870        .map_err(|e| format!("Failed to download PocketIC server: {}", e))?
1871        .to_vec();
1872    let mut gz = GzDecoder::new(&binary[..]);
1873    let _ = std::io::copy(&mut gz, &mut out)
1874        .map_err(|e| format!("Failed to write PocketIC server binary: {}", e));
1875    Ok(())
1876}
1877
1878#[derive(Default)]
1879pub struct StartServerParams {
1880    pub server_binary: Option<PathBuf>,
1881    pub reuse: bool,
1883}
1884
1885pub async fn start_server(params: StartServerParams) -> (Child, Url) {
1887    let default_bin_dir =
1888        std::env::temp_dir().join(format!("pocket-ic-server-{}", EXPECTED_SERVER_VERSION));
1889    let default_bin_path = default_bin_dir.join("pocket-ic");
1890    let mut bin_path: PathBuf = params.server_binary.unwrap_or_else(|| {
1891        std::env::var_os("POCKET_IC_BIN")
1892            .unwrap_or_else(|| default_bin_path.clone().into())
1893            .into()
1894    });
1895
1896    if let Err(e) = check_pocketic_server_version(&bin_path) {
1897        bin_path = default_bin_path.clone();
1898        std::fs::create_dir_all(&default_bin_dir)
1899            .expect("Failed to create PocketIC server directory");
1900        let mut options = OpenOptions::new();
1901        options.write(true).create_new(true);
1902        #[cfg(unix)]
1903        options.mode(0o777);
1904        if let Ok(out) = options.open(&default_bin_path) {
1905            #[cfg(target_os = "macos")]
1906            let os = "darwin";
1907            #[cfg(not(target_os = "macos"))]
1908            let os = "linux";
1909            #[cfg(target_arch = "aarch64")]
1910            let arch = "arm64";
1911            #[cfg(not(target_arch = "aarch64"))]
1912            let arch = "x86_64";
1913            let server_url = format!(
1914                "https://github.com/dfinity/pocketic/releases/download/{}/pocket-ic-{}-{}.gz",
1915                EXPECTED_SERVER_VERSION, arch, os
1916            );
1917            println!("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.", e, EXPECTED_SERVER_VERSION, server_url, default_bin_path.display(), EXPECTED_SERVER_VERSION);
1918            if let Err(e) = download_pocketic_server(server_url, out).await {
1919                let _ = std::fs::remove_file(default_bin_path);
1920                panic!("{}", e);
1921            }
1922        } else {
1923            let start = std::time::Instant::now();
1925            loop {
1926                if check_pocketic_server_version(&default_bin_path).is_ok() {
1927                    break;
1928                }
1929                if start.elapsed() > std::time::Duration::from_secs(60) {
1930                    let _ = std::fs::remove_file(&default_bin_path);
1931                    panic!("Timed out waiting for PocketIC server being available at the local path {}.", default_bin_path.display());
1932                }
1933                std::thread::sleep(std::time::Duration::from_millis(100));
1934            }
1935        }
1936    }
1937
1938    let port_file_path = if params.reuse {
1939        let test_driver_pid = std::process::id();
1942        std::env::temp_dir().join(format!("pocket_ic_{}.port", test_driver_pid))
1943    } else {
1944        NamedTempFile::new().unwrap().into_temp_path().to_path_buf()
1945    };
1946    let mut cmd = pocket_ic_server_cmd(&bin_path);
1947    cmd.arg("--port-file");
1948    #[cfg(windows)]
1949    cmd.arg(wsl_path(&port_file_path, "PocketIC port file"));
1950    #[cfg(not(windows))]
1951    cmd.arg(port_file_path.clone());
1952    if let Ok(mute_server) = std::env::var("POCKET_IC_MUTE_SERVER") {
1953        if !mute_server.is_empty() {
1954            cmd.stdout(std::process::Stdio::null());
1955            cmd.stderr(std::process::Stdio::null());
1956        }
1957    }
1958
1959    #[cfg(unix)]
1962    {
1963        use std::os::unix::process::CommandExt;
1964        cmd.process_group(0);
1965    }
1966
1967    #[allow(clippy::zombie_processes)]
1969    let child = cmd
1970        .spawn()
1971        .unwrap_or_else(|_| panic!("Failed to start PocketIC binary ({})", bin_path.display()));
1972
1973    loop {
1974        if let Ok(port_string) = std::fs::read_to_string(port_file_path.clone()) {
1975            if port_string.contains("\n") {
1976                let port: u16 = port_string
1977                    .trim_end()
1978                    .parse()
1979                    .expect("Failed to parse port to number");
1980                break (
1981                    child,
1982                    Url::parse(&format!("http://{}:{}/", LOCALHOST, port)).unwrap(),
1983                );
1984            }
1985        }
1986        std::thread::sleep(Duration::from_millis(20));
1987    }
1988}
1989
1990#[derive(Error, Debug)]
1991pub enum DefaultEffectiveCanisterIdError {
1992    ReqwestError(#[from] reqwest::Error),
1993    JsonError(#[from] serde_json::Error),
1994    Utf8Error(#[from] std::string::FromUtf8Error),
1995}
1996
1997impl std::fmt::Display for DefaultEffectiveCanisterIdError {
1998    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1999        match self {
2000            DefaultEffectiveCanisterIdError::ReqwestError(err) => {
2001                write!(f, "ReqwestError({})", err)
2002            }
2003            DefaultEffectiveCanisterIdError::JsonError(err) => write!(f, "JsonError({})", err),
2004            DefaultEffectiveCanisterIdError::Utf8Error(err) => write!(f, "Utf8Error({})", err),
2005        }
2006    }
2007}
2008
2009pub fn get_default_effective_canister_id(
2017    pocket_ic_url: String,
2018) -> Result<Principal, DefaultEffectiveCanisterIdError> {
2019    let runtime = Runtime::new().expect("Unable to create a runtime");
2020    runtime.block_on(crate::nonblocking::get_default_effective_canister_id(
2021        pocket_ic_url,
2022    ))
2023}
2024
2025pub fn copy_dir(
2026    src: impl AsRef<std::path::Path>,
2027    dst: impl AsRef<std::path::Path>,
2028) -> std::io::Result<()> {
2029    std::fs::create_dir_all(&dst)?;
2030    for entry in std::fs::read_dir(src)? {
2031        let entry = entry?;
2032        let ty = entry.file_type()?;
2033        if ty.is_dir() {
2034            copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
2035        } else {
2036            std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
2037        }
2038    }
2039    Ok(())
2040}
2041
2042#[cfg(test)]
2043mod test {
2044    use crate::{ErrorCode, RejectCode};
2045    use strum::IntoEnumIterator;
2046
2047    #[test]
2048    fn reject_code_round_trip() {
2049        for initial in RejectCode::iter() {
2050            let round_trip = RejectCode::try_from(initial as u64).unwrap();
2051
2052            assert_eq!(initial, round_trip);
2053        }
2054    }
2055
2056    #[test]
2057    fn error_code_round_trip() {
2058        for initial in ErrorCode::iter() {
2059            let round_trip = ErrorCode::try_from(initial as u64).unwrap();
2060
2061            assert_eq!(initial, round_trip);
2062        }
2063    }
2064
2065    #[test]
2066    fn reject_code_matches_ic_error_code() {
2067        assert_eq!(
2068            RejectCode::iter().len(),
2069            ic_error_types::RejectCode::iter().len()
2070        );
2071        for ic_reject_code in ic_error_types::RejectCode::iter() {
2072            let reject_code: RejectCode = (ic_reject_code as u64).try_into().unwrap();
2073            assert_eq!(
2074                format!("{:?}", reject_code),
2075                format!("{:?}", ic_reject_code)
2076            );
2077        }
2078    }
2079
2080    #[test]
2081    fn error_code_matches_ic_error_code() {
2082        assert_eq!(
2083            ErrorCode::iter().len(),
2084            ic_error_types::ErrorCode::iter().len()
2085        );
2086        for ic_error_code in ic_error_types::ErrorCode::iter() {
2087            let error_code: ErrorCode = (ic_error_code as u64).try_into().unwrap();
2088            assert_eq!(format!("{:?}", error_code), format!("{:?}", ic_error_code));
2089        }
2090    }
2091}