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(Error, Debug)]
102#[error("Failed to convert U256 to Amount. {0} has more than 128 bits")]
103pub struct AmountConversionError(U256);
104
105impl TryFrom<U256> for Amount {
106 type Error = AmountConversionError;
107 fn try_from(value: U256) -> Result<Amount, Self::Error> {
108 let value = u128::try_from(&value).map_err(|_| AmountConversionError(value))?;
109 Ok(Amount(value))
110 }
111}
112
113#[derive(
115 Eq,
116 PartialEq,
117 Ord,
118 PartialOrd,
119 Copy,
120 Clone,
121 Hash,
122 Default,
123 Debug,
124 Serialize,
125 Deserialize,
126 WitType,
127 WitLoad,
128 WitStore,
129 Allocative,
130)]
131#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
132pub struct BlockHeight(pub u64);
133
134#[derive(
136 Eq,
137 PartialEq,
138 Ord,
139 PartialOrd,
140 Copy,
141 Clone,
142 Hash,
143 Default,
144 Debug,
145 Serialize,
146 Deserialize,
147 Allocative,
148)]
149#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
150pub enum Round {
151 #[default]
153 Fast,
154 MultiLeader(u32),
156 SingleLeader(u32),
158 Validator(u32),
160}
161
162#[derive(
164 Eq,
165 PartialEq,
166 Ord,
167 PartialOrd,
168 Copy,
169 Clone,
170 Hash,
171 Default,
172 Debug,
173 Serialize,
174 Deserialize,
175 WitType,
176 WitLoad,
177 WitStore,
178 Allocative,
179)]
180pub struct TimeDelta(u64);
181
182impl TimeDelta {
183 pub const fn from_micros(micros: u64) -> Self {
185 TimeDelta(micros)
186 }
187
188 pub const fn from_millis(millis: u64) -> Self {
190 TimeDelta(millis.saturating_mul(1_000))
191 }
192
193 pub const fn from_secs(secs: u64) -> Self {
195 TimeDelta(secs.saturating_mul(1_000_000))
196 }
197
198 pub fn from_duration(duration: Duration) -> Self {
201 TimeDelta::from_micros(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX))
202 }
203
204 pub const fn as_micros(&self) -> u64 {
206 self.0
207 }
208
209 pub const fn as_duration(&self) -> Duration {
211 Duration::from_micros(self.as_micros())
212 }
213}
214
215#[derive(
217 Eq,
218 PartialEq,
219 Ord,
220 PartialOrd,
221 Copy,
222 Clone,
223 Hash,
224 Default,
225 Debug,
226 Serialize,
227 Deserialize,
228 WitType,
229 WitLoad,
230 WitStore,
231 Allocative,
232)]
233pub struct Timestamp(u64);
234
235impl Timestamp {
236 pub fn now() -> Timestamp {
238 Timestamp(
239 SystemTime::UNIX_EPOCH
240 .elapsed()
241 .expect("system time should be after Unix epoch")
242 .as_micros()
243 .try_into()
244 .unwrap_or(u64::MAX),
245 )
246 }
247
248 pub const fn micros(&self) -> u64 {
250 self.0
251 }
252
253 pub const fn delta_since(&self, other: Timestamp) -> TimeDelta {
256 TimeDelta::from_micros(self.0.saturating_sub(other.0))
257 }
258
259 pub const fn duration_since(&self, other: Timestamp) -> Duration {
262 Duration::from_micros(self.0.saturating_sub(other.0))
263 }
264
265 pub const fn saturating_add(&self, duration: TimeDelta) -> Timestamp {
267 Timestamp(self.0.saturating_add(duration.0))
268 }
269
270 pub const fn saturating_sub(&self, duration: TimeDelta) -> Timestamp {
272 Timestamp(self.0.saturating_sub(duration.0))
273 }
274
275 pub const fn saturating_sub_micros(&self, micros: u64) -> Timestamp {
278 Timestamp(self.0.saturating_sub(micros))
279 }
280}
281
282impl From<u64> for Timestamp {
283 fn from(t: u64) -> Timestamp {
284 Timestamp(t)
285 }
286}
287
288impl Display for Timestamp {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 if let Some(date_time) = chrono::DateTime::from_timestamp(
291 (self.0 / 1_000_000) as i64,
292 ((self.0 % 1_000_000) * 1_000) as u32,
293 ) {
294 return date_time.naive_utc().fmt(f);
295 }
296 self.0.fmt(f)
297 }
298}
299
300#[derive(
303 Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, WitLoad, WitStore, WitType,
304)]
305pub struct Resources {
306 pub wasm_fuel: u64,
308 pub evm_fuel: u64,
310 pub read_operations: u32,
312 pub write_operations: u32,
314 pub bytes_runtime: u32,
316 pub bytes_to_read: u32,
318 pub bytes_to_write: u32,
320 pub blobs_to_read: u32,
322 pub blobs_to_publish: u32,
324 pub blob_bytes_to_read: u32,
326 pub blob_bytes_to_publish: u32,
328 pub messages: u32,
330 pub message_size: u32,
333 pub storage_size_delta: u32,
335 pub service_as_oracle_queries: u32,
337 pub http_requests: u32,
339 }
342
343#[derive(Clone, Debug, Deserialize, Serialize, WitLoad, WitType)]
345#[cfg_attr(with_testing, derive(Eq, PartialEq, WitStore))]
346#[witty_specialize_with(Message = Vec<u8>)]
347pub struct SendMessageRequest<Message> {
348 pub destination: ChainId,
350 pub authenticated: bool,
352 pub is_tracked: bool,
354 pub grant: Resources,
356 pub message: Message,
358}
359
360impl<Message> SendMessageRequest<Message>
361where
362 Message: Serialize,
363{
364 pub fn into_raw(self) -> SendMessageRequest<Vec<u8>> {
366 let message = bcs::to_bytes(&self.message).expect("Failed to serialize message");
367
368 SendMessageRequest {
369 destination: self.destination,
370 authenticated: self.authenticated,
371 is_tracked: self.is_tracked,
372 grant: self.grant,
373 message,
374 }
375 }
376}
377
378#[derive(Debug, Error)]
380#[allow(missing_docs)]
381pub enum ArithmeticError {
382 #[error("Number overflow")]
383 Overflow,
384 #[error("Number underflow")]
385 Underflow,
386}
387
388macro_rules! impl_wrapped_number {
389 ($name:ident, $wrapped:ident) => {
390 impl $name {
391 pub const ZERO: Self = Self(0);
393
394 pub const MAX: Self = Self($wrapped::MAX);
396
397 pub fn try_add(self, other: Self) -> Result<Self, ArithmeticError> {
399 let val = self
400 .0
401 .checked_add(other.0)
402 .ok_or(ArithmeticError::Overflow)?;
403 Ok(Self(val))
404 }
405
406 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
408 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
409 Ok(Self(val))
410 }
411
412 pub const fn saturating_add(self, other: Self) -> Self {
414 let val = self.0.saturating_add(other.0);
415 Self(val)
416 }
417
418 pub fn try_sub(self, other: Self) -> Result<Self, ArithmeticError> {
420 let val = self
421 .0
422 .checked_sub(other.0)
423 .ok_or(ArithmeticError::Underflow)?;
424 Ok(Self(val))
425 }
426
427 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
429 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
430 Ok(Self(val))
431 }
432
433 pub const fn saturating_sub(self, other: Self) -> Self {
435 let val = self.0.saturating_sub(other.0);
436 Self(val)
437 }
438
439 pub fn abs_diff(self, other: Self) -> Self {
441 Self(self.0.abs_diff(other.0))
442 }
443
444 pub const fn midpoint(self, other: Self) -> Self {
446 Self(self.0.midpoint(other.0))
447 }
448
449 pub fn try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
451 self.0 = self
452 .0
453 .checked_add(other.0)
454 .ok_or(ArithmeticError::Overflow)?;
455 Ok(())
456 }
457
458 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
460 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
461 Ok(())
462 }
463
464 pub const fn saturating_add_assign(&mut self, other: Self) {
466 self.0 = self.0.saturating_add(other.0);
467 }
468
469 pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
471 self.0 = self
472 .0
473 .checked_sub(other.0)
474 .ok_or(ArithmeticError::Underflow)?;
475 Ok(())
476 }
477
478 pub fn saturating_div(&self, other: $wrapped) -> Self {
480 Self(self.0.checked_div(other).unwrap_or($wrapped::MAX))
481 }
482
483 pub const fn saturating_mul(&self, other: $wrapped) -> Self {
485 Self(self.0.saturating_mul(other))
486 }
487
488 pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
490 let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
491 Ok(Self(val))
492 }
493
494 pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
496 self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
497 Ok(())
498 }
499 }
500
501 impl From<$name> for $wrapped {
502 fn from(value: $name) -> Self {
503 value.0
504 }
505 }
506
507 #[cfg(with_testing)]
509 impl From<$wrapped> for $name {
510 fn from(value: $wrapped) -> Self {
511 Self(value)
512 }
513 }
514
515 #[cfg(with_testing)]
516 impl ops::Add for $name {
517 type Output = Self;
518
519 fn add(self, other: Self) -> Self {
520 Self(self.0 + other.0)
521 }
522 }
523
524 #[cfg(with_testing)]
525 impl ops::Sub for $name {
526 type Output = Self;
527
528 fn sub(self, other: Self) -> Self {
529 Self(self.0 - other.0)
530 }
531 }
532
533 #[cfg(with_testing)]
534 impl ops::Mul<$wrapped> for $name {
535 type Output = Self;
536
537 fn mul(self, other: $wrapped) -> Self {
538 Self(self.0 * other)
539 }
540 }
541 };
542}
543
544impl TryFrom<BlockHeight> for usize {
545 type Error = ArithmeticError;
546
547 fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
548 usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
549 }
550}
551
552impl_wrapped_number!(Amount, u128);
553impl_wrapped_number!(BlockHeight, u64);
554impl_wrapped_number!(TimeDelta, u64);
555
556impl Display for Amount {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558 let places = Amount::DECIMAL_PLACES as usize;
560 let min_digits = places + 1;
561 let decimals = format!("{:0min_digits$}", self.0);
562 let integer_part = &decimals[..(decimals.len() - places)];
563 let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
564
565 let precision = f.precision().unwrap_or(0).max(fractional_part.len());
567 let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
568 let pad_width = f.width().map_or(0, |w| {
570 w.saturating_sub(precision)
571 .saturating_sub(sign.len() + integer_part.len() + 1)
572 });
573 let left_pad = match f.align() {
574 None | Some(fmt::Alignment::Right) => pad_width,
575 Some(fmt::Alignment::Center) => pad_width / 2,
576 Some(fmt::Alignment::Left) => 0,
577 };
578
579 for _ in 0..left_pad {
580 write!(f, "{}", f.fill())?;
581 }
582 write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
583 for _ in left_pad..pad_width {
584 write!(f, "{}", f.fill())?;
585 }
586 Ok(())
587 }
588}
589
590#[derive(Error, Debug)]
591#[allow(missing_docs)]
592pub enum ParseAmountError {
593 #[error("cannot parse amount")]
594 Parse,
595 #[error("cannot represent amount: number too high")]
596 TooHigh,
597 #[error("cannot represent amount: too many decimal places after the point")]
598 TooManyDigits,
599}
600
601impl FromStr for Amount {
602 type Err = ParseAmountError;
603
604 fn from_str(src: &str) -> Result<Self, Self::Err> {
605 let mut result: u128 = 0;
606 let mut decimals: Option<u8> = None;
607 let mut chars = src.trim().chars().peekable();
608 if chars.peek() == Some(&'+') {
609 chars.next();
610 }
611 for char in chars {
612 match char {
613 '_' => {}
614 '.' if decimals.is_some() => return Err(ParseAmountError::Parse),
615 '.' => decimals = Some(Amount::DECIMAL_PLACES),
616 char => {
617 let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
618 if let Some(d) = &mut decimals {
619 *d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
620 }
621 result = result
622 .checked_mul(10)
623 .and_then(|r| r.checked_add(digit))
624 .ok_or(ParseAmountError::TooHigh)?;
625 }
626 }
627 }
628 result = result
629 .checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
630 .ok_or(ParseAmountError::TooHigh)?;
631 Ok(Amount(result))
632 }
633}
634
635impl Display for BlockHeight {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 self.0.fmt(f)
638 }
639}
640
641impl FromStr for BlockHeight {
642 type Err = ParseIntError;
643
644 fn from_str(src: &str) -> Result<Self, Self::Err> {
645 Ok(Self(u64::from_str(src)?))
646 }
647}
648
649impl Display for Round {
650 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651 match self {
652 Round::Fast => write!(f, "fast round"),
653 Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
654 Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
655 Round::Validator(r) => write!(f, "validator round {}", r),
656 }
657 }
658}
659
660impl Round {
661 pub fn is_multi_leader(&self) -> bool {
663 matches!(self, Round::MultiLeader(_))
664 }
665
666 pub fn multi_leader(&self) -> Option<u32> {
668 match self {
669 Round::MultiLeader(number) => Some(*number),
670 _ => None,
671 }
672 }
673
674 pub fn is_validator(&self) -> bool {
676 matches!(self, Round::Validator(_))
677 }
678
679 pub fn is_fast(&self) -> bool {
681 matches!(self, Round::Fast)
682 }
683
684 pub fn number(&self) -> u32 {
686 match self {
687 Round::Fast => 0,
688 Round::MultiLeader(r) | Round::SingleLeader(r) | Round::Validator(r) => *r,
689 }
690 }
691
692 pub fn type_name(&self) -> &'static str {
694 match self {
695 Round::Fast => "fast",
696 Round::MultiLeader(_) => "multi",
697 Round::SingleLeader(_) => "single",
698 Round::Validator(_) => "validator",
699 }
700 }
701}
702
703impl<'a> iter::Sum<&'a Amount> for Amount {
704 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
705 iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
706 }
707}
708
709impl Amount {
710 pub const DECIMAL_PLACES: u8 = 18;
712
713 pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
715
716 pub const fn from_tokens(tokens: u128) -> Amount {
718 Self::ONE.saturating_mul(tokens)
719 }
720
721 pub const fn from_millis(millitokens: u128) -> Amount {
723 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
724 }
725
726 pub const fn from_micros(microtokens: u128) -> Amount {
728 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
729 }
730
731 pub const fn from_nanos(nanotokens: u128) -> Amount {
733 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
734 }
735
736 pub const fn from_attos(attotokens: u128) -> Amount {
738 Amount(attotokens)
739 }
740
741 pub const fn to_attos(self) -> u128 {
743 self.0
744 }
745
746 pub const fn upper_half(self) -> u64 {
748 (self.0 >> 64) as u64
749 }
750
751 pub const fn lower_half(self) -> u64 {
753 self.0 as u64
754 }
755
756 pub fn saturating_ratio(self, other: Amount) -> u128 {
758 self.0.checked_div(other.0).unwrap_or(u128::MAX)
759 }
760
761 pub fn is_zero(&self) -> bool {
763 *self == Amount::ZERO
764 }
765}
766
767#[derive(
769 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize, Allocative,
770)]
771pub enum ChainOrigin {
772 Root(u32),
774 Child {
776 parent: ChainId,
778 block_height: BlockHeight,
780 chain_index: u32,
783 },
784}
785
786impl ChainOrigin {
787 pub fn is_child(&self) -> bool {
789 matches!(self, ChainOrigin::Child { .. })
790 }
791
792 pub fn root(&self) -> Option<u32> {
794 match self {
795 ChainOrigin::Root(i) => Some(*i),
796 ChainOrigin::Child { .. } => None,
797 }
798 }
799}
800
801#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Allocative)]
803pub struct Epoch(pub u32);
804
805impl Epoch {
806 pub const ZERO: Epoch = Epoch(0);
808}
809
810impl Serialize for Epoch {
811 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
812 where
813 S: serde::ser::Serializer,
814 {
815 if serializer.is_human_readable() {
816 serializer.serialize_str(&self.0.to_string())
817 } else {
818 serializer.serialize_newtype_struct("Epoch", &self.0)
819 }
820 }
821}
822
823impl<'de> Deserialize<'de> for Epoch {
824 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
825 where
826 D: serde::de::Deserializer<'de>,
827 {
828 if deserializer.is_human_readable() {
829 let s = String::deserialize(deserializer)?;
830 Ok(Epoch(u32::from_str(&s).map_err(serde::de::Error::custom)?))
831 } else {
832 #[derive(Deserialize)]
833 #[serde(rename = "Epoch")]
834 struct EpochDerived(u32);
835
836 let value = EpochDerived::deserialize(deserializer)?;
837 Ok(Self(value.0))
838 }
839 }
840}
841
842impl std::fmt::Display for Epoch {
843 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
844 write!(f, "{}", self.0)
845 }
846}
847
848impl std::str::FromStr for Epoch {
849 type Err = CryptoError;
850
851 fn from_str(s: &str) -> Result<Self, Self::Err> {
852 Ok(Epoch(s.parse()?))
853 }
854}
855
856impl From<u32> for Epoch {
857 fn from(value: u32) -> Self {
858 Epoch(value)
859 }
860}
861
862impl Epoch {
863 #[inline]
866 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
867 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
868 Ok(Self(val))
869 }
870
871 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
874 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
875 Ok(Self(val))
876 }
877
878 #[inline]
880 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
881 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
882 Ok(())
883 }
884}
885
886#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
888pub struct InitialChainConfig {
889 pub ownership: ChainOwnership,
891 pub epoch: Epoch,
893 pub min_active_epoch: Epoch,
895 pub max_active_epoch: Epoch,
897 pub balance: Amount,
899 pub application_permissions: ApplicationPermissions,
901}
902
903#[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize, Allocative)]
905pub struct ChainDescription {
906 origin: ChainOrigin,
907 timestamp: Timestamp,
908 config: InitialChainConfig,
909}
910
911impl ChainDescription {
912 pub fn new(origin: ChainOrigin, config: InitialChainConfig, timestamp: Timestamp) -> Self {
914 Self {
915 origin,
916 config,
917 timestamp,
918 }
919 }
920
921 pub fn id(&self) -> ChainId {
923 ChainId::from(self)
924 }
925
926 pub fn origin(&self) -> ChainOrigin {
928 self.origin
929 }
930
931 pub fn config(&self) -> &InitialChainConfig {
933 &self.config
934 }
935
936 pub fn timestamp(&self) -> Timestamp {
938 self.timestamp
939 }
940
941 pub fn is_child(&self) -> bool {
943 self.origin.is_child()
944 }
945}
946
947impl BcsHashable<'_> for ChainDescription {}
948
949#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
951pub struct NetworkDescription {
952 pub name: String,
954 pub genesis_config_hash: CryptoHash,
956 pub genesis_timestamp: Timestamp,
958 pub genesis_committee_blob_hash: CryptoHash,
960 pub admin_chain_id: ChainId,
962}
963
964#[derive(
966 Default,
967 Debug,
968 PartialEq,
969 Eq,
970 PartialOrd,
971 Ord,
972 Hash,
973 Clone,
974 Serialize,
975 Deserialize,
976 WitType,
977 WitLoad,
978 WitStore,
979 InputObject,
980 Allocative,
981)]
982pub struct ApplicationPermissions {
983 #[debug(skip_if = Option::is_none)]
987 pub execute_operations: Option<Vec<ApplicationId>>,
988 #[graphql(default)]
991 #[debug(skip_if = Vec::is_empty)]
992 pub mandatory_applications: Vec<ApplicationId>,
993 #[graphql(default)]
996 #[debug(skip_if = Vec::is_empty)]
997 pub manage_chain: Vec<ApplicationId>,
998 #[graphql(default)]
1000 #[debug(skip_if = Option::is_none)]
1001 pub call_service_as_oracle: Option<Vec<ApplicationId>>,
1002 #[graphql(default)]
1004 #[debug(skip_if = Option::is_none)]
1005 pub make_http_requests: Option<Vec<ApplicationId>>,
1006}
1007
1008impl ApplicationPermissions {
1009 pub fn new_single(app_id: ApplicationId) -> Self {
1012 Self {
1013 execute_operations: Some(vec![app_id]),
1014 mandatory_applications: vec![app_id],
1015 manage_chain: vec![app_id],
1016 call_service_as_oracle: Some(vec![app_id]),
1017 make_http_requests: Some(vec![app_id]),
1018 }
1019 }
1020
1021 pub fn new_multiple(app_ids: Vec<ApplicationId>) -> Self {
1024 Self {
1025 execute_operations: Some(app_ids.clone()),
1026 mandatory_applications: app_ids.clone(),
1027 manage_chain: app_ids.clone(),
1028 call_service_as_oracle: Some(app_ids.clone()),
1029 make_http_requests: Some(app_ids),
1030 }
1031 }
1032
1033 pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
1035 match (app_id, &self.execute_operations) {
1036 (_, None) => true,
1037 (GenericApplicationId::System, Some(_)) => false,
1038 (GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
1039 }
1040 }
1041
1042 pub fn can_manage_chain(&self, app_id: &ApplicationId) -> bool {
1045 self.manage_chain.contains(app_id)
1046 }
1047
1048 pub fn can_call_services(&self, app_id: &ApplicationId) -> bool {
1050 self.call_service_as_oracle
1051 .as_ref()
1052 .is_none_or(|app_ids| app_ids.contains(app_id))
1053 }
1054
1055 pub fn can_make_http_requests(&self, app_id: &ApplicationId) -> bool {
1057 self.make_http_requests
1058 .as_ref()
1059 .is_none_or(|app_ids| app_ids.contains(app_id))
1060 }
1061}
1062
1063#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
1065pub enum OracleResponse {
1066 Service(
1068 #[debug(with = "hex_debug")]
1069 #[serde(with = "serde_bytes")]
1070 Vec<u8>,
1071 ),
1072 Http(http::Response),
1074 Blob(BlobId),
1076 Assert,
1078 Round(Option<u32>),
1080 Event(
1082 EventId,
1083 #[debug(with = "hex_debug")]
1084 #[serde(with = "serde_bytes")]
1085 Vec<u8>,
1086 ),
1087 EventExists(EventId),
1089}
1090
1091impl BcsHashable<'_> for OracleResponse {}
1092
1093#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
1095pub struct ApplicationDescription {
1096 pub module_id: ModuleId,
1098 pub creator_chain_id: ChainId,
1100 pub block_height: BlockHeight,
1102 pub application_index: u32,
1104 #[serde(with = "serde_bytes")]
1106 #[debug(with = "hex_debug")]
1107 pub parameters: Vec<u8>,
1108 pub required_application_ids: Vec<ApplicationId>,
1110}
1111
1112impl From<&ApplicationDescription> for ApplicationId {
1113 fn from(description: &ApplicationDescription) -> Self {
1114 let mut hash = CryptoHash::new(&BlobContent::new_application_description(description));
1115 if matches!(description.module_id.vm_runtime, VmRuntime::Evm) {
1116 hash.make_evm_compatible();
1117 }
1118 ApplicationId::new(hash)
1119 }
1120}
1121
1122impl BcsHashable<'_> for ApplicationDescription {}
1123
1124impl ApplicationDescription {
1125 pub fn to_bytes(&self) -> Vec<u8> {
1127 bcs::to_bytes(self).expect("Serializing blob bytes should not fail!")
1128 }
1129
1130 pub fn contract_bytecode_blob_id(&self) -> BlobId {
1132 self.module_id.contract_bytecode_blob_id()
1133 }
1134
1135 pub fn service_bytecode_blob_id(&self) -> BlobId {
1137 self.module_id.service_bytecode_blob_id()
1138 }
1139}
1140
1141#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, WitType, WitLoad, WitStore)]
1143pub struct Bytecode {
1144 #[serde(with = "serde_bytes")]
1146 #[debug(with = "hex_debug")]
1147 pub bytes: Vec<u8>,
1148}
1149
1150impl Bytecode {
1151 pub fn new(bytes: Vec<u8>) -> Self {
1153 Bytecode { bytes }
1154 }
1155
1156 #[cfg(not(target_arch = "wasm32"))]
1158 pub async fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
1159 let bytes = tokio::fs::read(path).await?;
1160 Ok(Bytecode { bytes })
1161 }
1162
1163 #[cfg(not(target_arch = "wasm32"))]
1165 pub fn compress(&self) -> CompressedBytecode {
1166 #[cfg(with_metrics)]
1167 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1168 let compressed_bytes_vec = zstd::stream::encode_all(&*self.bytes, 19)
1169 .expect("Compressing bytes in memory should not fail");
1170
1171 CompressedBytecode {
1172 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1173 }
1174 }
1175
1176 #[cfg(target_arch = "wasm32")]
1178 pub fn compress(&self) -> CompressedBytecode {
1179 use ruzstd::encoding::{CompressionLevel, FrameCompressor};
1180
1181 #[cfg(with_metrics)]
1182 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1183
1184 let mut compressed_bytes_vec = Vec::new();
1185 let mut compressor = FrameCompressor::new(CompressionLevel::Fastest);
1186 compressor.set_source(&*self.bytes);
1187 compressor.set_drain(&mut compressed_bytes_vec);
1188 compressor.compress();
1189
1190 CompressedBytecode {
1191 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1192 }
1193 }
1194}
1195
1196impl AsRef<[u8]> for Bytecode {
1197 fn as_ref(&self) -> &[u8] {
1198 self.bytes.as_ref()
1199 }
1200}
1201
1202#[derive(Error, Debug)]
1204pub enum DecompressionError {
1205 #[error("Bytecode could not be decompressed: {0}")]
1207 InvalidCompressedBytecode(#[from] io::Error),
1208}
1209
1210#[serde_as]
1212#[derive(Clone, Debug, Deserialize, Hash, Serialize, WitType, WitStore)]
1213#[cfg_attr(with_testing, derive(Eq, PartialEq))]
1214pub struct CompressedBytecode {
1215 #[serde_as(as = "Arc<Bytes>")]
1217 #[debug(skip)]
1218 pub compressed_bytes: Arc<Box<[u8]>>,
1219}
1220
1221#[cfg(not(target_arch = "wasm32"))]
1222impl CompressedBytecode {
1223 pub fn decompressed_size_at_most(
1225 compressed_bytes: &[u8],
1226 limit: u64,
1227 ) -> Result<bool, DecompressionError> {
1228 let mut decoder = zstd::stream::Decoder::new(compressed_bytes)?;
1229 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1230 let mut writer = LimitedWriter::new(io::sink(), limit);
1231 match io::copy(&mut decoder, &mut writer) {
1232 Ok(_) => Ok(true),
1233 Err(error) => {
1234 error.downcast::<LimitedWriterError>()?;
1235 Ok(false)
1236 }
1237 }
1238 }
1239
1240 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1242 #[cfg(with_metrics)]
1243 let _decompression_latency = metrics::BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1244 let bytes = zstd::stream::decode_all(&**self.compressed_bytes)?;
1245
1246 Ok(Bytecode { bytes })
1247 }
1248}
1249
1250#[cfg(target_arch = "wasm32")]
1251impl CompressedBytecode {
1252 pub fn decompressed_size_at_most(
1254 compressed_bytes: &[u8],
1255 limit: u64,
1256 ) -> Result<bool, DecompressionError> {
1257 use ruzstd::decoding::StreamingDecoder;
1258 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1259 let mut writer = LimitedWriter::new(io::sink(), limit);
1260 let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
1261
1262 match io::copy(&mut decoder, &mut writer) {
1264 Ok(_) => Ok(true),
1265 Err(error) => {
1266 error.downcast::<LimitedWriterError>()?;
1267 Ok(false)
1268 }
1269 }
1270 }
1271
1272 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1274 use ruzstd::{decoding::StreamingDecoder, io::Read};
1275
1276 #[cfg(with_metrics)]
1277 let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1278
1279 let compressed_bytes = &*self.compressed_bytes;
1280 let mut bytes = Vec::new();
1281 let mut decoder = StreamingDecoder::new(&**compressed_bytes).map_err(io::Error::other)?;
1282
1283 while !decoder.get_ref().is_empty() {
1285 decoder
1286 .read_to_end(&mut bytes)
1287 .expect("Reading from a slice in memory should not result in I/O errors");
1288 }
1289
1290 Ok(Bytecode { bytes })
1291 }
1292}
1293
1294impl BcsHashable<'_> for BlobContent {}
1295
1296#[serde_as]
1298#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Allocative)]
1299pub struct BlobContent {
1300 blob_type: BlobType,
1302 #[debug(skip)]
1304 #[serde_as(as = "Arc<Bytes>")]
1305 bytes: Arc<Box<[u8]>>,
1306}
1307
1308impl BlobContent {
1309 pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
1311 let bytes = bytes.into();
1312 BlobContent {
1313 blob_type,
1314 bytes: Arc::new(bytes),
1315 }
1316 }
1317
1318 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1320 BlobContent::new(BlobType::Data, bytes)
1321 }
1322
1323 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1325 BlobContent {
1326 blob_type: BlobType::ContractBytecode,
1327 bytes: compressed_bytecode.compressed_bytes,
1328 }
1329 }
1330
1331 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1333 BlobContent {
1334 blob_type: BlobType::EvmBytecode,
1335 bytes: compressed_bytecode.compressed_bytes,
1336 }
1337 }
1338
1339 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1341 BlobContent {
1342 blob_type: BlobType::ServiceBytecode,
1343 bytes: compressed_bytecode.compressed_bytes,
1344 }
1345 }
1346
1347 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1349 let bytes = application_description.to_bytes();
1350 BlobContent::new(BlobType::ApplicationDescription, bytes)
1351 }
1352
1353 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1355 BlobContent::new(BlobType::Committee, committee)
1356 }
1357
1358 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1360 let bytes = bcs::to_bytes(&chain_description)
1361 .expect("Serializing a ChainDescription should not fail!");
1362 BlobContent::new(BlobType::ChainDescription, bytes)
1363 }
1364
1365 pub fn bytes(&self) -> &[u8] {
1367 &self.bytes
1368 }
1369
1370 pub fn into_vec_or_clone(self) -> Vec<u8> {
1372 let bytes = Arc::unwrap_or_clone(self.bytes);
1373 bytes.into_vec()
1374 }
1375
1376 pub fn into_arc_bytes(self) -> Arc<Box<[u8]>> {
1378 self.bytes
1379 }
1380
1381 pub fn blob_type(&self) -> BlobType {
1383 self.blob_type
1384 }
1385}
1386
1387impl From<Blob> for BlobContent {
1388 fn from(blob: Blob) -> BlobContent {
1389 blob.content
1390 }
1391}
1392
1393#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
1395pub struct Blob {
1396 hash: CryptoHash,
1398 content: BlobContent,
1400}
1401
1402impl Blob {
1403 pub fn new(content: BlobContent) -> Self {
1405 let mut hash = CryptoHash::new(&content);
1406 if matches!(content.blob_type, BlobType::ApplicationDescription) {
1407 let application_description = bcs::from_bytes::<ApplicationDescription>(&content.bytes)
1408 .expect("to obtain an application description");
1409 if matches!(application_description.module_id.vm_runtime, VmRuntime::Evm) {
1410 hash.make_evm_compatible();
1411 }
1412 }
1413 Blob { hash, content }
1414 }
1415
1416 pub fn new_with_hash_unchecked(blob_id: BlobId, content: BlobContent) -> Self {
1418 Blob {
1419 hash: blob_id.hash,
1420 content,
1421 }
1422 }
1423
1424 pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1426 let bytes = bytes.into();
1427 Blob {
1428 hash: blob_id.hash,
1429 content: BlobContent {
1430 blob_type: blob_id.blob_type,
1431 bytes: Arc::new(bytes),
1432 },
1433 }
1434 }
1435
1436 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1438 Blob::new(BlobContent::new_data(bytes))
1439 }
1440
1441 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1443 Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode))
1444 }
1445
1446 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1448 Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode))
1449 }
1450
1451 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1453 Blob::new(BlobContent::new_service_bytecode(compressed_bytecode))
1454 }
1455
1456 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1458 Blob::new(BlobContent::new_application_description(
1459 application_description,
1460 ))
1461 }
1462
1463 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1465 Blob::new(BlobContent::new_committee(committee))
1466 }
1467
1468 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1470 Blob::new(BlobContent::new_chain_description(chain_description))
1471 }
1472
1473 pub fn id(&self) -> BlobId {
1475 BlobId {
1476 hash: self.hash,
1477 blob_type: self.content.blob_type,
1478 }
1479 }
1480
1481 pub fn content(&self) -> &BlobContent {
1483 &self.content
1484 }
1485
1486 pub fn into_content(self) -> BlobContent {
1488 self.content
1489 }
1490
1491 pub fn bytes(&self) -> &[u8] {
1493 self.content.bytes()
1494 }
1495
1496 pub fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
1498 Ok(Self::new_data(fs::read(path)?))
1499 }
1500
1501 pub fn is_committee_blob(&self) -> bool {
1503 self.content().blob_type().is_committee_blob()
1504 }
1505}
1506
1507impl Serialize for Blob {
1508 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1509 where
1510 S: Serializer,
1511 {
1512 if serializer.is_human_readable() {
1513 let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
1514 serializer.serialize_str(&hex::encode(blob_bytes))
1515 } else {
1516 BlobContent::serialize(self.content(), serializer)
1517 }
1518 }
1519}
1520
1521impl<'a> Deserialize<'a> for Blob {
1522 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1523 where
1524 D: Deserializer<'a>,
1525 {
1526 if deserializer.is_human_readable() {
1527 let s = String::deserialize(deserializer)?;
1528 let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1529 let content: BlobContent =
1530 bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
1531
1532 Ok(Blob::new(content))
1533 } else {
1534 let content = BlobContent::deserialize(deserializer)?;
1535 Ok(Blob::new(content))
1536 }
1537 }
1538}
1539
1540impl BcsHashable<'_> for Blob {}
1541
1542#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1544pub struct Event {
1545 pub stream_id: StreamId,
1547 pub index: u32,
1549 #[debug(with = "hex_debug")]
1551 #[serde(with = "serde_bytes")]
1552 pub value: Vec<u8>,
1553}
1554
1555impl Event {
1556 pub fn id(&self, chain_id: ChainId) -> EventId {
1558 EventId {
1559 chain_id,
1560 stream_id: self.stream_id.clone(),
1561 index: self.index,
1562 }
1563 }
1564}
1565
1566#[derive(Clone, Debug, Serialize, Deserialize, WitType, WitLoad, WitStore)]
1568pub struct StreamUpdate {
1569 pub chain_id: ChainId,
1571 pub stream_id: StreamId,
1573 pub previous_index: u32,
1575 pub next_index: u32,
1577}
1578
1579impl StreamUpdate {
1580 pub fn new_indices(&self) -> impl Iterator<Item = u32> {
1582 self.previous_index..self.next_index
1583 }
1584}
1585
1586impl BcsHashable<'_> for Event {}
1587
1588#[derive(
1590 Clone, Debug, Default, serde::Serialize, serde::Deserialize, async_graphql::SimpleObject,
1591)]
1592pub struct MessagePolicy {
1593 pub blanket: BlanketMessagePolicy,
1595 pub restrict_chain_ids_to: Option<HashSet<ChainId>>,
1599 pub reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
1602 pub reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
1605}
1606
1607#[derive(
1609 Default,
1610 Copy,
1611 Clone,
1612 Debug,
1613 PartialEq,
1614 Eq,
1615 serde::Serialize,
1616 serde::Deserialize,
1617 async_graphql::Enum,
1618)]
1619#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
1620#[cfg_attr(any(web, not(target_arch = "wasm32")), derive(clap::ValueEnum))]
1621pub enum BlanketMessagePolicy {
1622 #[default]
1624 Accept,
1625 Reject,
1628 Ignore,
1631}
1632
1633impl MessagePolicy {
1634 pub fn new(
1636 blanket: BlanketMessagePolicy,
1637 restrict_chain_ids_to: Option<HashSet<ChainId>>,
1638 reject_message_bundles_without_application_ids: Option<HashSet<GenericApplicationId>>,
1639 reject_message_bundles_with_other_application_ids: Option<HashSet<GenericApplicationId>>,
1640 ) -> Self {
1641 Self {
1642 blanket,
1643 restrict_chain_ids_to,
1644 reject_message_bundles_without_application_ids,
1645 reject_message_bundles_with_other_application_ids,
1646 }
1647 }
1648
1649 #[cfg(with_testing)]
1651 pub fn new_accept_all() -> Self {
1652 Self {
1653 blanket: BlanketMessagePolicy::Accept,
1654 restrict_chain_ids_to: None,
1655 reject_message_bundles_without_application_ids: None,
1656 reject_message_bundles_with_other_application_ids: None,
1657 }
1658 }
1659
1660 #[instrument(level = "trace", skip(self))]
1662 pub fn is_ignore(&self) -> bool {
1663 matches!(self.blanket, BlanketMessagePolicy::Ignore)
1664 }
1665
1666 #[instrument(level = "trace", skip(self))]
1668 pub fn is_reject(&self) -> bool {
1669 matches!(self.blanket, BlanketMessagePolicy::Reject)
1670 }
1671}
1672
1673doc_scalar!(Bytecode, "A module bytecode (WebAssembly or EVM)");
1674doc_scalar!(Amount, "A non-negative amount of tokens.");
1675doc_scalar!(
1676 Epoch,
1677 "A number identifying the configuration of the chain (aka the committee)"
1678);
1679doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
1680doc_scalar!(
1681 Timestamp,
1682 "A timestamp, in microseconds since the Unix epoch"
1683);
1684doc_scalar!(TimeDelta, "A duration in microseconds");
1685doc_scalar!(
1686 Round,
1687 "A number to identify successive attempts to decide a value in a consensus protocol."
1688);
1689doc_scalar!(
1690 ChainDescription,
1691 "Initial chain configuration and chain origin."
1692);
1693doc_scalar!(OracleResponse, "A record of a single oracle response.");
1694doc_scalar!(BlobContent, "A blob of binary data.");
1695doc_scalar!(
1696 Blob,
1697 "A blob of binary data, with its content-addressed blob ID."
1698);
1699doc_scalar!(ApplicationDescription, "Description of a user application");
1700
1701#[cfg(with_metrics)]
1702mod metrics {
1703 use std::sync::LazyLock;
1704
1705 use prometheus::HistogramVec;
1706
1707 use crate::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
1708
1709 pub static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1711 register_histogram_vec(
1712 "bytecode_compression_latency",
1713 "Bytecode compression latency",
1714 &[],
1715 exponential_bucket_latencies(10.0),
1716 )
1717 });
1718
1719 pub static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1721 register_histogram_vec(
1722 "bytecode_decompression_latency",
1723 "Bytecode decompression latency",
1724 &[],
1725 exponential_bucket_latencies(10.0),
1726 )
1727 });
1728}
1729
1730#[cfg(test)]
1731mod tests {
1732 use std::str::FromStr;
1733
1734 use alloy_primitives::U256;
1735
1736 use super::{Amount, BlobContent};
1737 use crate::identifiers::BlobType;
1738
1739 #[test]
1740 fn display_amount() {
1741 assert_eq!("1.", Amount::ONE.to_string());
1742 assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
1743 assert_eq!(
1744 Amount(10_000_000_000_000_000_000),
1745 Amount::from_str("10").unwrap()
1746 );
1747 assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
1748 assert_eq!(
1749 "1001.3",
1750 (Amount::from_str("1.1")
1751 .unwrap()
1752 .saturating_add(Amount::from_str("1_000.2").unwrap()))
1753 .to_string()
1754 );
1755 assert_eq!(
1756 " 1.00000000000000000000",
1757 format!("{:25.20}", Amount::ONE)
1758 );
1759 assert_eq!(
1760 "~+12.34~~",
1761 format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
1762 );
1763 }
1764
1765 #[test]
1766 fn blob_content_serialization_deserialization() {
1767 let test_data = b"Hello, world!".as_slice();
1768 let original_blob = BlobContent::new(BlobType::Data, test_data);
1769
1770 let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1771 let deserialized: BlobContent =
1772 bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1773 assert_eq!(original_blob, deserialized);
1774
1775 let serialized =
1776 serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1777 let deserialized: BlobContent =
1778 serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1779 assert_eq!(original_blob, deserialized);
1780 }
1781
1782 #[test]
1783 fn blob_content_hash_consistency() {
1784 let test_data = b"Hello, world!";
1785 let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1786 let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1787
1788 let hash1 = crate::crypto::CryptoHash::new(&blob1);
1790 let hash2 = crate::crypto::CryptoHash::new(&blob2);
1791
1792 assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1793 assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1794 }
1795
1796 #[test]
1797 fn test_conversion_amount_u256() {
1798 let value_amount = Amount::from_tokens(15656565652209004332);
1799 let value_u256: U256 = value_amount.into();
1800 let value_amount_rev = Amount::try_from(value_u256).expect("Failed conversion");
1801 assert_eq!(value_amount, value_amount_rev);
1802 }
1803}