ic_utils/
canister.rs

1use crate::call::{AsyncCaller, SyncCaller};
2use candid::utils::ArgumentEncoder;
3use candid::{ser::IDLBuilder, types::value::IDLValue, utils::ArgumentDecoder, CandidType, Encode};
4use ic_agent::{export::Principal, Agent, AgentError, RequestId};
5use std::convert::TryInto;
6use thiserror::Error;
7
8/// An error happened while building a canister.
9#[derive(Debug, Error)]
10pub enum CanisterBuilderError {
11    /// There was an error parsing the canister ID.
12    #[error("Getting the Canister ID returned an error: {0}")]
13    PrincipalError(#[from] Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>),
14
15    /// The agent was not provided.
16    #[error("Must specify an Agent")]
17    MustSpecifyAnAgent(),
18
19    /// The canister ID was not provided.
20    #[error("Must specify a Canister ID")]
21    MustSpecifyCanisterId(),
22}
23
24/// A canister builder, which can be used to create a canister abstraction.
25#[derive(Debug, Default)]
26pub struct CanisterBuilder<'agent> {
27    agent: Option<&'agent Agent>,
28    canister_id: Option<Result<Principal, CanisterBuilderError>>,
29}
30
31impl<'agent> CanisterBuilder<'agent> {
32    /// Create a canister builder with no value.
33    pub fn new() -> CanisterBuilder<'static> {
34        Default::default()
35    }
36
37    /// Attach a canister ID to this canister.
38    pub fn with_canister_id<E, P>(self, canister_id: P) -> Self
39    where
40        E: 'static + std::error::Error + std::marker::Send + std::marker::Sync,
41        P: TryInto<Principal, Error = E>,
42    {
43        Self {
44            canister_id: Some(
45                canister_id
46                    .try_into()
47                    .map_err(|e| CanisterBuilderError::PrincipalError(Box::new(e))),
48            ),
49            ..self
50        }
51    }
52
53    /// Assign an agent to the canister being built.
54    pub fn with_agent(self, agent: &'agent Agent) -> Self {
55        CanisterBuilder {
56            agent: Some(agent),
57            ..self
58        }
59    }
60
61    /// Create this canister abstraction after passing in all the necessary state.
62    pub fn build(self) -> Result<Canister<'agent>, CanisterBuilderError> {
63        let canister_id = if let Some(cid) = self.canister_id {
64            cid?
65        } else {
66            return Err(CanisterBuilderError::MustSpecifyCanisterId());
67        };
68
69        let agent = self
70            .agent
71            .ok_or(CanisterBuilderError::MustSpecifyAnAgent())?;
72        Ok(Canister { agent, canister_id })
73    }
74}
75
76/// Create an encapsulation of a Canister running on the Internet Computer.
77/// This supports making calls to methods, installing code if needed, and various
78/// utilities related to a canister.
79///
80/// This is the higher level construct for talking to a canister on the Internet
81/// Computer.
82#[derive(Debug, Clone)]
83pub struct Canister<'agent> {
84    pub(super) agent: &'agent Agent,
85    pub(super) canister_id: Principal,
86}
87
88impl<'agent> Canister<'agent> {
89    /// Get the canister ID of this canister.
90    /// Prefer using [`canister_id`](Canister::canister_id) instead.
91    pub fn canister_id_(&self) -> &Principal {
92        &self.canister_id
93    }
94
95    /// Get the canister ID of this canister.
96    pub fn canister_id(&self) -> &Principal {
97        &self.canister_id
98    }
99
100    /// Create an `AsyncCallBuilder` to do an update call.
101    /// Prefer using [`update`](Canister::update) instead.
102    pub fn update_<'canister>(
103        &'canister self,
104        method_name: &str,
105    ) -> AsyncCallBuilder<'agent, 'canister> {
106        AsyncCallBuilder::new(self, method_name)
107    }
108
109    /// Create an `AsyncCallBuilder` to do an update call.
110    pub fn update<'canister>(
111        &'canister self,
112        method_name: &str,
113    ) -> AsyncCallBuilder<'agent, 'canister> {
114        AsyncCallBuilder::new(self, method_name)
115    }
116
117    /// Create a `SyncCallBuilder` to do a query call.
118    /// Prefer using [`query`](Canister::query) instead.
119    pub fn query_<'canister>(
120        &'canister self,
121        method_name: &str,
122    ) -> SyncCallBuilder<'agent, 'canister> {
123        SyncCallBuilder::new(self, method_name)
124    }
125
126    /// Create a `SyncCallBuilder` to do a query call.
127    pub fn query<'canister>(
128        &'canister self,
129        method_name: &str,
130    ) -> SyncCallBuilder<'agent, 'canister> {
131        SyncCallBuilder::new(self, method_name)
132    }
133
134    /// Call `request_status` on the `RequestId` in a loop and return the response as a byte vector.
135    pub async fn wait<'canister>(
136        &'canister self,
137        request_id: &RequestId,
138    ) -> Result<Vec<u8>, AgentError> {
139        self.agent
140            .wait(request_id, self.canister_id)
141            .await
142            .map(|x| x.0)
143    }
144
145    /// Creates a copy of this canister, changing the canister ID to the provided principal.
146    /// Prefer using [`clone_with`](Canister::clone_with) instead.
147    pub fn clone_with_(&self, id: Principal) -> Self {
148        Self {
149            agent: self.agent,
150            canister_id: id,
151        }
152    }
153    /// Creates a copy of this canister, changing the canister ID to the provided principal.
154    pub fn clone_with(&self, id: Principal) -> Self {
155        Self {
156            agent: self.agent,
157            canister_id: id,
158        }
159    }
160
161    /// Create a `CanisterBuilder` instance to build a canister abstraction.
162    pub fn builder() -> CanisterBuilder<'agent> {
163        Default::default()
164    }
165}
166
167/// A buffer to hold canister argument blob.
168#[derive(Debug, Default)]
169pub struct Argument(pub(crate) Option<Result<Vec<u8>, AgentError>>);
170
171impl Argument {
172    /// Set an IDL Argument. Can only be called at most once.
173    pub fn set_idl_arg<A: CandidType>(&mut self, arg: A) {
174        match self.0 {
175            None => self.0 = Some(Encode!(&arg).map_err(|e| e.into())),
176            Some(_) => panic!("argument is being set more than once"),
177        }
178    }
179
180    /// Set an `IDLValue` Argument. Can only be called at most once.
181    pub fn set_value_arg(&mut self, arg: IDLValue) {
182        match self.0 {
183            None => {
184                let mut builder = IDLBuilder::new();
185                let result = builder
186                    .value_arg(&arg)
187                    .and_then(|builder| builder.serialize_to_vec())
188                    .map_err(Into::into);
189                self.0 = Some(result);
190            }
191            Some(_) => panic!("argument is being set more than once"),
192        }
193    }
194
195    /// Set the argument as raw. Can only be called at most once.
196    pub fn set_raw_arg(&mut self, arg: Vec<u8>) {
197        match self.0 {
198            None => self.0 = Some(Ok(arg)),
199            Some(_) => panic!("argument is being set more than once"),
200        }
201    }
202
203    /// Return the argument blob.
204    pub fn serialize(self) -> Result<Vec<u8>, AgentError> {
205        self.0.unwrap_or_else(|| Ok(Encode!()?))
206    }
207
208    /// Resets the argument to an empty message.
209    pub fn reset(&mut self) {
210        *self = Default::default();
211    }
212
213    /// Creates an empty argument.
214    pub fn new() -> Self {
215        Default::default()
216    }
217
218    /// Creates an argument from an arbitrary blob. Equivalent to [`set_raw_arg`](Argument::set_raw_arg).
219    pub fn from_raw(raw: Vec<u8>) -> Self {
220        Self(Some(Ok(raw)))
221    }
222
223    /// Creates an argument from an existing Candid `ArgumentEncoder`.
224    pub fn from_candid(tuple: impl ArgumentEncoder) -> Self {
225        let mut builder = IDLBuilder::new();
226        let result = tuple
227            .encode(&mut builder)
228            .and_then(|_| builder.serialize_to_vec())
229            .map_err(Into::into);
230        Self(Some(result))
231    }
232}
233
234/// A builder for a synchronous call (ie. query) to the Internet Computer.
235///
236/// See [`SyncCaller`] for a description of this structure once built.
237#[derive(Debug)]
238pub struct SyncCallBuilder<'agent, 'canister> {
239    canister: &'canister Canister<'agent>,
240    method_name: String,
241    effective_canister_id: Principal,
242    arg: Argument,
243}
244
245impl<'agent: 'canister, 'canister> SyncCallBuilder<'agent, 'canister> {
246    /// Create a new instance of an `AsyncCallBuilder`.
247    pub(super) fn new<M: Into<String>>(
248        canister: &'canister Canister<'agent>,
249        method_name: M,
250    ) -> Self {
251        Self {
252            canister,
253            method_name: method_name.into(),
254            effective_canister_id: canister.canister_id().to_owned(),
255            arg: Default::default(),
256        }
257    }
258}
259
260impl<'agent: 'canister, 'canister> SyncCallBuilder<'agent, 'canister> {
261    /// Set the argument with candid argument. Can be called at most once.
262    pub fn with_arg<Argument>(mut self, arg: Argument) -> Self
263    where
264        Argument: CandidType + Sync + Send,
265    {
266        self.arg.set_idl_arg(arg);
267        self
268    }
269    /// Set the argument with multiple arguments as tuple. Can be called at most once.
270    pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
271        assert!(self.arg.0.is_none(), "argument is being set more than once");
272        self.arg = Argument::from_candid(tuple);
273        self
274    }
275
276    /// Set the argument with `IDLValue` argument. Can be called at most once.
277    ///
278    /// TODO: make this method unnecessary ([#132](https://github.com/dfinity/agent-rs/issues/132))
279    pub fn with_value_arg(mut self, arg: IDLValue) -> Self {
280        self.arg.set_value_arg(arg);
281        self
282    }
283
284    /// Set the argument with raw argument bytes. Can be called at most once.
285    pub fn with_arg_raw(mut self, arg: Vec<u8>) -> Self {
286        self.arg.set_raw_arg(arg);
287        self
288    }
289
290    /// Sets the [effective canister ID](https://internetcomputer.org/docs/references/current/ic-interface-spec#http-effective-canister-id) of the destination.
291    pub fn with_effective_canister_id(mut self, canister_id: Principal) -> Self {
292        self.effective_canister_id = canister_id;
293        self
294    }
295
296    /// Builds a [`SyncCaller`] from this builder's state.
297    pub fn build<Output>(self) -> SyncCaller<'agent, Output>
298    where
299        Output: for<'de> ArgumentDecoder<'de> + Send + Sync,
300    {
301        let c = self.canister;
302        SyncCaller {
303            agent: c.agent,
304            effective_canister_id: self.effective_canister_id,
305            canister_id: c.canister_id,
306            method_name: self.method_name.clone(),
307            arg: self.arg.serialize(),
308            expiry: Default::default(),
309            phantom_out: std::marker::PhantomData,
310        }
311    }
312}
313
314/// A builder for an asynchronous call (ie. update) to the Internet Computer.
315///
316/// See [`AsyncCaller`] for a description of this structure.
317#[derive(Debug)]
318pub struct AsyncCallBuilder<'agent, 'canister> {
319    canister: &'canister Canister<'agent>,
320    method_name: String,
321    effective_canister_id: Principal,
322    arg: Argument,
323}
324
325impl<'agent: 'canister, 'canister> AsyncCallBuilder<'agent, 'canister> {
326    /// Create a new instance of an `AsyncCallBuilder`.
327    pub(super) fn new(
328        canister: &'canister Canister<'agent>,
329        method_name: &str,
330    ) -> AsyncCallBuilder<'agent, 'canister> {
331        Self {
332            canister,
333            method_name: method_name.to_string(),
334            effective_canister_id: canister.canister_id().to_owned(),
335            arg: Default::default(),
336        }
337    }
338}
339
340impl<'agent: 'canister, 'canister> AsyncCallBuilder<'agent, 'canister> {
341    /// Set the argument with Candid argument. Can be called at most once.
342    pub fn with_arg<Argument>(mut self, arg: Argument) -> Self
343    where
344        Argument: CandidType + Sync + Send,
345    {
346        self.arg.set_idl_arg(arg);
347        self
348    }
349    /// Set the argument with multiple arguments as tuple. Can be called at most once.
350    pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
351        assert!(self.arg.0.is_none(), "argument is being set more than once");
352        self.arg = Argument::from_candid(tuple);
353        self
354    }
355
356    /// Set the argument with raw argument bytes. Can be called at most once.
357    pub fn with_arg_raw(mut self, arg: Vec<u8>) -> Self {
358        self.arg.set_raw_arg(arg);
359        self
360    }
361
362    /// Sets the [effective canister ID](https://internetcomputer.org/docs/current/references/ic-interface-spec#http-effective-canister-id) of the destination.
363    pub fn with_effective_canister_id(mut self, canister_id: Principal) -> Self {
364        self.effective_canister_id = canister_id;
365        self
366    }
367
368    /// Builds an [`AsyncCaller`] from this builder's state.
369    pub fn build<Output>(self) -> AsyncCaller<'agent, Output>
370    where
371        Output: for<'de> ArgumentDecoder<'de> + Send + Sync,
372    {
373        let c = self.canister;
374        AsyncCaller {
375            agent: c.agent,
376            effective_canister_id: self.effective_canister_id,
377            canister_id: c.canister_id,
378            method_name: self.method_name.clone(),
379            arg: self.arg.serialize(),
380            expiry: Default::default(),
381            phantom_out: std::marker::PhantomData,
382        }
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::super::interfaces::ManagementCanister;
389    use crate::call::AsyncCall;
390    use candid::Principal;
391    use ic_agent::identity::BasicIdentity;
392
393    const POCKET_IC: &str = "POCKET_IC";
394
395    async fn get_effective_canister_id() -> Principal {
396        let default_effective_canister_id =
397            Principal::from_text("rwlgt-iiaaa-aaaaa-aaaaa-cai").unwrap();
398        if let Ok(pocket_ic_url) = std::env::var(POCKET_IC) {
399            pocket_ic::nonblocking::get_default_effective_canister_id(pocket_ic_url)
400                .await
401                .unwrap_or(default_effective_canister_id)
402        } else {
403            default_effective_canister_id
404        }
405    }
406
407    #[ignore]
408    #[tokio::test]
409    async fn simple() {
410        use super::Canister;
411
412        let identity =
413            BasicIdentity::from_raw_key(&ic_ed25519::PrivateKey::generate().serialize_raw());
414
415        let port = std::env::var("IC_REF_PORT").unwrap_or_else(|_| "4943".into());
416
417        let agent = ic_agent::Agent::builder()
418            .with_url(format!("http://localhost:{port}"))
419            .with_identity(identity)
420            .build()
421            .unwrap();
422        agent.fetch_root_key().await.unwrap();
423
424        let management_canister = ManagementCanister::from_canister(
425            Canister::builder()
426                .with_agent(&agent)
427                .with_canister_id("aaaaa-aa")
428                .build()
429                .unwrap(),
430        );
431
432        let (new_canister_id,) = management_canister
433            .create_canister()
434            .as_provisional_create_with_amount(None)
435            .with_effective_canister_id(get_effective_canister_id().await)
436            .call_and_wait()
437            .await
438            .unwrap();
439
440        let (status,) = management_canister
441            .canister_status(&new_canister_id)
442            .call_and_wait()
443            .await
444            .unwrap();
445
446        assert_eq!(format!("{:?}", status.status), "Running");
447
448        let canister_wasm = b"\0asm\x01\0\0\0";
449        management_canister
450            .install_code(&new_canister_id, canister_wasm)
451            .call_and_wait()
452            .await
453            .unwrap();
454
455        let canister = Canister::builder()
456            .with_agent(&agent)
457            .with_canister_id(new_canister_id)
458            .build()
459            .unwrap();
460
461        assert!(canister
462            .update("hello")
463            .build::<()>()
464            .call_and_wait()
465            .await
466            .is_err());
467    }
468}