1use alloc::{
4 collections::{btree_map::Entry, BTreeMap, BTreeSet},
5 vec::Vec,
6};
7use core::{
8 fmt,
9 fmt::{Display, Formatter},
10};
11
12#[cfg(feature = "datasize")]
13use datasize::DataSize;
14#[cfg(feature = "json-schema")]
15use schemars::JsonSchema;
16use serde::{Deserialize, Serialize};
17#[cfg(feature = "json-schema")]
18use serde_map_to_array::KeyValueJsonSchema;
19use serde_map_to_array::{BTreeMapToArray, KeyValueLabels};
20
21use crate::{
22 account::{AccountHash, TryFromIntError, Weight},
23 bytesrepr::{self, FromBytes, ToBytes},
24};
25
26#[derive(PartialEq, Eq, Debug, Copy, Clone)]
28#[repr(i32)]
29#[non_exhaustive]
30pub enum AddKeyFailure {
31 MaxKeysLimit = 1,
33 DuplicateKey = 2,
35 PermissionDenied = 3,
38}
39
40impl Display for AddKeyFailure {
41 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
42 match self {
43 AddKeyFailure::MaxKeysLimit => formatter.write_str(
44 "Unable to add new associated key because maximum amount of keys is reached",
45 ),
46 AddKeyFailure::DuplicateKey => formatter
47 .write_str("Unable to add new associated key because given key already exists"),
48 AddKeyFailure::PermissionDenied => formatter
49 .write_str("Unable to add new associated key due to insufficient permissions"),
50 }
51 }
52}
53
54#[doc(hidden)]
56impl TryFrom<i32> for AddKeyFailure {
57 type Error = TryFromIntError;
58
59 fn try_from(value: i32) -> Result<Self, Self::Error> {
60 match value {
61 d if d == AddKeyFailure::MaxKeysLimit as i32 => Ok(AddKeyFailure::MaxKeysLimit),
62 d if d == AddKeyFailure::DuplicateKey as i32 => Ok(AddKeyFailure::DuplicateKey),
63 d if d == AddKeyFailure::PermissionDenied as i32 => Ok(AddKeyFailure::PermissionDenied),
64 _ => Err(TryFromIntError(())),
65 }
66 }
67}
68
69#[derive(Debug, Eq, PartialEq, Copy, Clone)]
71#[repr(i32)]
72#[non_exhaustive]
73pub enum RemoveKeyFailure {
74 MissingKey = 1,
76 PermissionDenied = 2,
79 ThresholdViolation = 3,
82}
83
84impl Display for RemoveKeyFailure {
85 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
86 match self {
87 RemoveKeyFailure::MissingKey => {
88 formatter.write_str("Unable to remove a key that does not exist")
89 }
90 RemoveKeyFailure::PermissionDenied => formatter
91 .write_str("Unable to remove associated key due to insufficient permissions"),
92 RemoveKeyFailure::ThresholdViolation => formatter.write_str(
93 "Unable to remove a key which would violate action threshold constraints",
94 ),
95 }
96 }
97}
98
99#[doc(hidden)]
101impl TryFrom<i32> for RemoveKeyFailure {
102 type Error = TryFromIntError;
103
104 fn try_from(value: i32) -> Result<Self, Self::Error> {
105 match value {
106 d if d == RemoveKeyFailure::MissingKey as i32 => Ok(RemoveKeyFailure::MissingKey),
107 d if d == RemoveKeyFailure::PermissionDenied as i32 => {
108 Ok(RemoveKeyFailure::PermissionDenied)
109 }
110 d if d == RemoveKeyFailure::ThresholdViolation as i32 => {
111 Ok(RemoveKeyFailure::ThresholdViolation)
112 }
113 _ => Err(TryFromIntError(())),
114 }
115 }
116}
117
118#[derive(PartialEq, Eq, Debug, Copy, Clone)]
121#[repr(i32)]
122#[non_exhaustive]
123pub enum UpdateKeyFailure {
124 MissingKey = 1,
126 PermissionDenied = 2,
129 ThresholdViolation = 3,
133}
134
135impl Display for UpdateKeyFailure {
136 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
137 match self {
138 UpdateKeyFailure::MissingKey => formatter.write_str(
139 "Unable to update the value under an associated key that does not exist",
140 ),
141 UpdateKeyFailure::PermissionDenied => formatter
142 .write_str("Unable to update associated key due to insufficient permissions"),
143 UpdateKeyFailure::ThresholdViolation => formatter.write_str(
144 "Unable to update weight that would fall below any of action thresholds",
145 ),
146 }
147 }
148}
149
150#[doc(hidden)]
152impl TryFrom<i32> for UpdateKeyFailure {
153 type Error = TryFromIntError;
154
155 fn try_from(value: i32) -> Result<Self, Self::Error> {
156 match value {
157 d if d == UpdateKeyFailure::MissingKey as i32 => Ok(UpdateKeyFailure::MissingKey),
158 d if d == UpdateKeyFailure::PermissionDenied as i32 => {
159 Ok(UpdateKeyFailure::PermissionDenied)
160 }
161 d if d == UpdateKeyFailure::ThresholdViolation as i32 => {
162 Ok(UpdateKeyFailure::ThresholdViolation)
163 }
164 _ => Err(TryFromIntError(())),
165 }
166 }
167}
168
169#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
171#[cfg_attr(feature = "datasize", derive(DataSize))]
172#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
173#[cfg_attr(feature = "json-schema", schemars(rename = "AccountAssociatedKeys"))]
174#[serde(deny_unknown_fields)]
175#[rustfmt::skip]
176pub struct AssociatedKeys(
177 #[serde(with = "BTreeMapToArray::<AccountHash, Weight, Labels>")]
178 BTreeMap<AccountHash, Weight>,
179);
180
181impl AssociatedKeys {
182 pub fn new(key: AccountHash, weight: Weight) -> AssociatedKeys {
184 let mut bt: BTreeMap<AccountHash, Weight> = BTreeMap::new();
185 bt.insert(key, weight);
186 AssociatedKeys(bt)
187 }
188
189 pub fn add_key(&mut self, key: AccountHash, weight: Weight) -> Result<(), AddKeyFailure> {
193 match self.0.entry(key) {
194 Entry::Vacant(entry) => {
195 entry.insert(weight);
196 }
197 Entry::Occupied(_) => return Err(AddKeyFailure::DuplicateKey),
198 }
199 Ok(())
200 }
201
202 pub fn remove_key(&mut self, key: &AccountHash) -> Result<(), RemoveKeyFailure> {
206 self.0
207 .remove(key)
208 .map(|_| ())
209 .ok_or(RemoveKeyFailure::MissingKey)
210 }
211
212 pub fn update_key(&mut self, key: AccountHash, weight: Weight) -> Result<(), UpdateKeyFailure> {
215 match self.0.entry(key) {
216 Entry::Vacant(_) => {
217 return Err(UpdateKeyFailure::MissingKey);
218 }
219 Entry::Occupied(mut entry) => {
220 *entry.get_mut() = weight;
221 }
222 }
223 Ok(())
224 }
225
226 pub fn get(&self, key: &AccountHash) -> Option<&Weight> {
228 self.0.get(key)
229 }
230
231 pub fn contains_key(&self, key: &AccountHash) -> bool {
233 self.0.contains_key(key)
234 }
235
236 pub fn iter(&self) -> impl Iterator<Item = (&AccountHash, &Weight)> {
238 self.0.iter()
239 }
240
241 pub fn len(&self) -> usize {
243 self.0.len()
244 }
245
246 pub fn is_empty(&self) -> bool {
248 self.0.is_empty()
249 }
250
251 fn calculate_any_keys_weight<'a>(&self, keys: impl Iterator<Item = &'a AccountHash>) -> Weight {
259 let total = keys
260 .filter_map(|key| self.0.get(key))
261 .fold(0u8, |acc, w| acc.saturating_add(w.value()));
262
263 Weight::new(total)
264 }
265
266 pub fn calculate_keys_weight(&self, authorization_keys: &BTreeSet<AccountHash>) -> Weight {
268 self.calculate_any_keys_weight(authorization_keys.iter())
269 }
270
271 pub fn total_keys_weight(&self) -> Weight {
273 self.calculate_any_keys_weight(self.0.keys())
274 }
275
276 pub fn total_keys_weight_excluding(&self, account_hash: AccountHash) -> Weight {
278 self.calculate_any_keys_weight(self.0.keys().filter(|&&element| element != account_hash))
279 }
280}
281
282impl From<BTreeMap<AccountHash, Weight>> for AssociatedKeys {
283 fn from(associated_keys: BTreeMap<AccountHash, Weight>) -> Self {
284 Self(associated_keys)
285 }
286}
287
288impl From<AssociatedKeys> for BTreeMap<AccountHash, Weight> {
289 fn from(associated_keys: AssociatedKeys) -> Self {
290 associated_keys.0
291 }
292}
293
294impl ToBytes for AssociatedKeys {
295 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
296 self.0.to_bytes()
297 }
298
299 fn serialized_length(&self) -> usize {
300 self.0.serialized_length()
301 }
302
303 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
304 self.0.write_bytes(writer)
305 }
306}
307
308impl FromBytes for AssociatedKeys {
309 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
310 let (associated_keys, rem) = FromBytes::from_bytes(bytes)?;
311 Ok((AssociatedKeys(associated_keys), rem))
312 }
313}
314
315struct Labels;
316
317impl KeyValueLabels for Labels {
318 const KEY: &'static str = "account_hash";
319 const VALUE: &'static str = "weight";
320}
321
322#[cfg(feature = "json-schema")]
323impl KeyValueJsonSchema for Labels {
324 const JSON_SCHEMA_KV_NAME: Option<&'static str> = Some("AssociatedKey");
325 const JSON_SCHEMA_KV_DESCRIPTION: Option<&'static str> = Some("A weighted public key.");
326 const JSON_SCHEMA_KEY_DESCRIPTION: Option<&'static str> =
327 Some("The account hash of the public key.");
328 const JSON_SCHEMA_VALUE_DESCRIPTION: Option<&'static str> =
329 Some("The weight assigned to the public key.");
330}
331
332#[doc(hidden)]
333#[cfg(any(feature = "testing", feature = "gens", test))]
334pub mod gens {
335 use proptest::prelude::*;
336
337 use crate::gens::{account_hash_arb, account_weight_arb};
338
339 use super::AssociatedKeys;
340
341 pub fn account_associated_keys_arb() -> impl Strategy<Value = AssociatedKeys> {
342 proptest::collection::btree_map(account_hash_arb(), account_weight_arb(), 10).prop_map(
343 |keys| {
344 let mut associated_keys = AssociatedKeys::default();
345 keys.into_iter().for_each(|(k, v)| {
346 associated_keys.add_key(k, v).unwrap();
347 });
348 associated_keys
349 },
350 )
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use std::{collections::BTreeSet, iter::FromIterator};
357
358 use crate::{
359 account::{AccountHash, Weight, ACCOUNT_HASH_LENGTH},
360 bytesrepr,
361 };
362
363 use super::*;
364
365 #[test]
366 fn associated_keys_add() {
367 let mut keys =
368 AssociatedKeys::new(AccountHash::new([0u8; ACCOUNT_HASH_LENGTH]), Weight::new(1));
369 let new_pk = AccountHash::new([1u8; ACCOUNT_HASH_LENGTH]);
370 let new_pk_weight = Weight::new(2);
371 assert!(keys.add_key(new_pk, new_pk_weight).is_ok());
372 assert_eq!(keys.get(&new_pk), Some(&new_pk_weight))
373 }
374
375 #[test]
376 fn associated_keys_add_duplicate() {
377 let pk = AccountHash::new([0u8; ACCOUNT_HASH_LENGTH]);
378 let weight = Weight::new(1);
379 let mut keys = AssociatedKeys::new(pk, weight);
380 assert_eq!(
381 keys.add_key(pk, Weight::new(10)),
382 Err(AddKeyFailure::DuplicateKey)
383 );
384 assert_eq!(keys.get(&pk), Some(&weight));
385 }
386
387 #[test]
388 fn associated_keys_remove() {
389 let pk = AccountHash::new([0u8; ACCOUNT_HASH_LENGTH]);
390 let weight = Weight::new(1);
391 let mut keys = AssociatedKeys::new(pk, weight);
392 assert!(keys.remove_key(&pk).is_ok());
393 assert!(keys
394 .remove_key(&AccountHash::new([1u8; ACCOUNT_HASH_LENGTH]))
395 .is_err());
396 }
397
398 #[test]
399 fn associated_keys_update() {
400 let pk1 = AccountHash::new([0u8; ACCOUNT_HASH_LENGTH]);
401 let pk2 = AccountHash::new([1u8; ACCOUNT_HASH_LENGTH]);
402 let weight = Weight::new(1);
403 let mut keys = AssociatedKeys::new(pk1, weight);
404 assert!(matches!(
405 keys.update_key(pk2, Weight::new(2))
406 .expect_err("should get error"),
407 UpdateKeyFailure::MissingKey
408 ));
409 keys.add_key(pk2, Weight::new(1)).unwrap();
410 assert_eq!(keys.get(&pk2), Some(&Weight::new(1)));
411 keys.update_key(pk2, Weight::new(2)).unwrap();
412 assert_eq!(keys.get(&pk2), Some(&Weight::new(2)));
413 }
414
415 #[test]
416 fn associated_keys_calculate_keys_once() {
417 let key_1 = AccountHash::new([0; 32]);
418 let key_2 = AccountHash::new([1; 32]);
419 let key_3 = AccountHash::new([2; 32]);
420 let mut keys = AssociatedKeys::default();
421
422 keys.add_key(key_2, Weight::new(2))
423 .expect("should add key_1");
424 keys.add_key(key_1, Weight::new(1))
425 .expect("should add key_1");
426 keys.add_key(key_3, Weight::new(3))
427 .expect("should add key_1");
428
429 assert_eq!(
430 keys.calculate_keys_weight(&BTreeSet::from_iter(vec![
431 key_1, key_2, key_3, key_1, key_2, key_3,
432 ])),
433 Weight::new(1 + 2 + 3)
434 );
435 }
436
437 #[test]
438 fn associated_keys_total_weight() {
439 let associated_keys = {
440 let mut res = AssociatedKeys::new(AccountHash::new([1u8; 32]), Weight::new(1));
441 res.add_key(AccountHash::new([2u8; 32]), Weight::new(11))
442 .expect("should add key 1");
443 res.add_key(AccountHash::new([3u8; 32]), Weight::new(12))
444 .expect("should add key 2");
445 res.add_key(AccountHash::new([4u8; 32]), Weight::new(13))
446 .expect("should add key 3");
447 res
448 };
449 assert_eq!(
450 associated_keys.total_keys_weight(),
451 Weight::new(1 + 11 + 12 + 13)
452 );
453 }
454
455 #[test]
456 fn associated_keys_total_weight_excluding() {
457 let identity_key = AccountHash::new([1u8; 32]);
458 let identity_key_weight = Weight::new(1);
459
460 let key_1 = AccountHash::new([2u8; 32]);
461 let key_1_weight = Weight::new(11);
462
463 let key_2 = AccountHash::new([3u8; 32]);
464 let key_2_weight = Weight::new(12);
465
466 let key_3 = AccountHash::new([4u8; 32]);
467 let key_3_weight = Weight::new(13);
468
469 let associated_keys = {
470 let mut res = AssociatedKeys::new(identity_key, identity_key_weight);
471 res.add_key(key_1, key_1_weight).expect("should add key 1");
472 res.add_key(key_2, key_2_weight).expect("should add key 2");
473 res.add_key(key_3, key_3_weight).expect("should add key 3");
474 res
475 };
476 assert_eq!(
477 associated_keys.total_keys_weight_excluding(key_2),
478 Weight::new(identity_key_weight.value() + key_1_weight.value() + key_3_weight.value())
479 );
480 }
481
482 #[test]
483 fn overflowing_keys_weight() {
484 let identity_key = AccountHash::new([1u8; 32]);
485 let key_1 = AccountHash::new([2u8; 32]);
486 let key_2 = AccountHash::new([3u8; 32]);
487 let key_3 = AccountHash::new([4u8; 32]);
488
489 let identity_key_weight = Weight::new(250);
490 let weight_1 = Weight::new(1);
491 let weight_2 = Weight::new(2);
492 let weight_3 = Weight::new(3);
493
494 let saturated_weight = Weight::new(u8::MAX);
495
496 let associated_keys = {
497 let mut res = AssociatedKeys::new(identity_key, identity_key_weight);
498
499 res.add_key(key_1, weight_1).expect("should add key 1");
500 res.add_key(key_2, weight_2).expect("should add key 2");
501 res.add_key(key_3, weight_3).expect("should add key 3");
502 res
503 };
504
505 assert_eq!(
506 associated_keys.calculate_keys_weight(&BTreeSet::from_iter(vec![
507 identity_key, key_1, key_2, key_3, ])),
512 saturated_weight,
513 );
514 }
515
516 #[test]
517 fn serialization_roundtrip() {
518 let mut keys = AssociatedKeys::default();
519 keys.add_key(AccountHash::new([1; 32]), Weight::new(1))
520 .unwrap();
521 keys.add_key(AccountHash::new([2; 32]), Weight::new(2))
522 .unwrap();
523 keys.add_key(AccountHash::new([3; 32]), Weight::new(3))
524 .unwrap();
525 bytesrepr::test_serialization_roundtrip(&keys);
526 }
527}