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