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