1#![allow(clippy::field_reassign_with_default)]
4
5mod named_keys;
6
7use alloc::{
8 collections::{BTreeMap, BTreeSet},
9 format,
10 string::{String, ToString},
11 vec::Vec,
12};
13use core::{
14 array::TryFromSliceError,
15 convert::{TryFrom, TryInto},
16 fmt::{self, Debug, Display, Formatter},
17};
18use serde_bytes::ByteBuf;
19
20#[cfg(feature = "datasize")]
21use datasize::DataSize;
22#[cfg(feature = "json-schema")]
23use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
24use serde::{
25 de::{self, Error as SerdeError},
26 ser, Deserialize, Deserializer, Serialize, Serializer,
27};
28
29pub use self::named_keys::NamedKeys;
30
31use crate::{
32 account,
33 addressable_entity::TryFromSliceForAccountHashError,
34 bytesrepr::{self, FromBytes, ToBytes, U32_SERIALIZED_LENGTH},
35 checksummed_hex,
36 contract_wasm::ContractWasmHash,
37 package::PackageStatus,
38 serde_helpers::contract_package::HumanReadableContractPackage,
39 uref::{self, URef},
40 AddressableEntityHash, CLType, CLTyped, EntityAddr, EntityEntryPoint, EntityVersionKey,
41 EntryPointAccess, EntryPointPayment, EntryPointType, EntryPoints as EntityEntryPoints, Group,
42 Groups, HashAddr, Key, Package, PackageHash, Parameter, Parameters, ProtocolVersion,
43 KEY_HASH_LENGTH,
44};
45
46const CONTRACT_STRING_PREFIX: &str = "contract-";
47const CONTRACT_PACKAGE_STRING_PREFIX: &str = "contract-package-";
48const CONTRACT_PACKAGE_STRING_LEGACY_EXTRA_PREFIX: &str = "wasm";
50
51#[derive(Debug, PartialEq, Eq)]
53#[repr(u8)]
54#[non_exhaustive]
55pub enum Error {
56 PreviouslyUsedVersion = 1,
64 ContractNotFound = 2,
70 GroupAlreadyExists = 3,
77 MaxGroupsExceeded = 4,
84 MaxTotalURefsExceeded = 5,
91 GroupDoesNotExist = 6,
98 UnableToRemoveURef = 7,
104 GroupInUse = 8,
110 URefAlreadyExists = 9,
116}
117
118impl TryFrom<u8> for Error {
119 type Error = ();
120
121 fn try_from(value: u8) -> Result<Self, Self::Error> {
122 let error = match value {
123 v if v == Self::PreviouslyUsedVersion as u8 => Self::PreviouslyUsedVersion,
124 v if v == Self::ContractNotFound as u8 => Self::ContractNotFound,
125 v if v == Self::GroupAlreadyExists as u8 => Self::GroupAlreadyExists,
126 v if v == Self::MaxGroupsExceeded as u8 => Self::MaxGroupsExceeded,
127 v if v == Self::MaxTotalURefsExceeded as u8 => Self::MaxTotalURefsExceeded,
128 v if v == Self::GroupDoesNotExist as u8 => Self::GroupDoesNotExist,
129 v if v == Self::UnableToRemoveURef as u8 => Self::UnableToRemoveURef,
130 v if v == Self::GroupInUse as u8 => Self::GroupInUse,
131 v if v == Self::URefAlreadyExists as u8 => Self::URefAlreadyExists,
132 _ => return Err(()),
133 };
134 Ok(error)
135 }
136}
137
138#[derive(Debug)]
140pub struct TryFromSliceForContractHashError(());
141
142impl Display for TryFromSliceForContractHashError {
143 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
144 write!(f, "failed to retrieve from slice")
145 }
146}
147
148#[derive(Debug)]
150#[non_exhaustive]
151pub enum FromStrError {
152 InvalidPrefix,
154 Hex(base16::DecodeError),
156 Account(TryFromSliceForAccountHashError),
158 Hash(TryFromSliceError),
160 AccountHash(account::FromStrError),
162 URef(uref::FromStrError),
164}
165
166impl From<base16::DecodeError> for FromStrError {
167 fn from(error: base16::DecodeError) -> Self {
168 FromStrError::Hex(error)
169 }
170}
171
172impl From<TryFromSliceForAccountHashError> for FromStrError {
173 fn from(error: TryFromSliceForAccountHashError) -> Self {
174 FromStrError::Account(error)
175 }
176}
177
178impl From<TryFromSliceError> for FromStrError {
179 fn from(error: TryFromSliceError) -> Self {
180 FromStrError::Hash(error)
181 }
182}
183
184impl From<account::FromStrError> for FromStrError {
185 fn from(error: account::FromStrError) -> Self {
186 FromStrError::AccountHash(error)
187 }
188}
189
190impl From<uref::FromStrError> for FromStrError {
191 fn from(error: uref::FromStrError) -> Self {
192 FromStrError::URef(error)
193 }
194}
195
196impl Display for FromStrError {
197 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
198 match self {
199 FromStrError::InvalidPrefix => write!(f, "invalid prefix"),
200 FromStrError::Hex(error) => write!(f, "decode from hex: {}", error),
201 FromStrError::Account(error) => write!(f, "account from string error: {:?}", error),
202 FromStrError::Hash(error) => write!(f, "hash from string error: {}", error),
203 FromStrError::AccountHash(error) => {
204 write!(f, "account hash from string error: {:?}", error)
205 }
206 FromStrError::URef(error) => write!(f, "uref from string error: {:?}", error),
207 }
208 }
209}
210
211pub type ContractVersion = u32;
213
214pub const CONTRACT_INITIAL_VERSION: ContractVersion = 1;
216
217pub type ProtocolVersionMajor = u32;
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
222#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
223#[cfg_attr(feature = "datasize", derive(DataSize))]
224pub struct ContractVersionKey(ProtocolVersionMajor, ContractVersion);
225
226impl ContractVersionKey {
227 pub fn new(
229 protocol_version_major: ProtocolVersionMajor,
230 contract_version: ContractVersion,
231 ) -> Self {
232 Self(protocol_version_major, contract_version)
233 }
234
235 pub fn protocol_version_major(self) -> ProtocolVersionMajor {
237 self.0
238 }
239
240 pub fn contract_version(self) -> ContractVersion {
242 self.1
243 }
244}
245
246impl From<ContractVersionKey> for (ProtocolVersionMajor, ContractVersion) {
247 fn from(contract_version_key: ContractVersionKey) -> Self {
248 (contract_version_key.0, contract_version_key.1)
249 }
250}
251
252pub const CONTRACT_VERSION_KEY_SERIALIZED_LENGTH: usize =
254 U32_SERIALIZED_LENGTH + U32_SERIALIZED_LENGTH;
255
256impl ToBytes for ContractVersionKey {
257 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
258 let mut ret = bytesrepr::unchecked_allocate_buffer(self);
259 ret.append(&mut self.0.to_bytes()?);
260 ret.append(&mut self.1.to_bytes()?);
261 Ok(ret)
262 }
263
264 fn serialized_length(&self) -> usize {
265 CONTRACT_VERSION_KEY_SERIALIZED_LENGTH
266 }
267
268 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
269 self.0.write_bytes(writer)?;
270 self.1.write_bytes(writer)?;
271 Ok(())
272 }
273}
274
275impl FromBytes for ContractVersionKey {
276 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
277 let (major, rem): (u32, &[u8]) = FromBytes::from_bytes(bytes)?;
278 let (contract, rem): (ContractVersion, &[u8]) = FromBytes::from_bytes(rem)?;
279 Ok((ContractVersionKey::new(major, contract), rem))
280 }
281}
282
283impl fmt::Display for ContractVersionKey {
284 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
285 write!(f, "{}.{}", self.0, self.1)
286 }
287}
288
289pub type ContractVersions = BTreeMap<ContractVersionKey, ContractHash>;
291
292pub type DisabledVersions = BTreeSet<ContractVersionKey>;
295
296#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
298#[cfg_attr(feature = "datasize", derive(DataSize))]
299pub struct ContractHash(HashAddr);
300
301impl ContractHash {
302 pub const fn new(value: HashAddr) -> ContractHash {
304 ContractHash(value)
305 }
306
307 pub fn value(&self) -> HashAddr {
309 self.0
310 }
311
312 pub fn as_bytes(&self) -> &[u8] {
314 &self.0
315 }
316
317 pub fn to_formatted_string(self) -> String {
319 format!(
320 "{}{}",
321 CONTRACT_STRING_PREFIX,
322 base16::encode_lower(&self.0),
323 )
324 }
325
326 pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
329 let remainder = input
330 .strip_prefix(CONTRACT_STRING_PREFIX)
331 .ok_or(FromStrError::InvalidPrefix)?;
332 let bytes = HashAddr::try_from(checksummed_hex::decode(remainder)?.as_ref())?;
333 Ok(ContractHash(bytes))
334 }
335}
336
337impl Display for ContractHash {
338 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
339 write!(f, "{}", base16::encode_lower(&self.0))
340 }
341}
342
343impl Debug for ContractHash {
344 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
345 write!(f, "ContractHash({})", base16::encode_lower(&self.0))
346 }
347}
348
349impl CLTyped for ContractHash {
350 fn cl_type() -> CLType {
351 CLType::ByteArray(KEY_HASH_LENGTH as u32)
352 }
353}
354
355impl From<AddressableEntityHash> for ContractHash {
356 fn from(entity_hash: AddressableEntityHash) -> Self {
357 ContractHash::new(entity_hash.value())
358 }
359}
360
361impl ToBytes for ContractHash {
362 #[inline(always)]
363 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
364 self.0.to_bytes()
365 }
366
367 #[inline(always)]
368 fn serialized_length(&self) -> usize {
369 self.0.serialized_length()
370 }
371
372 #[inline(always)]
373 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
374 writer.extend_from_slice(&self.0);
375 Ok(())
376 }
377}
378
379impl FromBytes for ContractHash {
380 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
381 let (bytes, rem) = FromBytes::from_bytes(bytes)?;
382 Ok((ContractHash::new(bytes), rem))
383 }
384}
385
386impl From<[u8; 32]> for ContractHash {
387 fn from(bytes: [u8; 32]) -> Self {
388 ContractHash(bytes)
389 }
390}
391
392impl Serialize for ContractHash {
393 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
394 if serializer.is_human_readable() {
395 self.to_formatted_string().serialize(serializer)
396 } else {
397 self.0.serialize(serializer)
398 }
399 }
400}
401
402impl<'de> Deserialize<'de> for ContractHash {
403 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
404 if deserializer.is_human_readable() {
405 let formatted_string = String::deserialize(deserializer)?;
406 ContractHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
407 } else {
408 let bytes = HashAddr::deserialize(deserializer)?;
409 Ok(ContractHash(bytes))
410 }
411 }
412}
413
414impl AsRef<[u8]> for ContractHash {
415 fn as_ref(&self) -> &[u8] {
416 self.0.as_ref()
417 }
418}
419
420impl TryFrom<&[u8]> for ContractHash {
421 type Error = TryFromSliceForContractHashError;
422
423 fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceForContractHashError> {
424 HashAddr::try_from(bytes)
425 .map(ContractHash::new)
426 .map_err(|_| TryFromSliceForContractHashError(()))
427 }
428}
429
430impl TryFrom<&Vec<u8>> for ContractHash {
431 type Error = TryFromSliceForContractHashError;
432
433 fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
434 HashAddr::try_from(bytes as &[u8])
435 .map(ContractHash::new)
436 .map_err(|_| TryFromSliceForContractHashError(()))
437 }
438}
439
440#[cfg(feature = "json-schema")]
441impl JsonSchema for ContractHash {
442 fn schema_name() -> String {
443 String::from("ContractHash")
444 }
445
446 fn json_schema(gen: &mut SchemaGenerator) -> Schema {
447 let schema = gen.subschema_for::<String>();
448 let mut schema_object = schema.into_object();
449 schema_object.metadata().description = Some("The hash address of the contract".to_string());
450 schema_object.into()
451 }
452}
453
454#[derive(Default, PartialOrd, Ord, PartialEq, Eq, Hash, Clone, Copy)]
456#[cfg_attr(feature = "datasize", derive(DataSize))]
457pub struct ContractPackageHash(HashAddr);
458
459impl ContractPackageHash {
460 pub const fn new(value: HashAddr) -> ContractPackageHash {
462 ContractPackageHash(value)
463 }
464
465 pub fn value(&self) -> HashAddr {
467 self.0
468 }
469
470 pub fn as_bytes(&self) -> &[u8] {
472 &self.0
473 }
474
475 pub fn to_formatted_string(self) -> String {
477 format!(
478 "{}{}",
479 CONTRACT_PACKAGE_STRING_PREFIX,
480 base16::encode_lower(&self.0),
481 )
482 }
483
484 pub fn from_formatted_str(input: &str) -> Result<Self, FromStrError> {
487 let remainder = input
488 .strip_prefix(CONTRACT_PACKAGE_STRING_PREFIX)
489 .ok_or(FromStrError::InvalidPrefix)?;
490
491 let hex_addr = remainder
492 .strip_prefix(CONTRACT_PACKAGE_STRING_LEGACY_EXTRA_PREFIX)
493 .unwrap_or(remainder);
494
495 let bytes = HashAddr::try_from(checksummed_hex::decode(hex_addr)?.as_ref())?;
496 Ok(ContractPackageHash(bytes))
497 }
498}
499
500impl From<PackageHash> for ContractPackageHash {
501 fn from(value: PackageHash) -> Self {
502 ContractPackageHash::new(value.value())
503 }
504}
505
506impl Display for ContractPackageHash {
507 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
508 write!(f, "{}", base16::encode_lower(&self.0))
509 }
510}
511
512impl Debug for ContractPackageHash {
513 fn fmt(&self, f: &mut Formatter) -> core::fmt::Result {
514 write!(f, "ContractPackageHash({})", base16::encode_lower(&self.0))
515 }
516}
517
518impl CLTyped for ContractPackageHash {
519 fn cl_type() -> CLType {
520 CLType::ByteArray(KEY_HASH_LENGTH as u32)
521 }
522}
523
524impl ToBytes for ContractPackageHash {
525 #[inline(always)]
526 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
527 self.0.to_bytes()
528 }
529
530 #[inline(always)]
531 fn serialized_length(&self) -> usize {
532 self.0.serialized_length()
533 }
534
535 #[inline(always)]
536 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
537 writer.extend_from_slice(&self.0);
538 Ok(())
539 }
540}
541
542impl FromBytes for ContractPackageHash {
543 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
544 let (bytes, rem) = FromBytes::from_bytes(bytes)?;
545 Ok((ContractPackageHash::new(bytes), rem))
546 }
547}
548
549impl From<[u8; 32]> for ContractPackageHash {
550 fn from(bytes: [u8; 32]) -> Self {
551 ContractPackageHash(bytes)
552 }
553}
554
555impl Serialize for ContractPackageHash {
556 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
557 if serializer.is_human_readable() {
558 self.to_formatted_string().serialize(serializer)
559 } else {
560 self.0.serialize(serializer)
561 }
562 }
563}
564
565impl<'de> Deserialize<'de> for ContractPackageHash {
566 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
567 if deserializer.is_human_readable() {
568 let formatted_string = String::deserialize(deserializer)?;
569 ContractPackageHash::from_formatted_str(&formatted_string).map_err(SerdeError::custom)
570 } else {
571 let bytes = HashAddr::deserialize(deserializer)?;
572 Ok(ContractPackageHash(bytes))
573 }
574 }
575}
576
577impl AsRef<[u8]> for ContractPackageHash {
578 fn as_ref(&self) -> &[u8] {
579 self.0.as_ref()
580 }
581}
582
583impl TryFrom<&[u8]> for ContractPackageHash {
584 type Error = TryFromSliceForContractHashError;
585
586 fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceForContractHashError> {
587 HashAddr::try_from(bytes)
588 .map(ContractPackageHash::new)
589 .map_err(|_| TryFromSliceForContractHashError(()))
590 }
591}
592
593impl TryFrom<&Vec<u8>> for ContractPackageHash {
594 type Error = TryFromSliceForContractHashError;
595
596 fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
597 HashAddr::try_from(bytes as &[u8])
598 .map(ContractPackageHash::new)
599 .map_err(|_| TryFromSliceForContractHashError(()))
600 }
601}
602
603#[cfg(feature = "json-schema")]
604impl JsonSchema for ContractPackageHash {
605 fn schema_name() -> String {
606 String::from("ContractPackageHash")
607 }
608
609 fn json_schema(gen: &mut SchemaGenerator) -> Schema {
610 let schema = gen.subschema_for::<String>();
611 let mut schema_object = schema.into_object();
612 schema_object.metadata().description =
613 Some("The hash address of the contract package".to_string());
614 schema_object.into()
615 }
616}
617
618#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
620#[cfg_attr(feature = "datasize", derive(DataSize))]
621#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
622pub enum ContractPackageStatus {
623 Locked,
625 Unlocked,
627}
628
629impl ContractPackageStatus {
630 pub fn new(is_locked: bool) -> Self {
632 if is_locked {
633 ContractPackageStatus::Locked
634 } else {
635 ContractPackageStatus::Unlocked
636 }
637 }
638}
639
640impl Default for ContractPackageStatus {
641 fn default() -> Self {
642 Self::Unlocked
643 }
644}
645
646impl ToBytes for ContractPackageStatus {
647 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
648 let mut result = bytesrepr::allocate_buffer(self)?;
649 match self {
650 ContractPackageStatus::Unlocked => result.append(&mut false.to_bytes()?),
651 ContractPackageStatus::Locked => result.append(&mut true.to_bytes()?),
652 }
653 Ok(result)
654 }
655
656 fn serialized_length(&self) -> usize {
657 match self {
658 ContractPackageStatus::Unlocked => false.serialized_length(),
659 ContractPackageStatus::Locked => true.serialized_length(),
660 }
661 }
662
663 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
664 match self {
665 ContractPackageStatus::Locked => writer.push(u8::from(true)),
666 ContractPackageStatus::Unlocked => writer.push(u8::from(false)),
667 }
668 Ok(())
669 }
670}
671
672impl FromBytes for ContractPackageStatus {
673 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
674 let (val, bytes) = bool::from_bytes(bytes)?;
675 let status = ContractPackageStatus::new(val);
676 Ok((status, bytes))
677 }
678}
679
680#[derive(Debug, Clone, PartialEq, Eq, Default)]
682#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
683#[cfg_attr(feature = "datasize", derive(DataSize))]
684pub struct ContractPackage {
685 access_key: URef,
687 #[cfg_attr(
689 feature = "json-schema",
690 schemars(
691 with = "Vec<crate::serde_helpers::contract_package::HumanReadableContractVersion>"
692 )
693 )]
694 versions: ContractVersions,
695 disabled_versions: DisabledVersions,
697 groups: Groups,
702 lock_status: ContractPackageStatus,
704}
705
706impl CLTyped for ContractPackage {
707 fn cl_type() -> CLType {
708 CLType::Any
709 }
710}
711
712impl ContractPackage {
713 pub fn new(
715 access_key: URef,
716 versions: ContractVersions,
717 disabled_versions: DisabledVersions,
718 groups: Groups,
719 lock_status: ContractPackageStatus,
720 ) -> Self {
721 ContractPackage {
722 access_key,
723 versions,
724 disabled_versions,
725 groups,
726 lock_status,
727 }
728 }
729
730 pub fn access_key(&self) -> URef {
732 self.access_key
733 }
734
735 pub fn groups(&self) -> &Groups {
737 &self.groups
738 }
739
740 pub fn versions(&self) -> &ContractVersions {
742 &self.versions
743 }
744
745 pub fn versions_mut(&mut self) -> &mut ContractVersions {
747 &mut self.versions
748 }
749
750 pub fn take_versions(self) -> ContractVersions {
752 self.versions
753 }
754
755 pub fn take_groups(self) -> Groups {
757 self.groups
758 }
759
760 pub fn disabled_versions(&self) -> &DisabledVersions {
762 &self.disabled_versions
763 }
764
765 pub fn disabled_versions_mut(&mut self) -> &mut DisabledVersions {
767 &mut self.disabled_versions
768 }
769
770 pub fn lock_status(&self) -> ContractPackageStatus {
772 self.lock_status.clone()
773 }
774
775 pub fn is_locked(&self) -> bool {
776 match self.lock_status {
777 ContractPackageStatus::Locked => true,
778 ContractPackageStatus::Unlocked => false,
779 }
780 }
781
782 pub fn disable_contract_version(&mut self, contract_hash: ContractHash) -> Result<(), Error> {
784 let contract_version_key = self
785 .find_contract_version_key_by_hash(&contract_hash)
786 .copied()
787 .ok_or(Error::ContractNotFound)?;
788
789 if !self.disabled_versions.contains(&contract_version_key) {
790 self.disabled_versions.insert(contract_version_key);
791 }
792
793 Ok(())
794 }
795
796 pub fn enable_contract_version(&mut self, contract_hash: ContractHash) -> Result<(), Error> {
798 let contract_version_key = self
799 .find_contract_version_key_by_hash(&contract_hash)
800 .copied()
801 .ok_or(Error::ContractNotFound)?;
802
803 self.disabled_versions.remove(&contract_version_key);
804
805 Ok(())
806 }
807
808 fn find_contract_version_key_by_hash(
809 &self,
810 contract_hash: &ContractHash,
811 ) -> Option<&ContractVersionKey> {
812 self.versions
813 .iter()
814 .filter_map(|(k, v)| if v == contract_hash { Some(k) } else { None })
815 .next()
816 }
817
818 pub fn remove_group(&mut self, group: &Group) -> bool {
820 self.groups.0.remove(group).is_some()
821 }
822 fn next_contract_version_for(&self, protocol_version: ProtocolVersionMajor) -> ContractVersion {
823 let current_version = self
824 .versions
825 .keys()
826 .rev()
827 .find_map(|&contract_version_key| {
828 if contract_version_key.protocol_version_major() == protocol_version {
829 Some(contract_version_key.contract_version())
830 } else {
831 None
832 }
833 })
834 .unwrap_or(0);
835
836 current_version + 1
837 }
838
839 pub fn is_version_enabled(&self, contract_version_key: ContractVersionKey) -> bool {
841 !self.disabled_versions.contains(&contract_version_key)
842 && self.versions.contains_key(&contract_version_key)
843 }
844
845 pub fn enabled_versions(&self) -> ContractVersions {
847 let mut ret = ContractVersions::new();
848 for version in &self.versions {
849 if !self.is_version_enabled(*version.0) {
850 continue;
851 }
852 ret.insert(*version.0, *version.1);
853 }
854 ret
855 }
856
857 pub fn current_contract_version(&self) -> Option<ContractVersionKey> {
859 self.enabled_versions().keys().next_back().copied()
860 }
861
862 pub fn current_contract_hash(&self) -> Option<ContractHash> {
864 self.enabled_versions().values().next_back().copied()
865 }
866
867 pub fn insert_contract_version(
868 &mut self,
869 protocol_version_major: ProtocolVersionMajor,
870 contract_hash: ContractHash,
871 ) -> ContractVersionKey {
872 let contract_version = self.next_contract_version_for(protocol_version_major);
873 let key = ContractVersionKey::new(protocol_version_major, contract_version);
874 self.versions.insert(key, contract_hash);
875 key
876 }
877
878 pub fn groups_mut(&mut self) -> &mut Groups {
879 &mut self.groups
880 }
881}
882
883impl Serialize for ContractPackage {
884 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
885 if serializer.is_human_readable() {
886 HumanReadableContractPackage::from(self).serialize(serializer)
887 } else {
888 let bytes = self
889 .to_bytes()
890 .map_err(|error| ser::Error::custom(format!("{:?}", error)))?;
891 ByteBuf::from(bytes).serialize(serializer)
892 }
893 }
894}
895
896impl<'de> Deserialize<'de> for ContractPackage {
897 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
898 if deserializer.is_human_readable() {
899 let json_helper = HumanReadableContractPackage::deserialize(deserializer)?;
900 json_helper.try_into().map_err(de::Error::custom)
901 } else {
902 let bytes = ByteBuf::deserialize(deserializer)?.into_vec();
903 bytesrepr::deserialize::<ContractPackage>(bytes)
904 .map_err(|error| de::Error::custom(format!("{:?}", error)))
905 }
906 }
907}
908
909impl ToBytes for ContractPackage {
910 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
911 let mut result = bytesrepr::allocate_buffer(self)?;
912 self.access_key().write_bytes(&mut result)?;
913 self.versions().write_bytes(&mut result)?;
914 self.disabled_versions().write_bytes(&mut result)?;
915 self.groups().write_bytes(&mut result)?;
916 self.lock_status.write_bytes(&mut result)?;
917 Ok(result)
918 }
919
920 fn serialized_length(&self) -> usize {
921 self.access_key.serialized_length()
922 + self.versions.serialized_length()
923 + self.disabled_versions.serialized_length()
924 + self.groups.serialized_length()
925 + self.lock_status.serialized_length()
926 }
927
928 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
929 self.access_key().write_bytes(writer)?;
930 self.versions().write_bytes(writer)?;
931 self.disabled_versions().write_bytes(writer)?;
932 self.groups().write_bytes(writer)?;
933 self.lock_status.write_bytes(writer)?;
934 Ok(())
935 }
936}
937
938impl FromBytes for ContractPackage {
939 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
940 let (access_key, bytes) = URef::from_bytes(bytes)?;
941 let (versions, bytes) = ContractVersions::from_bytes(bytes)?;
942 let (disabled_versions, bytes) = DisabledVersions::from_bytes(bytes)?;
943 let (groups, bytes) = Groups::from_bytes(bytes)?;
944 let (lock_status, bytes) = ContractPackageStatus::from_bytes(bytes)?;
945 let result = ContractPackage {
946 access_key,
947 versions,
948 disabled_versions,
949 groups,
950 lock_status,
951 };
952
953 Ok((result, bytes))
954 }
955}
956
957impl From<ContractPackage> for Package {
958 fn from(value: ContractPackage) -> Self {
959 let versions: BTreeMap<EntityVersionKey, EntityAddr> = value
960 .versions
961 .into_iter()
962 .map(|(version, contract_hash)| {
963 let entity_version = EntityVersionKey::new(2, version.contract_version());
964 let entity_hash = EntityAddr::SmartContract(contract_hash.value());
965 (entity_version, entity_hash)
966 })
967 .collect();
968
969 let disabled_versions = value
970 .disabled_versions
971 .into_iter()
972 .map(|contract_versions| {
973 EntityVersionKey::new(
974 contract_versions.protocol_version_major(),
975 contract_versions.contract_version(),
976 )
977 })
978 .collect();
979
980 let lock_status = if value.lock_status == ContractPackageStatus::Locked {
981 PackageStatus::Locked
982 } else {
983 PackageStatus::Unlocked
984 };
985
986 Package::new(
987 versions.into(),
988 disabled_versions,
989 value.groups,
990 lock_status,
991 )
992 }
993}
994
995#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
998#[cfg_attr(feature = "datasize", derive(DataSize))]
999#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
1000pub struct EntryPoint {
1001 name: String,
1002 args: Parameters,
1003 ret: CLType,
1004 access: EntryPointAccess,
1005 entry_point_type: EntryPointType,
1006}
1007
1008impl From<EntryPoint> for (String, Parameters, CLType, EntryPointAccess, EntryPointType) {
1009 fn from(entry_point: EntryPoint) -> Self {
1010 (
1011 entry_point.name,
1012 entry_point.args,
1013 entry_point.ret,
1014 entry_point.access,
1015 entry_point.entry_point_type,
1016 )
1017 }
1018}
1019
1020impl EntryPoint {
1021 pub fn new<T: Into<String>>(
1023 name: T,
1024 args: Parameters,
1025 ret: CLType,
1026 access: EntryPointAccess,
1027 entry_point_type: EntryPointType,
1028 ) -> Self {
1029 EntryPoint {
1030 name: name.into(),
1031 args,
1032 ret,
1033 access,
1034 entry_point_type,
1035 }
1036 }
1037
1038 pub fn default_with_name<T: Into<String>>(name: T) -> Self {
1040 EntryPoint {
1041 name: name.into(),
1042 ..Default::default()
1043 }
1044 }
1045
1046 pub fn name(&self) -> &str {
1048 &self.name
1049 }
1050
1051 pub fn access(&self) -> &EntryPointAccess {
1053 &self.access
1054 }
1055
1056 pub fn args(&self) -> &[Parameter] {
1058 self.args.as_slice()
1059 }
1060
1061 pub fn ret(&self) -> &CLType {
1063 &self.ret
1064 }
1065
1066 pub fn entry_point_type(&self) -> EntryPointType {
1068 self.entry_point_type
1069 }
1070}
1071
1072impl Default for EntryPoint {
1073 fn default() -> Self {
1075 EntryPoint {
1076 name: DEFAULT_ENTRY_POINT_NAME.to_string(),
1077 args: Vec::new(),
1078 ret: CLType::Unit,
1079 access: EntryPointAccess::Public,
1080 entry_point_type: EntryPointType::Caller,
1081 }
1082 }
1083}
1084
1085impl ToBytes for EntryPoint {
1086 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
1087 let mut buffer = bytesrepr::allocate_buffer(self)?;
1088 self.write_bytes(&mut buffer)?;
1089 Ok(buffer)
1090 }
1091
1092 fn serialized_length(&self) -> usize {
1093 self.name.serialized_length()
1094 + self.args.serialized_length()
1095 + self.ret.serialized_length()
1096 + self.access.serialized_length()
1097 + self.entry_point_type.serialized_length()
1098 }
1099
1100 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
1101 self.name.write_bytes(writer)?;
1102 self.args.write_bytes(writer)?;
1103 self.ret.append_bytes(writer)?;
1104 self.access.write_bytes(writer)?;
1105 self.entry_point_type.write_bytes(writer)?;
1106 Ok(())
1107 }
1108}
1109
1110impl FromBytes for EntryPoint {
1111 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
1112 let (name, bytes) = String::from_bytes(bytes)?;
1113 let (args, bytes) = Vec::<Parameter>::from_bytes(bytes)?;
1114 let (ret, bytes) = CLType::from_bytes(bytes)?;
1115 let (access, bytes) = EntryPointAccess::from_bytes(bytes)?;
1116 let (entry_point_type, bytes) = EntryPointType::from_bytes(bytes)?;
1117
1118 Ok((
1119 EntryPoint {
1120 name,
1121 args,
1122 ret,
1123 access,
1124 entry_point_type,
1125 },
1126 bytes,
1127 ))
1128 }
1129}
1130
1131#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
1133#[cfg_attr(feature = "datasize", derive(DataSize))]
1134#[serde(transparent, deny_unknown_fields)]
1135pub struct EntryPoints(BTreeMap<String, EntryPoint>);
1136
1137impl From<crate::addressable_entity::EntryPoints> for EntryPoints {
1138 fn from(value: EntityEntryPoints) -> Self {
1139 let mut ret = EntryPoints::new();
1140 for entity_entry_point in value.take_entry_points() {
1141 let entry_point = EntryPoint::new(
1142 entity_entry_point.name(),
1143 Parameters::from(entity_entry_point.args()),
1144 entity_entry_point.ret().clone(),
1145 entity_entry_point.access().clone(),
1146 entity_entry_point.entry_point_type(),
1147 );
1148 ret.add_entry_point(entry_point);
1149 }
1150 ret
1151 }
1152}
1153
1154impl ToBytes for EntryPoints {
1155 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
1156 self.0.to_bytes()
1157 }
1158
1159 fn serialized_length(&self) -> usize {
1160 self.0.serialized_length()
1161 }
1162
1163 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
1164 self.0.write_bytes(writer)
1165 }
1166}
1167
1168impl FromBytes for EntryPoints {
1169 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
1170 let (entry_points_map, remainder) = BTreeMap::<String, EntryPoint>::from_bytes(bytes)?;
1171 Ok((EntryPoints(entry_points_map), remainder))
1172 }
1173}
1174
1175impl Default for EntryPoints {
1176 fn default() -> Self {
1177 let mut entry_points = EntryPoints::new();
1178 let entry_point = EntryPoint::default();
1179 entry_points.add_entry_point(entry_point);
1180 entry_points
1181 }
1182}
1183
1184impl From<EntryPoint> for EntityEntryPoint {
1185 fn from(value: EntryPoint) -> Self {
1186 EntityEntryPoint::from(&value)
1187 }
1188}
1189
1190impl From<&EntryPoint> for EntityEntryPoint {
1191 fn from(value: &EntryPoint) -> Self {
1192 EntityEntryPoint::new(
1193 value.name.clone(),
1194 value.args.clone(),
1195 value.ret.clone(),
1196 value.access.clone(),
1197 value.entry_point_type,
1198 EntryPointPayment::Caller,
1199 )
1200 }
1201}
1202
1203impl EntryPoints {
1204 pub const fn new() -> EntryPoints {
1206 EntryPoints(BTreeMap::<String, EntryPoint>::new())
1207 }
1208
1209 pub fn new_with_default_entry_point() -> Self {
1211 let mut entry_points = EntryPoints::new();
1212 let entry_point = EntryPoint::default();
1213 entry_points.add_entry_point(entry_point);
1214 entry_points
1215 }
1216
1217 pub fn add_entry_point(&mut self, entry_point: EntryPoint) -> Option<EntryPoint> {
1219 self.0.insert(entry_point.name().to_string(), entry_point)
1220 }
1221
1222 pub fn has_entry_point(&self, entry_point_name: &str) -> bool {
1224 self.0.contains_key(entry_point_name)
1225 }
1226
1227 pub fn get(&self, entry_point_name: &str) -> Option<&EntryPoint> {
1229 self.0.get(entry_point_name)
1230 }
1231
1232 pub fn keys(&self) -> impl Iterator<Item = &String> {
1234 self.0.keys()
1235 }
1236
1237 pub fn take_entry_points(self) -> Vec<EntryPoint> {
1239 self.0.into_values().collect()
1240 }
1241
1242 pub fn len(&self) -> usize {
1244 self.0.len()
1245 }
1246
1247 pub fn is_empty(&self) -> bool {
1249 self.0.is_empty()
1250 }
1251
1252 pub fn contains_stored_session(&self) -> bool {
1254 self.0
1255 .values()
1256 .any(|entry_point| entry_point.entry_point_type == EntryPointType::Caller)
1257 }
1258}
1259
1260impl From<Vec<EntryPoint>> for EntryPoints {
1261 fn from(entry_points: Vec<EntryPoint>) -> EntryPoints {
1262 let entries = entry_points
1263 .into_iter()
1264 .map(|entry_point| (String::from(entry_point.name()), entry_point))
1265 .collect();
1266 EntryPoints(entries)
1267 }
1268}
1269
1270impl From<EntryPoints> for EntityEntryPoints {
1271 fn from(value: EntryPoints) -> Self {
1272 let mut entry_points = EntityEntryPoints::new();
1273 for contract_entry_point in value.take_entry_points() {
1274 entry_points.add_entry_point(EntityEntryPoint::from(contract_entry_point));
1275 }
1276 entry_points
1277 }
1278}
1279
1280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1282#[cfg_attr(feature = "datasize", derive(DataSize))]
1283#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
1284pub struct Contract {
1285 contract_package_hash: ContractPackageHash,
1286 contract_wasm_hash: ContractWasmHash,
1287 named_keys: NamedKeys,
1288 #[cfg_attr(feature = "json-schema", schemars(with = "Vec<EntryPoint>"))]
1289 entry_points: EntryPoints,
1290 protocol_version: ProtocolVersion,
1291}
1292
1293impl Contract {
1294 pub fn new(
1296 contract_package_hash: ContractPackageHash,
1297 contract_wasm_hash: ContractWasmHash,
1298 named_keys: NamedKeys,
1299 entry_points: EntryPoints,
1300 protocol_version: ProtocolVersion,
1301 ) -> Self {
1302 Contract {
1303 contract_package_hash,
1304 contract_wasm_hash,
1305 named_keys,
1306 entry_points,
1307 protocol_version,
1308 }
1309 }
1310
1311 pub fn contract_package_hash(&self) -> ContractPackageHash {
1313 self.contract_package_hash
1314 }
1315
1316 pub fn contract_wasm_hash(&self) -> ContractWasmHash {
1318 self.contract_wasm_hash
1319 }
1320
1321 pub fn has_entry_point(&self, name: &str) -> bool {
1323 self.entry_points.has_entry_point(name)
1324 }
1325
1326 pub fn entry_point(&self, method: &str) -> Option<&EntryPoint> {
1328 self.entry_points.get(method)
1329 }
1330
1331 pub fn protocol_version(&self) -> ProtocolVersion {
1333 self.protocol_version
1334 }
1335
1336 pub fn add_entry_point<T: Into<String>>(&mut self, entry_point: EntryPoint) {
1338 self.entry_points.add_entry_point(entry_point);
1339 }
1340
1341 pub fn contract_wasm_key(&self) -> Key {
1343 self.contract_wasm_hash.into()
1344 }
1345
1346 pub fn entry_points(&self) -> &EntryPoints {
1348 &self.entry_points
1349 }
1350
1351 pub fn take_named_keys(self) -> NamedKeys {
1353 self.named_keys
1354 }
1355
1356 pub fn named_keys(&self) -> &NamedKeys {
1358 &self.named_keys
1359 }
1360
1361 pub fn named_keys_append(&mut self, keys: NamedKeys) {
1363 self.named_keys.append(keys);
1364 }
1365
1366 pub fn remove_named_key(&mut self, key: &str) -> Option<Key> {
1368 self.named_keys.remove(key)
1369 }
1370
1371 pub fn set_protocol_version(&mut self, protocol_version: ProtocolVersion) {
1373 self.protocol_version = protocol_version;
1374 }
1375
1376 pub fn is_compatible_protocol_version(&self, protocol_version: ProtocolVersion) -> bool {
1378 self.protocol_version.value().major == protocol_version.value().major
1379 }
1380}
1381
1382impl ToBytes for Contract {
1383 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
1384 let mut result = bytesrepr::allocate_buffer(self)?;
1385 self.contract_package_hash().write_bytes(&mut result)?;
1386 self.contract_wasm_hash().write_bytes(&mut result)?;
1387 self.named_keys().write_bytes(&mut result)?;
1388 self.entry_points().write_bytes(&mut result)?;
1389 self.protocol_version().write_bytes(&mut result)?;
1390 Ok(result)
1391 }
1392
1393 fn serialized_length(&self) -> usize {
1394 ToBytes::serialized_length(&self.entry_points)
1395 + ToBytes::serialized_length(&self.contract_package_hash)
1396 + ToBytes::serialized_length(&self.contract_wasm_hash)
1397 + ToBytes::serialized_length(&self.protocol_version)
1398 + ToBytes::serialized_length(&self.named_keys)
1399 }
1400
1401 fn write_bytes(&self, writer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
1402 self.contract_package_hash().write_bytes(writer)?;
1403 self.contract_wasm_hash().write_bytes(writer)?;
1404 self.named_keys().write_bytes(writer)?;
1405 self.entry_points().write_bytes(writer)?;
1406 self.protocol_version().write_bytes(writer)?;
1407 Ok(())
1408 }
1409}
1410
1411impl FromBytes for Contract {
1412 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
1413 let (contract_package_hash, bytes) = FromBytes::from_bytes(bytes)?;
1414 let (contract_wasm_hash, bytes) = FromBytes::from_bytes(bytes)?;
1415 let (named_keys, bytes) = NamedKeys::from_bytes(bytes)?;
1416 let (entry_points, bytes) = EntryPoints::from_bytes(bytes)?;
1417 let (protocol_version, bytes) = ProtocolVersion::from_bytes(bytes)?;
1418 Ok((
1419 Contract {
1420 contract_package_hash,
1421 contract_wasm_hash,
1422 named_keys,
1423 entry_points,
1424 protocol_version,
1425 },
1426 bytes,
1427 ))
1428 }
1429}
1430
1431impl Default for Contract {
1432 fn default() -> Self {
1433 Contract {
1434 named_keys: NamedKeys::default(),
1435 entry_points: EntryPoints::default(),
1436 contract_wasm_hash: [0; KEY_HASH_LENGTH].into(),
1437 contract_package_hash: [0; KEY_HASH_LENGTH].into(),
1438 protocol_version: ProtocolVersion::V1_0_0,
1439 }
1440 }
1441}
1442
1443pub const DEFAULT_ENTRY_POINT_NAME: &str = "call";
1445
1446pub const ENTRY_POINT_NAME_INSTALL: &str = "install";
1448
1449pub const UPGRADE_ENTRY_POINT_NAME: &str = "upgrade";
1451
1452#[cfg(test)]
1453mod tests {
1454 use super::*;
1455 use crate::{AccessRights, EntryPointAccess, EntryPointType, Group, Parameter, URef};
1456 use alloc::borrow::ToOwned;
1457
1458 const CONTRACT_HASH_V1: ContractHash = ContractHash::new([42; 32]);
1459 const CONTRACT_HASH_V2: ContractHash = ContractHash::new([84; 32]);
1460
1461 fn make_contract_package() -> ContractPackage {
1462 let mut contract_package = ContractPackage::new(
1463 URef::new([0; 32], AccessRights::NONE),
1464 ContractVersions::default(),
1465 DisabledVersions::default(),
1466 Groups::default(),
1467 ContractPackageStatus::default(),
1468 );
1469
1470 {
1472 let group_urefs = {
1473 let mut ret = BTreeSet::new();
1474 ret.insert(URef::new([1; 32], AccessRights::READ));
1475 ret
1476 };
1477
1478 contract_package
1479 .groups_mut()
1480 .insert(Group::new("Group 1"), group_urefs.clone());
1481
1482 contract_package
1483 .groups_mut()
1484 .insert(Group::new("Group 2"), group_urefs);
1485 }
1486
1487 let _entry_points = {
1489 let mut ret = BTreeMap::new();
1490 let entrypoint = EntryPoint::new(
1491 "method0".to_string(),
1492 vec![],
1493 CLType::U32,
1494 EntryPointAccess::groups(&["Group 2"]),
1495 EntryPointType::Caller,
1496 );
1497 ret.insert(entrypoint.name().to_owned(), entrypoint);
1498 let entrypoint = EntryPoint::new(
1499 "method1".to_string(),
1500 vec![Parameter::new("Foo", CLType::U32)],
1501 CLType::U32,
1502 EntryPointAccess::groups(&["Group 1"]),
1503 EntryPointType::Caller,
1504 );
1505 ret.insert(entrypoint.name().to_owned(), entrypoint);
1506 ret
1507 };
1508
1509 let _contract_package_hash = [41; 32];
1510 let _contract_wasm_hash = [43; 32];
1511 let _named_keys = NamedKeys::new();
1512 let protocol_version = ProtocolVersion::V1_0_0;
1513
1514 let v1 = contract_package
1515 .insert_contract_version(protocol_version.value().major, CONTRACT_HASH_V1);
1516 let v2 = contract_package
1517 .insert_contract_version(protocol_version.value().major, CONTRACT_HASH_V2);
1518
1519 assert!(v2 > v1);
1520
1521 contract_package
1522 }
1523
1524 #[test]
1525 fn roundtrip_serialization() {
1526 let contract_package = make_contract_package();
1527 let bytes = contract_package.to_bytes().expect("should serialize");
1528 let (decoded_package, rem) =
1529 ContractPackage::from_bytes(&bytes).expect("should deserialize");
1530 assert_eq!(contract_package, decoded_package);
1531 assert_eq!(rem.len(), 0);
1532 }
1533
1534 #[test]
1535 fn contract_hash_from_slice() {
1536 let bytes: Vec<u8> = (0..32).collect();
1537 let contract_hash = HashAddr::try_from(&bytes[..]).expect("should create contract hash");
1538 let contract_hash = ContractHash::new(contract_hash);
1539 assert_eq!(&bytes, &contract_hash.as_bytes());
1540 }
1541
1542 #[test]
1543 fn contract_package_hash_from_slice() {
1544 let bytes: Vec<u8> = (0..32).collect();
1545 let contract_hash = HashAddr::try_from(&bytes[..]).expect("should create contract hash");
1546 let contract_hash = ContractPackageHash::new(contract_hash);
1547 assert_eq!(&bytes, &contract_hash.as_bytes());
1548 }
1549
1550 #[test]
1551 fn contract_hash_from_str() {
1552 let contract_hash = ContractHash([3; 32]);
1553 let encoded = contract_hash.to_formatted_string();
1554 let decoded = ContractHash::from_formatted_str(&encoded).unwrap();
1555 assert_eq!(contract_hash, decoded);
1556
1557 let invalid_prefix =
1558 "contract--0000000000000000000000000000000000000000000000000000000000000000";
1559 assert!(ContractHash::from_formatted_str(invalid_prefix).is_err());
1560
1561 let short_addr = "contract-00000000000000000000000000000000000000000000000000000000000000";
1562 assert!(ContractHash::from_formatted_str(short_addr).is_err());
1563
1564 let long_addr =
1565 "contract-000000000000000000000000000000000000000000000000000000000000000000";
1566 assert!(ContractHash::from_formatted_str(long_addr).is_err());
1567
1568 let invalid_hex =
1569 "contract-000000000000000000000000000000000000000000000000000000000000000g";
1570 assert!(ContractHash::from_formatted_str(invalid_hex).is_err());
1571 }
1572
1573 #[test]
1574 fn contract_package_hash_from_str() {
1575 let contract_package_hash = ContractPackageHash([3; 32]);
1576 let encoded = contract_package_hash.to_formatted_string();
1577 let decoded = ContractPackageHash::from_formatted_str(&encoded).unwrap();
1578 assert_eq!(contract_package_hash, decoded);
1579
1580 let invalid_prefix =
1581 "contract-package0000000000000000000000000000000000000000000000000000000000000000";
1582 assert!(matches!(
1583 ContractPackageHash::from_formatted_str(invalid_prefix).unwrap_err(),
1584 FromStrError::InvalidPrefix
1585 ));
1586
1587 let short_addr =
1588 "contract-package-00000000000000000000000000000000000000000000000000000000000000";
1589 assert!(matches!(
1590 ContractPackageHash::from_formatted_str(short_addr).unwrap_err(),
1591 FromStrError::Hash(_)
1592 ));
1593
1594 let long_addr =
1595 "contract-package-000000000000000000000000000000000000000000000000000000000000000000";
1596 assert!(matches!(
1597 ContractPackageHash::from_formatted_str(long_addr).unwrap_err(),
1598 FromStrError::Hash(_)
1599 ));
1600
1601 let invalid_hex =
1602 "contract-package-000000000000000000000000000000000000000000000000000000000000000g";
1603 assert!(matches!(
1604 ContractPackageHash::from_formatted_str(invalid_hex).unwrap_err(),
1605 FromStrError::Hex(_)
1606 ));
1607 }
1608
1609 #[test]
1610 fn contract_package_hash_from_legacy_str() {
1611 let contract_package_hash = ContractPackageHash([3; 32]);
1612 let hex_addr = contract_package_hash.to_string();
1613 let legacy_encoded = format!("contract-package-wasm{}", hex_addr);
1614 let decoded_from_legacy = ContractPackageHash::from_formatted_str(&legacy_encoded)
1615 .expect("should accept legacy prefixed string");
1616 assert_eq!(
1617 contract_package_hash, decoded_from_legacy,
1618 "decoded_from_legacy should equal decoded"
1619 );
1620
1621 let invalid_prefix =
1622 "contract-packagewasm0000000000000000000000000000000000000000000000000000000000000000";
1623 assert!(matches!(
1624 ContractPackageHash::from_formatted_str(invalid_prefix).unwrap_err(),
1625 FromStrError::InvalidPrefix
1626 ));
1627
1628 let short_addr =
1629 "contract-package-wasm00000000000000000000000000000000000000000000000000000000000000";
1630 assert!(matches!(
1631 ContractPackageHash::from_formatted_str(short_addr).unwrap_err(),
1632 FromStrError::Hash(_)
1633 ));
1634
1635 let long_addr =
1636 "contract-package-wasm000000000000000000000000000000000000000000000000000000000000000000";
1637 assert!(matches!(
1638 ContractPackageHash::from_formatted_str(long_addr).unwrap_err(),
1639 FromStrError::Hash(_)
1640 ));
1641
1642 let invalid_hex =
1643 "contract-package-wasm000000000000000000000000000000000000000000000000000000000000000g";
1644 assert!(matches!(
1645 ContractPackageHash::from_formatted_str(invalid_hex).unwrap_err(),
1646 FromStrError::Hex(_)
1647 ));
1648 }
1649
1650 #[test]
1651 fn contract_hash_serde_roundtrip() {
1652 let contract_hash = ContractHash([255; 32]);
1653 let serialized = bincode::serialize(&contract_hash).unwrap();
1654 let deserialized = bincode::deserialize(&serialized).unwrap();
1655 assert_eq!(contract_hash, deserialized)
1656 }
1657
1658 #[test]
1659 fn contract_hash_json_roundtrip() {
1660 let contract_hash = ContractHash([255; 32]);
1661 let json_string = serde_json::to_string_pretty(&contract_hash).unwrap();
1662 let decoded = serde_json::from_str(&json_string).unwrap();
1663 assert_eq!(contract_hash, decoded)
1664 }
1665
1666 #[test]
1667 fn contract_package_hash_serde_roundtrip() {
1668 let contract_hash = ContractPackageHash([255; 32]);
1669 let serialized = bincode::serialize(&contract_hash).unwrap();
1670 let deserialized = bincode::deserialize(&serialized).unwrap();
1671 assert_eq!(contract_hash, deserialized)
1672 }
1673
1674 #[test]
1675 fn contract_package_hash_json_roundtrip() {
1676 let contract_hash = ContractPackageHash([255; 32]);
1677 let json_string = serde_json::to_string_pretty(&contract_hash).unwrap();
1678 let decoded = serde_json::from_str(&json_string).unwrap();
1679 assert_eq!(contract_hash, decoded)
1680 }
1681
1682 #[test]
1683 fn package_hash_from_legacy_str() {
1684 let package_hash = ContractPackageHash([3; 32]);
1685 let hex_addr = package_hash.to_string();
1686 let legacy_encoded = format!("contract-package-wasm{}", hex_addr);
1687 let decoded_from_legacy = ContractPackageHash::from_formatted_str(&legacy_encoded)
1688 .expect("should accept legacy prefixed string");
1689 assert_eq!(
1690 package_hash, decoded_from_legacy,
1691 "decoded_from_legacy should equal decoded"
1692 );
1693
1694 let invalid_prefix =
1695 "contract-packagewasm0000000000000000000000000000000000000000000000000000000000000000";
1696 assert!(matches!(
1697 ContractPackageHash::from_formatted_str(invalid_prefix).unwrap_err(),
1698 FromStrError::InvalidPrefix
1699 ));
1700
1701 let short_addr =
1702 "contract-package-wasm00000000000000000000000000000000000000000000000000000000000000";
1703 assert!(matches!(
1704 ContractPackageHash::from_formatted_str(short_addr).unwrap_err(),
1705 FromStrError::Hash(_)
1706 ));
1707
1708 let long_addr =
1709 "contract-package-wasm000000000000000000000000000000000000000000000000000000000000000000";
1710 assert!(matches!(
1711 ContractPackageHash::from_formatted_str(long_addr).unwrap_err(),
1712 FromStrError::Hash(_)
1713 ));
1714
1715 let invalid_hex =
1716 "contract-package-wasm000000000000000000000000000000000000000000000000000000000000000g";
1717 assert!(matches!(
1718 ContractPackageHash::from_formatted_str(invalid_hex).unwrap_err(),
1719 FromStrError::Hex(_)
1720 ));
1721 }
1722}
1723
1724#[cfg(test)]
1725mod prop_tests {
1726 use proptest::prelude::*;
1727
1728 use crate::{bytesrepr, contracts::ContractPackage, gens};
1729
1730 proptest! {
1731 #![proptest_config(ProptestConfig {
1732 cases: 1024,
1733 .. ProptestConfig::default()
1734 })]
1735
1736 #[test]
1737 fn test_value_contract(contract in gens::contract_arb()) {
1738 bytesrepr::test_serialization_roundtrip(&contract);
1739 }
1740
1741 #[test]
1742 fn test_value_contract_package(contract_pkg in gens::contract_package_arb()) {
1743 bytesrepr::test_serialization_roundtrip(&contract_pkg);
1744 }
1745
1746 #[test]
1747 fn test_json_contract_package(v in gens::contract_package_arb()) {
1748 let json_str = serde_json::to_string(&v).unwrap();
1749 let deserialized = serde_json::from_str::<ContractPackage>(&json_str).unwrap();
1750 assert_eq!(v, deserialized);
1751 }
1752 }
1753}