icrc1_test_env_pocket_ic/
lib.rs1use anyhow::Context;
2use async_trait::async_trait;
3use candid::utils::{decode_args, encode_args, ArgumentDecoder, ArgumentEncoder};
4use candid::Principal;
5use icrc1_test_env::LedgerEnv;
6use pocket_ic::nonblocking::PocketIc;
7use std::convert::TryInto;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::sync::Arc;
10
11fn new_principal(n: u64) -> Principal {
12 let mut bytes = n.to_le_bytes().to_vec();
13 bytes.push(0xfe);
14 bytes.push(0x01);
15 Principal::try_from_slice(&bytes[..]).unwrap()
16}
17
18#[derive(Clone)]
19pub struct PICLedger {
20 counter: Arc<AtomicU64>,
21 pic: Arc<PocketIc>,
22 sender: Principal,
23 canister_id: Principal,
24}
25
26#[async_trait(?Send)]
27impl LedgerEnv for PICLedger {
28 fn fork(&self) -> Self {
29 Self {
30 counter: self.counter.clone(),
31 pic: self.pic.clone(),
32 sender: new_principal(self.counter.fetch_add(1, Ordering::Relaxed)),
33 canister_id: self.canister_id,
34 }
35 }
36
37 fn principal(&self) -> Principal {
38 self.sender
39 }
40
41 async fn time(&self) -> std::time::SystemTime {
42 self.pic
43 .get_time()
44 .await
45 .try_into()
46 .expect("Failed to convert PocketIC time to SystemTime")
47 }
48
49 async fn query<Input, Output>(&self, method: &str, input: Input) -> anyhow::Result<Output>
50 where
51 Input: ArgumentEncoder + std::fmt::Debug,
52 Output: for<'a> ArgumentDecoder<'a>,
53 {
54 let debug_inputs = format!("{:?}", input);
55 let in_bytes = encode_args(input)
56 .with_context(|| format!("Failed to encode arguments {}", debug_inputs))?;
57 match self
58 .pic
59 .query_call(self.canister_id, self.sender, method, in_bytes)
60 .await
61 {
62 Ok(bytes) => decode_args(&bytes).with_context(|| {
63 format!(
64 "Failed to decode method {} response into type {}, bytes: {}",
65 method,
66 std::any::type_name::<Output>(),
67 hex::encode(bytes)
68 )
69 }),
70 Err(reject_response) => Err(anyhow::Error::msg(format!(
71 "Query call to ledger {:?} was rejected: {}",
72 self.canister_id, reject_response.reject_message
73 ))),
74 }
75 }
76
77 async fn update<Input, Output>(&self, method: &str, input: Input) -> anyhow::Result<Output>
78 where
79 Input: ArgumentEncoder + std::fmt::Debug,
80 Output: for<'a> ArgumentDecoder<'a>,
81 {
82 let debug_inputs = format!("{:?}", input);
83 let in_bytes = encode_args(input)
84 .with_context(|| format!("Failed to encode arguments {}", debug_inputs))?;
85 match self
86 .pic
87 .update_call(self.canister_id, self.sender, method, in_bytes)
88 .await
89 {
90 Ok(bytes) => decode_args(&bytes).with_context(|| {
91 format!(
92 "Failed to decode method {} response into type {}, bytes: {}",
93 method,
94 std::any::type_name::<Output>(),
95 hex::encode(&bytes)
96 )
97 }),
98 Err(reject_response) => Err(anyhow::Error::msg(format!(
99 "Update call to ledger {:?} was rejected: {}",
100 self.canister_id, reject_response.reject_message
101 ))),
102 }
103 }
104}
105
106impl PICLedger {
107 pub fn new(pic: Arc<PocketIc>, canister_id: Principal, sender: Principal) -> Self {
108 Self {
109 counter: Arc::new(AtomicU64::new(0)),
110 pic,
111 canister_id,
112 sender,
113 }
114 }
115}