#![allow(clippy::field_reassign_with_default)]
mod named_keys;
use alloc::{
collections::{BTreeMap, BTreeSet},
format,
string::{String, ToString},
vec::Vec,
};
use core::{
array::TryFromSliceError,
convert::{TryFrom, TryInto},
fmt::{self, Debug, Display, Formatter},
};
use serde_bytes::ByteBuf;
#[cfg(feature = "datasize")]
use datasize::DataSize;
#[cfg(feature = "json-schema")]
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{
de::{self, Error as SerdeError},
ser, Deserialize, Deserializer, Serialize, Serializer,
};
#[cfg(feature = "json-schema")]
use serde_map_to_array::KeyValueJsonSchema;
use serde_map_to_array::{BTreeMapToArray, KeyValueLabels};
pub use self::named_keys::NamedKeys;
use crate::{
account,
addressable_entity::TryFromSliceForAccountHashError,
bytesrepr::{self, FromBytes, ToBytes, U32_SERIALIZED_LENGTH},
checksummed_hex,
contract_wasm::ContractWasmHash,
package::PackageStatus,
serde_helpers::contract_package::HumanReadableContractPackage,
uref::{self, URef},
AddressableEntityHash, CLType, CLTyped, EntityVersionKey, EntryPoint as EntityEntryPoint,
EntryPointAccess, EntryPointPayment, EntryPointType, EntryPoints as EntityEntryPoints, Group,
Groups, HashAddr, Key, Package, PackageHash, Parameter, Parameters, ProtocolVersion,
KEY_HASH_LENGTH,
};
const CONTRACT_STRING_PREFIX: &str = "contract-";
const CONTRACT_PACKAGE_STRING_PREFIX: &str = "contract-package-";
const CONTRACT_PACKAGE_STRING_LEGACY_EXTRA_PREFIX: &str = "wasm";
#[derive(Debug, PartialEq, Eq)]
#[repr(u8)]
#[non_exhaustive]
pub enum Error {
PreviouslyUsedVersion = 1,
ContractNotFound = 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::ContractNotFound as u8 => Self::ContractNotFound,
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),
AccountHash(account::FromStrError),
URef(uref::FromStrError),
}
impl From<base16::DecodeError> for FromStrError {
fn from(error: base16::DecodeError) -> Self {
FromStrError::Hex(error)
}
}
impl From<TryFromSliceForAccountHashError> for FromStrError {
fn from(error: TryFromSliceForAccountHashError) -> Self {
FromStrError::Account(error)
}
}
impl From<TryFromSliceError> for FromStrError {
fn from(error: TryFromSliceError) -> Self {
FromStrError::Hash(error)
}
}
impl From<account::FromStrError> for FromStrError {
fn from(error: account::FromStrError) -> Self {
FromStrError::AccountHash(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::Account(error) => write!(f, "account from string error: {:?}", error),
FromStrError::Hash(error) => write!(f, "hash from string error: {}", error),
FromStrError::AccountHash(error) => {
write!(f, "account hash from string error: {:?}", error)
}
FromStrError::URef(error) => write!(f, "uref from string error: {:?}", error),
}
}
}
pub type ContractVersion = u32;
pub const CONTRACT_INITIAL_VERSION: ContractVersion = 1;
pub type ProtocolVersionMajor = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
#[cfg_attr(feature = "datasize", derive(DataSize))]
pub struct ContractVersionKey(ProtocolVersionMajor, ContractVersion);
impl ContractVersionKey {
pub fn new(
protocol_version_major: ProtocolVersionMajor,
contract_version: ContractVersion,
) -> Self {
Self(protocol_version_major, contract_version)
}
pub fn protocol_version_major(self) -> ProtocolVersionMajor {
self.0
}
pub fn contract_version(self) -> ContractVersion {
self.1
}
}
impl From<ContractVersionKey> for (ProtocolVersionMajor, ContractVersion) {
fn from(contract_version_key: ContractVersionKey) -> Self {
(contract_version_key.0, contract_version_key.1)
}
}
pub const CONTRACT_VERSION_KEY_SERIALIZED_LENGTH: usize =
U32_SERIALIZED_LENGTH + U32_SERIALIZED_LENGTH;
impl ToBytes for ContractVersionKey {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut ret = bytesrepr::unchecked_allocate_buffer(self);
ret.append(&mut self.0.to_bytes()?);
ret.append(&mut self.1.to_bytes()?);
Ok(ret)
}
fn serialized_length(&self) -> usize {
CONTRACT_VERSION_KEY_SERIALIZED_LENGTH
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
self.0.write_bytes(writer)?;
self.1.write_bytes(writer)?;
Ok(())
}
}
impl FromBytes for ContractVersionKey {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (major, rem): (u32, &[u8]) = FromBytes::from_bytes(bytes)?;
let (contract, rem): (ContractVersion, &[u8]) = FromBytes::from_bytes(rem)?;
Ok((ContractVersionKey::new(major, contract), rem))
}
}
impl fmt::Display for ContractVersionKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}", self.0, self.1)
}
}
pub type ContractVersions = BTreeMap<ContractVersionKey, ContractHash>;
pub type DisabledVersions = BTreeSet<ContractVersionKey>;
#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
pub struct ContractHash(HashAddr);
impl ContractHash {
pub const fn new(value: HashAddr) -> ContractHash {
ContractHash(value)
}
pub fn value(&self) -> HashAddr {
self.0
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_formatted_string(self) -> String {
format!(
"{}{}",
CONTRACT_STRING_PREFIX,
base16::encode_lower(&self.0),
)
}
pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
let remainder = input
.strip_prefix(CONTRACT_STRING_PREFIX)
.ok_or(FromStrError::InvalidPrefix)?;
let bytes = HashAddr::try_from(checksummed_hex::decode(remainder)?.as_ref())?;
Ok(ContractHash(bytes))
}
}
impl Display for ContractHash {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", base16::encode_lower(&self.0))
}
}
impl Debug for ContractHash {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
write!(f, "ContractHash({})", base16::encode_lower(&self.0))
}
}
impl CLTyped for ContractHash {
fn cl_type() -> CLType {
CLType::ByteArray(KEY_HASH_LENGTH as u32)
}
}
impl From<AddressableEntityHash> for ContractHash {
fn from(entity_hash: AddressableEntityHash) -> Self {
ContractHash::new(entity_hash.value())
}
}
impl ToBytes for ContractHash {
#[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 ContractHash {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (bytes, rem) = FromBytes::from_bytes(bytes)?;
Ok((ContractHash::new(bytes), rem))
}
}
impl From<[u8; 32]> for ContractHash {
fn from(bytes: [u8; 32]) -> Self {
ContractHash(bytes)
}
}
impl Serialize for ContractHash {
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 ContractHash {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let formatted_string = String::deserialize(deserializer)?;
ContractHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
} else {
let bytes = HashAddr::deserialize(deserializer)?;
Ok(ContractHash(bytes))
}
}
}
impl AsRef<[u8]> for ContractHash {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl TryFrom<&[u8]> for ContractHash {
type Error = TryFromSliceForContractHashError;
fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceForContractHashError> {
HashAddr::try_from(bytes)
.map(ContractHash::new)
.map_err(|_| TryFromSliceForContractHashError(()))
}
}
impl TryFrom<&Vec<u8>> for ContractHash {
type Error = TryFromSliceForContractHashError;
fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
HashAddr::try_from(bytes as &[u8])
.map(ContractHash::new)
.map_err(|_| TryFromSliceForContractHashError(()))
}
}
#[cfg(feature = "json-schema")]
impl JsonSchema for ContractHash {
fn schema_name() -> String {
String::from("ContractHash")
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let schema = gen.subschema_for::<String>();
let mut schema_object = schema.into_object();
schema_object.metadata().description = Some("The hash address of the contract".to_string());
schema_object.into()
}
}
#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
pub struct ContractPackageHash(HashAddr);
impl ContractPackageHash {
pub const fn new(value: HashAddr) -> ContractPackageHash {
ContractPackageHash(value)
}
pub fn value(&self) -> HashAddr {
self.0
}
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
pub fn to_formatted_string(self) -> String {
format!(
"{}{}",
CONTRACT_PACKAGE_STRING_PREFIX,
base16::encode_lower(&self.0),
)
}
pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
let remainder = input
.strip_prefix(CONTRACT_PACKAGE_STRING_PREFIX)
.ok_or(FromStrError::InvalidPrefix)?;
let hex_addr = remainder
.strip_prefix(CONTRACT_PACKAGE_STRING_LEGACY_EXTRA_PREFIX)
.unwrap_or(remainder);
let bytes = HashAddr::try_from(checksummed_hex::decode(hex_addr)?.as_ref())?;
Ok(ContractPackageHash(bytes))
}
}
impl From<PackageHash> for ContractPackageHash {
fn from(value: PackageHash) -> Self {
ContractPackageHash::new(value.value())
}
}
impl Display for ContractPackageHash {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", base16::encode_lower(&self.0))
}
}
impl Debug for ContractPackageHash {
fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
write!(f, "ContractPackageHash({})", base16::encode_lower(&self.0))
}
}
impl CLTyped for ContractPackageHash {
fn cl_type() -> CLType {
CLType::ByteArray(KEY_HASH_LENGTH as u32)
}
}
impl ToBytes for ContractPackageHash {
#[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 ContractPackageHash {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (bytes, rem) = FromBytes::from_bytes(bytes)?;
Ok((ContractPackageHash::new(bytes), rem))
}
}
impl From<[u8; 32]> for ContractPackageHash {
fn from(bytes: [u8; 32]) -> Self {
ContractPackageHash(bytes)
}
}
impl Serialize for ContractPackageHash {
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 ContractPackageHash {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let formatted_string = String::deserialize(deserializer)?;
ContractPackageHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
} else {
let bytes = HashAddr::deserialize(deserializer)?;
Ok(ContractPackageHash(bytes))
}
}
}
impl AsRef<[u8]> for ContractPackageHash {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl TryFrom<&[u8]> for ContractPackageHash {
type Error = TryFromSliceForContractHashError;
fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceForContractHashError> {
HashAddr::try_from(bytes)
.map(ContractPackageHash::new)
.map_err(|_| TryFromSliceForContractHashError(()))
}
}
impl TryFrom<&Vec<u8>> for ContractPackageHash {
type Error = TryFromSliceForContractHashError;
fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
HashAddr::try_from(bytes as &[u8])
.map(ContractPackageHash::new)
.map_err(|_| TryFromSliceForContractHashError(()))
}
}
#[cfg(feature = "json-schema")]
impl JsonSchema for ContractPackageHash {
fn schema_name() -> String {
String::from("ContractPackageHash")
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let schema = gen.subschema_for::<String>();
let mut schema_object = schema.into_object();
schema_object.metadata().description =
Some("The hash address of the contract package".to_string());
schema_object.into()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub enum ContractPackageStatus {
Locked,
Unlocked,
}
impl ContractPackageStatus {
pub fn new(is_locked: bool) -> Self {
if is_locked {
ContractPackageStatus::Locked
} else {
ContractPackageStatus::Unlocked
}
}
}
impl Default for ContractPackageStatus {
fn default() -> Self {
Self::Unlocked
}
}
impl ToBytes for ContractPackageStatus {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut result = bytesrepr::allocate_buffer(self)?;
match self {
ContractPackageStatus::Unlocked => result.append(&mut false.to_bytes()?),
ContractPackageStatus::Locked => result.append(&mut true.to_bytes()?),
}
Ok(result)
}
fn serialized_length(&self) -> usize {
match self {
ContractPackageStatus::Unlocked => false.serialized_length(),
ContractPackageStatus::Locked => true.serialized_length(),
}
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
match self {
ContractPackageStatus::Locked => writer.push(u8::from(true)),
ContractPackageStatus::Unlocked => writer.push(u8::from(false)),
}
Ok(())
}
}
impl FromBytes for ContractPackageStatus {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (val, bytes) = bool::from_bytes(bytes)?;
let status = ContractPackageStatus::new(val);
Ok((status, bytes))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
#[cfg_attr(feature = "datasize", derive(DataSize))]
pub struct ContractPackage {
access_key: URef,
#[cfg_attr(
feature = "json-schema",
schemars(
with = "Vec<crate::serde_helpers::contract_package::HumanReadableContractVersion>"
)
)]
versions: ContractVersions,
disabled_versions: DisabledVersions,
groups: Groups,
lock_status: ContractPackageStatus,
}
impl CLTyped for ContractPackage {
fn cl_type() -> CLType {
CLType::Any
}
}
impl ContractPackage {
pub fn new(
access_key: URef,
versions: ContractVersions,
disabled_versions: DisabledVersions,
groups: Groups,
lock_status: ContractPackageStatus,
) -> Self {
ContractPackage {
access_key,
versions,
disabled_versions,
groups,
lock_status,
}
}
pub fn access_key(&self) -> URef {
self.access_key
}
pub fn groups(&self) -> &Groups {
&self.groups
}
pub fn versions(&self) -> &ContractVersions {
&self.versions
}
pub fn versions_mut(&mut self) -> &mut ContractVersions {
&mut self.versions
}
pub fn take_versions(self) -> ContractVersions {
self.versions
}
pub fn disabled_versions(&self) -> &DisabledVersions {
&self.disabled_versions
}
pub fn disabled_versions_mut(&mut self) -> &mut DisabledVersions {
&mut self.disabled_versions
}
pub fn lock_status(&self) -> ContractPackageStatus {
self.lock_status.clone()
}
pub fn is_locked(&self) -> bool {
match self.lock_status {
ContractPackageStatus::Locked => true,
ContractPackageStatus::Unlocked => false,
}
}
pub fn disable_contract_version(&mut self, contract_hash: ContractHash) -> Result<(), Error> {
let contract_version_key = self
.find_contract_version_key_by_hash(&contract_hash)
.copied()
.ok_or(Error::ContractNotFound)?;
if !self.disabled_versions.contains(&contract_version_key) {
self.disabled_versions.insert(contract_version_key);
}
Ok(())
}
pub fn enable_contract_version(&mut self, contract_hash: ContractHash) -> Result<(), Error> {
let contract_version_key = self
.find_contract_version_key_by_hash(&contract_hash)
.copied()
.ok_or(Error::ContractNotFound)?;
self.disabled_versions.remove(&contract_version_key);
Ok(())
}
fn find_contract_version_key_by_hash(
&self,
contract_hash: &ContractHash,
) -> Option<&ContractVersionKey> {
self.versions
.iter()
.filter_map(|(k, v)| if v == contract_hash { Some(k) } else { None })
.next()
}
pub fn remove_group(&mut self, group: &Group) -> bool {
self.groups.0.remove(group).is_some()
}
fn next_contract_version_for(&self, protocol_version: ProtocolVersionMajor) -> ContractVersion {
let current_version = self
.versions
.keys()
.rev()
.find_map(|&contract_version_key| {
if contract_version_key.protocol_version_major() == protocol_version {
Some(contract_version_key.contract_version())
} else {
None
}
})
.unwrap_or(0);
current_version + 1
}
pub fn is_version_enabled(&self, contract_version_key: ContractVersionKey) -> bool {
!self.disabled_versions.contains(&contract_version_key)
&& self.versions.contains_key(&contract_version_key)
}
pub fn enabled_versions(&self) -> ContractVersions {
let mut ret = ContractVersions::new();
for version in &self.versions {
if !self.is_version_enabled(*version.0) {
continue;
}
ret.insert(*version.0, *version.1);
}
ret
}
pub fn current_contract_version(&self) -> Option<ContractVersionKey> {
self.enabled_versions().keys().next_back().copied()
}
pub fn current_contract_hash(&self) -> Option<ContractHash> {
self.enabled_versions().values().next_back().copied()
}
pub fn insert_contract_version(
&mut self,
protocol_version_major: ProtocolVersionMajor,
contract_hash: ContractHash,
) -> ContractVersionKey {
let contract_version = self.next_contract_version_for(protocol_version_major);
let key = ContractVersionKey::new(protocol_version_major, contract_version);
self.versions.insert(key, contract_hash);
key
}
pub fn groups_mut(&mut self) -> &mut Groups {
&mut self.groups
}
}
impl Serialize for ContractPackage {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
HumanReadableContractPackage::from(self).serialize(serializer)
} else {
let bytes = self
.to_bytes()
.map_err(|error| ser::Error::custom(format!("{:?}", error)))?;
ByteBuf::from(bytes).serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for ContractPackage {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
let json_helper = HumanReadableContractPackage::deserialize(deserializer)?;
json_helper.try_into().map_err(de::Error::custom)
} else {
let bytes = ByteBuf::deserialize(deserializer)?.into_vec();
bytesrepr::deserialize::<ContractPackage>(bytes)
.map_err(|error| de::Error::custom(format!("{:?}", error)))
}
}
}
impl ToBytes for ContractPackage {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut result = bytesrepr::allocate_buffer(self)?;
self.access_key().write_bytes(&mut result)?;
self.versions().write_bytes(&mut result)?;
self.disabled_versions().write_bytes(&mut result)?;
self.groups().write_bytes(&mut result)?;
self.lock_status.write_bytes(&mut result)?;
Ok(result)
}
fn serialized_length(&self) -> usize {
self.access_key.serialized_length()
+ self.versions.serialized_length()
+ self.disabled_versions.serialized_length()
+ self.groups.serialized_length()
+ self.lock_status.serialized_length()
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
self.access_key().write_bytes(writer)?;
self.versions().write_bytes(writer)?;
self.disabled_versions().write_bytes(writer)?;
self.groups().write_bytes(writer)?;
self.lock_status.write_bytes(writer)?;
Ok(())
}
}
impl FromBytes for ContractPackage {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (access_key, bytes) = URef::from_bytes(bytes)?;
let (versions, bytes) = ContractVersions::from_bytes(bytes)?;
let (disabled_versions, bytes) = DisabledVersions::from_bytes(bytes)?;
let (groups, bytes) = Groups::from_bytes(bytes)?;
let (lock_status, bytes) = ContractPackageStatus::from_bytes(bytes)?;
let result = ContractPackage {
access_key,
versions,
disabled_versions,
groups,
lock_status,
};
Ok((result, bytes))
}
}
impl From<ContractPackage> for Package {
fn from(value: ContractPackage) -> Self {
let versions: BTreeMap<EntityVersionKey, AddressableEntityHash> = value
.versions
.into_iter()
.map(|(version, contract_hash)| {
let entity_version = EntityVersionKey::new(2, version.contract_version());
let entity_hash: AddressableEntityHash =
AddressableEntityHash::new(contract_hash.value());
(entity_version, entity_hash)
})
.collect();
let disabled_versions = value
.disabled_versions
.into_iter()
.map(|contract_versions| {
EntityVersionKey::new(
contract_versions.protocol_version_major(),
contract_versions.contract_version(),
)
})
.collect();
let lock_status = if value.lock_status == ContractPackageStatus::Locked {
PackageStatus::Locked
} else {
PackageStatus::Unlocked
};
Package::new(
versions.into(),
disabled_versions,
value.groups,
lock_status,
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub struct EntryPoint {
name: String,
args: Parameters,
ret: CLType,
access: EntryPointAccess,
entry_point_type: EntryPointType,
}
impl From<EntryPoint> for (String, Parameters, CLType, EntryPointAccess, EntryPointType) {
fn from(entry_point: EntryPoint) -> Self {
(
entry_point.name,
entry_point.args,
entry_point.ret,
entry_point.access,
entry_point.entry_point_type,
)
}
}
impl EntryPoint {
pub fn new<T: Into<String>>(
name: T,
args: Parameters,
ret: CLType,
access: EntryPointAccess,
entry_point_type: EntryPointType,
) -> Self {
EntryPoint {
name: name.into(),
args,
ret,
access,
entry_point_type,
}
}
pub fn default_with_name<T: Into<String>>(name: T) -> Self {
EntryPoint {
name: name.into(),
..Default::default()
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn access(&self) -> &EntryPointAccess {
&self.access
}
pub fn args(&self) -> &[Parameter] {
self.args.as_slice()
}
pub fn ret(&self) -> &CLType {
&self.ret
}
pub fn entry_point_type(&self) -> EntryPointType {
self.entry_point_type
}
}
impl Default for EntryPoint {
fn default() -> Self {
EntryPoint {
name: DEFAULT_ENTRY_POINT_NAME.to_string(),
args: Vec::new(),
ret: CLType::Unit,
access: EntryPointAccess::Public,
entry_point_type: EntryPointType::Caller,
}
}
}
impl ToBytes for EntryPoint {
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 {
self.name.serialized_length()
+ self.args.serialized_length()
+ self.ret.serialized_length()
+ self.access.serialized_length()
+ self.entry_point_type.serialized_length()
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
self.name.write_bytes(writer)?;
self.args.write_bytes(writer)?;
self.ret.append_bytes(writer)?;
self.access.write_bytes(writer)?;
self.entry_point_type.write_bytes(writer)?;
Ok(())
}
}
impl FromBytes for EntryPoint {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (name, bytes) = String::from_bytes(bytes)?;
let (args, bytes) = Vec::<Parameter>::from_bytes(bytes)?;
let (ret, bytes) = CLType::from_bytes(bytes)?;
let (access, bytes) = EntryPointAccess::from_bytes(bytes)?;
let (entry_point_type, bytes) = EntryPointType::from_bytes(bytes)?;
Ok((
EntryPoint {
name,
args,
ret,
access,
entry_point_type,
},
bytes,
))
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
#[serde(transparent, deny_unknown_fields)]
pub struct EntryPoints(
#[serde(with = "BTreeMapToArray::<String, EntryPoint, EntryPointLabels>")]
BTreeMap<String, EntryPoint>,
);
impl From<crate::addressable_entity::EntryPoints> for EntryPoints {
fn from(value: EntityEntryPoints) -> Self {
let mut ret = EntryPoints::new();
for entity_entry_point in value.take_entry_points() {
let entry_point = EntryPoint::new(
entity_entry_point.name(),
Parameters::from(entity_entry_point.args()),
entity_entry_point.ret().clone(),
entity_entry_point.access().clone(),
entity_entry_point.entry_point_type(),
);
ret.add_entry_point(entry_point);
}
ret
}
}
impl ToBytes for EntryPoints {
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 EntryPoints {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (entry_points_map, remainder) = BTreeMap::<String, EntryPoint>::from_bytes(bytes)?;
Ok((EntryPoints(entry_points_map), remainder))
}
}
impl Default for EntryPoints {
fn default() -> Self {
let mut entry_points = EntryPoints::new();
let entry_point = EntryPoint::default();
entry_points.add_entry_point(entry_point);
entry_points
}
}
impl From<EntryPoint> for EntityEntryPoint {
fn from(value: EntryPoint) -> Self {
EntityEntryPoint::from(&value)
}
}
impl From<&EntryPoint> for EntityEntryPoint {
fn from(value: &EntryPoint) -> Self {
EntityEntryPoint::new(
value.name.clone(),
value.args.clone(),
value.ret.clone(),
value.access.clone(),
value.entry_point_type,
EntryPointPayment::Caller,
)
}
}
impl EntryPoints {
pub const fn new() -> EntryPoints {
EntryPoints(BTreeMap::<String, EntryPoint>::new())
}
pub fn new_with_default_entry_point() -> Self {
let mut entry_points = EntryPoints::new();
let entry_point = EntryPoint::default();
entry_points.add_entry_point(entry_point);
entry_points
}
pub fn add_entry_point(&mut self, entry_point: EntryPoint) {
self.0.insert(entry_point.name().to_string(), entry_point);
}
pub fn has_entry_point(&self, entry_point_name: &str) -> bool {
self.0.contains_key(entry_point_name)
}
pub fn get(&self, entry_point_name: &str) -> Option<&EntryPoint> {
self.0.get(entry_point_name)
}
pub fn keys(&self) -> impl Iterator<Item = &String> {
self.0.keys()
}
pub fn take_entry_points(self) -> Vec<EntryPoint> {
self.0.into_values().collect()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn contains_stored_session(&self) -> bool {
self.0
.values()
.any(|entry_point| entry_point.entry_point_type == EntryPointType::Caller)
}
}
impl From<Vec<EntryPoint>> for EntryPoints {
fn from(entry_points: Vec<EntryPoint>) -> EntryPoints {
let entries = entry_points
.into_iter()
.map(|entry_point| (String::from(entry_point.name()), entry_point))
.collect();
EntryPoints(entries)
}
}
impl From<EntryPoints> for EntityEntryPoints {
fn from(value: EntryPoints) -> Self {
let mut entry_points = EntityEntryPoints::new();
for contract_entry_point in value.take_entry_points() {
entry_points.add_entry_point(EntityEntryPoint::from(contract_entry_point));
}
entry_points
}
}
struct EntryPointLabels;
impl KeyValueLabels for EntryPointLabels {
const KEY: &'static str = "name";
const VALUE: &'static str = "entry_point";
}
#[cfg(feature = "json-schema")]
impl KeyValueJsonSchema for EntryPointLabels {
const JSON_SCHEMA_KV_NAME: Option<&'static str> = Some("NamedEntryPoint");
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "datasize", derive(DataSize))]
#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
pub struct Contract {
contract_package_hash: ContractPackageHash,
contract_wasm_hash: ContractWasmHash,
named_keys: NamedKeys,
entry_points: EntryPoints,
protocol_version: ProtocolVersion,
}
impl Contract {
pub fn new(
contract_package_hash: ContractPackageHash,
contract_wasm_hash: ContractWasmHash,
named_keys: NamedKeys,
entry_points: EntryPoints,
protocol_version: ProtocolVersion,
) -> Self {
Contract {
contract_package_hash,
contract_wasm_hash,
named_keys,
entry_points,
protocol_version,
}
}
pub fn contract_package_hash(&self) -> ContractPackageHash {
self.contract_package_hash
}
pub fn contract_wasm_hash(&self) -> ContractWasmHash {
self.contract_wasm_hash
}
pub fn has_entry_point(&self, name: &str) -> bool {
self.entry_points.has_entry_point(name)
}
pub fn entry_point(&self, method: &str) -> Option<&EntryPoint> {
self.entry_points.get(method)
}
pub fn protocol_version(&self) -> ProtocolVersion {
self.protocol_version
}
pub fn add_entry_point<T: Into<String>>(&mut self, entry_point: EntryPoint) {
self.entry_points.add_entry_point(entry_point);
}
pub fn contract_wasm_key(&self) -> Key {
self.contract_wasm_hash.into()
}
pub fn entry_points(&self) -> &EntryPoints {
&self.entry_points
}
pub fn take_named_keys(self) -> NamedKeys {
self.named_keys
}
pub fn named_keys(&self) -> &NamedKeys {
&self.named_keys
}
pub fn named_keys_append(&mut self, keys: NamedKeys) {
self.named_keys.append(keys);
}
pub fn remove_named_key(&mut self, key: &str) -> Option<Key> {
self.named_keys.remove(key)
}
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 {
self.protocol_version.value().major == protocol_version.value().major
}
}
impl ToBytes for Contract {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut result = bytesrepr::allocate_buffer(self)?;
self.contract_package_hash().write_bytes(&mut result)?;
self.contract_wasm_hash().write_bytes(&mut result)?;
self.named_keys().write_bytes(&mut result)?;
self.entry_points().write_bytes(&mut result)?;
self.protocol_version().write_bytes(&mut result)?;
Ok(result)
}
fn serialized_length(&self) -> usize {
ToBytes::serialized_length(&self.entry_points)
+ ToBytes::serialized_length(&self.contract_package_hash)
+ ToBytes::serialized_length(&self.contract_wasm_hash)
+ ToBytes::serialized_length(&self.protocol_version)
+ ToBytes::serialized_length(&self.named_keys)
}
fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
self.contract_package_hash().write_bytes(writer)?;
self.contract_wasm_hash().write_bytes(writer)?;
self.named_keys().write_bytes(writer)?;
self.entry_points().write_bytes(writer)?;
self.protocol_version().write_bytes(writer)?;
Ok(())
}
}
impl FromBytes for Contract {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (contract_package_hash, bytes) = FromBytes::from_bytes(bytes)?;
let (contract_wasm_hash, bytes) = FromBytes::from_bytes(bytes)?;
let (named_keys, bytes) = NamedKeys::from_bytes(bytes)?;
let (entry_points, bytes) = EntryPoints::from_bytes(bytes)?;
let (protocol_version, bytes) = ProtocolVersion::from_bytes(bytes)?;
Ok((
Contract {
contract_package_hash,
contract_wasm_hash,
named_keys,
entry_points,
protocol_version,
},
bytes,
))
}
}
impl Default for Contract {
fn default() -> Self {
Contract {
named_keys: NamedKeys::default(),
entry_points: EntryPoints::default(),
contract_wasm_hash: [0; KEY_HASH_LENGTH].into(),
contract_package_hash: [0; KEY_HASH_LENGTH].into(),
protocol_version: ProtocolVersion::V1_0_0,
}
}
}
pub const DEFAULT_ENTRY_POINT_NAME: &str = "call";
pub const ENTRY_POINT_NAME_INSTALL: &str = "install";
pub const UPGRADE_ENTRY_POINT_NAME: &str = "upgrade";
#[cfg(test)]
mod tests {
use super::*;
use crate::{AccessRights, EntryPointAccess, EntryPointType, Group, Parameter, URef};
use alloc::borrow::ToOwned;
const CONTRACT_HASH_V1: ContractHash = ContractHash::new([42; 32]);
const CONTRACT_HASH_V2: ContractHash = ContractHash::new([84; 32]);
fn make_contract_package() -> ContractPackage {
let mut contract_package = ContractPackage::new(
URef::new([0; 32], AccessRights::NONE),
ContractVersions::default(),
DisabledVersions::default(),
Groups::default(),
ContractPackageStatus::default(),
);
{
let group_urefs = {
let mut ret = BTreeSet::new();
ret.insert(URef::new([1; 32], AccessRights::READ));
ret
};
contract_package
.groups_mut()
.insert(Group::new("Group 1"), group_urefs.clone());
contract_package
.groups_mut()
.insert(Group::new("Group 2"), group_urefs);
}
let _entry_points = {
let mut ret = BTreeMap::new();
let entrypoint = EntryPoint::new(
"method0".to_string(),
vec![],
CLType::U32,
EntryPointAccess::groups(&["Group 2"]),
EntryPointType::Caller,
);
ret.insert(entrypoint.name().to_owned(), entrypoint);
let entrypoint = EntryPoint::new(
"method1".to_string(),
vec![Parameter::new("Foo", CLType::U32)],
CLType::U32,
EntryPointAccess::groups(&["Group 1"]),
EntryPointType::Caller,
);
ret.insert(entrypoint.name().to_owned(), entrypoint);
ret
};
let _contract_package_hash = [41; 32];
let _contract_wasm_hash = [43; 32];
let _named_keys = NamedKeys::new();
let protocol_version = ProtocolVersion::V1_0_0;
let v1 = contract_package
.insert_contract_version(protocol_version.value().major, CONTRACT_HASH_V1);
let v2 = contract_package
.insert_contract_version(protocol_version.value().major, CONTRACT_HASH_V2);
assert!(v2 > v1);
contract_package
}
#[test]
fn roundtrip_serialization() {
let contract_package = make_contract_package();
let bytes = contract_package.to_bytes().expect("should serialize");
let (decoded_package, rem) =
ContractPackage::from_bytes(&bytes).expect("should deserialize");
assert_eq!(contract_package, decoded_package);
assert_eq!(rem.len(), 0);
}
#[test]
fn contract_hash_from_slice() {
let bytes: Vec<u8> = (0..32).collect();
let contract_hash = HashAddr::try_from(&bytes[..]).expect("should create contract hash");
let contract_hash = ContractHash::new(contract_hash);
assert_eq!(&bytes, &contract_hash.as_bytes());
}
#[test]
fn contract_package_hash_from_slice() {
let bytes: Vec<u8> = (0..32).collect();
let contract_hash = HashAddr::try_from(&bytes[..]).expect("should create contract hash");
let contract_hash = ContractPackageHash::new(contract_hash);
assert_eq!(&bytes, &contract_hash.as_bytes());
}
#[test]
fn contract_hash_from_str() {
let contract_hash = ContractHash([3; 32]);
let encoded = contract_hash.to_formatted_string();
let decoded = ContractHash::from_formatted_str(&encoded).unwrap();
assert_eq!(contract_hash, decoded);
let invalid_prefix =
"contract--0000000000000000000000000000000000000000000000000000000000000000";
assert!(ContractHash::from_formatted_str(invalid_prefix).is_err());
let short_addr = "contract-00000000000000000000000000000000000000000000000000000000000000";
assert!(ContractHash::from_formatted_str(short_addr).is_err());
let long_addr =
"contract-000000000000000000000000000000000000000000000000000000000000000000";
assert!(ContractHash::from_formatted_str(long_addr).is_err());
let invalid_hex =
"contract-000000000000000000000000000000000000000000000000000000000000000g";
assert!(ContractHash::from_formatted_str(invalid_hex).is_err());
}
#[test]
fn contract_package_hash_from_str() {
let contract_package_hash = ContractPackageHash([3; 32]);
let encoded = contract_package_hash.to_formatted_string();
let decoded = ContractPackageHash::from_formatted_str(&encoded).unwrap();
assert_eq!(contract_package_hash, decoded);
let invalid_prefix =
"contract-package0000000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(invalid_prefix).unwrap_err(),
FromStrError::InvalidPrefix
));
let short_addr =
"contract-package-00000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(short_addr).unwrap_err(),
FromStrError::Hash(_)
));
let long_addr =
"contract-package-000000000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(long_addr).unwrap_err(),
FromStrError::Hash(_)
));
let invalid_hex =
"contract-package-000000000000000000000000000000000000000000000000000000000000000g";
assert!(matches!(
ContractPackageHash::from_formatted_str(invalid_hex).unwrap_err(),
FromStrError::Hex(_)
));
}
#[test]
fn contract_package_hash_from_legacy_str() {
let contract_package_hash = ContractPackageHash([3; 32]);
let hex_addr = contract_package_hash.to_string();
let legacy_encoded = format!("contract-package-wasm{}", hex_addr);
let decoded_from_legacy = ContractPackageHash::from_formatted_str(&legacy_encoded)
.expect("should accept legacy prefixed string");
assert_eq!(
contract_package_hash, decoded_from_legacy,
"decoded_from_legacy should equal decoded"
);
let invalid_prefix =
"contract-packagewasm0000000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(invalid_prefix).unwrap_err(),
FromStrError::InvalidPrefix
));
let short_addr =
"contract-package-wasm00000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(short_addr).unwrap_err(),
FromStrError::Hash(_)
));
let long_addr =
"contract-package-wasm000000000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(long_addr).unwrap_err(),
FromStrError::Hash(_)
));
let invalid_hex =
"contract-package-wasm000000000000000000000000000000000000000000000000000000000000000g";
assert!(matches!(
ContractPackageHash::from_formatted_str(invalid_hex).unwrap_err(),
FromStrError::Hex(_)
));
}
#[test]
fn contract_hash_serde_roundtrip() {
let contract_hash = ContractHash([255; 32]);
let serialized = bincode::serialize(&contract_hash).unwrap();
let deserialized = bincode::deserialize(&serialized).unwrap();
assert_eq!(contract_hash, deserialized)
}
#[test]
fn contract_hash_json_roundtrip() {
let contract_hash = ContractHash([255; 32]);
let json_string = serde_json::to_string_pretty(&contract_hash).unwrap();
let decoded = serde_json::from_str(&json_string).unwrap();
assert_eq!(contract_hash, decoded)
}
#[test]
fn contract_package_hash_serde_roundtrip() {
let contract_hash = ContractPackageHash([255; 32]);
let serialized = bincode::serialize(&contract_hash).unwrap();
let deserialized = bincode::deserialize(&serialized).unwrap();
assert_eq!(contract_hash, deserialized)
}
#[test]
fn contract_package_hash_json_roundtrip() {
let contract_hash = ContractPackageHash([255; 32]);
let json_string = serde_json::to_string_pretty(&contract_hash).unwrap();
let decoded = serde_json::from_str(&json_string).unwrap();
assert_eq!(contract_hash, decoded)
}
#[test]
fn package_hash_from_legacy_str() {
let package_hash = ContractPackageHash([3; 32]);
let hex_addr = package_hash.to_string();
let legacy_encoded = format!("contract-package-wasm{}", hex_addr);
let decoded_from_legacy = ContractPackageHash::from_formatted_str(&legacy_encoded)
.expect("should accept legacy prefixed string");
assert_eq!(
package_hash, decoded_from_legacy,
"decoded_from_legacy should equal decoded"
);
let invalid_prefix =
"contract-packagewasm0000000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(invalid_prefix).unwrap_err(),
FromStrError::InvalidPrefix
));
let short_addr =
"contract-package-wasm00000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(short_addr).unwrap_err(),
FromStrError::Hash(_)
));
let long_addr =
"contract-package-wasm000000000000000000000000000000000000000000000000000000000000000000";
assert!(matches!(
ContractPackageHash::from_formatted_str(long_addr).unwrap_err(),
FromStrError::Hash(_)
));
let invalid_hex =
"contract-package-wasm000000000000000000000000000000000000000000000000000000000000000g";
assert!(matches!(
ContractPackageHash::from_formatted_str(invalid_hex).unwrap_err(),
FromStrError::Hex(_)
));
}
}
#[cfg(test)]
mod prop_tests {
use proptest::prelude::*;
use crate::{bytesrepr, contracts::ContractPackage, gens};
proptest! {
#![proptest_config(ProptestConfig {
cases: 1024,
.. ProptestConfig::default()
})]
#[test]
fn test_value_contract(contract in gens::contract_arb()) {
bytesrepr::test_serialization_roundtrip(&contract);
}
#[test]
fn test_value_contract_package(contract_pkg in gens::contract_package_arb()) {
bytesrepr::test_serialization_roundtrip(&contract_pkg);
}
#[test]
fn test_json_contract_package(v in gens::contract_package_arb()) {
let json_str = serde_json::to_string(&v).unwrap();
let deserialized = serde_json::from_str::<ContractPackage>(&json_str).unwrap();
assert_eq!(v, deserialized);
}
}
}