1#[cfg(with_testing)]
8use std::ops;
9use std::{
10 collections::HashSet,
11 fmt::{self, Display},
12 fs,
13 hash::Hash,
14 io, iter,
15 num::ParseIntError,
16 path::Path,
17 str::FromStr,
18 sync::Arc,
19};
20
21use allocative::{Allocative, Visitor};
22use alloy_primitives::U256;
23use async_graphql::{InputObject, SimpleObject};
24use custom_debug_derive::Debug;
25use linera_witty::{WitLoad, WitStore, WitType};
26use serde::{Deserialize, Deserializer, Serialize, Serializer};
27use serde_with::{serde_as, Bytes};
28use thiserror::Error;
29use tracing::instrument;
30
31#[cfg(with_metrics)]
32use crate::prometheus_util::MeasureLatency as _;
33use crate::{
34 crypto::{BcsHashable, CryptoError, CryptoHash},
35 doc_scalar, hex_debug, http,
36 identifiers::{
37 ApplicationId, BlobId, BlobType, ChainId, EventId, GenericApplicationId, ModuleId, StreamId,
38 },
39 limited_writer::{LimitedWriter, LimitedWriterError},
40 ownership::ChainOwnership,
41 time::{Duration, SystemTime},
42 vm::VmRuntime,
43};
44
45#[derive(
50 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, WitType, WitLoad, WitStore,
51)]
52#[cfg_attr(
53 all(with_testing, not(target_arch = "wasm32")),
54 derive(test_strategy::Arbitrary)
55)]
56pub struct Amount(u128);
57
58impl Allocative for Amount {
59 fn visit<'a, 'b: 'a>(&self, visitor: &'a mut Visitor<'b>) {
60 visitor.visit_simple_sized::<Self>();
61 }
62}
63
64#[derive(Serialize, Deserialize)]
65#[serde(rename = "Amount")]
66struct AmountString(String);
67
68#[derive(Serialize, Deserialize)]
69#[serde(rename = "Amount")]
70struct AmountU128(u128);
71
72impl Serialize for Amount {
73 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
74 if serializer.is_human_readable() {
75 AmountString(self.to_string()).serialize(serializer)
76 } else {
77 AmountU128(self.0).serialize(serializer)
78 }
79 }
80}
81
82impl<'de> Deserialize<'de> for Amount {
83 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
84 if deserializer.is_human_readable() {
85 let AmountString(s) = AmountString::deserialize(deserializer)?;
86 s.parse().map_err(serde::de::Error::custom)
87 } else {
88 Ok(Amount(AmountU128::deserialize(deserializer)?.0))
89 }
90 }
91}
92
93impl From<Amount> for U256 {
94 fn from(amount: Amount) -> U256 {
95 U256::from(amount.0)
96 }
97}
98
99#[derive(
101 Eq,
102 PartialEq,
103 Ord,
104 PartialOrd,
105 Copy,
106 Clone,
107 Hash,
108 Default,
109 Debug,
110 Serialize,
111 Deserialize,
112 WitType,
113 WitLoad,
114 WitStore,
115 Allocative,
116)]
117#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
118pub struct BlockHeight(pub u64);
119
120#[derive(
122 Eq,
123 PartialEq,
124 Ord,
125 PartialOrd,
126 Copy,
127 Clone,
128 Hash,
129 Default,
130 Debug,
131 Serialize,
132 Deserialize,
133 Allocative,
134)]
135#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
136pub enum Round {
137 #[default]
139 Fast,
140 MultiLeader(u32),
142 SingleLeader(u32),
144 Validator(u32),
146}
147
148#[derive(
150 Eq,
151 PartialEq,
152 Ord,
153 PartialOrd,
154 Copy,
155 Clone,
156 Hash,
157 Default,
158 Debug,
159 Serialize,
160 Deserialize,
161 WitType,
162 WitLoad,
163 WitStore,
164 Allocative,
165)]
166pub struct TimeDelta(u64);
167
168impl TimeDelta {
169 pub const fn from_micros(micros: u64) -> Self {
171 TimeDelta(micros)
172 }
173
174 pub const fn from_millis(millis: u64) -> Self {
176 TimeDelta(millis.saturating_mul(1_000))
177 }
178
179 pub const fn from_secs(secs: u64) -> Self {
181 TimeDelta(secs.saturating_mul(1_000_000))
182 }
183
184 pub fn from_duration(duration: Duration) -> Self {
187 TimeDelta::from_micros(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX))
188 }
189
190 pub const fn as_micros(&self) -> u64 {
192 self.0
193 }
194
195 pub const fn as_duration(&self) -> Duration {
197 Duration::from_micros(self.as_micros())
198 }
199}
200
201#[derive(
203 Eq,
204 PartialEq,
205 Ord,
206 PartialOrd,
207 Copy,
208 Clone,
209 Hash,
210 Default,
211 Debug,
212 Serialize,
213 Deserialize,
214 WitType,
215 WitLoad,
216 WitStore,
217 Allocative,
218)]
219pub struct Timestamp(u64);
220
221impl Timestamp {
222 pub fn now() -> Timestamp {
224 Timestamp(
225 SystemTime::UNIX_EPOCH
226 .elapsed()
227 .expect("system time should be after Unix epoch")
228 .as_micros()
229 .try_into()
230 .unwrap_or(u64::MAX),
231 )
232 }
233
234 pub const fn micros(&self) -> u64 {
236 self.0
237 }
238
239 pub const fn delta_since(&self, other: Timestamp) -> TimeDelta {
242 TimeDelta::from_micros(self.0.saturating_sub(other.0))
243 }
244
245 pub const fn duration_since(&self, other: Timestamp) -> Duration {
248 Duration::from_micros(self.0.saturating_sub(other.0))
249 }
250
251 pub const fn saturating_add(&self, duration: TimeDelta) -> Timestamp {
253 Timestamp(self.0.saturating_add(duration.0))
254 }
255
256 pub const fn saturating_sub(&self, duration: TimeDelta) -> Timestamp {
258 Timestamp(self.0.saturating_sub(duration.0))
259 }
260
261 pub const fn saturating_sub_micros(&self, micros: u64) -> Timestamp {
264 Timestamp(self.0.saturating_sub(micros))
265 }
266}
267
268impl From<u64> for Timestamp {
269 fn from(t: u64) -> Timestamp {
270 Timestamp(t)
271 }
272}
273
274impl Display for Timestamp {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 if let Some(date_time) = chrono::DateTime::from_timestamp(
277 (self.0 / 1_000_000) as i64,
278 ((self.0 % 1_000_000) * 1_000) as u32,
279 ) {
280 return date_time.naive_utc().fmt(f);
281 }
282 self.0.fmt(f)
283 }
284}
285
286#[derive(
289 Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, WitLoad, WitStore, WitType,
290)]
291pub struct Resources {
292 pub wasm_fuel: u64,
294 pub evm_fuel: u64,
296 pub read_operations: u32,
298 pub write_operations: u32,
300 pub bytes_runtime: u32,
302 pub bytes_to_read: u32,
304 pub bytes_to_write: u32,
306 pub blobs_to_read: u32,
308 pub blobs_to_publish: u32,
310 pub blob_bytes_to_read: u32,
312 pub blob_bytes_to_publish: u32,
314 pub messages: u32,
316 pub message_size: u32,
319 pub storage_size_delta: u32,
321 pub service_as_oracle_queries: u32,
323 pub http_requests: u32,
325 }
328
329#[derive(Clone, Debug, Deserialize, Serialize, WitLoad, WitType)]
331#[cfg_attr(with_testing, derive(Eq, PartialEq, WitStore))]
332#[witty_specialize_with(Message = Vec<u8>)]
333pub struct SendMessageRequest<Message> {
334 pub destination: ChainId,
336 pub authenticated: bool,
338 pub is_tracked: bool,
340 pub grant: Resources,
342 pub message: Message,
344}
345
346impl<Message> SendMessageRequest<Message>
347where
348 Message: Serialize,
349{
350 pub fn into_raw(self) -> SendMessageRequest<Vec<u8>> {
352 let message = bcs::to_bytes(&self.message).expect("Failed to serialize message");
353
354 SendMessageRequest {
355 destination: self.destination,
356 authenticated: self.authenticated,
357 is_tracked: self.is_tracked,
358 grant: self.grant,
359 message,
360 }
361 }
362}
363
364#[derive(Debug, Error)]
366#[allow(missing_docs)]
367pub enum ArithmeticError {
368 #[error("Number overflow")]
369 Overflow,
370 #[error("Number underflow")]
371 Underflow,
372}
373
374macro_rules! impl_wrapped_number {
375 ($name:ident, $wrapped:ident) => {
376 impl $name {
377 pub const ZERO: Self = Self(0);
379
380 pub const MAX: Self = Self($wrapped::MAX);
382
383 pub fn try_add(self, other: Self) -> Result<Self, ArithmeticError> {
385 let val = self
386 .0
387 .checked_add(other.0)
388 .ok_or(ArithmeticError::Overflow)?;
389 Ok(Self(val))
390 }
391
392 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
394 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
395 Ok(Self(val))
396 }
397
398 pub const fn saturating_add(self, other: Self) -> Self {
400 let val = self.0.saturating_add(other.0);
401 Self(val)
402 }
403
404 pub fn try_sub(self, other: Self) -> Result<Self, ArithmeticError> {
406 let val = self
407 .0
408 .checked_sub(other.0)
409 .ok_or(ArithmeticError::Underflow)?;
410 Ok(Self(val))
411 }
412
413 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
415 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
416 Ok(Self(val))
417 }
418
419 pub const fn saturating_sub(self, other: Self) -> Self {
421 let val = self.0.saturating_sub(other.0);
422 Self(val)
423 }
424
425 pub fn abs_diff(self, other: Self) -> Self {
427 Self(self.0.abs_diff(other.0))
428 }
429
430 pub fn try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
432 self.0 = self
433 .0
434 .checked_add(other.0)
435 .ok_or(ArithmeticError::Overflow)?;
436 Ok(())
437 }
438
439 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
441 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
442 Ok(())
443 }
444
445 pub const fn saturating_add_assign(&mut self, other: Self) {
447 self.0 = self.0.saturating_add(other.0);
448 }
449
450 pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
452 self.0 = self
453 .0
454 .checked_sub(other.0)
455 .ok_or(ArithmeticError::Underflow)?;
456 Ok(())
457 }
458
459 pub fn saturating_div(&self, other: $wrapped) -> Self {
461 Self(self.0.checked_div(other).unwrap_or($wrapped::MAX))
462 }
463
464 pub const fn saturating_mul(&self, other: $wrapped) -> Self {
466 Self(self.0.saturating_mul(other))
467 }
468
469 pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
471 let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
472 Ok(Self(val))
473 }
474
475 pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
477 self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
478 Ok(())
479 }
480 }
481
482 impl From<$name> for $wrapped {
483 fn from(value: $name) -> Self {
484 value.0
485 }
486 }
487
488 #[cfg(with_testing)]
490 impl From<$wrapped> for $name {
491 fn from(value: $wrapped) -> Self {
492 Self(value)
493 }
494 }
495
496 #[cfg(with_testing)]
497 impl ops::Add for $name {
498 type Output = Self;
499
500 fn add(self, other: Self) -> Self {
501 Self(self.0 + other.0)
502 }
503 }
504
505 #[cfg(with_testing)]
506 impl ops::Sub for $name {
507 type Output = Self;
508
509 fn sub(self, other: Self) -> Self {
510 Self(self.0 - other.0)
511 }
512 }
513
514 #[cfg(with_testing)]
515 impl ops::Mul<$wrapped> for $name {
516 type Output = Self;
517
518 fn mul(self, other: $wrapped) -> Self {
519 Self(self.0 * other)
520 }
521 }
522 };
523}
524
525impl TryFrom<BlockHeight> for usize {
526 type Error = ArithmeticError;
527
528 fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
529 usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
530 }
531}
532
533impl_wrapped_number!(Amount, u128);
534impl_wrapped_number!(BlockHeight, u64);
535impl_wrapped_number!(TimeDelta, u64);
536
537impl Display for Amount {
538 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539 let places = Amount::DECIMAL_PLACES as usize;
541 let min_digits = places + 1;
542 let decimals = format!("{:0min_digits$}", self.0);
543 let integer_part = &decimals[..(decimals.len() - places)];
544 let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
545
546 let precision = f.precision().unwrap_or(0).max(fractional_part.len());
548 let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
549 let pad_width = f.width().map_or(0, |w| {
551 w.saturating_sub(precision)
552 .saturating_sub(sign.len() + integer_part.len() + 1)
553 });
554 let left_pad = match f.align() {
555 None | Some(fmt::Alignment::Right) => pad_width,
556 Some(fmt::Alignment::Center) => pad_width / 2,
557 Some(fmt::Alignment::Left) => 0,
558 };
559
560 for _ in 0..left_pad {
561 write!(f, "{}", f.fill())?;
562 }
563 write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
564 for _ in left_pad..pad_width {
565 write!(f, "{}", f.fill())?;
566 }
567 Ok(())
568 }
569}
570
571#[derive(Error, Debug)]
572#[allow(missing_docs)]
573pub enum ParseAmountError {
574 #[error("cannot parse amount")]
575 Parse,
576 #[error("cannot represent amount: number too high")]
577 TooHigh,
578 #[error("cannot represent amount: too many decimal places after the point")]
579 TooManyDigits,
580}
581
582impl FromStr for Amount {
583 type Err = ParseAmountError;
584
585 fn from_str(src: &str) -> Result<Self, Self::Err> {
586 let mut result: u128 = 0;
587 let mut decimals: Option<u8> = None;
588 let mut chars = src.trim().chars().peekable();
589 if chars.peek() == Some(&'+') {
590 chars.next();
591 }
592 for char in chars {
593 match char {
594 '_' => {}
595 '.' if decimals.is_some() => return Err(ParseAmountError::Parse),
596 '.' => decimals = Some(Amount::DECIMAL_PLACES),
597 char => {
598 let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
599 if let Some(d) = &mut decimals {
600 *d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
601 }
602 result = result
603 .checked_mul(10)
604 .and_then(|r| r.checked_add(digit))
605 .ok_or(ParseAmountError::TooHigh)?;
606 }
607 }
608 }
609 result = result
610 .checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
611 .ok_or(ParseAmountError::TooHigh)?;
612 Ok(Amount(result))
613 }
614}
615
616impl Display for BlockHeight {
617 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618 self.0.fmt(f)
619 }
620}
621
622impl FromStr for BlockHeight {
623 type Err = ParseIntError;
624
625 fn from_str(src: &str) -> Result<Self, Self::Err> {
626 Ok(Self(u64::from_str(src)?))
627 }
628}
629
630impl Display for Round {
631 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632 match self {
633 Round::Fast => write!(f, "fast round"),
634 Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
635 Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
636 Round::Validator(r) => write!(f, "validator round {}", r),
637 }
638 }
639}
640
641impl Round {
642 pub fn is_multi_leader(&self) -> bool {
644 matches!(self, Round::MultiLeader(_))
645 }
646
647 pub fn multi_leader(&self) -> Option<u32> {
649 match self {
650 Round::MultiLeader(number) => Some(*number),
651 _ => None,
652 }
653 }
654
655 pub fn is_fast(&self) -> bool {
657 matches!(self, Round::Fast)
658 }
659
660 pub fn number(&self) -> u32 {
662 match self {
663 Round::Fast => 0,
664 Round::MultiLeader(r) | Round::SingleLeader(r) | Round::Validator(r) => *r,
665 }
666 }
667
668 pub fn type_name(&self) -> &'static str {
670 match self {
671 Round::Fast => "fast",
672 Round::MultiLeader(_) => "multi",
673 Round::SingleLeader(_) => "single",
674 Round::Validator(_) => "validator",
675 }
676 }
677}
678
679impl<'a> iter::Sum<&'a Amount> for Amount {
680 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
681 iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
682 }
683}
684
685impl Amount {
686 pub const DECIMAL_PLACES: u8 = 18;
688
689 pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
691
692 pub const fn from_tokens(tokens: u128) -> Amount {
694 Self::ONE.saturating_mul(tokens)
695 }
696
697 pub const fn from_millis(millitokens: u128) -> Amount {
699 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
700 }
701
702 pub const fn from_micros(microtokens: u128) -> Amount {
704 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
705 }
706
707 pub const fn from_nanos(nanotokens: u128) -> Amount {
709 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
710 }
711
712 pub const fn from_attos(attotokens: u128) -> Amount {
714 Amount(attotokens)
715 }
716
717 pub const fn to_attos(self) -> u128 {
719 self.0
720 }
721
722 pub const fn upper_half(self) -> u64 {
724 (self.0 >> 64) as u64
725 }
726
727 pub const fn lower_half(self) -> u64 {
729 self.0 as u64
730 }
731
732 pub fn saturating_ratio(self, other: Amount) -> u128 {
734 self.0.checked_div(other.0).unwrap_or(u128::MAX)
735 }
736
737 pub fn is_zero(&self) -> bool {
739 *self == Amount::ZERO
740 }
741}
742
743#[derive(
745 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize, Allocative,
746)]
747pub enum ChainOrigin {
748 Root(u32),
750 Child {
752 parent: ChainId,
754 block_height: BlockHeight,
756 chain_index: u32,
759 },
760}
761
762impl ChainOrigin {
763 pub fn is_child(&self) -> bool {
765 matches!(self, ChainOrigin::Child { .. })
766 }
767
768 pub fn root(&self) -> Option<u32> {
770 match self {
771 ChainOrigin::Root(i) => Some(*i),
772 ChainOrigin::Child { .. } => None,
773 }
774 }
775}
776
777#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Allocative)]
779pub struct Epoch(pub u32);
780
781impl Epoch {
782 pub const ZERO: Epoch = Epoch(0);
784}
785
786impl Serialize for Epoch {
787 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
788 where
789 S: serde::ser::Serializer,
790 {
791 if serializer.is_human_readable() {
792 serializer.serialize_str(&self.0.to_string())
793 } else {
794 serializer.serialize_newtype_struct("Epoch", &self.0)
795 }
796 }
797}
798
799impl<'de> Deserialize<'de> for Epoch {
800 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
801 where
802 D: serde::de::Deserializer<'de>,
803 {
804 if deserializer.is_human_readable() {
805 let s = String::deserialize(deserializer)?;
806 Ok(Epoch(u32::from_str(&s).map_err(serde::de::Error::custom)?))
807 } else {
808 #[derive(Deserialize)]
809 #[serde(rename = "Epoch")]
810 struct EpochDerived(u32);
811
812 let value = EpochDerived::deserialize(deserializer)?;
813 Ok(Self(value.0))
814 }
815 }
816}
817
818impl std::fmt::Display for Epoch {
819 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
820 write!(f, "{}", self.0)
821 }
822}
823
824impl std::str::FromStr for Epoch {
825 type Err = CryptoError;
826
827 fn from_str(s: &str) -> Result<Self, Self::Err> {
828 Ok(Epoch(s.parse()?))
829 }
830}
831
832impl From<u32> for Epoch {
833 fn from(value: u32) -> Self {
834 Epoch(value)
835 }
836}
837
838impl Epoch {
839 #[inline]
842 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
843 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
844 Ok(Self(val))
845 }
846
847 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
850 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
851 Ok(Self(val))
852 }
853
854 #[inline]
856 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
857 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
858 Ok(())
859 }
860}
861
862#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
864pub struct InitialChainConfig {
865 pub ownership: ChainOwnership,
867 pub epoch: Epoch,
869 pub min_active_epoch: Epoch,
871 pub max_active_epoch: Epoch,
873 pub balance: Amount,
875 pub application_permissions: ApplicationPermissions,
877}
878
879#[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize, Allocative)]
881pub struct ChainDescription {
882 origin: ChainOrigin,
883 timestamp: Timestamp,
884 config: InitialChainConfig,
885}
886
887impl ChainDescription {
888 pub fn new(origin: ChainOrigin, config: InitialChainConfig, timestamp: Timestamp) -> Self {
890 Self {
891 origin,
892 config,
893 timestamp,
894 }
895 }
896
897 pub fn id(&self) -> ChainId {
899 ChainId::from(self)
900 }
901
902 pub fn origin(&self) -> ChainOrigin {
904 self.origin
905 }
906
907 pub fn config(&self) -> &InitialChainConfig {
909 &self.config
910 }
911
912 pub fn timestamp(&self) -> Timestamp {
914 self.timestamp
915 }
916
917 pub fn is_child(&self) -> bool {
919 self.origin.is_child()
920 }
921}
922
923impl BcsHashable<'_> for ChainDescription {}
924
925#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
927pub struct NetworkDescription {
928 pub name: String,
930 pub genesis_config_hash: CryptoHash,
932 pub genesis_timestamp: Timestamp,
934 pub genesis_committee_blob_hash: CryptoHash,
936 pub admin_chain_id: ChainId,
938}
939
940#[derive(
942 Default,
943 Debug,
944 PartialEq,
945 Eq,
946 PartialOrd,
947 Ord,
948 Hash,
949 Clone,
950 Serialize,
951 Deserialize,
952 WitType,
953 WitLoad,
954 WitStore,
955 InputObject,
956 Allocative,
957)]
958pub struct ApplicationPermissions {
959 #[debug(skip_if = Option::is_none)]
963 pub execute_operations: Option<Vec<ApplicationId>>,
964 #[graphql(default)]
967 #[debug(skip_if = Vec::is_empty)]
968 pub mandatory_applications: Vec<ApplicationId>,
969 #[graphql(default)]
971 #[debug(skip_if = Vec::is_empty)]
972 pub close_chain: Vec<ApplicationId>,
973 #[graphql(default)]
975 #[debug(skip_if = Vec::is_empty)]
976 pub change_application_permissions: Vec<ApplicationId>,
977 #[graphql(default)]
979 #[debug(skip_if = Option::is_none)]
980 pub call_service_as_oracle: Option<Vec<ApplicationId>>,
981 #[graphql(default)]
983 #[debug(skip_if = Option::is_none)]
984 pub make_http_requests: Option<Vec<ApplicationId>>,
985}
986
987impl ApplicationPermissions {
988 pub fn new_single(app_id: ApplicationId) -> Self {
991 Self {
992 execute_operations: Some(vec![app_id]),
993 mandatory_applications: vec![app_id],
994 close_chain: vec![app_id],
995 change_application_permissions: vec![app_id],
996 call_service_as_oracle: Some(vec![app_id]),
997 make_http_requests: Some(vec![app_id]),
998 }
999 }
1000
1001 pub fn new_multiple(app_ids: Vec<ApplicationId>) -> Self {
1004 Self {
1005 execute_operations: Some(app_ids.clone()),
1006 mandatory_applications: app_ids.clone(),
1007 close_chain: app_ids.clone(),
1008 change_application_permissions: app_ids.clone(),
1009 call_service_as_oracle: Some(app_ids.clone()),
1010 make_http_requests: Some(app_ids),
1011 }
1012 }
1013
1014 pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
1016 match (app_id, &self.execute_operations) {
1017 (_, None) => true,
1018 (GenericApplicationId::System, Some(_)) => false,
1019 (GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
1020 }
1021 }
1022
1023 pub fn can_close_chain(&self, app_id: &ApplicationId) -> bool {
1025 self.close_chain.contains(app_id)
1026 }
1027
1028 pub fn can_change_application_permissions(&self, app_id: &ApplicationId) -> bool {
1031 self.change_application_permissions.contains(app_id)
1032 }
1033
1034 pub fn can_call_services(&self, app_id: &ApplicationId) -> bool {
1036 self.call_service_as_oracle
1037 .as_ref()
1038 .is_none_or(|app_ids| app_ids.contains(app_id))
1039 }
1040
1041 pub fn can_make_http_requests(&self, app_id: &ApplicationId) -> bool {
1043 self.make_http_requests
1044 .as_ref()
1045 .is_none_or(|app_ids| app_ids.contains(app_id))
1046 }
1047}
1048
1049#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
1051pub enum OracleResponse {
1052 Service(
1054 #[debug(with = "hex_debug")]
1055 #[serde(with = "serde_bytes")]
1056 Vec<u8>,
1057 ),
1058 Http(http::Response),
1060 Blob(BlobId),
1062 Assert,
1064 Round(Option<u32>),
1066 Event(
1068 EventId,
1069 #[debug(with = "hex_debug")]
1070 #[serde(with = "serde_bytes")]
1071 Vec<u8>,
1072 ),
1073 EventExists(EventId),
1075}
1076
1077impl BcsHashable<'_> for OracleResponse {}
1078
1079#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
1081pub struct ApplicationDescription {
1082 pub module_id: ModuleId,
1084 pub creator_chain_id: ChainId,
1086 pub block_height: BlockHeight,
1088 pub application_index: u32,
1090 #[serde(with = "serde_bytes")]
1092 #[debug(with = "hex_debug")]
1093 pub parameters: Vec<u8>,
1094 pub required_application_ids: Vec<ApplicationId>,
1096}
1097
1098impl From<&ApplicationDescription> for ApplicationId {
1099 fn from(description: &ApplicationDescription) -> Self {
1100 let mut hash = CryptoHash::new(&BlobContent::new_application_description(description));
1101 if matches!(description.module_id.vm_runtime, VmRuntime::Evm) {
1102 hash.make_evm_compatible();
1103 }
1104 ApplicationId::new(hash)
1105 }
1106}
1107
1108impl BcsHashable<'_> for ApplicationDescription {}
1109
1110impl ApplicationDescription {
1111 pub fn to_bytes(&self) -> Vec<u8> {
1113 bcs::to_bytes(self).expect("Serializing blob bytes should not fail!")
1114 }
1115
1116 pub fn contract_bytecode_blob_id(&self) -> BlobId {
1118 self.module_id.contract_bytecode_blob_id()
1119 }
1120
1121 pub fn service_bytecode_blob_id(&self) -> BlobId {
1123 self.module_id.service_bytecode_blob_id()
1124 }
1125}
1126
1127#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, WitType, WitLoad, WitStore)]
1129pub struct Bytecode {
1130 #[serde(with = "serde_bytes")]
1132 #[debug(with = "hex_debug")]
1133 pub bytes: Vec<u8>,
1134}
1135
1136impl Bytecode {
1137 pub fn new(bytes: Vec<u8>) -> Self {
1139 Bytecode { bytes }
1140 }
1141
1142 pub fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
1144 let bytes = fs::read(path)?;
1145 Ok(Bytecode { bytes })
1146 }
1147
1148 #[cfg(not(target_arch = "wasm32"))]
1150 pub fn compress(&self) -> CompressedBytecode {
1151 #[cfg(with_metrics)]
1152 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1153 let compressed_bytes_vec = zstd::stream::encode_all(&*self.bytes, 19)
1154 .expect("Compressing bytes in memory should not fail");
1155
1156 CompressedBytecode {
1157 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1158 }
1159 }
1160
1161 #[cfg(target_arch = "wasm32")]
1163 pub fn compress(&self) -> CompressedBytecode {
1164 use ruzstd::encoding::{CompressionLevel, FrameCompressor};
1165
1166 #[cfg(with_metrics)]
1167 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1168
1169 let mut compressed_bytes_vec = Vec::new();
1170 let mut compressor = FrameCompressor::new(CompressionLevel::Fastest);
1171 compressor.set_source(&*self.bytes);
1172 compressor.set_drain(&mut compressed_bytes_vec);
1173 compressor.compress();
1174
1175 CompressedBytecode {
1176 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1177 }
1178 }
1179}
1180
1181impl AsRef<[u8]> for Bytecode {
1182 fn as_ref(&self) -> &[u8] {
1183 self.bytes.as_ref()
1184 }
1185}
1186
1187#[derive(Error, Debug)]
1189pub enum DecompressionError {
1190 #[error("Bytecode could not be decompressed: {0}")]
1192 InvalidCompressedBytecode(#[from] io::Error),
1193}
1194
1195#[serde_as]
1197#[derive(Clone, Debug, Deserialize, Hash, Serialize, WitType, WitStore)]
1198#[cfg_attr(with_testing, derive(Eq, PartialEq))]
1199pub struct CompressedBytecode {
1200 #[serde_as(as = "Arc<Bytes>")]
1202 #[debug(skip)]
1203 pub compressed_bytes: Arc<Box<[u8]>>,
1204}
1205
1206#[cfg(not(target_arch = "wasm32"))]
1207impl CompressedBytecode {
1208 pub fn decompressed_size_at_most(
1210 compressed_bytes: &[u8],
1211 limit: u64,
1212 ) -> Result<bool, DecompressionError> {
1213 let mut decoder = zstd::stream::Decoder::new(compressed_bytes)?;
1214 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1215 let mut writer = LimitedWriter::new(io::sink(), limit);
1216 match io::copy(&mut decoder, &mut writer) {
1217 Ok(_) => Ok(true),
1218 Err(error) => {
1219 error.downcast::<LimitedWriterError>()?;
1220 Ok(false)
1221 }
1222 }
1223 }
1224
1225 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1227 #[cfg(with_metrics)]
1228 let _decompression_latency = metrics::BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1229 let bytes = zstd::stream::decode_all(&**self.compressed_bytes)?;
1230
1231 Ok(Bytecode { bytes })
1232 }
1233}
1234
1235#[cfg(target_arch = "wasm32")]
1236impl CompressedBytecode {
1237 pub fn decompressed_size_at_most(
1239 compressed_bytes: &[u8],
1240 limit: u64,
1241 ) -> Result<bool, DecompressionError> {
1242 use ruzstd::decoding::StreamingDecoder;
1243 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1244 let mut writer = LimitedWriter::new(io::sink(), limit);
1245 let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
1246
1247 match io::copy(&mut decoder, &mut writer) {
1249 Ok(_) => Ok(true),
1250 Err(error) => {
1251 error.downcast::<LimitedWriterError>()?;
1252 Ok(false)
1253 }
1254 }
1255 }
1256
1257 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1259 use ruzstd::{decoding::StreamingDecoder, io::Read};
1260
1261 #[cfg(with_metrics)]
1262 let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1263
1264 let compressed_bytes = &*self.compressed_bytes;
1265 let mut bytes = Vec::new();
1266 let mut decoder = StreamingDecoder::new(&**compressed_bytes).map_err(io::Error::other)?;
1267
1268 while !decoder.get_ref().is_empty() {
1270 decoder
1271 .read_to_end(&mut bytes)
1272 .expect("Reading from a slice in memory should not result in I/O errors");
1273 }
1274
1275 Ok(Bytecode { bytes })
1276 }
1277}
1278
1279impl BcsHashable<'_> for BlobContent {}
1280
1281#[serde_as]
1283#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Allocative)]
1284pub struct BlobContent {
1285 blob_type: BlobType,
1287 #[debug(skip)]
1289 #[serde_as(as = "Arc<Bytes>")]
1290 bytes: Arc<Box<[u8]>>,
1291}
1292
1293impl BlobContent {
1294 pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
1296 let bytes = bytes.into();
1297 BlobContent {
1298 blob_type,
1299 bytes: Arc::new(bytes),
1300 }
1301 }
1302
1303 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1305 BlobContent::new(BlobType::Data, bytes)
1306 }
1307
1308 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1310 BlobContent {
1311 blob_type: BlobType::ContractBytecode,
1312 bytes: compressed_bytecode.compressed_bytes,
1313 }
1314 }
1315
1316 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1318 BlobContent {
1319 blob_type: BlobType::EvmBytecode,
1320 bytes: compressed_bytecode.compressed_bytes,
1321 }
1322 }
1323
1324 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1326 BlobContent {
1327 blob_type: BlobType::ServiceBytecode,
1328 bytes: compressed_bytecode.compressed_bytes,
1329 }
1330 }
1331
1332 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1334 let bytes = application_description.to_bytes();
1335 BlobContent::new(BlobType::ApplicationDescription, bytes)
1336 }
1337
1338 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1340 BlobContent::new(BlobType::Committee, committee)
1341 }
1342
1343 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1345 let bytes = bcs::to_bytes(&chain_description)
1346 .expect("Serializing a ChainDescription should not fail!");
1347 BlobContent::new(BlobType::ChainDescription, bytes)
1348 }
1349
1350 pub fn bytes(&self) -> &[u8] {
1352 &self.bytes
1353 }
1354
1355 pub fn into_vec_or_clone(self) -> Vec<u8> {
1357 let bytes = Arc::unwrap_or_clone(self.bytes);
1358 bytes.into_vec()
1359 }
1360
1361 pub fn into_arc_bytes(self) -> Arc<Box<[u8]>> {
1363 self.bytes
1364 }
1365
1366 pub fn blob_type(&self) -> BlobType {
1368 self.blob_type
1369 }
1370}
1371
1372impl From<Blob> for BlobContent {
1373 fn from(blob: Blob) -> BlobContent {
1374 blob.content
1375 }
1376}
1377
1378#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
1380pub struct Blob {
1381 hash: CryptoHash,
1383 content: BlobContent,
1385}
1386
1387impl Blob {
1388 pub fn new(content: BlobContent) -> Self {
1390 let mut hash = CryptoHash::new(&content);
1391 if matches!(content.blob_type, BlobType::ApplicationDescription) {
1392 let application_description = bcs::from_bytes::<ApplicationDescription>(&content.bytes)
1393 .expect("to obtain an application description");
1394 if matches!(application_description.module_id.vm_runtime, VmRuntime::Evm) {
1395 hash.make_evm_compatible();
1396 }
1397 }
1398 Blob { hash, content }
1399 }
1400
1401 pub fn new_with_hash_unchecked(blob_id: BlobId, content: BlobContent) -> Self {
1403 Blob {
1404 hash: blob_id.hash,
1405 content,
1406 }
1407 }
1408
1409 pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1411 let bytes = bytes.into();
1412 Blob {
1413 hash: blob_id.hash,
1414 content: BlobContent {
1415 blob_type: blob_id.blob_type,
1416 bytes: Arc::new(bytes),
1417 },
1418 }
1419 }
1420
1421 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1423 Blob::new(BlobContent::new_data(bytes))
1424 }
1425
1426 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1428 Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode))
1429 }
1430
1431 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1433 Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode))
1434 }
1435
1436 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1438 Blob::new(BlobContent::new_service_bytecode(compressed_bytecode))
1439 }
1440
1441 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1443 Blob::new(BlobContent::new_application_description(
1444 application_description,
1445 ))
1446 }
1447
1448 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1450 Blob::new(BlobContent::new_committee(committee))
1451 }
1452
1453 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1455 Blob::new(BlobContent::new_chain_description(chain_description))
1456 }
1457
1458 pub fn id(&self) -> BlobId {
1460 BlobId {
1461 hash: self.hash,
1462 blob_type: self.content.blob_type,
1463 }
1464 }
1465
1466 pub fn content(&self) -> &BlobContent {
1468 &self.content
1469 }
1470
1471 pub fn into_content(self) -> BlobContent {
1473 self.content
1474 }
1475
1476 pub fn bytes(&self) -> &[u8] {
1478 self.content.bytes()
1479 }
1480
1481 pub fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
1483 Ok(Self::new_data(fs::read(path)?))
1484 }
1485
1486 pub fn is_committee_blob(&self) -> bool {
1488 self.content().blob_type().is_committee_blob()
1489 }
1490}
1491
1492impl Serialize for Blob {
1493 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1494 where
1495 S: Serializer,
1496 {
1497 if serializer.is_human_readable() {
1498 let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
1499 serializer.serialize_str(&hex::encode(blob_bytes))
1500 } else {
1501 BlobContent::serialize(self.content(), serializer)
1502 }
1503 }
1504}
1505
1506impl<'a> Deserialize<'a> for Blob {
1507 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1508 where
1509 D: Deserializer<'a>,
1510 {
1511 if deserializer.is_human_readable() {
1512 let s = String::deserialize(deserializer)?;
1513 let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1514 let content: BlobContent =
1515 bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
1516
1517 Ok(Blob::new(content))
1518 } else {
1519 let content = BlobContent::deserialize(deserializer)?;
1520 Ok(Blob::new(content))
1521 }
1522 }
1523}
1524
1525impl BcsHashable<'_> for Blob {}
1526
1527#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1529pub struct Event {
1530 pub stream_id: StreamId,
1532 pub index: u32,
1534 #[debug(with = "hex_debug")]
1536 #[serde(with = "serde_bytes")]
1537 pub value: Vec<u8>,
1538}
1539
1540impl Event {
1541 pub fn id(&self, chain_id: ChainId) -> EventId {
1543 EventId {
1544 chain_id,
1545 stream_id: self.stream_id.clone(),
1546 index: self.index,
1547 }
1548 }
1549}
1550
1551#[derive(Clone, Debug, Serialize, Deserialize, WitType, WitLoad, WitStore)]
1553pub struct StreamUpdate {
1554 pub chain_id: ChainId,
1556 pub stream_id: StreamId,
1558 pub previous_index: u32,
1560 pub next_index: u32,
1562}
1563
1564impl StreamUpdate {
1565 pub fn new_indices(&self) -> impl Iterator<Item = u32> {
1567 self.previous_index..self.next_index
1568 }
1569}
1570
1571impl BcsHashable<'_> for Event {}
1572
1573#[derive(
1575 Clone, Debug, Default, serde::Serialize, serde::Deserialize, async_graphql::SimpleObject,
1576)]
1577pub struct MessagePolicy {
1578 pub blanket: BlanketMessagePolicy,
1580 pub restrict_chain_ids_to: Option<HashSet<ChainId>>,
1584 pub reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
1587 pub reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
1590}
1591
1592#[derive(
1594 Default,
1595 Copy,
1596 Clone,
1597 Debug,
1598 PartialEq,
1599 Eq,
1600 serde::Serialize,
1601 serde::Deserialize,
1602 async_graphql::Enum,
1603)]
1604#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
1605#[cfg_attr(any(web, not(target_arch = "wasm32")), derive(clap::ValueEnum))]
1606pub enum BlanketMessagePolicy {
1607 #[default]
1609 Accept,
1610 Reject,
1613 Ignore,
1616}
1617
1618impl MessagePolicy {
1619 pub fn new(
1621 blanket: BlanketMessagePolicy,
1622 restrict_chain_ids_to: Option<HashSet<ChainId>>,
1623 reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
1624 reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
1625 ) -> Self {
1626 Self {
1627 blanket,
1628 restrict_chain_ids_to,
1629 reject_message_bundles_without_application_ids,
1630 reject_message_bundles_with_other_application_ids,
1631 }
1632 }
1633
1634 #[cfg(with_testing)]
1636 pub fn new_accept_all() -> Self {
1637 Self {
1638 blanket: BlanketMessagePolicy::Accept,
1639 restrict_chain_ids_to: None,
1640 reject_message_bundles_without_application_ids: None,
1641 reject_message_bundles_with_other_application_ids: None,
1642 }
1643 }
1644
1645 #[instrument(level = "trace", skip(self))]
1647 pub fn is_ignore(&self) -> bool {
1648 matches!(self.blanket, BlanketMessagePolicy::Ignore)
1649 }
1650
1651 #[instrument(level = "trace", skip(self))]
1653 pub fn is_reject(&self) -> bool {
1654 matches!(self.blanket, BlanketMessagePolicy::Reject)
1655 }
1656}
1657
1658doc_scalar!(Bytecode, "A WebAssembly module's bytecode");
1659doc_scalar!(Amount, "A non-negative amount of tokens.");
1660doc_scalar!(
1661 Epoch,
1662 "A number identifying the configuration of the chain (aka the committee)"
1663);
1664doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
1665doc_scalar!(
1666 Timestamp,
1667 "A timestamp, in microseconds since the Unix epoch"
1668);
1669doc_scalar!(TimeDelta, "A duration in microseconds");
1670doc_scalar!(
1671 Round,
1672 "A number to identify successive attempts to decide a value in a consensus protocol."
1673);
1674doc_scalar!(
1675 ChainDescription,
1676 "Initial chain configuration and chain origin."
1677);
1678doc_scalar!(OracleResponse, "A record of a single oracle response.");
1679doc_scalar!(BlobContent, "A blob of binary data.");
1680doc_scalar!(
1681 Blob,
1682 "A blob of binary data, with its content-addressed blob ID."
1683);
1684doc_scalar!(ApplicationDescription, "Description of a user application");
1685
1686#[cfg(with_metrics)]
1687mod metrics {
1688 use std::sync::LazyLock;
1689
1690 use prometheus::HistogramVec;
1691
1692 use crate::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
1693
1694 pub static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1696 register_histogram_vec(
1697 "bytecode_compression_latency",
1698 "Bytecode compression latency",
1699 &[],
1700 exponential_bucket_latencies(10.0),
1701 )
1702 });
1703
1704 pub static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1706 register_histogram_vec(
1707 "bytecode_decompression_latency",
1708 "Bytecode decompression latency",
1709 &[],
1710 exponential_bucket_latencies(10.0),
1711 )
1712 });
1713}
1714
1715#[cfg(test)]
1716mod tests {
1717 use std::str::FromStr;
1718
1719 use super::{Amount, BlobContent};
1720 use crate::identifiers::BlobType;
1721
1722 #[test]
1723 fn display_amount() {
1724 assert_eq!("1.", Amount::ONE.to_string());
1725 assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
1726 assert_eq!(
1727 Amount(10_000_000_000_000_000_000),
1728 Amount::from_str("10").unwrap()
1729 );
1730 assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
1731 assert_eq!(
1732 "1001.3",
1733 (Amount::from_str("1.1")
1734 .unwrap()
1735 .saturating_add(Amount::from_str("1_000.2").unwrap()))
1736 .to_string()
1737 );
1738 assert_eq!(
1739 " 1.00000000000000000000",
1740 format!("{:25.20}", Amount::ONE)
1741 );
1742 assert_eq!(
1743 "~+12.34~~",
1744 format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
1745 );
1746 }
1747
1748 #[test]
1749 fn blob_content_serialization_deserialization() {
1750 let test_data = b"Hello, world!".as_slice();
1751 let original_blob = BlobContent::new(BlobType::Data, test_data);
1752
1753 let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1754 let deserialized: BlobContent =
1755 bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1756 assert_eq!(original_blob, deserialized);
1757
1758 let serialized =
1759 serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1760 let deserialized: BlobContent =
1761 serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1762 assert_eq!(original_blob, deserialized);
1763 }
1764
1765 #[test]
1766 fn blob_content_hash_consistency() {
1767 let test_data = b"Hello, world!";
1768 let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1769 let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1770
1771 let hash1 = crate::crypto::CryptoHash::new(&blob1);
1773 let hash2 = crate::crypto::CryptoHash::new(&blob2);
1774
1775 assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1776 assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1777 }
1778}