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