#![allow(clippy::field_reassign_with_default)]
pub mod action_thresholds;
mod action_type;
pub mod associated_keys;
mod entry_points;
mod error;
mod weight;
use alloc::{
collections::{btree_map::Entry, BTreeMap, BTreeSet},
format,
string::{String, ToString},
vec::Vec,
};
use blake2::{
digest::{Update, VariableOutput},
VarBlake2b,
};
use core::{
array::TryFromSliceError,
convert::{TryFrom, TryInto},
fmt::{self, Debug, Display, Formatter},
iter,
};
#[cfg(feature = "json-schema")]
use crate::SecretKey;
#[cfg(feature = "datasize")]
use datasize::DataSize;
#[cfg(feature = "json-schema")]
use once_cell::sync::Lazy;
#[cfg(any(feature = "testing", test))]
use rand::{
distributions::{Distribution, Standard},
Rng,
};
#[cfg(feature = "json-schema")]
use schemars::JsonSchema;
use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "json-schema")]
use serde_map_to_array::KeyValueJsonSchema;
use serde_map_to_array::{BTreeMapToArray, KeyValueLabels};
pub use self::{
action_thresholds::ActionThresholds,
action_type::ActionType,
associated_keys::AssociatedKeys,
entry_points::{
EntryPoint, EntryPointAccess, EntryPointAddr, EntryPointPayment, EntryPointType,
EntryPointValue, EntryPoints, Parameter, Parameters, DEFAULT_ENTRY_POINT_NAME,
},
error::{FromAccountHashStrError, TryFromIntError, TryFromSliceForAccountHashError},
weight::{Weight, WEIGHT_SERIALIZED_LENGTH},
};
use crate::{
account::{
Account, AccountHash, AddKeyFailure, RemoveKeyFailure, SetThresholdFailure,
UpdateKeyFailure,
},
byte_code::ByteCodeHash,
bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
checksummed_hex,
contract_messages::TopicNameHash,
contracts::{Contract, ContractHash},
system::SystemEntityType,
transaction::TransactionRuntime,
uref::{self, URef},
AccessRights, ApiError, CLType, CLTyped, CLValue, CLValueError, ContextAccessRights, HashAddr,
Key, NamedKeys, PackageHash, ProtocolVersion, PublicKey, Tagged, BLAKE2B_DIGEST_LENGTH,
KEY_HASH_LENGTH,
};
pub const MAX_GROUPS: u8 = 10;
pub const MAX_TOTAL_UREFS: usize = 100;
pub const ADDRESSABLE_ENTITY_STRING_PREFIX: &str = "addressable-entity-";
pub const ENTITY_PREFIX: &str = "entity-";
pub const ACCOUNT_ENTITY_PREFIX: &str = "account-";
pub const CONTRACT_ENTITY_PREFIX: &str = "contract-";
pub const SYSTEM_ENTITY_PREFIX: &str = "system-";
pub const NAMED_KEY_PREFIX: &str = "named-key-";
#[derive(Debug, PartialEq, Eq)]
#[repr(u8)]
#[non_exhaustive]
pub enum Error {
PreviouslyUsedVersion = 1,
EntityNotFound = 2,
GroupAlreadyExists = 3,
MaxGroupsExceeded = 4,
MaxTotalURefsExceeded = 5,
GroupDoesNotExist = 6,
UnableToRemoveURef = 7,
GroupInUse = 8,
URefAlreadyExists = 9,
}
impl TryFrom<u8> for Error {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
let error = match value {
v if v == Self::PreviouslyUsedVersion as u8 => Self::PreviouslyUsedVersion,
v if v == Self::EntityNotFound as u8 => Self::EntityNotFound,
v if v == Self::GroupAlreadyExists as u8 => Self::GroupAlreadyExists,
v if v == Self::MaxGroupsExceeded as u8 => Self::MaxGroupsExceeded,
v if v == Self::MaxTotalURefsExceeded as u8 => Self::MaxTotalURefsExceeded,
v if v == Self::GroupDoesNotExist as u8 => Self::GroupDoesNotExist,
v if v == Self::UnableToRemoveURef as u8 => Self::UnableToRemoveURef,
v if v == Self::GroupInUse as u8 => Self::GroupInUse,
v if v == Self::URefAlreadyExists as u8 => Self::URefAlreadyExists,
_ => return Err(()),
};
Ok(error)
}
}
#[derive(Debug)]
pub struct TryFromSliceForContractHashError(());
impl Display for TryFromSliceForContractHashError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "failed to retrieve from slice")
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FromStrError {
InvalidPrefix,
Hex(base16::DecodeError),
Account(TryFromSliceForAccountHashError),
Hash(TryFromSliceError),
URef(uref::FromStrError),
BytesRepr(bytesrepr::Error),
}
impl From<base16::DecodeError> for FromStrError {
fn from(error: base16::DecodeError) -> Self {
FromStrError::Hex(error)
}
}
impl From<TryFromSliceError> for FromStrError {
fn from(error: TryFromSliceError) -> Self {
FromStrError::Hash(error)
}
}
impl From<uref::FromStrError> for FromStrError {
fn from(error: uref::FromStrError) -> Self {
FromStrError::URef(error)
}
}
impl Display for FromStrError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
FromStrError::InvalidPrefix => write!(f, "invalid prefix"),
FromStrError::Hex(error) => write!(f, "decode from hex: {}", error),
FromStrError::Hash(error) => write!(f, "hash from string error: {}", error),
FromStrError::URef(error) => write!(f, "uref from string error: {:?}", error),
FromStrError::Account(error) => {
write!(f, "account hash from string error: {:?}", error)
}
FromStrError::BytesRepr(error) => {
write!(f, "bytesrepr error: {:?}", error)
}
}
}
}
#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(
feature = "json-schema",
derive(JsonSchema),
schemars(description = "The hex-encoded address of the addressable entity.")
)]
pub struct AddressableEntityHash(
#[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))] HashAddr,
);
impl AddressableEntityHash {
pub const fn new(value: HashAddr) -> AddressableEntityHash {
AddressableEntityHash(value)
}
pub fn entity_addr(&self, entity: AddressableEntity) -> EntityAddr {
entity.entity_addr(*self)
}
pub fn value(&self) -> HashAddr {
self.0
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_formatted_string(self) -> String {
format!(
"{}{}",
ADDRESSABLE_ENTITY_STRING_PREFIX,
base16::encode_lower(&self.0),
)
}
pub fn to_hex_string(&self) -> String {
base16::encode_lower(&self.0)
}
pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
let remainder = input
.strip_prefix(ADDRESSABLE_ENTITY_STRING_PREFIX)
.ok_or(FromStrError::InvalidPrefix)?;
let bytes = HashAddr::try_from(checksummed_hex::decode(remainder)?.as_ref())?;
Ok(AddressableEntityHash(bytes))
}
}
impl From<ContractHash> for AddressableEntityHash {
fn from(contract_hash: ContractHash) -> Self {
AddressableEntityHash::new(contract_hash.value())
}
}
impl Display for AddressableEntityHash {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", base16::encode_lower(&self.0))
}
}
impl Debug for AddressableEntityHash {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"AddressableEntityHash({})",
base16::encode_lower(&self.0)
)
}
}
impl CLTyped for AddressableEntityHash {
fn cl_type() -> CLType {
CLType::ByteArray(KEY_HASH_LENGTH as u32)
}
}
impl ToBytes for AddressableEntityHash {
#[inline(always)]
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
self.0.to_bytes()
}
#[inline(always)]
fn serialized_length(&self) -> usize {
self.0.serialized_length()
}
#[inline(always)]
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
writer.extend_from_slice(&self.0);
Ok(())
}
}
impl FromBytes for AddressableEntityHash {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (bytes, rem) = FromBytes::from_bytes(bytes)?;
Ok((AddressableEntityHash::new(bytes), rem))
}
}
impl From<[u8; 32]> for AddressableEntityHash {
fn from(bytes: [u8; 32]) -> Self {
AddressableEntityHash(bytes)
}
}
impl TryFrom<Key> for AddressableEntityHash {
type Error = ApiError;
fn try_from(value: Key) -> Result<Self, Self::Error> {
if let Key::AddressableEntity(entity_addr) = value {
Ok(AddressableEntityHash::new(entity_addr.value()))
} else {
Err(ApiError::Formatting)
}
}
}
impl Serialize for AddressableEntityHash {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_formatted_string().serialize(serializer)
} else {
self.0.serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for AddressableEntityHash {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let formatted_string = String::deserialize(deserializer)?;
AddressableEntityHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
} else {
let bytes = HashAddr::deserialize(deserializer)?;
Ok(AddressableEntityHash(bytes))
}
}
}
impl AsRef<[u8]> for AddressableEntityHash {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl TryFrom<&[u8]> for AddressableEntityHash {
type Error = TryFromSliceForContractHashError;
fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceForContractHashError> {
HashAddr::try_from(bytes)
.map(AddressableEntityHash::new)
.map_err(|_| TryFromSliceForContractHashError(()))
}
}
impl TryFrom<&Vec<u8>> for AddressableEntityHash {
type Error = TryFromSliceForContractHashError;
fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
HashAddr::try_from(bytes as &[u8])
.map(AddressableEntityHash::new)
.map_err(|_| TryFromSliceForContractHashError(()))
}
}
#[cfg(any(feature = "testing", test))]
impl Distribution<AddressableEntityHash> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> AddressableEntityHash {
AddressableEntityHash(rng.gen())
}
}
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[repr(u8)]
pub enum EntityKindTag {
System = 0,
Account = 1,
SmartContract = 2,
}
impl TryFrom<u8> for EntityKindTag {
type Error = bytesrepr::Error;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(EntityKindTag::System),
1 => Ok(EntityKindTag::Account),
2 => Ok(EntityKindTag::SmartContract),
_ => Err(bytesrepr::Error::Formatting),
}
}
}
impl ToBytes for EntityKindTag {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
(*self as u8).to_bytes()
}
fn serialized_length(&self) -> usize {
U8_SERIALIZED_LENGTH
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
(*self as u8).write_bytes(writer)
}
}
impl FromBytes for EntityKindTag {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (entity_kind_tag, remainder) = u8::from_bytes(bytes)?;
Ok((entity_kind_tag.try_into()?, remainder))
}
}
impl Display for EntityKindTag {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
EntityKindTag::System => {
write!(f, "system")
}
EntityKindTag::Account => {
write!(f, "account")
}
EntityKindTag::SmartContract => {
write!(f, "contract")
}
}
}
}
#[cfg(any(feature = "testing", test))]
impl Distribution<EntityKindTag> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> EntityKindTag {
match rng.gen_range(0..=2) {
0 => EntityKindTag::System,
1 => EntityKindTag::Account,
2 => EntityKindTag::SmartContract,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub enum EntityKind {
System(SystemEntityType),
Account(AccountHash),
SmartContract(TransactionRuntime),
}
impl EntityKind {
pub fn maybe_account_hash(&self) -> Option<AccountHash> {
match self {
Self::Account(account_hash) => Some(*account_hash),
Self::SmartContract(_) | Self::System(_) => None,
}
}
pub fn associated_keys(&self) -> AssociatedKeys {
match self {
Self::Account(account_hash) => AssociatedKeys::new(*account_hash, Weight::new(1)),
Self::SmartContract(_) | Self::System(_) => AssociatedKeys::default(),
}
}
pub fn is_system(&self) -> bool {
matches!(self, Self::System(_))
}
pub fn is_system_mint(&self) -> bool {
matches!(self, Self::System(SystemEntityType::Mint))
}
pub fn is_system_auction(&self) -> bool {
matches!(self, Self::System(SystemEntityType::Auction))
}
pub fn is_system_account(&self) -> bool {
match self {
Self::Account(account_hash) => {
if *account_hash == PublicKey::System.to_account_hash() {
return true;
}
false
}
_ => false,
}
}
}
impl Tagged<EntityKindTag> for EntityKind {
fn tag(&self) -> EntityKindTag {
match self {
EntityKind::System(_) => EntityKindTag::System,
EntityKind::Account(_) => EntityKindTag::Account,
EntityKind::SmartContract(_) => EntityKindTag::SmartContract,
}
}
}
impl Tagged<u8> for EntityKind {
fn tag(&self) -> u8 {
let package_kind_tag: EntityKindTag = self.tag();
package_kind_tag as u8
}
}
impl ToBytes for EntityKind {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
self.write_bytes(&mut buffer)?;
Ok(buffer)
}
fn serialized_length(&self) -> usize {
U8_SERIALIZED_LENGTH
+ match self {
EntityKind::SmartContract(transaction_runtime) => {
transaction_runtime.serialized_length()
}
EntityKind::System(system_entity_type) => system_entity_type.serialized_length(),
EntityKind::Account(account_hash) => account_hash.serialized_length(),
}
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
match self {
EntityKind::SmartContract(transaction_runtime) => {
writer.push(self.tag());
transaction_runtime.write_bytes(writer)
}
EntityKind::System(system_entity_type) => {
writer.push(self.tag());
system_entity_type.write_bytes(writer)
}
EntityKind::Account(account_hash) => {
writer.push(self.tag());
account_hash.write_bytes(writer)
}
}
}
}
impl FromBytes for EntityKind {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (tag, remainder) = EntityKindTag::from_bytes(bytes)?;
match tag {
EntityKindTag::System => {
let (entity_type, remainder) = SystemEntityType::from_bytes(remainder)?;
Ok((EntityKind::System(entity_type), remainder))
}
EntityKindTag::Account => {
let (account_hash, remainder) = AccountHash::from_bytes(remainder)?;
Ok((EntityKind::Account(account_hash), remainder))
}
EntityKindTag::SmartContract => {
let (transaction_runtime, remainder) = TransactionRuntime::from_bytes(remainder)?;
Ok((EntityKind::SmartContract(transaction_runtime), remainder))
}
}
}
}
impl Display for EntityKind {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
EntityKind::System(system_entity) => {
write!(f, "system-entity-kind({})", system_entity)
}
EntityKind::Account(account_hash) => {
write!(f, "account-entity-kind({})", account_hash)
}
EntityKind::SmartContract(transaction_runtime) => {
write!(f, "smart-contract-entity-kind({})", transaction_runtime)
}
}
}
}
#[cfg(any(feature = "testing", test))]
impl Distribution<EntityKind> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> EntityKind {
match rng.gen_range(0..=2) {
0 => EntityKind::System(rng.gen()),
1 => EntityKind::Account(rng.gen()),
2 => EntityKind::SmartContract(rng.gen()),
_ => unreachable!(),
}
}
}
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema), schemars(untagged))]
pub enum EntityAddr {
System(#[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))] HashAddr),
Account(#[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))] HashAddr),
SmartContract(#[cfg_attr(feature = "json-schema", schemars(skip, with = "String"))] HashAddr),
}
impl EntityAddr {
pub const LENGTH: usize = U8_SERIALIZED_LENGTH + KEY_HASH_LENGTH;
pub const fn new_system(hash_addr: HashAddr) -> Self {
Self::System(hash_addr)
}
pub const fn new_account(hash_addr: HashAddr) -> Self {
Self::Account(hash_addr)
}
pub const fn new_smart_contract(hash_addr: HashAddr) -> Self {
Self::SmartContract(hash_addr)
}
pub fn new_of_kind(entity_kind: EntityKind, hash_addr: HashAddr) -> Self {
match entity_kind {
EntityKind::System(_) => Self::new_system(hash_addr),
EntityKind::Account(_) => Self::new_account(hash_addr),
EntityKind::SmartContract(_) => Self::new_smart_contract(hash_addr),
}
}
pub fn tag(&self) -> EntityKindTag {
match self {
EntityAddr::System(_) => EntityKindTag::System,
EntityAddr::Account(_) => EntityKindTag::Account,
EntityAddr::SmartContract(_) => EntityKindTag::SmartContract,
}
}
pub fn is_system(&self) -> bool {
self.tag() == EntityKindTag::System
|| self.value() == PublicKey::System.to_account_hash().value()
}
pub fn is_contract(&self) -> bool {
self.tag() == EntityKindTag::SmartContract
}
pub fn is_account(&self) -> bool {
self.tag() == EntityKindTag::Account
}
pub fn value(&self) -> HashAddr {
match self {
EntityAddr::System(hash_addr)
| EntityAddr::Account(hash_addr)
| EntityAddr::SmartContract(hash_addr) => *hash_addr,
}
}
pub fn to_formatted_string(&self) -> String {
match self {
EntityAddr::System(addr) => {
format!(
"{}{}{}",
ENTITY_PREFIX,
SYSTEM_ENTITY_PREFIX,
base16::encode_lower(addr)
)
}
EntityAddr::Account(addr) => {
format!(
"{}{}{}",
ENTITY_PREFIX,
ACCOUNT_ENTITY_PREFIX,
base16::encode_lower(addr)
)
}
EntityAddr::SmartContract(addr) => {
format!(
"{}{}{}",
ENTITY_PREFIX,
CONTRACT_ENTITY_PREFIX,
base16::encode_lower(addr)
)
}
}
}
pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
if let Some(entity) = input.strip_prefix(ENTITY_PREFIX) {
let (addr_str, tag) = if let Some(str) = entity.strip_prefix(SYSTEM_ENTITY_PREFIX) {
(str, EntityKindTag::System)
} else if let Some(str) = entity.strip_prefix(ACCOUNT_ENTITY_PREFIX) {
(str, EntityKindTag::Account)
} else if let Some(str) = entity.strip_prefix(CONTRACT_ENTITY_PREFIX) {
(str, EntityKindTag::SmartContract)
} else {
return Err(FromStrError::InvalidPrefix);
};
let addr = checksummed_hex::decode(addr_str).map_err(FromStrError::Hex)?;
let hash_addr = HashAddr::try_from(addr.as_ref()).map_err(FromStrError::Hash)?;
let entity_addr = match tag {
EntityKindTag::System => EntityAddr::new_system(hash_addr),
EntityKindTag::Account => EntityAddr::new_account(hash_addr),
EntityKindTag::SmartContract => EntityAddr::new_smart_contract(hash_addr),
};
return Ok(entity_addr);
}
Err(FromStrError::InvalidPrefix)
}
pub fn into_smart_contract(&self) -> Option<[u8; 32]> {
match self {
EntityAddr::SmartContract(addr) => Some(*addr),
_ => None,
}
}
}
impl ToBytes for EntityAddr {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
self.write_bytes(&mut buffer)?;
Ok(buffer)
}
fn serialized_length(&self) -> usize {
EntityAddr::LENGTH
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
match self {
EntityAddr::System(addr) => {
EntityKindTag::System.write_bytes(writer)?;
addr.write_bytes(writer)
}
EntityAddr::Account(addr) => {
EntityKindTag::Account.write_bytes(writer)?;
addr.write_bytes(writer)
}
EntityAddr::SmartContract(addr) => {
EntityKindTag::SmartContract.write_bytes(writer)?;
addr.write_bytes(writer)
}
}
}
}
impl FromBytes for EntityAddr {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (tag, remainder) = EntityKindTag::from_bytes(bytes)?;
let (addr, remainder) = HashAddr::from_bytes(remainder)?;
let entity_addr = match tag {
EntityKindTag::System => EntityAddr::System(addr),
EntityKindTag::Account => EntityAddr::Account(addr),
EntityKindTag::SmartContract => EntityAddr::SmartContract(addr),
};
Ok((entity_addr, remainder))
}
}
impl CLTyped for EntityAddr {
fn cl_type() -> CLType {
CLType::Any
}
}
impl From<EntityAddr> for AddressableEntityHash {
fn from(entity_addr: EntityAddr) -> Self {
AddressableEntityHash::new(entity_addr.value())
}
}
impl Display for EntityAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(&self.to_formatted_string())
}
}
impl Debug for EntityAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
EntityAddr::System(hash_addr) => {
write!(f, "EntityAddr::System({})", base16::encode_lower(hash_addr))
}
EntityAddr::Account(hash_addr) => {
write!(
f,
"EntityAddr::Account({})",
base16::encode_lower(hash_addr)
)
}
EntityAddr::SmartContract(hash_addr) => {
write!(
f,
"EntityAddr::SmartContract({})",
base16::encode_lower(hash_addr)
)
}
}
}
}
impl Serialize for EntityAddr {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_formatted_string().serialize(serializer)
} else {
let (tag, value): (EntityKindTag, HashAddr) = (self.tag(), self.value());
(tag, value).serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for EntityAddr {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let formatted_string = String::deserialize(deserializer)?;
Self::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
} else {
let (tag, addr) = <(EntityKindTag, HashAddr)>::deserialize(deserializer)?;
match tag {
EntityKindTag::System => Ok(EntityAddr::new_system(addr)),
EntityKindTag::Account => Ok(EntityAddr::new_account(addr)),
EntityKindTag::SmartContract => Ok(EntityAddr::new_smart_contract(addr)),
}
}
}
}
#[cfg(any(feature = "testing", test))]
impl Distribution<EntityAddr> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> EntityAddr {
match rng.gen_range(0..=2) {
0 => EntityAddr::System(rng.gen()),
1 => EntityAddr::Account(rng.gen()),
2 => EntityAddr::SmartContract(rng.gen()),
_ => unreachable!(),
}
}
}
#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub struct NamedKeyAddr {
base_addr: EntityAddr,
string_bytes: [u8; KEY_HASH_LENGTH],
}
impl NamedKeyAddr {
pub const NAMED_KEY_ADDR_BASE_LENGTH: usize = 1 + EntityAddr::LENGTH;
pub const fn new_named_key_entry(
entity_addr: EntityAddr,
string_bytes: [u8; KEY_HASH_LENGTH],
) -> Self {
Self {
base_addr: entity_addr,
string_bytes,
}
}
pub fn new_from_string(
entity_addr: EntityAddr,
entry: String,
) -> Result<Self, bytesrepr::Error> {
let bytes = entry.to_bytes()?;
let mut hasher = {
match VarBlake2b::new(BLAKE2B_DIGEST_LENGTH) {
Ok(hasher) => hasher,
Err(_) => return Err(bytesrepr::Error::Formatting),
}
};
hasher.update(bytes);
let mut string_bytes = HashAddr::default();
hasher.finalize_variable(|hash| string_bytes.clone_from_slice(hash));
Ok(Self::new_named_key_entry(entity_addr, string_bytes))
}
pub fn entity_addr(&self) -> EntityAddr {
self.base_addr
}
pub fn to_formatted_string(&self) -> String {
format!("{}", self)
}
pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
if let Some(named_key) = input.strip_prefix(NAMED_KEY_PREFIX) {
if let Some((entity_addr_str, string_bytes_str)) = named_key.rsplit_once('-') {
let entity_addr = EntityAddr::from_formatted_str(entity_addr_str)?;
let string_bytes =
checksummed_hex::decode(string_bytes_str).map_err(FromStrError::Hex)?;
let (string_bytes, _) =
FromBytes::from_vec(string_bytes).map_err(FromStrError::BytesRepr)?;
return Ok(Self::new_named_key_entry(entity_addr, string_bytes));
};
}
Err(FromStrError::InvalidPrefix)
}
}
impl Default for NamedKeyAddr {
fn default() -> Self {
NamedKeyAddr {
base_addr: EntityAddr::System(HashAddr::default()),
string_bytes: Default::default(),
}
}
}
impl ToBytes for NamedKeyAddr {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.append(&mut self.base_addr.to_bytes()?);
buffer.append(&mut self.string_bytes.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.base_addr.serialized_length() + self.string_bytes.serialized_length()
}
}
impl FromBytes for NamedKeyAddr {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (base_addr, remainder) = EntityAddr::from_bytes(bytes)?;
let (string_bytes, remainder) = FromBytes::from_bytes(remainder)?;
Ok((
Self {
base_addr,
string_bytes,
},
remainder,
))
}
}
impl Display for NamedKeyAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}-{}",
NAMED_KEY_PREFIX,
self.base_addr,
base16::encode_lower(&self.string_bytes)
)
}
}
impl Debug for NamedKeyAddr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"NamedKeyAddr({:?}-{:?})",
self.base_addr,
base16::encode_lower(&self.string_bytes)
)
}
}
#[cfg(any(feature = "testing", test))]
impl Distribution<NamedKeyAddr> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> NamedKeyAddr {
NamedKeyAddr {
base_addr: rng.gen(),
string_bytes: rng.gen(),
}
}
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub struct NamedKeyValue {
named_key: CLValue,
name: CLValue,
}
impl NamedKeyValue {
pub fn new(key: CLValue, name: CLValue) -> Self {
Self {
named_key: key,
name,
}
}
pub fn from_concrete_values(named_key: Key, name: String) -> Result<Self, CLValueError> {
let key_cl_value = CLValue::from_t(named_key)?;
let string_cl_value = CLValue::from_t(name)?;
Ok(Self::new(key_cl_value, string_cl_value))
}
pub fn get_key_as_cl_value(&self) -> &CLValue {
&self.named_key
}
pub fn get_name_as_cl_value(&self) -> &CLValue {
&self.name
}
pub fn get_key(&self) -> Result<Key, CLValueError> {
self.named_key.clone().into_t::<Key>()
}
pub fn get_name(&self) -> Result<String, CLValueError> {
self.name.clone().into_t::<String>()
}
}
impl ToBytes for NamedKeyValue {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.append(&mut self.named_key.to_bytes()?);
buffer.append(&mut self.name.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.named_key.serialized_length() + self.name.serialized_length()
}
}
impl FromBytes for NamedKeyValue {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (named_key, remainder) = CLValue::from_bytes(bytes)?;
let (name, remainder) = CLValue::from_bytes(remainder)?;
Ok((Self { named_key, name }, remainder))
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Default)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
#[serde(transparent, deny_unknown_fields)]
pub struct MessageTopics(
#[serde(with = "BTreeMapToArray::<String, TopicNameHash, MessageTopicLabels>")]
BTreeMap<String, TopicNameHash>,
);
impl ToBytes for MessageTopics {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
self.0.to_bytes()
}
fn serialized_length(&self) -> usize {
self.0.serialized_length()
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
self.0.write_bytes(writer)
}
}
impl FromBytes for MessageTopics {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (message_topics_map, remainder) = BTreeMap::<String, TopicNameHash>::from_bytes(bytes)?;
Ok((MessageTopics(message_topics_map), remainder))
}
}
impl MessageTopics {
pub fn add_topic(
&mut self,
topic_name: &str,
topic_name_hash: TopicNameHash,
) -> Result<(), MessageTopicError> {
match self.0.entry(topic_name.to_string()) {
Entry::Vacant(entry) => {
entry.insert(topic_name_hash);
Ok(())
}
Entry::Occupied(_) => Err(MessageTopicError::DuplicateTopic),
}
}
pub fn has_topic(&self, topic_name: &str) -> bool {
self.0.contains_key(topic_name)
}
pub fn get(&self, topic_name: &str) -> Option<&TopicNameHash> {
self.0.get(topic_name)
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &TopicNameHash)> {
self.0.iter()
}
}
struct MessageTopicLabels;
impl KeyValueLabels for MessageTopicLabels {
const KEY: &'static str = "topic_name";
const VALUE: &'static str = "topic_name_hash";
}
#[cfg(feature = "json-schema")]
impl KeyValueJsonSchema for MessageTopicLabels {
const JSON_SCHEMA_KV_NAME: Option<&'static str> = Some("MessageTopic");
}
impl From<BTreeMap<String, TopicNameHash>> for MessageTopics {
fn from(topics: BTreeMap<String, TopicNameHash>) -> MessageTopics {
MessageTopics(topics)
}
}
#[derive(PartialEq, Eq, Debug, Clone)]
#[non_exhaustive]
pub enum MessageTopicError {
DuplicateTopic,
MaxTopicsExceeded,
TopicNameSizeExceeded,
}
#[cfg(feature = "json-schema")]
static ADDRESSABLE_ENTITY: Lazy<AddressableEntity> = Lazy::new(|| {
let secret_key = SecretKey::ed25519_from_bytes([0; 32]).unwrap();
let account_hash = PublicKey::from(&secret_key).to_account_hash();
let package_hash = PackageHash::new([0; 32]);
let byte_code_hash = ByteCodeHash::new([0; 32]);
let main_purse = URef::from_formatted_str(
"uref-09480c3248ef76b603d386f3f4f8a5f87f597d4eaffd475433f861af187ab5db-007",
)
.unwrap();
let weight = Weight::new(1);
let associated_keys = AssociatedKeys::new(account_hash, weight);
let action_thresholds = ActionThresholds::new(weight, weight, weight).unwrap();
let protocol_version = ProtocolVersion::from_parts(2, 0, 0);
AddressableEntity {
protocol_version,
entity_kind: EntityKind::Account(account_hash),
package_hash,
byte_code_hash,
main_purse,
associated_keys,
action_thresholds,
}
});
pub type ContractAddress = PackageHash;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub struct AddressableEntity {
protocol_version: ProtocolVersion,
entity_kind: EntityKind,
package_hash: PackageHash,
byte_code_hash: ByteCodeHash,
main_purse: URef,
associated_keys: AssociatedKeys,
action_thresholds: ActionThresholds,
}
impl From<AddressableEntity>
for (
PackageHash,
ByteCodeHash,
ProtocolVersion,
URef,
AssociatedKeys,
ActionThresholds,
)
{
fn from(entity: AddressableEntity) -> Self {
(
entity.package_hash,
entity.byte_code_hash,
entity.protocol_version,
entity.main_purse,
entity.associated_keys,
entity.action_thresholds,
)
}
}
impl AddressableEntity {
#[allow(clippy::too_many_arguments)]
pub fn new(
package_hash: PackageHash,
byte_code_hash: ByteCodeHash,
protocol_version: ProtocolVersion,
main_purse: URef,
associated_keys: AssociatedKeys,
action_thresholds: ActionThresholds,
entity_kind: EntityKind,
) -> Self {
AddressableEntity {
package_hash,
byte_code_hash,
protocol_version,
main_purse,
action_thresholds,
associated_keys,
entity_kind,
}
}
pub fn entity_addr(&self, entity_hash: AddressableEntityHash) -> EntityAddr {
let hash_addr = entity_hash.value();
match self.entity_kind {
EntityKind::System(_) => EntityAddr::new_system(hash_addr),
EntityKind::Account(_) => EntityAddr::new_account(hash_addr),
EntityKind::SmartContract(_) => EntityAddr::new_smart_contract(hash_addr),
}
}
pub fn entity_kind(&self) -> EntityKind {
self.entity_kind
}
pub fn package_hash(&self) -> PackageHash {
self.package_hash
}
pub fn byte_code_hash(&self) -> ByteCodeHash {
self.byte_code_hash
}
pub fn protocol_version(&self) -> ProtocolVersion {
self.protocol_version
}
pub fn main_purse(&self) -> URef {
self.main_purse
}
pub fn main_purse_add_only(&self) -> URef {
URef::new(self.main_purse.addr(), AccessRights::ADD)
}
pub fn associated_keys(&self) -> &AssociatedKeys {
&self.associated_keys
}
pub fn action_thresholds(&self) -> &ActionThresholds {
&self.action_thresholds
}
pub fn add_associated_key(
&mut self,
account_hash: AccountHash,
weight: Weight,
) -> Result<(), AddKeyFailure> {
self.associated_keys.add_key(account_hash, weight)
}
fn can_remove_key(&self, account_hash: AccountHash) -> bool {
let total_weight_without = self
.associated_keys
.total_keys_weight_excluding(account_hash);
total_weight_without >= *self.action_thresholds().deployment()
&& total_weight_without >= *self.action_thresholds().key_management()
}
fn can_update_key(&self, account_hash: AccountHash, weight: Weight) -> bool {
let total_weight = self
.associated_keys
.total_keys_weight_excluding(account_hash);
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,
account_hash: AccountHash,
) -> Result<(), RemoveKeyFailure> {
if self.associated_keys.contains_key(&account_hash) {
if !self.can_remove_key(account_hash) {
return Err(RemoveKeyFailure::ThresholdViolation);
}
}
self.associated_keys.remove_key(&account_hash)
}
pub fn update_associated_key(
&mut self,
account_hash: AccountHash,
weight: Weight,
) -> Result<(), UpdateKeyFailure> {
if let Some(current_weight) = self.associated_keys.get(&account_hash) {
if weight < *current_weight {
if !self.can_update_key(account_hash, weight) {
return Err(UpdateKeyFailure::ThresholdViolation);
}
}
}
self.associated_keys.update_key(account_hash, weight)
}
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 set_action_threshold_unchecked(
&mut self,
action_type: ActionType,
threshold: Weight,
) -> Result<(), SetThresholdFailure> {
self.action_thresholds.set_threshold(action_type, threshold)
}
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<AccountHash>) -> bool {
!authorization_keys.is_empty()
&& authorization_keys
.iter()
.any(|e| self.associated_keys.contains_key(e))
}
pub fn can_deploy_with(&self, authorization_keys: &BTreeSet<AccountHash>) -> 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<AccountHash>) -> bool {
let total_weight = self
.associated_keys
.calculate_keys_weight(authorization_keys);
total_weight >= *self.action_thresholds().key_management()
}
pub fn can_upgrade_with(&self, authorization_keys: &BTreeSet<AccountHash>) -> bool {
let total_weight = self
.associated_keys
.calculate_keys_weight(authorization_keys);
total_weight >= *self.action_thresholds().upgrade_management()
}
pub fn byte_code_addr(&self) -> HashAddr {
self.byte_code_hash.value()
}
pub fn set_protocol_version(&mut self, protocol_version: ProtocolVersion) {
self.protocol_version = protocol_version;
}
pub fn is_compatible_protocol_version(&self, protocol_version: ProtocolVersion) -> bool {
let entity_protocol_version = self.protocol_version.value();
let context_protocol_version = protocol_version.value();
if entity_protocol_version.major == context_protocol_version.major {
return true;
}
if entity_protocol_version.major == 1 && context_protocol_version.major == 2 {
return true;
}
false
}
pub fn kind(&self) -> EntityKind {
self.entity_kind
}
pub fn is_account_kind(&self) -> bool {
matches!(self.entity_kind, EntityKind::Account(_))
}
pub fn entity_key(&self, entity_hash: AddressableEntityHash) -> Key {
match self.entity_kind {
EntityKind::System(_) => {
Key::addressable_entity_key(EntityKindTag::System, entity_hash)
}
EntityKind::Account(_) => {
Key::addressable_entity_key(EntityKindTag::Account, entity_hash)
}
EntityKind::SmartContract(_) => {
Key::addressable_entity_key(EntityKindTag::SmartContract, entity_hash)
}
}
}
pub fn extract_access_rights(
&self,
entity_hash: AddressableEntityHash,
named_keys: &NamedKeys,
) -> ContextAccessRights {
let urefs_iter = named_keys
.keys()
.filter_map(|key| key.as_uref().copied())
.chain(iter::once(self.main_purse));
ContextAccessRights::new(entity_hash.value(), urefs_iter)
}
#[doc(hidden)]
#[cfg(feature = "json-schema")]
pub fn example() -> &'static Self {
&ADDRESSABLE_ENTITY
}
}
impl ToBytes for AddressableEntity {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut result = bytesrepr::allocate_buffer(self)?;
self.package_hash().write_bytes(&mut result)?;
self.byte_code_hash().write_bytes(&mut result)?;
self.protocol_version().write_bytes(&mut result)?;
self.main_purse().write_bytes(&mut result)?;
self.associated_keys().write_bytes(&mut result)?;
self.action_thresholds().write_bytes(&mut result)?;
self.kind().write_bytes(&mut result)?;
Ok(result)
}
fn serialized_length(&self) -> usize {
ToBytes::serialized_length(&self.package_hash)
+ ToBytes::serialized_length(&self.byte_code_hash)
+ ToBytes::serialized_length(&self.protocol_version)
+ ToBytes::serialized_length(&self.main_purse)
+ ToBytes::serialized_length(&self.associated_keys)
+ ToBytes::serialized_length(&self.action_thresholds)
+ ToBytes::serialized_length(&self.entity_kind)
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
self.package_hash().write_bytes(writer)?;
self.byte_code_hash().write_bytes(writer)?;
self.protocol_version().write_bytes(writer)?;
self.main_purse().write_bytes(writer)?;
self.associated_keys().write_bytes(writer)?;
self.action_thresholds().write_bytes(writer)?;
self.kind().write_bytes(writer)?;
Ok(())
}
}
impl FromBytes for AddressableEntity {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (package_hash, bytes) = PackageHash::from_bytes(bytes)?;
let (byte_code_hash, bytes) = ByteCodeHash::from_bytes(bytes)?;
let (protocol_version, bytes) = ProtocolVersion::from_bytes(bytes)?;
let (main_purse, bytes) = URef::from_bytes(bytes)?;
let (associated_keys, bytes) = AssociatedKeys::from_bytes(bytes)?;
let (action_thresholds, bytes) = ActionThresholds::from_bytes(bytes)?;
let (entity_kind, bytes) = EntityKind::from_bytes(bytes)?;
Ok((
AddressableEntity {
package_hash,
byte_code_hash,
protocol_version,
main_purse,
associated_keys,
action_thresholds,
entity_kind,
},
bytes,
))
}
}
impl Default for AddressableEntity {
fn default() -> Self {
AddressableEntity {
byte_code_hash: [0; KEY_HASH_LENGTH].into(),
package_hash: [0; KEY_HASH_LENGTH].into(),
protocol_version: ProtocolVersion::V1_0_0,
main_purse: URef::default(),
action_thresholds: ActionThresholds::default(),
associated_keys: AssociatedKeys::default(),
entity_kind: EntityKind::SmartContract(TransactionRuntime::VmCasperV1),
}
}
}
impl From<Contract> for AddressableEntity {
fn from(value: Contract) -> Self {
AddressableEntity::new(
PackageHash::new(value.contract_package_hash().value()),
ByteCodeHash::new(value.contract_wasm_hash().value()),
value.protocol_version(),
URef::default(),
AssociatedKeys::default(),
ActionThresholds::default(),
EntityKind::SmartContract(TransactionRuntime::VmCasperV1),
)
}
}
impl From<Account> for AddressableEntity {
fn from(value: Account) -> Self {
AddressableEntity::new(
PackageHash::default(),
ByteCodeHash::new([0u8; 32]),
ProtocolVersion::default(),
value.main_purse(),
value.associated_keys().clone().into(),
value.action_thresholds().clone().into(),
EntityKind::Account(value.account_hash()),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{AccessRights, URef, UREF_ADDR_LENGTH};
#[cfg(feature = "json-schema")]
use schemars::{gen::SchemaGenerator, schema::InstanceType};
#[test]
fn entity_hash_from_slice() {
let bytes: Vec<u8> = (0..32).collect();
let entity_hash = HashAddr::try_from(&bytes[..]).expect("should create contract hash");
let entity_hash = AddressableEntityHash::new(entity_hash);
assert_eq!(&bytes, &entity_hash.as_bytes());
}
#[test]
fn entity_hash_from_str() {
let entity_hash = AddressableEntityHash([3; 32]);
let encoded = entity_hash.to_formatted_string();
let decoded = AddressableEntityHash::from_formatted_str(&encoded).unwrap();
assert_eq!(entity_hash, decoded);
let invalid_prefix =
"addressable-entity--0000000000000000000000000000000000000000000000000000000000000000";
assert!(AddressableEntityHash::from_formatted_str(invalid_prefix).is_err());
let short_addr =
"addressable-entity-00000000000000000000000000000000000000000000000000000000000000";
assert!(AddressableEntityHash::from_formatted_str(short_addr).is_err());
let long_addr =
"addressable-entity-000000000000000000000000000000000000000000000000000000000000000000";
assert!(AddressableEntityHash::from_formatted_str(long_addr).is_err());
let invalid_hex =
"addressable-entity-000000000000000000000000000000000000000000000000000000000000000g";
assert!(AddressableEntityHash::from_formatted_str(invalid_hex).is_err());
}
#[test]
fn named_key_addr_from_str() {
let named_key_addr =
NamedKeyAddr::new_named_key_entry(EntityAddr::new_smart_contract([3; 32]), [4; 32]);
let encoded = named_key_addr.to_formatted_string();
let decoded = NamedKeyAddr::from_formatted_str(&encoded).unwrap();
assert_eq!(named_key_addr, decoded);
}
#[test]
fn entity_hash_serde_roundtrip() {
let entity_hash = AddressableEntityHash([255; 32]);
let serialized = bincode::serialize(&entity_hash).unwrap();
let deserialized = bincode::deserialize(&serialized).unwrap();
assert_eq!(entity_hash, deserialized)
}
#[test]
fn entity_hash_json_roundtrip() {
let entity_hash = AddressableEntityHash([255; 32]);
let json_string = serde_json::to_string_pretty(&entity_hash).unwrap();
let decoded = serde_json::from_str(&json_string).unwrap();
assert_eq!(entity_hash, decoded)
}
#[test]
fn entity_addr_formatted_string_roundtrip() {
let entity_addr = EntityAddr::Account([5; 32]);
let encoded = entity_addr.to_formatted_string();
let decoded = EntityAddr::from_formatted_str(&encoded).expect("must get entity addr");
assert_eq!(decoded, entity_addr);
let entity_addr = EntityAddr::SmartContract([5; 32]);
let encoded = entity_addr.to_formatted_string();
let decoded = EntityAddr::from_formatted_str(&encoded).expect("must get entity addr");
assert_eq!(decoded, entity_addr);
let entity_addr = EntityAddr::System([5; 32]);
let encoded = entity_addr.to_formatted_string();
let decoded = EntityAddr::from_formatted_str(&encoded).expect("must get entity addr");
assert_eq!(decoded, entity_addr);
}
#[test]
fn entity_addr_serialization_roundtrip() {
for addr in [
EntityAddr::new_system([1; 32]),
EntityAddr::new_account([1; 32]),
EntityAddr::new_smart_contract([1; 32]),
] {
bytesrepr::test_serialization_roundtrip(&addr);
}
}
#[test]
fn entity_addr_serde_roundtrip() {
for addr in [
EntityAddr::new_system([1; 32]),
EntityAddr::new_account([1; 32]),
EntityAddr::new_smart_contract([1; 32]),
] {
let serialized = bincode::serialize(&addr).unwrap();
let deserialized = bincode::deserialize(&serialized).unwrap();
assert_eq!(addr, deserialized)
}
}
#[test]
fn entity_addr_json_roundtrip() {
for addr in [
EntityAddr::new_system([1; 32]),
EntityAddr::new_account([1; 32]),
EntityAddr::new_smart_contract([1; 32]),
] {
let json_string = serde_json::to_string_pretty(&addr).unwrap();
let decoded = serde_json::from_str(&json_string).unwrap();
assert_eq!(addr, decoded)
}
}
#[cfg(feature = "json-schema")]
#[test]
fn entity_addr_schema() {
let mut gen = SchemaGenerator::default();
let any_of = EntityAddr::json_schema(&mut gen)
.into_object()
.subschemas
.expect("should have subschemas")
.any_of
.expect("should have any_of");
for elem in any_of {
let schema = elem
.into_object()
.instance_type
.expect("should have instance type");
assert!(schema.contains(&InstanceType::String), "{:?}", schema);
}
}
#[test]
fn should_extract_access_rights() {
const MAIN_PURSE: URef = URef::new([2; 32], AccessRights::READ_ADD_WRITE);
let entity_hash = AddressableEntityHash([255; 32]);
let uref = URef::new([84; UREF_ADDR_LENGTH], AccessRights::READ_ADD);
let uref_r = URef::new([42; UREF_ADDR_LENGTH], AccessRights::READ);
let uref_a = URef::new([42; UREF_ADDR_LENGTH], AccessRights::ADD);
let uref_w = URef::new([42; UREF_ADDR_LENGTH], AccessRights::WRITE);
let mut named_keys = NamedKeys::new();
named_keys.insert("a".to_string(), Key::URef(uref_r));
named_keys.insert("b".to_string(), Key::URef(uref_a));
named_keys.insert("c".to_string(), Key::URef(uref_w));
named_keys.insert("d".to_string(), Key::URef(uref));
let associated_keys = AssociatedKeys::new(AccountHash::new([254; 32]), Weight::new(1));
let contract = AddressableEntity::new(
PackageHash::new([254; 32]),
ByteCodeHash::new([253; 32]),
ProtocolVersion::V1_0_0,
MAIN_PURSE,
associated_keys,
ActionThresholds::new(Weight::new(1), Weight::new(1), Weight::new(1))
.expect("should create thresholds"),
EntityKind::SmartContract(TransactionRuntime::VmCasperV1),
);
let access_rights = contract.extract_access_rights(entity_hash, &named_keys);
let expected_uref = URef::new([42; UREF_ADDR_LENGTH], AccessRights::READ_ADD_WRITE);
assert!(
access_rights.has_access_rights_to_uref(&uref),
"urefs in named keys should be included in access rights"
);
assert!(
access_rights.has_access_rights_to_uref(&expected_uref),
"multiple access right bits to the same uref should coalesce"
);
}
}
#[cfg(test)]
mod prop_tests {
use proptest::prelude::*;
use crate::{bytesrepr, gens};
proptest! {
#[test]
fn test_value_contract(contract in gens::addressable_entity_arb()) {
bytesrepr::test_serialization_roundtrip(&contract);
}
}
}