icrc1_test_env_replica/
lib.rs1use anyhow::Context;
2use async_trait::async_trait;
3use candid::utils::{decode_args, encode_args, ArgumentDecoder, ArgumentEncoder};
4use candid::Principal;
5use ic_agent::identity::BasicIdentity;
6use ic_agent::Agent;
7use icrc1_test_env::LedgerEnv;
8use ring::rand::SystemRandom;
9use std::sync::{Arc, Mutex};
10use std::time::SystemTime;
11
12pub fn fresh_identity(rand: &SystemRandom) -> BasicIdentity {
13 use ring::signature::Ed25519KeyPair as KeyPair;
14
15 let doc = KeyPair::generate_pkcs8(rand).expect("failed to generate an ed25519 key pair");
16
17 let key_pair = KeyPair::from_pkcs8(doc.as_ref())
18 .expect("failed to construct a key pair from a pkcs8 document");
19 BasicIdentity::from_key_pair(key_pair)
20}
21
22#[derive(Clone)]
23pub struct ReplicaLedger {
24 rand: Arc<Mutex<SystemRandom>>,
25 agent: Arc<Agent>,
26 canister_id: Principal,
27}
28
29#[async_trait(?Send)]
30impl LedgerEnv for ReplicaLedger {
31 fn fork(&self) -> Self {
32 let mut agent = Arc::clone(&self.agent);
33 Arc::make_mut(&mut agent).set_identity({
34 let r = self.rand.lock().expect("failed to grab a lock");
35 fresh_identity(&r)
36 });
37 Self {
38 rand: Arc::clone(&self.rand),
39 agent,
40 canister_id: self.canister_id,
41 }
42 }
43
44 fn principal(&self) -> Principal {
45 self.agent
46 .get_principal()
47 .expect("failed to get agent principal")
48 }
49
50 async fn time(&self) -> SystemTime {
51 SystemTime::now()
55 }
56
57 async fn query<Input, Output>(&self, method: &str, input: Input) -> anyhow::Result<Output>
58 where
59 Input: ArgumentEncoder + std::fmt::Debug,
60 Output: for<'a> ArgumentDecoder<'a>,
61 {
62 let debug_inputs = format!("{:?}", input);
63 let in_bytes = encode_args(input)
64 .with_context(|| format!("Failed to encode arguments {}", debug_inputs))?;
65 let bytes = self
66 .agent
67 .query(&self.canister_id, method)
68 .with_arg(in_bytes)
69 .call()
70 .await
71 .with_context(|| {
72 format!(
73 "failed to call method {} on {} with args {}",
74 method, self.canister_id, debug_inputs
75 )
76 })?;
77
78 decode_args(&bytes).with_context(|| {
79 format!(
80 "Failed to decode method {} response into type {}, bytes: {}",
81 method,
82 std::any::type_name::<Output>(),
83 hex::encode(bytes)
84 )
85 })
86 }
87
88 async fn update<Input, Output>(&self, method: &str, input: Input) -> anyhow::Result<Output>
89 where
90 Input: ArgumentEncoder + std::fmt::Debug,
91 Output: for<'a> ArgumentDecoder<'a>,
92 {
93 let debug_inputs = format!("{:?}", input);
94 let in_bytes = encode_args(input)
95 .with_context(|| format!("Failed to encode arguments {}", debug_inputs))?;
96 let bytes = self
97 .agent
98 .update(&self.canister_id, method)
99 .with_arg(in_bytes)
100 .call_and_wait()
101 .await
102 .with_context(|| {
103 format!(
104 "failed to call method {} on {} with args {}",
105 method, self.canister_id, debug_inputs
106 )
107 })?;
108
109 decode_args(&bytes).with_context(|| {
110 format!(
111 "Failed to decode method {} response into type {}, bytes: {}",
112 method,
113 std::any::type_name::<Output>(),
114 hex::encode(bytes)
115 )
116 })
117 }
118}
119
120impl ReplicaLedger {
121 pub fn new(agent: Agent, canister_id: Principal) -> Self {
122 Self {
123 rand: Arc::new(Mutex::new(SystemRandom::new())),
124 agent: Arc::new(agent),
125 canister_id,
126 }
127 }
128}