Skip to main content

ic_utils/
canister.rs

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