1use std::collections::HashMap;
2
3#[cfg(feature = "polymesh_v7")]
4use polymesh_api::polymesh::types::polymesh_primitives::secondary_key::ExtrinsicPermissions;
5use polymesh_api::{
6 client::{AccountId, AssetId, IdentityId},
7 polymesh::types::polymesh_primitives::{
8 secondary_key::{KeyRecord, Permissions, SecondaryKey},
9 subset::SubsetRestriction,
10 ticker::Ticker,
11 },
12 Api,
13};
14use polymesh_api_client_extras::*;
15
16use crate::*;
17
18async fn get_sudo_signer(api: &Api, signer: &DbAccountSigner) -> Option<DbAccountSigner> {
19 api.query().sudo().key().await.ok()?.and_then(|key| {
20 if key == signer.account() {
21 Some(signer.clone())
22 } else {
23 None
24 }
25 })
26}
27
28pub struct PolymeshTester {
29 pub api: Api,
30 seed: String,
31 init_polyx: u128,
32 db: Db,
33 pub cdd: DbAccountSigner,
34 pub sudo: Option<DbAccountSigner>,
35 users: HashMap<String, User>,
36}
37
38impl PolymeshTester {
39 pub async fn new() -> Result<Self> {
40 let api = client_api().await?;
41 let ts = std::time::SystemTime::now()
44 .duration_since(std::time::UNIX_EPOCH)
45 .expect("now later then epoch")
46 .as_nanos();
47 let url = std::env::var("DATABASE_URL").unwrap_or_else(|_| "accounts.db".into());
48 let db = Db::open(api.clone(), &url).await?;
49 let cdd = DbAccountSigner::alice(db.clone());
50 let sudo = get_sudo_signer(&api, &cdd).await;
51 Ok(Self {
52 api,
53 init_polyx: 10_000 * ONE_POLYX,
54 seed: format!("{}", ts),
55 cdd,
56 sudo,
57 users: Default::default(),
58 db,
59 })
60 }
61
62 pub fn set_init_polyx(&mut self, val: u128) {
64 self.init_polyx = val * ONE_POLYX;
65 }
66
67 pub fn has_sudo(&self) -> bool {
68 self.sudo.is_some()
69 }
70
71 pub fn get_seed(&self) -> &str {
72 &self.seed
73 }
74
75 pub fn dev_user(&self, name: &str) -> Result<DbAccountSigner> {
76 DbAccountSigner::from_string(self.db.clone(), &format!("//{}", name))
77 }
78
79 fn set_user_did(&mut self, name: &str, did: IdentityId) {
80 if let Some(user) = self.users.get_mut(name) {
81 user.did = Some(did);
82 }
83 }
84
85 fn update_user(&mut self, name: &str, user: &User) {
86 self.users.insert(name.to_string(), user.clone());
87 }
88
89 pub fn new_signer_idx(&self, name: &str, idx: usize) -> Result<AccountSigner> {
90 AccountSigner::from_string(&format!("//{}_{}_{}", self.seed, name, idx))
91 }
92
93 fn get_user(&mut self, name: &str) -> Result<User> {
94 use std::collections::hash_map::Entry;
95 match self.users.entry(name.to_string()) {
96 Entry::Occupied(entry) => Ok(entry.get().clone()),
97 Entry::Vacant(entry) => {
98 let signer = AccountSigner::from_string(&format!("//{}_{}", self.seed, entry.key()))?;
99 let user = User::new(&self.api, signer);
100 Ok(entry.insert(user).clone())
101 }
102 }
103 }
104
105 pub async fn user(&mut self, name: &str) -> Result<User> {
108 let mut user = self.get_user(name)?;
109 if user.did.is_none() {
110 let did = self.register_and_fund(user.account()).await?;
111 user.did = Some(did);
112 self.set_user_did(name, did);
113 }
114 Ok(user)
115 }
116
117 async fn load_dids(&mut self, users: &mut [User]) -> Result<()> {
118 for user in users {
119 if user.did.is_some() {
121 continue;
122 }
123 user.did = self.get_did(user.account()).await?;
125 }
126 Ok(())
127 }
128
129 pub async fn users(&mut self, names: &[&str]) -> Result<Vec<User>> {
132 let names = names.iter().map(|name| (*name, 0)).collect::<Vec<_>>();
133 self.users_with_secondary_keys(names.as_slice()).await
134 }
135
136 pub async fn users_with_secondary_keys(&mut self, names: &[(&str, usize)]) -> Result<Vec<User>> {
139 let mut users = Vec::new();
140 let mut accounts = Vec::new();
141 for (name, sk) in names {
142 let mut user = self.get_user(name)?;
144 if user.secondary_keys.len() < *sk {
145 for idx in 0..*sk {
146 let sk = self.new_signer_idx(name, idx)?;
147 accounts.push(sk.account());
148 user.secondary_keys.push(sk);
149 }
150 self.update_user(name, &user);
151 }
152 accounts.push(user.account());
153 users.push(user);
154 }
155 self.load_dids(users.as_mut_slice()).await?;
156 let mut calls = Vec::new();
158 let has_sudo = self.has_sudo();
159 let mut need_dids = Vec::new();
161 let mut has_secondary_keys = false;
162 for (idx, user) in users.iter().enumerate() {
163 if user.did.is_some() {
164 continue;
165 }
166 need_dids.push(idx);
167 if user.secondary_keys.len() > 0 {
168 has_secondary_keys = true;
169 }
170 let secondary_keys = user
171 .secondary_keys
172 .iter()
173 .map(|key| SecondaryKey {
174 key: key.account(),
175 permissions: Permissions {
176 asset: SubsetRestriction::Whole,
177 #[cfg(feature = "polymesh_v6")]
178 extrinsic: SubsetRestriction::Whole,
179 #[cfg(feature = "polymesh_v7")]
180 extrinsic: ExtrinsicPermissions::Whole,
181 portfolio: SubsetRestriction::Whole,
182 },
183 })
184 .collect();
185 calls.push(
187 self
188 .api
189 .call()
190 .identity()
191 .cdd_register_did_with_cdd(user.account(), secondary_keys, None)?
192 .into(),
193 );
194 }
195 let mut sudos = Vec::new();
197 for account in accounts {
198 if has_sudo {
199 sudos.push(
201 self
202 .api
203 .call()
204 .balances()
205 .set_balance(account.into(), self.init_polyx, 0)?
206 .into(),
207 );
208 } else {
209 calls.push(
211 self
212 .api
213 .call()
214 .balances()
215 .transfer(account.into(), self.init_polyx)?
216 .into(),
217 );
218 }
219 }
220 let signer = self.sudo.as_mut().unwrap_or_else(|| &mut self.cdd);
221 let mut res = self
223 .api
224 .call()
225 .utility()
226 .batch(calls)?
227 .submit_and_watch(signer)
228 .await?;
229 let sudo_res = if sudos.len() > 0 {
230 Some(
231 self
232 .api
233 .call()
234 .sudo()
235 .sudo(self.api.call().utility().batch(sudos)?.into())?
236 .submit_and_watch(signer)
237 .await?,
238 )
239 } else {
240 None
241 };
242 let mut auths = HashMap::new();
243 if has_secondary_keys {
244 if let Some(events) = res.events().await? {
245 for rec in &events.0 {
246 match &rec.event {
247 RuntimeEvent::Identity(IdentityEvent::AuthorizationAdded(
248 _,
249 _,
250 Some(sk),
251 auth,
252 _,
253 _,
254 )) => {
255 auths.insert(*sk, *auth);
256 }
257 _ => (),
258 }
259 }
260 }
261 }
262 let ids = get_created_ids(&mut res).await?;
264 let mut joins = Vec::new();
265 for idx in need_dids {
266 let (name, _) = names[idx];
267 match &ids[idx] {
268 CreatedIds::IdentityCreated(did) => {
269 let user = &mut users[idx];
270 user.did = Some(*did);
271 for sk in &mut user.secondary_keys {
272 if let Some(auth) = auths.remove(&sk.account()) {
273 joins.push((auth, sk.clone()));
274 }
275 }
276 self.set_user_did(name, *did);
277 }
278 id => {
279 log::warn!("Unexpected id: {id:?}");
280 }
281 }
282 }
283 if let Some(mut res) = sudo_res {
285 res.wait_finalized().await?;
286 }
287 res.wait_finalized().await?;
288
289 if joins.len() > 0 {
291 let mut results = Vec::new();
292 for (auth, mut sk) in joins {
293 results.push(
294 self
295 .api
296 .call()
297 .identity()
298 .join_identity_as_key(auth)?
299 .submit_and_watch(&mut sk)
300 .await?,
301 );
302 }
303 for mut res in results {
305 res.ok().await?;
306 }
307 }
308 Ok(users)
309 }
310
311 pub async fn key_records(&self, account: AccountId) -> Result<Option<KeyRecord<AccountId>>> {
312 Ok(self.api.query().identity().key_records(account).await?)
313 }
314
315 pub async fn get_did(&self, account: AccountId) -> Result<Option<IdentityId>> {
316 let did = match self.key_records(account).await? {
317 Some(KeyRecord::PrimaryKey(did)) => Some(did),
318 #[cfg(feature = "polymesh_v6")]
319 Some(KeyRecord::SecondaryKey(did, _)) => Some(did),
320 #[cfg(feature = "polymesh_v7")]
321 Some(KeyRecord::SecondaryKey(did)) => Some(did),
322 _ => None,
323 };
324 Ok(did)
325 }
326
327 pub async fn register_and_fund(&mut self, account: AccountId) -> Result<IdentityId> {
328 let did = match self.get_did(account).await? {
329 Some(did) => did,
330 None => {
331 let mut res = self
334 .api
335 .call()
336 .utility()
337 .batch(vec![
338 self
339 .api
340 .call()
341 .identity()
342 .cdd_register_did_with_cdd(account, vec![], None)?
343 .into(),
344 self
345 .api
346 .call()
347 .balances()
348 .transfer(account.into(), self.init_polyx)?
349 .into(),
350 ])?
351 .execute(&mut self.cdd)
352 .await?;
353 get_identity_id(&mut res).await?.unwrap()
354 }
355 };
356 Ok(did)
357 }
358
359 pub fn gen_ticker(&self) -> Ticker {
360 use rand::{thread_rng, Rng};
361 let mut data = [0u8; 6];
362 thread_rng().fill(&mut data[..]);
363 let name = hex::encode_upper(data);
364 Ticker(name.as_bytes().try_into().unwrap())
365 }
366
367 #[cfg(feature = "polymesh_v7")]
368 pub fn gen_asset_id(&self) -> AssetId {
369 use rand::{thread_rng, Rng};
370 let mut data = [0u8; 8];
371 thread_rng().fill(&mut data[..]);
372 let fake_name = hex::encode_upper(data);
373 AssetId(fake_name.as_bytes().try_into().unwrap())
374 }
375}