polymesh_api_tester/
tester.rs

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    // Generate a seed based on current timestamp.
41    // We use a 'seed' to allow running tests in parallel.
42    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  /// Set how much POLYX to fund each user.
62  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  /// Get the user if they exist, or create a new one.  Make sure the user
105  /// has an identity.
106  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      // Skip users that have an identity.
119      if user.did.is_some() {
120        continue;
121      }
122      // Try getting the user's identity from the chain.
123      user.did = self.get_did(user.account()).await?;
124    }
125    Ok(())
126  }
127
128  /// Get the users if they exist, or create them.  Make sure the users
129  /// have identities.
130  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  /// Get the users if they exist, or create them.  Make sure the users
136  /// have identities.
137  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      // Get or create user.
142      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    // Calls for registering users and funding them.
156    let mut calls = Vec::new();
157    let has_sudo = self.has_sudo();
158    // Add calls to register users missing identities.
159    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      // User needs an identity.
182      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    // Add calls to fund the users.
192    let mut sudos = Vec::new();
193    for account in accounts {
194      if has_sudo {
195        // We have sudo, just set their balance.
196        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        // Transfer some funds to the user.
206        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    // Execute batch.
218    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    // Get new identities from batch events.
259    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    // Wait for both batches to finalize.
280    if let Some(mut res) = sudo_res {
281      res.wait_finalized().await?;
282    }
283    res.wait_finalized().await?;
284
285    // Join Secondary keys to their identity.
286    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      // Wait for joins to execute.
300      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        // `account` is not linked to an identity.
325        // Create a new identity with `account` as the primary key.
326        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}