casper_types/
account.rs

1//! Contains types and constants associated with user accounts.
2
3mod account_hash;
4pub mod action_thresholds;
5pub mod action_type;
6pub mod associated_keys;
7mod error;
8mod weight;
9
10use serde::{Deserialize, Serialize};
11
12use alloc::{collections::BTreeSet, vec::Vec};
13
14#[cfg(feature = "datasize")]
15use datasize::DataSize;
16#[cfg(feature = "json-schema")]
17use once_cell::sync::Lazy;
18#[cfg(feature = "json-schema")]
19use schemars::JsonSchema;
20
21pub use self::{
22    account_hash::{AccountHash, ACCOUNT_HASH_FORMATTED_STRING_PREFIX, ACCOUNT_HASH_LENGTH},
23    action_thresholds::ActionThresholds,
24    action_type::ActionType,
25    associated_keys::{AddKeyFailure, AssociatedKeys, RemoveKeyFailure, UpdateKeyFailure},
26    error::{FromStrError, SetThresholdFailure, TryFromIntError},
27    weight::Weight,
28};
29
30use crate::{
31    bytesrepr::{self, FromBytes, ToBytes},
32    contracts::NamedKeys,
33    AccessRights, Key, URef,
34};
35#[cfg(feature = "json-schema")]
36use crate::{PublicKey, SecretKey};
37
38#[cfg(feature = "json-schema")]
39static ACCOUNT: Lazy<Account> = Lazy::new(|| {
40    let secret_key = SecretKey::ed25519_from_bytes([0; 32]).unwrap();
41    let account_hash = PublicKey::from(&secret_key).to_account_hash();
42    let main_purse = URef::from_formatted_str(
43        "uref-09480c3248ef76b603d386f3f4f8a5f87f597d4eaffd475433f861af187ab5db-007",
44    )
45    .unwrap();
46    let mut named_keys = NamedKeys::new();
47    named_keys.insert("main_purse".to_string(), Key::URef(main_purse));
48    let weight = Weight::new(1);
49    let associated_keys = AssociatedKeys::new(account_hash, weight);
50    let action_thresholds = ActionThresholds::new(weight, weight).unwrap();
51    Account {
52        account_hash,
53        named_keys,
54        main_purse,
55        associated_keys,
56        action_thresholds,
57    }
58});
59
60/// Represents an Account in the global state.
61#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Debug)]
62#[cfg_attr(feature = "datasize", derive(DataSize))]
63#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
64#[serde(deny_unknown_fields)]
65pub struct Account {
66    account_hash: AccountHash,
67    named_keys: NamedKeys,
68    main_purse: URef,
69    associated_keys: AssociatedKeys,
70    action_thresholds: ActionThresholds,
71}
72
73impl Account {
74    /// Creates a new account.
75    pub fn new(
76        account_hash: AccountHash,
77        named_keys: NamedKeys,
78        main_purse: URef,
79        associated_keys: AssociatedKeys,
80        action_thresholds: ActionThresholds,
81    ) -> Self {
82        Account {
83            account_hash,
84            named_keys,
85            main_purse,
86            associated_keys,
87            action_thresholds,
88        }
89    }
90
91    /// An Account constructor with presets for associated_keys and action_thresholds.
92    ///
93    /// An account created with this method is valid and can be used as the target of a transaction.
94    /// It will be created with an [`AssociatedKeys`] with a [`Weight`] of 1, and a default
95    /// [`ActionThresholds`].
96    pub fn create(account: AccountHash, named_keys: NamedKeys, main_purse: URef) -> Self {
97        let associated_keys = AssociatedKeys::new(account, Weight::new(1));
98
99        let action_thresholds: ActionThresholds = Default::default();
100        Account::new(
101            account,
102            named_keys,
103            main_purse,
104            associated_keys,
105            action_thresholds,
106        )
107    }
108
109    /// Appends named keys to an account's named_keys field.
110    pub fn named_keys_append(&mut self, keys: NamedKeys) {
111        self.named_keys.append(keys);
112    }
113
114    /// Returns named keys.
115    pub fn named_keys(&self) -> &NamedKeys {
116        &self.named_keys
117    }
118
119    /// Returns a mutable reference to named keys.
120    pub fn named_keys_mut(&mut self) -> &mut NamedKeys {
121        &mut self.named_keys
122    }
123
124    /// Removes the key under the given name from named keys.
125    pub fn remove_named_key(&mut self, name: &str) -> Option<Key> {
126        self.named_keys.remove(name)
127    }
128
129    /// Returns account hash.
130    pub fn account_hash(&self) -> AccountHash {
131        self.account_hash
132    }
133
134    /// Returns main purse.
135    pub fn main_purse(&self) -> URef {
136        self.main_purse
137    }
138
139    /// Returns an [`AccessRights::ADD`]-only version of the main purse's [`URef`].
140    pub fn main_purse_add_only(&self) -> URef {
141        URef::new(self.main_purse.addr(), AccessRights::ADD)
142    }
143
144    /// Returns associated keys.
145    pub fn associated_keys(&self) -> &AssociatedKeys {
146        &self.associated_keys
147    }
148
149    /// Returns action thresholds.
150    pub fn action_thresholds(&self) -> &ActionThresholds {
151        &self.action_thresholds
152    }
153
154    /// Adds an associated key to an account.
155    pub fn add_associated_key(
156        &mut self,
157        account_hash: AccountHash,
158        weight: Weight,
159    ) -> Result<(), AddKeyFailure> {
160        self.associated_keys.add_key(account_hash, weight)
161    }
162
163    /// Checks if removing given key would properly satisfy thresholds.
164    fn can_remove_key(&self, account_hash: AccountHash) -> bool {
165        let total_weight_without = self
166            .associated_keys
167            .total_keys_weight_excluding(account_hash);
168
169        // Returns true if the total weight calculated without given public key would be greater or
170        // equal to all of the thresholds.
171        total_weight_without >= *self.action_thresholds().deployment()
172            && total_weight_without >= *self.action_thresholds().key_management()
173    }
174
175    /// Checks if adding a weight to a sum of all weights excluding the given key would make the
176    /// resulting value to fall below any of the thresholds on account.
177    fn can_update_key(&self, account_hash: AccountHash, weight: Weight) -> bool {
178        // Calculates total weight of all keys excluding the given key
179        let total_weight = self
180            .associated_keys
181            .total_keys_weight_excluding(account_hash);
182
183        // Safely calculate new weight by adding the updated weight
184        let new_weight = total_weight.value().saturating_add(weight.value());
185
186        // Returns true if the new weight would be greater or equal to all of
187        // the thresholds.
188        new_weight >= self.action_thresholds().deployment().value()
189            && new_weight >= self.action_thresholds().key_management().value()
190    }
191
192    /// Removes an associated key from an account.
193    ///
194    /// Verifies that removing the key will not cause the remaining weight to fall below any action
195    /// thresholds.
196    pub fn remove_associated_key(
197        &mut self,
198        account_hash: AccountHash,
199    ) -> Result<(), RemoveKeyFailure> {
200        if self.associated_keys.contains_key(&account_hash) {
201            // Check if removing this weight would fall below thresholds
202            if !self.can_remove_key(account_hash) {
203                return Err(RemoveKeyFailure::ThresholdViolation);
204            }
205        }
206        self.associated_keys.remove_key(&account_hash)
207    }
208
209    /// Updates an associated key.
210    ///
211    /// Returns an error if the update would result in a violation of the key management thresholds.
212    pub fn update_associated_key(
213        &mut self,
214        account_hash: AccountHash,
215        weight: Weight,
216    ) -> Result<(), UpdateKeyFailure> {
217        if let Some(current_weight) = self.associated_keys.get(&account_hash) {
218            if weight < *current_weight {
219                // New weight is smaller than current weight
220                if !self.can_update_key(account_hash, weight) {
221                    return Err(UpdateKeyFailure::ThresholdViolation);
222                }
223            }
224        }
225        self.associated_keys.update_key(account_hash, weight)
226    }
227
228    /// Sets a new action threshold for a given action type for the account without checking against
229    /// the total weight of the associated keys.
230    ///
231    /// This should only be called when authorized by an administrator account.
232    ///
233    /// Returns an error if setting the action would cause the `ActionType::Deployment` threshold to
234    /// be greater than any of the other action types.
235    pub fn set_action_threshold_unchecked(
236        &mut self,
237        action_type: ActionType,
238        threshold: Weight,
239    ) -> Result<(), SetThresholdFailure> {
240        self.action_thresholds.set_threshold(action_type, threshold)
241    }
242
243    /// Sets a new action threshold for a given action type for the account.
244    ///
245    /// Returns an error if the new action threshold weight is greater than the total weight of the
246    /// account's associated keys.
247    pub fn set_action_threshold(
248        &mut self,
249        action_type: ActionType,
250        weight: Weight,
251    ) -> Result<(), SetThresholdFailure> {
252        // Verify if new threshold weight exceeds total weight of all associated
253        // keys.
254        self.can_set_threshold(weight)?;
255        // Set new weight for given action
256        self.action_thresholds.set_threshold(action_type, weight)
257    }
258
259    /// Verifies if user can set action threshold.
260    pub fn can_set_threshold(&self, new_threshold: Weight) -> Result<(), SetThresholdFailure> {
261        let total_weight = self.associated_keys.total_keys_weight();
262        if new_threshold > total_weight {
263            return Err(SetThresholdFailure::InsufficientTotalWeight);
264        }
265        Ok(())
266    }
267
268    /// Checks whether all authorization keys are associated with this account.
269    pub fn can_authorize(&self, authorization_keys: &BTreeSet<AccountHash>) -> bool {
270        !authorization_keys.is_empty()
271            && authorization_keys
272                .iter()
273                .all(|e| self.associated_keys.contains_key(e))
274    }
275
276    /// Checks whether the sum of the weights of all authorization keys is
277    /// greater or equal to deploy threshold.
278    pub fn can_deploy_with(&self, authorization_keys: &BTreeSet<AccountHash>) -> bool {
279        let total_weight = self
280            .associated_keys
281            .calculate_keys_weight(authorization_keys);
282
283        total_weight >= *self.action_thresholds().deployment()
284    }
285
286    /// Checks whether the sum of the weights of all authorization keys is
287    /// greater or equal to key management threshold.
288    pub fn can_manage_keys_with(&self, authorization_keys: &BTreeSet<AccountHash>) -> bool {
289        let total_weight = self
290            .associated_keys
291            .calculate_keys_weight(authorization_keys);
292
293        total_weight >= *self.action_thresholds().key_management()
294    }
295
296    // This method is not intended to be used by third party crates.
297    #[doc(hidden)]
298    #[cfg(feature = "json-schema")]
299    pub fn example() -> &'static Self {
300        &ACCOUNT
301    }
302}
303
304impl ToBytes for Account {
305    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
306        let mut result = bytesrepr::allocate_buffer(self)?;
307        self.account_hash().write_bytes(&mut result)?;
308        self.named_keys().write_bytes(&mut result)?;
309        self.main_purse.write_bytes(&mut result)?;
310        self.associated_keys().write_bytes(&mut result)?;
311        self.action_thresholds().write_bytes(&mut result)?;
312        Ok(result)
313    }
314
315    fn serialized_length(&self) -> usize {
316        self.account_hash.serialized_length()
317            + self.named_keys.serialized_length()
318            + self.main_purse.serialized_length()
319            + self.associated_keys.serialized_length()
320            + self.action_thresholds.serialized_length()
321    }
322
323    fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
324        self.account_hash().write_bytes(writer)?;
325        self.named_keys().write_bytes(writer)?;
326        self.main_purse().write_bytes(writer)?;
327        self.associated_keys().write_bytes(writer)?;
328        self.action_thresholds().write_bytes(writer)?;
329        Ok(())
330    }
331}
332
333impl FromBytes for Account {
334    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
335        let (account_hash, rem) = AccountHash::from_bytes(bytes)?;
336        let (named_keys, rem) = NamedKeys::from_bytes(rem)?;
337        let (main_purse, rem) = URef::from_bytes(rem)?;
338        let (associated_keys, rem) = AssociatedKeys::from_bytes(rem)?;
339        let (action_thresholds, rem) = ActionThresholds::from_bytes(rem)?;
340        Ok((
341            Account {
342                account_hash,
343                named_keys,
344                main_purse,
345                associated_keys,
346                action_thresholds,
347            },
348            rem,
349        ))
350    }
351}
352
353#[doc(hidden)]
354#[cfg(any(feature = "testing", feature = "gens", test))]
355pub mod gens {
356    use proptest::prelude::*;
357
358    use crate::{
359        account::{associated_keys::gens::account_associated_keys_arb, Account, Weight},
360        gens::{account_hash_arb, named_keys_arb, uref_arb},
361    };
362
363    use super::action_thresholds::gens::account_action_thresholds_arb;
364
365    prop_compose! {
366        pub fn account_arb()(
367            account_hash in account_hash_arb(),
368            urefs in named_keys_arb(3),
369            purse in uref_arb(),
370            thresholds in account_action_thresholds_arb(),
371            mut associated_keys in account_associated_keys_arb(),
372        ) -> Account {
373                associated_keys.add_key(account_hash, Weight::new(1)).unwrap();
374                Account::new(
375                    account_hash,
376                    urefs,
377                    purse,
378                    associated_keys,
379                    thresholds,
380                )
381        }
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use crate::{
388        account::{
389            Account, AccountHash, ActionThresholds, ActionType, AssociatedKeys, RemoveKeyFailure,
390            TryFromIntError, UpdateKeyFailure, Weight,
391        },
392        contracts::NamedKeys,
393        AccessRights, URef,
394    };
395    use std::{collections::BTreeSet, convert::TryFrom, iter::FromIterator, vec::Vec};
396
397    use super::*;
398
399    #[test]
400    fn account_hash_from_slice() {
401        let bytes: Vec<u8> = (0..32).collect();
402        let account_hash = AccountHash::try_from(&bytes[..]).expect(
403            "should create account
404hash",
405        );
406        assert_eq!(&bytes, &account_hash.as_bytes());
407    }
408
409    #[test]
410    fn account_hash_from_slice_too_small() {
411        let _account_hash =
412            AccountHash::try_from(&[0u8; 31][..]).expect_err("should not create account hash");
413    }
414
415    #[test]
416    fn account_hash_from_slice_too_big() {
417        let _account_hash =
418            AccountHash::try_from(&[0u8; 33][..]).expect_err("should not create account hash");
419    }
420
421    #[test]
422    fn try_from_i32_for_set_threshold_failure() {
423        let max_valid_value_for_variant = SetThresholdFailure::InsufficientTotalWeight as i32;
424        assert_eq!(
425            Err(TryFromIntError(())),
426            SetThresholdFailure::try_from(max_valid_value_for_variant + 1),
427            "Did you forget to update `SetThresholdFailure::try_from` for a new variant of \
428                   `SetThresholdFailure`, or `max_valid_value_for_variant` in this test?"
429        );
430    }
431
432    #[test]
433    fn try_from_i32_for_add_key_failure() {
434        let max_valid_value_for_variant = AddKeyFailure::PermissionDenied as i32;
435        assert_eq!(
436            Err(TryFromIntError(())),
437            AddKeyFailure::try_from(max_valid_value_for_variant + 1),
438            "Did you forget to update `AddKeyFailure::try_from` for a new variant of \
439                   `AddKeyFailure`, or `max_valid_value_for_variant` in this test?"
440        );
441    }
442
443    #[test]
444    fn try_from_i32_for_remove_key_failure() {
445        let max_valid_value_for_variant = RemoveKeyFailure::ThresholdViolation as i32;
446        assert_eq!(
447            Err(TryFromIntError(())),
448            RemoveKeyFailure::try_from(max_valid_value_for_variant + 1),
449            "Did you forget to update `RemoveKeyFailure::try_from` for a new variant of \
450                   `RemoveKeyFailure`, or `max_valid_value_for_variant` in this test?"
451        );
452    }
453
454    #[test]
455    fn try_from_i32_for_update_key_failure() {
456        let max_valid_value_for_variant = UpdateKeyFailure::ThresholdViolation as i32;
457        assert_eq!(
458            Err(TryFromIntError(())),
459            UpdateKeyFailure::try_from(max_valid_value_for_variant + 1),
460            "Did you forget to update `UpdateKeyFailure::try_from` for a new variant of \
461                   `UpdateKeyFailure`, or `max_valid_value_for_variant` in this test?"
462        );
463    }
464
465    #[test]
466    fn account_hash_from_str() {
467        let account_hash = AccountHash([3; 32]);
468        let encoded = account_hash.to_formatted_string();
469        let decoded = AccountHash::from_formatted_str(&encoded).unwrap();
470        assert_eq!(account_hash, decoded);
471
472        let invalid_prefix =
473            "accounthash-0000000000000000000000000000000000000000000000000000000000000000";
474        assert!(AccountHash::from_formatted_str(invalid_prefix).is_err());
475
476        let invalid_prefix =
477            "account-hash0000000000000000000000000000000000000000000000000000000000000000";
478        assert!(AccountHash::from_formatted_str(invalid_prefix).is_err());
479
480        let short_addr =
481            "account-hash-00000000000000000000000000000000000000000000000000000000000000";
482        assert!(AccountHash::from_formatted_str(short_addr).is_err());
483
484        let long_addr =
485            "account-hash-000000000000000000000000000000000000000000000000000000000000000000";
486        assert!(AccountHash::from_formatted_str(long_addr).is_err());
487
488        let invalid_hex =
489            "account-hash-000000000000000000000000000000000000000000000000000000000000000g";
490        assert!(AccountHash::from_formatted_str(invalid_hex).is_err());
491    }
492
493    #[test]
494    fn account_hash_serde_roundtrip() {
495        let account_hash = AccountHash([255; 32]);
496        let serialized = bincode::serialize(&account_hash).unwrap();
497        let decoded = bincode::deserialize(&serialized).unwrap();
498        assert_eq!(account_hash, decoded);
499    }
500
501    #[test]
502    fn account_hash_json_roundtrip() {
503        let account_hash = AccountHash([255; 32]);
504        let json_string = serde_json::to_string_pretty(&account_hash).unwrap();
505        let decoded = serde_json::from_str(&json_string).unwrap();
506        assert_eq!(account_hash, decoded);
507    }
508
509    #[test]
510    fn associated_keys_can_authorize_keys() {
511        let key_1 = AccountHash::new([0; 32]);
512        let key_2 = AccountHash::new([1; 32]);
513        let key_3 = AccountHash::new([2; 32]);
514        let mut keys = AssociatedKeys::default();
515
516        keys.add_key(key_2, Weight::new(2))
517            .expect("should add key_1");
518        keys.add_key(key_1, Weight::new(1))
519            .expect("should add key_1");
520        keys.add_key(key_3, Weight::new(3))
521            .expect("should add key_1");
522
523        let account = Account::new(
524            AccountHash::new([0u8; 32]),
525            NamedKeys::new(),
526            URef::new([0u8; 32], AccessRights::READ_ADD_WRITE),
527            keys,
528            // deploy: 33 (3*11)
529            ActionThresholds::new(Weight::new(33), Weight::new(48))
530                .expect("should create thresholds"),
531        );
532
533        assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_3, key_2, key_1])));
534        assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1, key_3, key_2])));
535
536        assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1, key_2])));
537        assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1])));
538
539        assert!(!account.can_authorize(&BTreeSet::from_iter(vec![
540            key_1,
541            key_2,
542            AccountHash::new([42; 32])
543        ])));
544        assert!(!account.can_authorize(&BTreeSet::from_iter(vec![
545            AccountHash::new([42; 32]),
546            key_1,
547            key_2
548        ])));
549        assert!(!account.can_authorize(&BTreeSet::from_iter(vec![
550            AccountHash::new([43; 32]),
551            AccountHash::new([44; 32]),
552            AccountHash::new([42; 32])
553        ])));
554        assert!(!account.can_authorize(&BTreeSet::new()));
555    }
556
557    #[test]
558    fn account_can_deploy_with() {
559        let associated_keys = {
560            let mut res = AssociatedKeys::new(AccountHash::new([1u8; 32]), Weight::new(1));
561            res.add_key(AccountHash::new([2u8; 32]), Weight::new(11))
562                .expect("should add key 1");
563            res.add_key(AccountHash::new([3u8; 32]), Weight::new(11))
564                .expect("should add key 2");
565            res.add_key(AccountHash::new([4u8; 32]), Weight::new(11))
566                .expect("should add key 3");
567            res
568        };
569        let account = Account::new(
570            AccountHash::new([0u8; 32]),
571            NamedKeys::new(),
572            URef::new([0u8; 32], AccessRights::READ_ADD_WRITE),
573            associated_keys,
574            // deploy: 33 (3*11)
575            ActionThresholds::new(Weight::new(33), Weight::new(48))
576                .expect("should create thresholds"),
577        );
578
579        // sum: 22, required 33 - can't deploy
580        assert!(!account.can_deploy_with(&BTreeSet::from_iter(vec![
581            AccountHash::new([3u8; 32]),
582            AccountHash::new([2u8; 32]),
583        ])));
584
585        // sum: 33, required 33 - can deploy
586        assert!(account.can_deploy_with(&BTreeSet::from_iter(vec![
587            AccountHash::new([4u8; 32]),
588            AccountHash::new([3u8; 32]),
589            AccountHash::new([2u8; 32]),
590        ])));
591
592        // sum: 34, required 33 - can deploy
593        assert!(account.can_deploy_with(&BTreeSet::from_iter(vec![
594            AccountHash::new([2u8; 32]),
595            AccountHash::new([1u8; 32]),
596            AccountHash::new([4u8; 32]),
597            AccountHash::new([3u8; 32]),
598        ])));
599    }
600
601    #[test]
602    fn account_can_manage_keys_with() {
603        let associated_keys = {
604            let mut res = AssociatedKeys::new(AccountHash::new([1u8; 32]), Weight::new(1));
605            res.add_key(AccountHash::new([2u8; 32]), Weight::new(11))
606                .expect("should add key 1");
607            res.add_key(AccountHash::new([3u8; 32]), Weight::new(11))
608                .expect("should add key 2");
609            res.add_key(AccountHash::new([4u8; 32]), Weight::new(11))
610                .expect("should add key 3");
611            res
612        };
613        let account = Account::new(
614            AccountHash::new([0u8; 32]),
615            NamedKeys::new(),
616            URef::new([0u8; 32], AccessRights::READ_ADD_WRITE),
617            associated_keys,
618            // deploy: 33 (3*11)
619            ActionThresholds::new(Weight::new(11), Weight::new(33))
620                .expect("should create thresholds"),
621        );
622
623        // sum: 22, required 33 - can't manage
624        assert!(!account.can_manage_keys_with(&BTreeSet::from_iter(vec![
625            AccountHash::new([3u8; 32]),
626            AccountHash::new([2u8; 32]),
627        ])));
628
629        // sum: 33, required 33 - can manage
630        assert!(account.can_manage_keys_with(&BTreeSet::from_iter(vec![
631            AccountHash::new([4u8; 32]),
632            AccountHash::new([3u8; 32]),
633            AccountHash::new([2u8; 32]),
634        ])));
635
636        // sum: 34, required 33 - can manage
637        assert!(account.can_manage_keys_with(&BTreeSet::from_iter(vec![
638            AccountHash::new([2u8; 32]),
639            AccountHash::new([1u8; 32]),
640            AccountHash::new([4u8; 32]),
641            AccountHash::new([3u8; 32]),
642        ])));
643    }
644
645    #[test]
646    fn set_action_threshold_higher_than_total_weight() {
647        let identity_key = AccountHash::new([1u8; 32]);
648        let key_1 = AccountHash::new([2u8; 32]);
649        let key_2 = AccountHash::new([3u8; 32]);
650        let key_3 = AccountHash::new([4u8; 32]);
651        let associated_keys = {
652            let mut res = AssociatedKeys::new(identity_key, Weight::new(1));
653            res.add_key(key_1, Weight::new(2))
654                .expect("should add key 1");
655            res.add_key(key_2, Weight::new(3))
656                .expect("should add key 2");
657            res.add_key(key_3, Weight::new(4))
658                .expect("should add key 3");
659            res
660        };
661        let mut account = Account::new(
662            AccountHash::new([0u8; 32]),
663            NamedKeys::new(),
664            URef::new([0u8; 32], AccessRights::READ_ADD_WRITE),
665            associated_keys,
666            // deploy: 33 (3*11)
667            ActionThresholds::new(Weight::new(33), Weight::new(48))
668                .expect("should create thresholds"),
669        );
670
671        assert_eq!(
672            account
673                .set_action_threshold(ActionType::Deployment, Weight::new(1 + 2 + 3 + 4 + 1))
674                .unwrap_err(),
675            SetThresholdFailure::InsufficientTotalWeight,
676        );
677        assert_eq!(
678            account
679                .set_action_threshold(ActionType::Deployment, Weight::new(1 + 2 + 3 + 4 + 245))
680                .unwrap_err(),
681            SetThresholdFailure::InsufficientTotalWeight,
682        )
683    }
684
685    #[test]
686    fn remove_key_would_violate_action_thresholds() {
687        let identity_key = AccountHash::new([1u8; 32]);
688        let key_1 = AccountHash::new([2u8; 32]);
689        let key_2 = AccountHash::new([3u8; 32]);
690        let key_3 = AccountHash::new([4u8; 32]);
691        let associated_keys = {
692            let mut res = AssociatedKeys::new(identity_key, Weight::new(1));
693            res.add_key(key_1, Weight::new(2))
694                .expect("should add key 1");
695            res.add_key(key_2, Weight::new(3))
696                .expect("should add key 2");
697            res.add_key(key_3, Weight::new(4))
698                .expect("should add key 3");
699            res
700        };
701        let mut account = Account::new(
702            AccountHash::new([0u8; 32]),
703            NamedKeys::new(),
704            URef::new([0u8; 32], AccessRights::READ_ADD_WRITE),
705            associated_keys,
706            // deploy: 33 (3*11)
707            ActionThresholds::new(Weight::new(1 + 2 + 3 + 4), Weight::new(1 + 2 + 3 + 4 + 5))
708                .expect("should create thresholds"),
709        );
710
711        assert_eq!(
712            account.remove_associated_key(key_3).unwrap_err(),
713            RemoveKeyFailure::ThresholdViolation,
714        )
715    }
716
717    #[test]
718    fn updating_key_would_violate_action_thresholds() {
719        let identity_key = AccountHash::new([1u8; 32]);
720        let identity_key_weight = Weight::new(1);
721        let key_1 = AccountHash::new([2u8; 32]);
722        let key_1_weight = Weight::new(2);
723        let key_2 = AccountHash::new([3u8; 32]);
724        let key_2_weight = Weight::new(3);
725        let key_3 = AccountHash::new([4u8; 32]);
726        let key_3_weight = Weight::new(4);
727        let associated_keys = {
728            let mut res = AssociatedKeys::new(identity_key, identity_key_weight);
729            res.add_key(key_1, key_1_weight).expect("should add key 1");
730            res.add_key(key_2, key_2_weight).expect("should add key 2");
731            res.add_key(key_3, key_3_weight).expect("should add key 3");
732            // 1 + 2 + 3 + 4
733            res
734        };
735
736        let deployment_threshold = Weight::new(
737            identity_key_weight.value()
738                + key_1_weight.value()
739                + key_2_weight.value()
740                + key_3_weight.value(),
741        );
742        let key_management_threshold = Weight::new(deployment_threshold.value() + 1);
743        let mut account = Account::new(
744            identity_key,
745            NamedKeys::new(),
746            URef::new([0u8; 32], AccessRights::READ_ADD_WRITE),
747            associated_keys,
748            // deploy: 33 (3*11)
749            ActionThresholds::new(deployment_threshold, key_management_threshold)
750                .expect("should create thresholds"),
751        );
752
753        // Decreases by 3
754        assert_eq!(
755            account
756                .clone()
757                .update_associated_key(key_3, Weight::new(1))
758                .unwrap_err(),
759            UpdateKeyFailure::ThresholdViolation,
760        );
761
762        // increase total weight (12)
763        account
764            .update_associated_key(identity_key, Weight::new(3))
765            .unwrap();
766
767        // variant a) decrease total weight by 1 (total 11)
768        account
769            .clone()
770            .update_associated_key(key_3, Weight::new(3))
771            .unwrap();
772        // variant b) decrease total weight by 3 (total 9) - fail
773        assert_eq!(
774            account
775                .update_associated_key(key_3, Weight::new(1))
776                .unwrap_err(),
777            UpdateKeyFailure::ThresholdViolation
778        );
779    }
780
781    #[test]
782    fn overflowing_should_allow_removal() {
783        let identity_key = AccountHash::new([42; 32]);
784        let key_1 = AccountHash::new([2u8; 32]);
785        let key_2 = AccountHash::new([3u8; 32]);
786
787        let associated_keys = {
788            // Identity
789            let mut res = AssociatedKeys::new(identity_key, Weight::new(1));
790
791            // Spare key
792            res.add_key(key_1, Weight::new(2))
793                .expect("should add key 1");
794            // Big key
795            res.add_key(key_2, Weight::new(255))
796                .expect("should add key 2");
797
798            res
799        };
800
801        let mut account = Account::new(
802            identity_key,
803            NamedKeys::new(),
804            URef::new([0u8; 32], AccessRights::READ_ADD_WRITE),
805            associated_keys,
806            ActionThresholds::new(Weight::new(1), Weight::new(254))
807                .expect("should create thresholds"),
808        );
809
810        account.remove_associated_key(key_1).expect("should work")
811    }
812
813    #[test]
814    fn overflowing_should_allow_updating() {
815        let identity_key = AccountHash::new([1; 32]);
816        let identity_key_weight = Weight::new(1);
817        let key_1 = AccountHash::new([2u8; 32]);
818        let key_1_weight = Weight::new(3);
819        let key_2 = AccountHash::new([3u8; 32]);
820        let key_2_weight = Weight::new(255);
821        let deployment_threshold = Weight::new(1);
822        let key_management_threshold = Weight::new(254);
823
824        let associated_keys = {
825            // Identity
826            let mut res = AssociatedKeys::new(identity_key, identity_key_weight);
827
828            // Spare key
829            res.add_key(key_1, key_1_weight).expect("should add key 1");
830            // Big key
831            res.add_key(key_2, key_2_weight).expect("should add key 2");
832
833            res
834        };
835
836        let mut account = Account::new(
837            identity_key,
838            NamedKeys::new(),
839            URef::new([0u8; 32], AccessRights::READ_ADD_WRITE),
840            associated_keys,
841            ActionThresholds::new(deployment_threshold, key_management_threshold)
842                .expect("should create thresholds"),
843        );
844
845        // decrease so total weight would be changed from 1 + 3 + 255 to 1 + 1 + 255
846        account
847            .update_associated_key(key_1, Weight::new(1))
848            .expect("should work");
849    }
850}
851
852#[cfg(test)]
853mod proptests {
854    use proptest::prelude::*;
855
856    use crate::bytesrepr;
857
858    use super::*;
859
860    proptest! {
861        #[test]
862        fn test_value_account(acct in gens::account_arb()) {
863            bytesrepr::test_serialization_roundtrip(&acct);
864        }
865    }
866}