use alloc::{
collections::{BTreeMap, BTreeSet},
string::String,
vec::Vec,
};
use core::{
convert::TryFrom,
fmt::{Debug, Display, Formatter},
};
use failure::Fail;
use hex_fmt::HexFmt;
use crate::{
bytesrepr::{
Error, FromBytes, ToBytes, U32_SERIALIZED_LENGTH, U64_SERIALIZED_LENGTH,
U8_SERIALIZED_LENGTH,
},
contract_api::{runtime, Error as ApiError},
key::{Key, KEY_UREF_SERIALIZED_LENGTH},
unwrap_or_revert::UnwrapOrRevert,
uref::{AccessRights, URef, UREF_SERIALIZED_LENGTH},
};
pub const PURSE_ID_SERIALIZED_LENGTH: usize = UREF_SERIALIZED_LENGTH;
#[derive(Debug)]
pub struct TryFromIntError(());
impl<T> UnwrapOrRevert<T> for Result<T, TryFromIntError> {
fn unwrap_or_revert(self) -> T {
self.unwrap_or_else(|_| runtime::revert(ApiError::Unhandled))
}
fn unwrap_or_revert_with<E: Into<ApiError>>(self, error: E) -> T {
self.unwrap_or_else(|_| runtime::revert(error.into()))
}
}
#[derive(Debug)]
pub struct TryFromSliceForPublicKeyError(());
impl<T> UnwrapOrRevert<T> for Result<T, TryFromSliceForPublicKeyError> {
fn unwrap_or_revert(self) -> T {
self.unwrap_or_else(|_| runtime::revert(ApiError::Deserialize))
}
fn unwrap_or_revert_with<E: Into<ApiError>>(self, error: E) -> T {
self.unwrap_or_else(|_| runtime::revert(error.into()))
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PurseId(URef);
impl PurseId {
pub fn new(uref: URef) -> Self {
PurseId(uref)
}
pub fn value(&self) -> URef {
self.0
}
}
impl From<PurseId> for URef {
fn from(purse_id: PurseId) -> URef {
purse_id.value()
}
}
impl ToBytes for PurseId {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
ToBytes::to_bytes(&self.0)
}
}
impl FromBytes for PurseId {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
<URef>::from_bytes(bytes).map(|(uref, rem)| (PurseId::new(uref), rem))
}
}
#[repr(u32)]
pub enum ActionType {
Deployment = 0,
KeyManagement = 1,
}
impl TryFrom<u32> for ActionType {
type Error = TryFromIntError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
d if d == ActionType::Deployment as u32 => Ok(ActionType::Deployment),
d if d == ActionType::KeyManagement as u32 => Ok(ActionType::KeyManagement),
_ => Err(TryFromIntError(())),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ActionThresholds {
deployment: Weight,
key_management: Weight,
}
#[repr(i32)]
#[derive(Debug, Fail, PartialEq, Eq)]
pub enum SetThresholdFailure {
#[fail(display = "New threshold should be lower or equal than deployment threshold")]
KeyManagementThresholdError = 1,
#[fail(display = "New threshold should be lower or equal than key management threshold")]
DeploymentThresholdError = 2,
#[fail(display = "Unable to set action threshold due to insufficient permissions")]
PermissionDeniedError = 3,
#[fail(
display = "New threshold should be lower or equal than total weight of associated keys"
)]
InsufficientTotalWeight = 4,
}
impl TryFrom<i32> for SetThresholdFailure {
type Error = TryFromIntError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
d if d == SetThresholdFailure::KeyManagementThresholdError as i32 => {
Ok(SetThresholdFailure::KeyManagementThresholdError)
}
d if d == SetThresholdFailure::DeploymentThresholdError as i32 => {
Ok(SetThresholdFailure::DeploymentThresholdError)
}
d if d == SetThresholdFailure::PermissionDeniedError as i32 => {
Ok(SetThresholdFailure::PermissionDeniedError)
}
d if d == SetThresholdFailure::InsufficientTotalWeight as i32 => {
Ok(SetThresholdFailure::InsufficientTotalWeight)
}
_ => Err(TryFromIntError(())),
}
}
}
impl ActionThresholds {
pub fn new(
deployment: Weight,
key_management: Weight,
) -> Result<ActionThresholds, SetThresholdFailure> {
if deployment > key_management {
return Err(SetThresholdFailure::DeploymentThresholdError);
}
Ok(ActionThresholds {
deployment,
key_management,
})
}
pub fn set_deployment_threshold(
&mut self,
new_threshold: Weight,
) -> Result<(), SetThresholdFailure> {
if new_threshold > self.key_management {
Err(SetThresholdFailure::DeploymentThresholdError)
} else {
self.deployment = new_threshold;
Ok(())
}
}
pub fn set_key_management_threshold(
&mut self,
new_threshold: Weight,
) -> Result<(), SetThresholdFailure> {
if self.deployment > new_threshold {
Err(SetThresholdFailure::KeyManagementThresholdError)
} else {
self.key_management = new_threshold;
Ok(())
}
}
pub fn deployment(&self) -> &Weight {
&self.deployment
}
pub fn key_management(&self) -> &Weight {
&self.key_management
}
pub fn set_threshold(
&mut self,
action_type: ActionType,
new_threshold: Weight,
) -> Result<(), SetThresholdFailure> {
match action_type {
ActionType::Deployment => self.set_deployment_threshold(new_threshold),
ActionType::KeyManagement => self.set_key_management_threshold(new_threshold),
}
}
}
impl Default for ActionThresholds {
fn default() -> Self {
ActionThresholds {
deployment: Weight::new(1),
key_management: Weight::new(1),
}
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd)]
pub struct BlockTime(u64);
impl BlockTime {
pub fn new(value: u64) -> Self {
BlockTime(value)
}
pub fn saturating_sub(self, other: BlockTime) -> Self {
BlockTime(self.0.saturating_sub(other.0))
}
}
impl Into<u64> for BlockTime {
fn into(self) -> u64 {
self.0
}
}
pub const PUBLIC_KEY_LENGTH: usize = 32;
pub const MAX_KEYS: usize = 10;
#[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Debug)]
pub struct Weight(u8);
impl Weight {
pub fn new(weight: u8) -> Weight {
Weight(weight)
}
pub fn value(self) -> u8 {
self.0
}
}
pub const WEIGHT_SERIALIZED_LENGTH: usize = U8_SERIALIZED_LENGTH;
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
pub struct PublicKey([u8; PUBLIC_KEY_LENGTH]);
impl Display for PublicKey {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
write!(f, "PublicKey({})", HexFmt(&self.0))
}
}
impl Debug for PublicKey {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
write!(f, "{}", self)
}
}
pub const PUBLIC_KEY_SERIALIZED_LENGTH: usize = PUBLIC_KEY_LENGTH;
impl PublicKey {
pub fn new(key: [u8; PUBLIC_KEY_LENGTH]) -> PublicKey {
PublicKey(key)
}
pub fn value(self) -> [u8; PUBLIC_KEY_LENGTH] {
self.0
}
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}
}
impl From<[u8; PUBLIC_KEY_LENGTH]> for PublicKey {
fn from(key: [u8; PUBLIC_KEY_LENGTH]) -> Self {
PublicKey(key)
}
}
impl TryFrom<&[u8]> for PublicKey {
type Error = TryFromSliceForPublicKeyError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
if bytes.len() != PUBLIC_KEY_LENGTH {
return Err(TryFromSliceForPublicKeyError(()));
}
let mut public_key = [0u8; 32];
public_key.copy_from_slice(bytes);
Ok(PublicKey::new(public_key))
}
}
impl ToBytes for PublicKey {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
ToBytes::to_bytes(&self.0)
}
}
impl FromBytes for PublicKey {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
let (key_bytes, rem): ([u8; PUBLIC_KEY_LENGTH], &[u8]) = FromBytes::from_bytes(bytes)?;
Ok((PublicKey::new(key_bytes), rem))
}
}
#[derive(PartialEq, Eq, Fail, Debug)]
#[repr(i32)]
pub enum AddKeyFailure {
#[fail(display = "Unable to add new associated key because maximum amount of keys is reached")]
MaxKeysLimit = 1,
#[fail(display = "Unable to add new associated key because given key already exists")]
DuplicateKey = 2,
#[fail(display = "Unable to add new associated key due to insufficient permissions")]
PermissionDenied = 3,
}
impl TryFrom<i32> for AddKeyFailure {
type Error = TryFromIntError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
d if d == AddKeyFailure::MaxKeysLimit as i32 => Ok(AddKeyFailure::MaxKeysLimit),
d if d == AddKeyFailure::DuplicateKey as i32 => Ok(AddKeyFailure::DuplicateKey),
d if d == AddKeyFailure::PermissionDenied as i32 => Ok(AddKeyFailure::PermissionDenied),
_ => Err(TryFromIntError(())),
}
}
}
#[derive(Fail, Debug, Eq, PartialEq)]
#[repr(i32)]
pub enum RemoveKeyFailure {
#[fail(display = "Unable to remove a key that does not exist")]
MissingKey = 1,
#[fail(display = "Unable to remove associated key due to insufficient permissions")]
PermissionDenied = 2,
#[fail(display = "Unable to remove a key which would violate action threshold constraints")]
ThresholdViolation = 3,
}
impl TryFrom<i32> for RemoveKeyFailure {
type Error = TryFromIntError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
d if d == RemoveKeyFailure::MissingKey as i32 => Ok(RemoveKeyFailure::MissingKey),
d if d == RemoveKeyFailure::PermissionDenied as i32 => {
Ok(RemoveKeyFailure::PermissionDenied)
}
d if d == RemoveKeyFailure::ThresholdViolation as i32 => {
Ok(RemoveKeyFailure::ThresholdViolation)
}
_ => Err(TryFromIntError(())),
}
}
}
#[derive(PartialEq, Eq, Fail, Debug)]
#[repr(i32)]
pub enum UpdateKeyFailure {
#[fail(display = "Unable to update the value under an associated key that does not exist")]
MissingKey = 1,
#[fail(display = "Unable to add new associated key due to insufficient permissions")]
PermissionDenied = 2,
#[fail(display = "Unable to update weight that would fall below any of action thresholds")]
ThresholdViolation = 3,
}
impl TryFrom<i32> for UpdateKeyFailure {
type Error = TryFromIntError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
d if d == UpdateKeyFailure::MissingKey as i32 => Ok(UpdateKeyFailure::MissingKey),
d if d == UpdateKeyFailure::PermissionDenied as i32 => {
Ok(UpdateKeyFailure::PermissionDenied)
}
d if d == UpdateKeyFailure::ThresholdViolation as i32 => {
Ok(UpdateKeyFailure::ThresholdViolation)
}
_ => Err(TryFromIntError(())),
}
}
}
#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Clone, Debug)]
pub struct AssociatedKeys(BTreeMap<PublicKey, Weight>);
impl AssociatedKeys {
pub fn new(key: PublicKey, weight: Weight) -> AssociatedKeys {
let mut bt: BTreeMap<PublicKey, Weight> = BTreeMap::new();
bt.insert(key, weight);
AssociatedKeys(bt)
}
#[allow(clippy::map_entry)]
pub fn add_key(&mut self, key: PublicKey, weight: Weight) -> Result<(), AddKeyFailure> {
if self.0.len() == MAX_KEYS {
Err(AddKeyFailure::MaxKeysLimit)
} else if self.0.contains_key(&key) {
Err(AddKeyFailure::DuplicateKey)
} else {
self.0.insert(key, weight);
Ok(())
}
}
pub fn remove_key(&mut self, key: &PublicKey) -> Result<(), RemoveKeyFailure> {
self.0
.remove(key)
.map(|_| ())
.ok_or(RemoveKeyFailure::MissingKey)
}
#[allow(clippy::map_entry)]
pub fn update_key(&mut self, key: PublicKey, weight: Weight) -> Result<(), UpdateKeyFailure> {
if !self.0.contains_key(&key) {
return Err(UpdateKeyFailure::MissingKey);
}
self.0.insert(key, weight);
Ok(())
}
pub fn get(&self, key: &PublicKey) -> Option<&Weight> {
self.0.get(key)
}
pub fn contains_key(&self, key: &PublicKey) -> bool {
self.0.contains_key(key)
}
pub fn iter(&self) -> impl Iterator<Item = (&PublicKey, &Weight)> {
self.0.iter()
}
fn calculate_any_keys_weight<'a>(&self, keys: impl Iterator<Item = &'a PublicKey>) -> Weight {
let total = keys
.filter_map(|key| self.0.get(key))
.fold(0u8, |acc, w| acc.saturating_add(w.value()));
Weight::new(total)
}
fn calculate_keys_weight(&self, authorization_keys: &BTreeSet<PublicKey>) -> Weight {
self.calculate_any_keys_weight(authorization_keys.iter())
}
fn total_keys_weight(&self) -> Weight {
self.calculate_any_keys_weight(self.0.keys())
}
fn total_keys_weight_excluding(&self, public_key: PublicKey) -> Weight {
self.calculate_any_keys_weight(self.0.keys().filter(|&&element| element != public_key))
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Account {
public_key: [u8; 32],
named_keys: BTreeMap<String, Key>,
purse_id: PurseId,
associated_keys: AssociatedKeys,
action_thresholds: ActionThresholds,
}
impl Account {
pub fn new(
public_key: [u8; 32],
named_keys: BTreeMap<String, Key>,
purse_id: PurseId,
associated_keys: AssociatedKeys,
action_thresholds: ActionThresholds,
) -> Self {
Account {
public_key,
named_keys,
purse_id,
associated_keys,
action_thresholds,
}
}
pub fn create(
account_addr: [u8; 32],
named_keys: BTreeMap<String, Key>,
purse_id: PurseId,
) -> Self {
let associated_keys = AssociatedKeys::new(PublicKey::new(account_addr), Weight::new(1));
let action_thresholds: ActionThresholds = Default::default();
Account::new(
account_addr,
named_keys,
purse_id,
associated_keys,
action_thresholds,
)
}
pub fn named_keys_append(&mut self, keys: &mut BTreeMap<String, Key>) {
self.named_keys.append(keys);
}
pub fn named_keys(&self) -> &BTreeMap<String, Key> {
&self.named_keys
}
pub fn named_keys_mut(&mut self) -> &mut BTreeMap<String, Key> {
&mut self.named_keys
}
pub fn pub_key(&self) -> [u8; 32] {
self.public_key
}
pub fn purse_id(&self) -> PurseId {
self.purse_id
}
pub fn purse_id_add_only(&self) -> PurseId {
let purse_id_uref = self.purse_id.value();
let add_only_uref = URef::new(purse_id_uref.addr(), AccessRights::ADD);
PurseId::new(add_only_uref)
}
pub fn get_associated_keys(&self) -> impl Iterator<Item = (&PublicKey, &Weight)> {
self.associated_keys.iter()
}
pub fn action_thresholds(&self) -> &ActionThresholds {
&self.action_thresholds
}
pub fn add_associated_key(
&mut self,
public_key: PublicKey,
weight: Weight,
) -> Result<(), AddKeyFailure> {
self.associated_keys.add_key(public_key, weight)
}
fn can_remove_key(&self, public_key: PublicKey) -> bool {
let total_weight_without = self.associated_keys.total_keys_weight_excluding(public_key);
total_weight_without >= *self.action_thresholds().deployment()
&& total_weight_without >= *self.action_thresholds().key_management()
}
fn can_update_key(&self, public_key: PublicKey, weight: Weight) -> bool {
let total_weight = self.associated_keys.total_keys_weight_excluding(public_key);
let new_weight = total_weight.value().saturating_add(weight.value());
new_weight >= self.action_thresholds().deployment().value()
&& new_weight >= self.action_thresholds().key_management().value()
}
pub fn remove_associated_key(&mut self, public_key: PublicKey) -> Result<(), RemoveKeyFailure> {
if self.associated_keys.contains_key(&public_key) {
if !self.can_remove_key(public_key) {
return Err(RemoveKeyFailure::ThresholdViolation);
}
}
self.associated_keys.remove_key(&public_key)
}
pub fn update_associated_key(
&mut self,
public_key: PublicKey,
weight: Weight,
) -> Result<(), UpdateKeyFailure> {
if let Some(current_weight) = self.associated_keys.get(&public_key) {
if weight < *current_weight {
if !self.can_update_key(public_key, weight) {
return Err(UpdateKeyFailure::ThresholdViolation);
}
}
}
self.associated_keys.update_key(public_key, weight)
}
pub fn get_associated_key_weight(&self, public_key: PublicKey) -> Option<&Weight> {
self.associated_keys.get(&public_key)
}
pub fn set_action_threshold(
&mut self,
action_type: ActionType,
weight: Weight,
) -> Result<(), SetThresholdFailure> {
self.can_set_threshold(weight)?;
self.action_thresholds.set_threshold(action_type, weight)
}
pub fn can_set_threshold(&self, new_threshold: Weight) -> Result<(), SetThresholdFailure> {
let total_weight = self.associated_keys.total_keys_weight();
if new_threshold > total_weight {
return Err(SetThresholdFailure::InsufficientTotalWeight);
}
Ok(())
}
pub fn can_authorize(&self, authorization_keys: &BTreeSet<PublicKey>) -> bool {
!authorization_keys.is_empty()
&& authorization_keys
.iter()
.all(|e| self.associated_keys.contains_key(e))
}
pub fn can_deploy_with(&self, authorization_keys: &BTreeSet<PublicKey>) -> bool {
let total_weight = self
.associated_keys
.calculate_keys_weight(authorization_keys);
total_weight >= *self.action_thresholds().deployment()
}
pub fn can_manage_keys_with(&self, authorization_keys: &BTreeSet<PublicKey>) -> bool {
let total_weight = self
.associated_keys
.calculate_keys_weight(authorization_keys);
total_weight >= *self.action_thresholds().key_management()
}
}
impl ToBytes for Weight {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
ToBytes::to_bytes(&self.0)
}
}
impl FromBytes for Weight {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
let (byte, rem): (u8, &[u8]) = FromBytes::from_bytes(bytes)?;
Ok((Weight::new(byte), rem))
}
}
impl ToBytes for AssociatedKeys {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
ToBytes::to_bytes(&self.0)
}
}
impl FromBytes for AssociatedKeys {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
let (keys_map, rem): (BTreeMap<PublicKey, Weight>, &[u8]) = FromBytes::from_bytes(bytes)?;
let mut keys = AssociatedKeys::default();
keys_map.into_iter().for_each(|(k, v)| {
keys.add_key(k, v).unwrap();
});
Ok((keys, rem))
}
}
pub const BLOCKTIME_SERIALIZED_LENGTH: usize = U64_SERIALIZED_LENGTH;
impl ToBytes for BlockTime {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
self.0.to_bytes()
}
}
impl FromBytes for BlockTime {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
let (time, rem) = FromBytes::from_bytes(bytes)?;
Ok((BlockTime::new(time), rem))
}
}
impl ToBytes for ActionThresholds {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
let mut result = Vec::with_capacity(2 * WEIGHT_SERIALIZED_LENGTH);
result.extend(&self.deployment.to_bytes()?);
result.extend(&self.key_management.to_bytes()?);
Ok(result)
}
}
impl FromBytes for ActionThresholds {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
let (deployment, rem): (Weight, &[u8]) = FromBytes::from_bytes(&bytes)?;
let (key_management, rem): (Weight, &[u8]) = FromBytes::from_bytes(&rem)?;
let ret = ActionThresholds {
deployment,
key_management,
};
Ok((ret, rem))
}
}
impl ToBytes for Account {
fn to_bytes(&self) -> Result<Vec<u8>, Error> {
let action_thresholds_size = 2 * (WEIGHT_SERIALIZED_LENGTH + U8_SERIALIZED_LENGTH);
let associated_keys_size = self.associated_keys.0.len()
* (PUBLIC_KEY_SERIALIZED_LENGTH + WEIGHT_SERIALIZED_LENGTH)
+ U32_SERIALIZED_LENGTH;
let named_keys_size =
KEY_UREF_SERIALIZED_LENGTH * self.named_keys.len() + U32_SERIALIZED_LENGTH;
let purse_id_size = KEY_UREF_SERIALIZED_LENGTH;
let serialized_account_size = PUBLIC_KEY_LENGTH
+ named_keys_size
+ purse_id_size
+ associated_keys_size
+ action_thresholds_size;
if serialized_account_size >= u32::max_value() as usize {
return Err(Error::OutOfMemoryError);
}
let mut result: Vec<u8> = Vec::with_capacity(serialized_account_size);
result.extend(&self.public_key.to_bytes()?);
result.append(&mut self.named_keys.to_bytes()?);
result.append(&mut self.purse_id.value().to_bytes()?);
result.append(&mut self.associated_keys.to_bytes()?);
result.append(&mut self.action_thresholds.to_bytes()?);
Ok(result)
}
}
impl FromBytes for Account {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> {
let (public_key, rem): ([u8; 32], &[u8]) = FromBytes::from_bytes(bytes)?;
let (named_keys, rem): (BTreeMap<String, Key>, &[u8]) = FromBytes::from_bytes(rem)?;
let (purse_id, rem): (URef, &[u8]) = FromBytes::from_bytes(rem)?;
let (associated_keys, rem): (AssociatedKeys, &[u8]) = FromBytes::from_bytes(rem)?;
let (action_thresholds, rem): (ActionThresholds, &[u8]) = FromBytes::from_bytes(rem)?;
let purse_id = PurseId::new(purse_id);
Ok((
Account {
public_key,
named_keys,
purse_id,
associated_keys,
action_thresholds,
},
rem,
))
}
}
#[cfg(test)]
mod tests {
#[rustfmt::skip]
use alloc::vec;
use alloc::{
collections::{BTreeMap, BTreeSet},
vec::Vec,
};
use core::{convert::TryFrom, iter::FromIterator};
use crate::{
uref::{AccessRights, URef},
value::account::{
Account, ActionThresholds, ActionType, AddKeyFailure, AssociatedKeys, PublicKey,
PurseId, RemoveKeyFailure, SetThresholdFailure, UpdateKeyFailure, Weight, MAX_KEYS,
PUBLIC_KEY_LENGTH,
},
};
#[test]
fn associated_keys_add() {
let mut keys = AssociatedKeys::new([0u8; PUBLIC_KEY_LENGTH].into(), Weight::new(1));
let new_pk = PublicKey([1u8; PUBLIC_KEY_LENGTH]);
let new_pk_weight = Weight::new(2);
assert!(keys.add_key(new_pk, new_pk_weight).is_ok());
assert_eq!(keys.get(&new_pk), Some(&new_pk_weight))
}
#[test]
fn associated_keys_add_full() {
let map = (0..MAX_KEYS).map(|k| {
(
PublicKey([k as u8; PUBLIC_KEY_LENGTH]),
Weight::new(k as u8),
)
});
assert_eq!(map.len(), 10);
let mut keys = {
let mut tmp = AssociatedKeys::default();
map.for_each(|(key, weight)| assert!(tmp.add_key(key, weight).is_ok()));
tmp
};
assert_eq!(
keys.add_key(PublicKey([100u8; PUBLIC_KEY_LENGTH]), Weight::new(100)),
Err(AddKeyFailure::MaxKeysLimit)
)
}
#[test]
fn associated_keys_add_duplicate() {
let pk = PublicKey([0u8; PUBLIC_KEY_LENGTH]);
let weight = Weight::new(1);
let mut keys = AssociatedKeys::new(pk, weight);
assert_eq!(
keys.add_key(pk, Weight::new(10)),
Err(AddKeyFailure::DuplicateKey)
);
assert_eq!(keys.get(&pk), Some(&weight));
}
#[test]
fn associated_keys_remove() {
let pk = PublicKey([0u8; PUBLIC_KEY_LENGTH]);
let weight = Weight::new(1);
let mut keys = AssociatedKeys::new(pk, weight);
assert!(keys.remove_key(&pk).is_ok());
assert!(keys
.remove_key(&PublicKey([1u8; PUBLIC_KEY_LENGTH]))
.is_err());
}
#[test]
fn associated_keys_can_authorize_keys() {
let key_1 = PublicKey::new([0; 32]);
let key_2 = PublicKey::new([1; 32]);
let key_3 = PublicKey::new([2; 32]);
let mut keys = AssociatedKeys::default();
keys.add_key(key_2, Weight::new(2))
.expect("should add key_1");
keys.add_key(key_1, Weight::new(1))
.expect("should add key_1");
keys.add_key(key_3, Weight::new(3))
.expect("should add key_1");
let account = Account::new(
[0u8; 32],
BTreeMap::new(),
PurseId::new(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)),
keys,
ActionThresholds::new(Weight::new(33), Weight::new(48))
.expect("should create thresholds"),
);
assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_3, key_2, key_1])));
assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1, key_3, key_2])));
assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1, key_2])));
assert!(account.can_authorize(&BTreeSet::from_iter(vec![key_1])));
assert!(!account.can_authorize(&BTreeSet::from_iter(vec![
key_1,
key_2,
PublicKey::new([42; 32])
])));
assert!(!account.can_authorize(&BTreeSet::from_iter(vec![
PublicKey::new([42; 32]),
key_1,
key_2
])));
assert!(!account.can_authorize(&BTreeSet::from_iter(vec![
PublicKey::new([43; 32]),
PublicKey::new([44; 32]),
PublicKey::new([42; 32])
])));
assert!(!account.can_authorize(&BTreeSet::new()));
}
#[test]
fn associated_keys_calculate_keys_once() {
let key_1 = PublicKey::new([0; 32]);
let key_2 = PublicKey::new([1; 32]);
let key_3 = PublicKey::new([2; 32]);
let mut keys = AssociatedKeys::default();
keys.add_key(key_2, Weight::new(2))
.expect("should add key_1");
keys.add_key(key_1, Weight::new(1))
.expect("should add key_1");
keys.add_key(key_3, Weight::new(3))
.expect("should add key_1");
assert_eq!(
keys.calculate_keys_weight(&BTreeSet::from_iter(vec![
key_1, key_2, key_3, key_1, key_2, key_3,
])),
Weight::new(1 + 2 + 3)
);
}
#[test]
fn account_can_deploy_with() {
let associated_keys = {
let mut res = AssociatedKeys::new(PublicKey::new([1u8; 32]), Weight::new(1));
res.add_key(PublicKey::new([2u8; 32]), Weight::new(11))
.expect("should add key 1");
res.add_key(PublicKey::new([3u8; 32]), Weight::new(11))
.expect("should add key 2");
res.add_key(PublicKey::new([4u8; 32]), Weight::new(11))
.expect("should add key 3");
res
};
let account = Account::new(
[0u8; 32],
BTreeMap::new(),
PurseId::new(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)),
associated_keys,
ActionThresholds::new(Weight::new(33), Weight::new(48))
.expect("should create thresholds"),
);
assert!(!account.can_deploy_with(&BTreeSet::from_iter(vec![
PublicKey::new([3u8; 32]),
PublicKey::new([2u8; 32]),
])));
assert!(account.can_deploy_with(&BTreeSet::from_iter(vec![
PublicKey::new([4u8; 32]),
PublicKey::new([3u8; 32]),
PublicKey::new([2u8; 32]),
])));
assert!(account.can_deploy_with(&BTreeSet::from_iter(vec![
PublicKey::new([2u8; 32]),
PublicKey::new([1u8; 32]),
PublicKey::new([4u8; 32]),
PublicKey::new([3u8; 32]),
])));
}
#[test]
fn associated_keys_total_weight() {
let associated_keys = {
let mut res = AssociatedKeys::new(PublicKey::new([1u8; 32]), Weight::new(1));
res.add_key(PublicKey::new([2u8; 32]), Weight::new(11))
.expect("should add key 1");
res.add_key(PublicKey::new([3u8; 32]), Weight::new(12))
.expect("should add key 2");
res.add_key(PublicKey::new([4u8; 32]), Weight::new(13))
.expect("should add key 3");
res
};
assert_eq!(
associated_keys.total_keys_weight(),
Weight::new(1 + 11 + 12 + 13)
);
}
#[test]
fn associated_keys_total_weight_excluding() {
let identity_key = PublicKey::new([1u8; 32]);
let identity_key_weight = Weight::new(1);
let key_1 = PublicKey::new([2u8; 32]);
let key_1_weight = Weight::new(11);
let key_2 = PublicKey::new([3u8; 32]);
let key_2_weight = Weight::new(12);
let key_3 = PublicKey::new([4u8; 32]);
let key_3_weight = Weight::new(13);
let associated_keys = {
let mut res = AssociatedKeys::new(identity_key, identity_key_weight);
res.add_key(key_1, key_1_weight).expect("should add key 1");
res.add_key(key_2, key_2_weight).expect("should add key 2");
res.add_key(key_3, key_3_weight).expect("should add key 3");
res
};
assert_eq!(
associated_keys.total_keys_weight_excluding(key_2),
Weight::new(identity_key_weight.value() + key_1_weight.value() + key_3_weight.value())
);
}
#[test]
fn account_can_manage_keys_with() {
let associated_keys = {
let mut res = AssociatedKeys::new(PublicKey::new([1u8; 32]), Weight::new(1));
res.add_key(PublicKey::new([2u8; 32]), Weight::new(11))
.expect("should add key 1");
res.add_key(PublicKey::new([3u8; 32]), Weight::new(11))
.expect("should add key 2");
res.add_key(PublicKey::new([4u8; 32]), Weight::new(11))
.expect("should add key 3");
res
};
let account = Account::new(
[0u8; 32],
BTreeMap::new(),
PurseId::new(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)),
associated_keys,
ActionThresholds::new(Weight::new(11), Weight::new(33))
.expect("should create thresholds"),
);
assert!(!account.can_manage_keys_with(&BTreeSet::from_iter(vec![
PublicKey::new([3u8; 32]),
PublicKey::new([2u8; 32]),
])));
assert!(account.can_manage_keys_with(&BTreeSet::from_iter(vec![
PublicKey::new([4u8; 32]),
PublicKey::new([3u8; 32]),
PublicKey::new([2u8; 32]),
])));
assert!(account.can_manage_keys_with(&BTreeSet::from_iter(vec![
PublicKey::new([2u8; 32]),
PublicKey::new([1u8; 32]),
PublicKey::new([4u8; 32]),
PublicKey::new([3u8; 32]),
])));
}
#[test]
fn public_key_from_slice() {
let bytes: Vec<u8> = (0..32).collect();
let public_key = PublicKey::try_from(&bytes[..]).expect("should create public key");
assert_eq!(&bytes, &public_key.value());
}
#[test]
fn public_key_from_slice_too_small() {
let _public_key =
PublicKey::try_from(&[0u8; 31][..]).expect_err("should not create public key");
}
#[test]
fn public_key_from_slice_too_big() {
let _public_key =
PublicKey::try_from(&[0u8; 33][..]).expect_err("should not create public key");
}
#[test]
fn should_create_new_action_thresholds() {
let action_thresholds = ActionThresholds::new(Weight::new(1), Weight::new(42)).unwrap();
assert_eq!(*action_thresholds.deployment(), Weight::new(1));
assert_eq!(*action_thresholds.key_management(), Weight::new(42));
}
#[test]
#[should_panic]
fn should_not_create_action_thresholds_with_invalid_deployment_threshold() {
ActionThresholds::new(Weight::new(5), Weight::new(1)).unwrap();
}
#[test]
fn set_action_threshold_higher_than_total_weight() {
let identity_key = PublicKey::new([1u8; 32]);
let key_1 = PublicKey::new([2u8; 32]);
let key_2 = PublicKey::new([3u8; 32]);
let key_3 = PublicKey::new([4u8; 32]);
let associated_keys = {
let mut res = AssociatedKeys::new(identity_key, Weight::new(1));
res.add_key(key_1, Weight::new(2))
.expect("should add key 1");
res.add_key(key_2, Weight::new(3))
.expect("should add key 2");
res.add_key(key_3, Weight::new(4))
.expect("should add key 3");
res
};
let mut account = Account::new(
[0u8; 32],
BTreeMap::new(),
PurseId::new(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)),
associated_keys,
ActionThresholds::new(Weight::new(33), Weight::new(48))
.expect("should create thresholds"),
);
assert_eq!(
account
.set_action_threshold(ActionType::Deployment, Weight::new(1 + 2 + 3 + 4 + 1))
.unwrap_err(),
SetThresholdFailure::InsufficientTotalWeight,
);
assert_eq!(
account
.set_action_threshold(ActionType::Deployment, Weight::new(1 + 2 + 3 + 4 + 245))
.unwrap_err(),
SetThresholdFailure::InsufficientTotalWeight,
)
}
#[test]
fn remove_key_would_violate_action_thresholds() {
let identity_key = PublicKey::new([1u8; 32]);
let key_1 = PublicKey::new([2u8; 32]);
let key_2 = PublicKey::new([3u8; 32]);
let key_3 = PublicKey::new([4u8; 32]);
let associated_keys = {
let mut res = AssociatedKeys::new(identity_key, Weight::new(1));
res.add_key(key_1, Weight::new(2))
.expect("should add key 1");
res.add_key(key_2, Weight::new(3))
.expect("should add key 2");
res.add_key(key_3, Weight::new(4))
.expect("should add key 3");
res
};
let mut account = Account::new(
[0u8; 32],
BTreeMap::new(),
PurseId::new(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)),
associated_keys,
ActionThresholds::new(Weight::new(1 + 2 + 3 + 4), Weight::new(1 + 2 + 3 + 4 + 5))
.expect("should create thresholds"),
);
assert_eq!(
account.remove_associated_key(key_3).unwrap_err(),
RemoveKeyFailure::ThresholdViolation,
)
}
#[test]
fn updating_key_would_violate_action_thresholds() {
let identity_key = PublicKey::new([1u8; 32]);
let identity_key_weight = Weight::new(1);
let key_1 = PublicKey::new([2u8; 32]);
let key_1_weight = Weight::new(2);
let key_2 = PublicKey::new([3u8; 32]);
let key_2_weight = Weight::new(3);
let key_3 = PublicKey::new([4u8; 32]);
let key_3_weight = Weight::new(4);
let associated_keys = {
let mut res = AssociatedKeys::new(identity_key, identity_key_weight);
res.add_key(key_1, key_1_weight).expect("should add key 1");
res.add_key(key_2, key_2_weight).expect("should add key 2");
res.add_key(key_3, key_3_weight).expect("should add key 3");
res
};
let deployment_threshold = Weight::new(
identity_key_weight.value()
+ key_1_weight.value()
+ key_2_weight.value()
+ key_3_weight.value(),
);
let key_management_threshold = Weight::new(deployment_threshold.value() + 1);
let mut account = Account::new(
identity_key.value(),
BTreeMap::new(),
PurseId::new(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)),
associated_keys,
ActionThresholds::new(deployment_threshold, key_management_threshold)
.expect("should create thresholds"),
);
assert_eq!(
account
.clone()
.update_associated_key(key_3, Weight::new(1))
.unwrap_err(),
UpdateKeyFailure::ThresholdViolation,
);
account
.update_associated_key(identity_key, Weight::new(3))
.unwrap();
account
.clone()
.update_associated_key(key_3, Weight::new(3))
.unwrap();
assert_eq!(
account
.clone()
.update_associated_key(key_3, Weight::new(1))
.unwrap_err(),
UpdateKeyFailure::ThresholdViolation
);
}
#[test]
fn overflowing_keys_weight() {
let identity_key = PublicKey::new([1u8; 32]);
let key_1 = PublicKey::new([2u8; 32]);
let key_2 = PublicKey::new([3u8; 32]);
let key_3 = PublicKey::new([4u8; 32]);
let identity_key_weight = Weight::new(250);
let weight_1 = Weight::new(1);
let weight_2 = Weight::new(2);
let weight_3 = Weight::new(3);
let saturated_weight = Weight::new(u8::max_value());
let associated_keys = {
let mut res = AssociatedKeys::new(identity_key, identity_key_weight);
res.add_key(key_1, weight_1).expect("should add key 1");
res.add_key(key_2, weight_2).expect("should add key 2");
res.add_key(key_3, weight_3).expect("should add key 3");
res
};
assert_eq!(
associated_keys.calculate_keys_weight(&BTreeSet::from_iter(vec![
identity_key,
key_1,
key_2,
key_3,
])),
saturated_weight,
);
}
#[test]
fn overflowing_should_allow_removal() {
let identity_key = PublicKey::new([42; 32]);
let key_1 = PublicKey::new([2u8; 32]);
let key_2 = PublicKey::new([3u8; 32]);
let associated_keys = {
let mut res = AssociatedKeys::new(identity_key, Weight::new(1));
res.add_key(key_1, Weight::new(2))
.expect("should add key 1");
res.add_key(key_2, Weight::new(255))
.expect("should add key 2");
res
};
let mut account = Account::new(
identity_key.value(),
BTreeMap::new(),
PurseId::new(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)),
associated_keys,
ActionThresholds::new(Weight::new(1), Weight::new(254))
.expect("should create thresholds"),
);
account.remove_associated_key(key_1).expect("should work")
}
#[test]
fn overflowing_should_allow_updating() {
let identity_key = PublicKey::new([1; 32]);
let identity_key_weight = Weight::new(1);
let key_1 = PublicKey::new([2u8; 32]);
let key_1_weight = Weight::new(3);
let key_2 = PublicKey::new([3u8; 32]);
let key_2_weight = Weight::new(255);
let deployment_threshold = Weight::new(1);
let key_management_threshold = Weight::new(254);
let associated_keys = {
let mut res = AssociatedKeys::new(identity_key, identity_key_weight);
res.add_key(key_1, key_1_weight).expect("should add key 1");
res.add_key(key_2, key_2_weight).expect("should add key 2");
res
};
let mut account = Account::new(
identity_key.value(),
BTreeMap::new(),
PurseId::new(URef::new([0u8; 32], AccessRights::READ_ADD_WRITE)),
associated_keys,
ActionThresholds::new(deployment_threshold, key_management_threshold)
.expect("should create thresholds"),
);
account
.update_associated_key(key_1, Weight::new(1))
.expect("should work");
}
}