1use candid::{CandidType, Principal, decode_one, encode_args, encode_one, utils::ArgumentEncoder};
2use canic::{
3 Error,
4 cdk::types::TC,
5 dto::{
6 abi::v1::CanisterInitPayload,
7 env::EnvBootstrapArgs,
8 subnet::SubnetIdentity,
9 topology::{AppDirectoryArgs, SubnetDirectoryArgs},
10 },
11 ids::CanisterRole,
12};
13use derive_more::{Deref, DerefMut};
14use pocket_ic::{PocketIc, PocketIcBuilder};
15use serde::de::DeserializeOwned;
16use std::time::Duration;
17
18const INSTALL_CYCLES: u128 = 500 * TC;
19
20#[must_use]
29pub fn pic() -> Pic {
30 PicBuilder::new().with_application_subnet().build()
31}
32
33pub struct PicBuilder(PocketIcBuilder);
43
44#[allow(clippy::new_without_default)]
45impl PicBuilder {
46 #[must_use]
48 pub fn new() -> Self {
49 Self(PocketIcBuilder::new())
50 }
51
52 #[must_use]
54 pub fn with_application_subnet(mut self) -> Self {
55 self.0 = self.0.with_application_subnet();
56 self
57 }
58
59 #[must_use]
61 pub fn with_nns_subnet(mut self) -> Self {
62 self.0 = self.0.with_nns_subnet();
63 self
64 }
65
66 #[must_use]
68 pub fn build(self) -> Pic {
69 Pic(self.0.build())
70 }
71}
72
73#[derive(Deref, DerefMut)]
81pub struct Pic(PocketIc);
82
83impl Pic {
84 pub fn create_and_install_root_canister(&self, wasm: Vec<u8>) -> Result<Principal, Error> {
86 let init_bytes = install_root_args()?;
87
88 Ok(self.create_funded_and_install(wasm, init_bytes))
89 }
90
91 pub fn create_and_install_canister(
95 &self,
96 role: CanisterRole,
97 wasm: Vec<u8>,
98 ) -> Result<Principal, Error> {
99 let init_bytes = install_args(role)?;
100
101 Ok(self.create_funded_and_install(wasm, init_bytes))
102 }
103
104 pub fn create_and_install_canister_with_directories(
109 &self,
110 role: CanisterRole,
111 wasm: Vec<u8>,
112 app_directory: AppDirectoryArgs,
113 subnet_directory: SubnetDirectoryArgs,
114 ) -> Result<Principal, Error> {
115 let init_bytes = install_args_with_directories(role, app_directory, subnet_directory)?;
116
117 Ok(self.create_funded_and_install(wasm, init_bytes))
118 }
119
120 fn create_funded_and_install(&self, wasm: Vec<u8>, init_bytes: Vec<u8>) -> Principal {
121 let canister_id = self.create_canister();
122 self.add_cycles(canister_id, INSTALL_CYCLES);
123 self.0.install_canister(canister_id, wasm, init_bytes, None);
124
125 canister_id
126 }
127
128 pub fn update_call<T, A>(
130 &self,
131 canister_id: Principal,
132 method: &str,
133 args: A,
134 ) -> Result<T, Error>
135 where
136 T: CandidType + DeserializeOwned,
137 A: ArgumentEncoder,
138 {
139 let bytes: Vec<u8> = encode_args(args)
140 .map_err(|err| Error::internal(format!("encode_args failed: {err}")))?;
141 let result = self
142 .0
143 .update_call(canister_id, Principal::anonymous(), method, bytes)
144 .map_err(|err| {
145 Error::internal(format!(
146 "pocket_ic update_call failed (canister={canister_id}, method={method}): {err}"
147 ))
148 })?;
149
150 decode_one(&result).map_err(|err| Error::internal(format!("decode_one failed: {err}")))
151 }
152
153 pub fn update_call_as<T, A>(
155 &self,
156 canister_id: Principal,
157 caller: Principal,
158 method: &str,
159 args: A,
160 ) -> Result<T, Error>
161 where
162 T: CandidType + DeserializeOwned,
163 A: ArgumentEncoder,
164 {
165 let bytes: Vec<u8> = encode_args(args)
166 .map_err(|err| Error::internal(format!("encode_args failed: {err}")))?;
167 let result = self
168 .0
169 .update_call(canister_id, caller, method, bytes)
170 .map_err(|err| {
171 Error::internal(format!(
172 "pocket_ic update_call failed (canister={canister_id}, method={method}): {err}"
173 ))
174 })?;
175
176 decode_one(&result).map_err(|err| Error::internal(format!("decode_one failed: {err}")))
177 }
178
179 pub fn query_call<T, A>(
181 &self,
182 canister_id: Principal,
183 method: &str,
184 args: A,
185 ) -> Result<T, Error>
186 where
187 T: CandidType + DeserializeOwned,
188 A: ArgumentEncoder,
189 {
190 let bytes: Vec<u8> = encode_args(args)
191 .map_err(|err| Error::internal(format!("encode_args failed: {err}")))?;
192 let result = self
193 .0
194 .query_call(canister_id, Principal::anonymous(), method, bytes)
195 .map_err(|err| {
196 Error::internal(format!(
197 "pocket_ic query_call failed (canister={canister_id}, method={method}): {err}"
198 ))
199 })?;
200
201 decode_one(&result).map_err(|err| Error::internal(format!("decode_one failed: {err}")))
202 }
203
204 pub fn query_call_as<T, A>(
206 &self,
207 canister_id: Principal,
208 caller: Principal,
209 method: &str,
210 args: A,
211 ) -> Result<T, Error>
212 where
213 T: CandidType + DeserializeOwned,
214 A: ArgumentEncoder,
215 {
216 let bytes: Vec<u8> = encode_args(args)
217 .map_err(|err| Error::internal(format!("encode_args failed: {err}")))?;
218 let result = self
219 .0
220 .query_call(canister_id, caller, method, bytes)
221 .map_err(|err| {
222 Error::internal(format!(
223 "pocket_ic query_call failed (canister={canister_id}, method={method}): {err}"
224 ))
225 })?;
226
227 decode_one(&result).map_err(|err| Error::internal(format!("decode_one failed: {err}")))
228 }
229
230 pub fn tick_n(&self, times: usize) {
231 for _ in 0..times {
232 self.tick();
233 }
234 }
235
236 pub fn certify_time(&self) {
237 let now = self.0.get_time();
238 let next = now + Duration::from_secs(1);
239 self.0.set_time(next);
240 self.0.set_certified_time(next);
241 self.0.tick();
242 }
243}
244
245fn install_args(role: CanisterRole) -> Result<Vec<u8>, Error> {
259 if role.is_root() {
260 install_root_args()
261 } else {
262 let env = EnvBootstrapArgs {
265 prime_root_pid: None,
266 subnet_role: None,
267 subnet_pid: None,
268 root_pid: None,
269 canister_role: Some(role),
270 parent_pid: None,
271 };
272
273 let payload = CanisterInitPayload {
276 env,
277 app_directory: AppDirectoryArgs(Vec::new()),
278 subnet_directory: SubnetDirectoryArgs(Vec::new()),
279 };
280
281 encode_args::<(CanisterInitPayload, Option<Vec<u8>>)>((payload, None))
282 .map_err(|err| Error::internal(format!("encode_args failed: {err}")))
283 }
284}
285
286fn install_root_args() -> Result<Vec<u8>, Error> {
287 encode_one(SubnetIdentity::Manual)
288 .map_err(|err| Error::internal(format!("encode_one failed: {err}")))
289}
290
291fn install_args_with_directories(
292 role: CanisterRole,
293 app_directory: AppDirectoryArgs,
294 subnet_directory: SubnetDirectoryArgs,
295) -> Result<Vec<u8>, Error> {
296 if role.is_root() {
297 encode_one(SubnetIdentity::Manual)
300 .map_err(|err| Error::internal(format!("encode_one failed: {err}")))
301 } else {
302 let env = EnvBootstrapArgs {
304 prime_root_pid: None,
305 subnet_role: None,
306 subnet_pid: None,
307 root_pid: None,
308 canister_role: Some(role),
309 parent_pid: None,
310 };
311
312 let payload = CanisterInitPayload {
313 env,
314 app_directory,
315 subnet_directory,
316 };
317
318 encode_args::<(CanisterInitPayload, Option<Vec<u8>>)>((payload, None))
319 .map_err(|err| Error::internal(format!("encode_args failed: {err}")))
320 }
321}