1#[cfg(with_testing)]
8use std::ops;
9use std::{
10 fmt::{self, Display},
11 fs,
12 hash::Hash,
13 io, iter,
14 num::ParseIntError,
15 path::Path,
16 str::FromStr,
17 sync::Arc,
18};
19
20use allocative::{Allocative, Visitor};
21use alloy_primitives::U256;
22use async_graphql::{InputObject, SimpleObject};
23use custom_debug_derive::Debug;
24use linera_witty::{WitLoad, WitStore, WitType};
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
26use serde_with::{serde_as, Bytes};
27use thiserror::Error;
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 fn from_duration(duration: Duration) -> Self {
199 TimeDelta::from_micros(u64::try_from(duration.as_micros()).unwrap_or(u64::MAX))
200 }
201
202 pub const fn as_micros(&self) -> u64 {
204 self.0
205 }
206
207 pub const fn as_duration(&self) -> Duration {
209 Duration::from_micros(self.as_micros())
210 }
211}
212
213#[derive(
215 Eq,
216 PartialEq,
217 Ord,
218 PartialOrd,
219 Copy,
220 Clone,
221 Hash,
222 Default,
223 Debug,
224 Serialize,
225 Deserialize,
226 WitType,
227 WitLoad,
228 WitStore,
229 Allocative,
230)]
231pub struct Timestamp(u64);
232
233impl Timestamp {
234 pub fn now() -> Timestamp {
236 Timestamp(
237 SystemTime::UNIX_EPOCH
238 .elapsed()
239 .expect("system time should be after Unix epoch")
240 .as_micros()
241 .try_into()
242 .unwrap_or(u64::MAX),
243 )
244 }
245
246 pub const fn micros(&self) -> u64 {
248 self.0
249 }
250
251 pub const fn delta_since(&self, other: Timestamp) -> TimeDelta {
254 TimeDelta::from_micros(self.0.saturating_sub(other.0))
255 }
256
257 pub const fn duration_since(&self, other: Timestamp) -> Duration {
260 Duration::from_micros(self.0.saturating_sub(other.0))
261 }
262
263 pub const fn saturating_add(&self, duration: TimeDelta) -> Timestamp {
265 Timestamp(self.0.saturating_add(duration.0))
266 }
267
268 pub const fn saturating_sub(&self, duration: TimeDelta) -> Timestamp {
270 Timestamp(self.0.saturating_sub(duration.0))
271 }
272
273 pub const fn saturating_sub_micros(&self, micros: u64) -> Timestamp {
276 Timestamp(self.0.saturating_sub(micros))
277 }
278}
279
280impl From<u64> for Timestamp {
281 fn from(t: u64) -> Timestamp {
282 Timestamp(t)
283 }
284}
285
286impl Display for Timestamp {
287 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288 if let Some(date_time) = chrono::DateTime::from_timestamp(
289 (self.0 / 1_000_000) as i64,
290 ((self.0 % 1_000_000) * 1_000) as u32,
291 ) {
292 return date_time.naive_utc().fmt(f);
293 }
294 self.0.fmt(f)
295 }
296}
297
298#[derive(
301 Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, WitLoad, WitStore, WitType,
302)]
303pub struct Resources {
304 pub wasm_fuel: u64,
306 pub evm_fuel: u64,
308 pub read_operations: u32,
310 pub write_operations: u32,
312 pub bytes_runtime: u32,
314 pub bytes_to_read: u32,
316 pub bytes_to_write: u32,
318 pub blobs_to_read: u32,
320 pub blobs_to_publish: u32,
322 pub blob_bytes_to_read: u32,
324 pub blob_bytes_to_publish: u32,
326 pub messages: u32,
328 pub message_size: u32,
331 pub storage_size_delta: u32,
333 pub service_as_oracle_queries: u32,
335 pub http_requests: u32,
337 }
340
341#[derive(Clone, Debug, Deserialize, Serialize, WitLoad, WitType)]
343#[cfg_attr(with_testing, derive(Eq, PartialEq, WitStore))]
344#[witty_specialize_with(Message = Vec<u8>)]
345pub struct SendMessageRequest<Message> {
346 pub destination: ChainId,
348 pub authenticated: bool,
350 pub is_tracked: bool,
352 pub grant: Resources,
354 pub message: Message,
356}
357
358impl<Message> SendMessageRequest<Message>
359where
360 Message: Serialize,
361{
362 pub fn into_raw(self) -> SendMessageRequest<Vec<u8>> {
364 let message = bcs::to_bytes(&self.message).expect("Failed to serialize message");
365
366 SendMessageRequest {
367 destination: self.destination,
368 authenticated: self.authenticated,
369 is_tracked: self.is_tracked,
370 grant: self.grant,
371 message,
372 }
373 }
374}
375
376#[derive(Debug, Error)]
378#[allow(missing_docs)]
379pub enum ArithmeticError {
380 #[error("Number overflow")]
381 Overflow,
382 #[error("Number underflow")]
383 Underflow,
384}
385
386macro_rules! impl_wrapped_number {
387 ($name:ident, $wrapped:ident) => {
388 impl $name {
389 pub const ZERO: Self = Self(0);
391
392 pub const MAX: Self = Self($wrapped::MAX);
394
395 pub fn try_add(self, other: Self) -> Result<Self, ArithmeticError> {
397 let val = self
398 .0
399 .checked_add(other.0)
400 .ok_or(ArithmeticError::Overflow)?;
401 Ok(Self(val))
402 }
403
404 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
406 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
407 Ok(Self(val))
408 }
409
410 pub const fn saturating_add(self, other: Self) -> Self {
412 let val = self.0.saturating_add(other.0);
413 Self(val)
414 }
415
416 pub fn try_sub(self, other: Self) -> Result<Self, ArithmeticError> {
418 let val = self
419 .0
420 .checked_sub(other.0)
421 .ok_or(ArithmeticError::Underflow)?;
422 Ok(Self(val))
423 }
424
425 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
427 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
428 Ok(Self(val))
429 }
430
431 pub const fn saturating_sub(self, other: Self) -> Self {
433 let val = self.0.saturating_sub(other.0);
434 Self(val)
435 }
436
437 pub fn abs_diff(self, other: Self) -> Self {
439 Self(self.0.abs_diff(other.0))
440 }
441
442 pub fn try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
444 self.0 = self
445 .0
446 .checked_add(other.0)
447 .ok_or(ArithmeticError::Overflow)?;
448 Ok(())
449 }
450
451 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
453 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
454 Ok(())
455 }
456
457 pub const fn saturating_add_assign(&mut self, other: Self) {
459 self.0 = self.0.saturating_add(other.0);
460 }
461
462 pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
464 self.0 = self
465 .0
466 .checked_sub(other.0)
467 .ok_or(ArithmeticError::Underflow)?;
468 Ok(())
469 }
470
471 pub fn saturating_div(&self, other: $wrapped) -> Self {
473 Self(self.0.checked_div(other).unwrap_or($wrapped::MAX))
474 }
475
476 pub const fn saturating_mul(&self, other: $wrapped) -> Self {
478 Self(self.0.saturating_mul(other))
479 }
480
481 pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
483 let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
484 Ok(Self(val))
485 }
486
487 pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
489 self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
490 Ok(())
491 }
492 }
493
494 impl From<$name> for $wrapped {
495 fn from(value: $name) -> Self {
496 value.0
497 }
498 }
499
500 #[cfg(with_testing)]
502 impl From<$wrapped> for $name {
503 fn from(value: $wrapped) -> Self {
504 Self(value)
505 }
506 }
507
508 #[cfg(with_testing)]
509 impl ops::Add for $name {
510 type Output = Self;
511
512 fn add(self, other: Self) -> Self {
513 Self(self.0 + other.0)
514 }
515 }
516
517 #[cfg(with_testing)]
518 impl ops::Sub for $name {
519 type Output = Self;
520
521 fn sub(self, other: Self) -> Self {
522 Self(self.0 - other.0)
523 }
524 }
525
526 #[cfg(with_testing)]
527 impl ops::Mul<$wrapped> for $name {
528 type Output = Self;
529
530 fn mul(self, other: $wrapped) -> Self {
531 Self(self.0 * other)
532 }
533 }
534 };
535}
536
537impl TryFrom<BlockHeight> for usize {
538 type Error = ArithmeticError;
539
540 fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
541 usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
542 }
543}
544
545impl_wrapped_number!(Amount, u128);
546impl_wrapped_number!(BlockHeight, u64);
547impl_wrapped_number!(TimeDelta, u64);
548
549impl Display for Amount {
550 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
551 let places = Amount::DECIMAL_PLACES as usize;
553 let min_digits = places + 1;
554 let decimals = format!("{:0min_digits$}", self.0);
555 let integer_part = &decimals[..(decimals.len() - places)];
556 let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
557
558 let precision = f.precision().unwrap_or(0).max(fractional_part.len());
560 let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
561 let pad_width = f.width().map_or(0, |w| {
563 w.saturating_sub(precision)
564 .saturating_sub(sign.len() + integer_part.len() + 1)
565 });
566 let left_pad = match f.align() {
567 None | Some(fmt::Alignment::Right) => pad_width,
568 Some(fmt::Alignment::Center) => pad_width / 2,
569 Some(fmt::Alignment::Left) => 0,
570 };
571
572 for _ in 0..left_pad {
573 write!(f, "{}", f.fill())?;
574 }
575 write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
576 for _ in left_pad..pad_width {
577 write!(f, "{}", f.fill())?;
578 }
579 Ok(())
580 }
581}
582
583#[derive(Error, Debug)]
584#[allow(missing_docs)]
585pub enum ParseAmountError {
586 #[error("cannot parse amount")]
587 Parse,
588 #[error("cannot represent amount: number too high")]
589 TooHigh,
590 #[error("cannot represent amount: too many decimal places after the point")]
591 TooManyDigits,
592}
593
594impl FromStr for Amount {
595 type Err = ParseAmountError;
596
597 fn from_str(src: &str) -> Result<Self, Self::Err> {
598 let mut result: u128 = 0;
599 let mut decimals: Option<u8> = None;
600 let mut chars = src.trim().chars().peekable();
601 if chars.peek() == Some(&'+') {
602 chars.next();
603 }
604 for char in chars {
605 match char {
606 '_' => {}
607 '.' if decimals.is_some() => return Err(ParseAmountError::Parse),
608 '.' => decimals = Some(Amount::DECIMAL_PLACES),
609 char => {
610 let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
611 if let Some(d) = &mut decimals {
612 *d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
613 }
614 result = result
615 .checked_mul(10)
616 .and_then(|r| r.checked_add(digit))
617 .ok_or(ParseAmountError::TooHigh)?;
618 }
619 }
620 }
621 result = result
622 .checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
623 .ok_or(ParseAmountError::TooHigh)?;
624 Ok(Amount(result))
625 }
626}
627
628impl Display for BlockHeight {
629 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
630 self.0.fmt(f)
631 }
632}
633
634impl FromStr for BlockHeight {
635 type Err = ParseIntError;
636
637 fn from_str(src: &str) -> Result<Self, Self::Err> {
638 Ok(Self(u64::from_str(src)?))
639 }
640}
641
642impl Display for Round {
643 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644 match self {
645 Round::Fast => write!(f, "fast round"),
646 Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
647 Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
648 Round::Validator(r) => write!(f, "validator round {}", r),
649 }
650 }
651}
652
653impl Round {
654 pub fn is_multi_leader(&self) -> bool {
656 matches!(self, Round::MultiLeader(_))
657 }
658
659 pub fn multi_leader(&self) -> Option<u32> {
661 match self {
662 Round::MultiLeader(number) => Some(*number),
663 _ => None,
664 }
665 }
666
667 pub fn is_validator(&self) -> bool {
669 matches!(self, Round::Validator(_))
670 }
671
672 pub fn is_fast(&self) -> bool {
674 matches!(self, Round::Fast)
675 }
676
677 pub fn number(&self) -> u32 {
679 match self {
680 Round::Fast => 0,
681 Round::MultiLeader(r) | Round::SingleLeader(r) | Round::Validator(r) => *r,
682 }
683 }
684
685 pub fn type_name(&self) -> &'static str {
687 match self {
688 Round::Fast => "fast",
689 Round::MultiLeader(_) => "multi",
690 Round::SingleLeader(_) => "single",
691 Round::Validator(_) => "validator",
692 }
693 }
694}
695
696impl<'a> iter::Sum<&'a Amount> for Amount {
697 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
698 iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
699 }
700}
701
702impl Amount {
703 pub const DECIMAL_PLACES: u8 = 18;
705
706 pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
708
709 pub const fn from_tokens(tokens: u128) -> Amount {
711 Self::ONE.saturating_mul(tokens)
712 }
713
714 pub const fn from_millis(millitokens: u128) -> Amount {
716 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
717 }
718
719 pub const fn from_micros(microtokens: u128) -> Amount {
721 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
722 }
723
724 pub const fn from_nanos(nanotokens: u128) -> Amount {
726 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
727 }
728
729 pub const fn from_attos(attotokens: u128) -> Amount {
731 Amount(attotokens)
732 }
733
734 pub const fn to_attos(self) -> u128 {
736 self.0
737 }
738
739 pub const fn upper_half(self) -> u64 {
741 (self.0 >> 64) as u64
742 }
743
744 pub const fn lower_half(self) -> u64 {
746 self.0 as u64
747 }
748
749 pub fn saturating_ratio(self, other: Amount) -> u128 {
751 self.0.checked_div(other.0).unwrap_or(u128::MAX)
752 }
753
754 pub fn is_zero(&self) -> bool {
756 *self == Amount::ZERO
757 }
758}
759
760#[derive(
762 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize, Allocative,
763)]
764pub enum ChainOrigin {
765 Root(u32),
767 Child {
769 parent: ChainId,
771 block_height: BlockHeight,
773 chain_index: u32,
776 },
777}
778
779impl ChainOrigin {
780 pub fn is_child(&self) -> bool {
782 matches!(self, ChainOrigin::Child { .. })
783 }
784
785 pub fn root(&self) -> Option<u32> {
787 match self {
788 ChainOrigin::Root(i) => Some(*i),
789 ChainOrigin::Child { .. } => None,
790 }
791 }
792}
793
794#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Allocative)]
796pub struct Epoch(pub u32);
797
798impl Epoch {
799 pub const ZERO: Epoch = Epoch(0);
801}
802
803impl Serialize for Epoch {
804 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
805 where
806 S: serde::ser::Serializer,
807 {
808 if serializer.is_human_readable() {
809 serializer.serialize_str(&self.0.to_string())
810 } else {
811 serializer.serialize_newtype_struct("Epoch", &self.0)
812 }
813 }
814}
815
816impl<'de> Deserialize<'de> for Epoch {
817 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
818 where
819 D: serde::de::Deserializer<'de>,
820 {
821 if deserializer.is_human_readable() {
822 let s = String::deserialize(deserializer)?;
823 Ok(Epoch(u32::from_str(&s).map_err(serde::de::Error::custom)?))
824 } else {
825 #[derive(Deserialize)]
826 #[serde(rename = "Epoch")]
827 struct EpochDerived(u32);
828
829 let value = EpochDerived::deserialize(deserializer)?;
830 Ok(Self(value.0))
831 }
832 }
833}
834
835impl std::fmt::Display for Epoch {
836 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
837 write!(f, "{}", self.0)
838 }
839}
840
841impl std::str::FromStr for Epoch {
842 type Err = CryptoError;
843
844 fn from_str(s: &str) -> Result<Self, Self::Err> {
845 Ok(Epoch(s.parse()?))
846 }
847}
848
849impl From<u32> for Epoch {
850 fn from(value: u32) -> Self {
851 Epoch(value)
852 }
853}
854
855impl Epoch {
856 #[inline]
859 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
860 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
861 Ok(Self(val))
862 }
863
864 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
867 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
868 Ok(Self(val))
869 }
870
871 #[inline]
873 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
874 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
875 Ok(())
876 }
877}
878
879#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
881pub struct InitialChainConfig {
882 pub ownership: ChainOwnership,
884 pub epoch: Epoch,
886 pub min_active_epoch: Epoch,
888 pub max_active_epoch: Epoch,
890 pub balance: Amount,
892 pub application_permissions: ApplicationPermissions,
894}
895
896#[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize, Allocative)]
898pub struct ChainDescription {
899 origin: ChainOrigin,
900 timestamp: Timestamp,
901 config: InitialChainConfig,
902}
903
904impl ChainDescription {
905 pub fn new(origin: ChainOrigin, config: InitialChainConfig, timestamp: Timestamp) -> Self {
907 Self {
908 origin,
909 config,
910 timestamp,
911 }
912 }
913
914 pub fn id(&self) -> ChainId {
916 ChainId::from(self)
917 }
918
919 pub fn origin(&self) -> ChainOrigin {
921 self.origin
922 }
923
924 pub fn config(&self) -> &InitialChainConfig {
926 &self.config
927 }
928
929 pub fn timestamp(&self) -> Timestamp {
931 self.timestamp
932 }
933
934 pub fn is_child(&self) -> bool {
936 self.origin.is_child()
937 }
938}
939
940impl BcsHashable<'_> for ChainDescription {}
941
942#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
944pub struct NetworkDescription {
945 pub name: String,
947 pub genesis_config_hash: CryptoHash,
949 pub genesis_timestamp: Timestamp,
951 pub genesis_committee_blob_hash: CryptoHash,
953 pub admin_chain_id: ChainId,
955}
956
957#[derive(
959 Default,
960 Debug,
961 PartialEq,
962 Eq,
963 PartialOrd,
964 Ord,
965 Hash,
966 Clone,
967 Serialize,
968 Deserialize,
969 WitType,
970 WitLoad,
971 WitStore,
972 InputObject,
973 Allocative,
974)]
975pub struct ApplicationPermissions {
976 #[debug(skip_if = Option::is_none)]
980 pub execute_operations: Option<Vec<ApplicationId>>,
981 #[graphql(default)]
984 #[debug(skip_if = Vec::is_empty)]
985 pub mandatory_applications: Vec<ApplicationId>,
986 #[graphql(default)]
988 #[debug(skip_if = Vec::is_empty)]
989 pub close_chain: Vec<ApplicationId>,
990 #[graphql(default)]
992 #[debug(skip_if = Vec::is_empty)]
993 pub change_application_permissions: Vec<ApplicationId>,
994 #[graphql(default)]
996 #[debug(skip_if = Option::is_none)]
997 pub call_service_as_oracle: Option<Vec<ApplicationId>>,
998 #[graphql(default)]
1000 #[debug(skip_if = Option::is_none)]
1001 pub make_http_requests: Option<Vec<ApplicationId>>,
1002}
1003
1004impl ApplicationPermissions {
1005 pub fn new_single(app_id: ApplicationId) -> Self {
1008 Self {
1009 execute_operations: Some(vec![app_id]),
1010 mandatory_applications: vec![app_id],
1011 close_chain: vec![app_id],
1012 change_application_permissions: vec![app_id],
1013 call_service_as_oracle: Some(vec![app_id]),
1014 make_http_requests: Some(vec![app_id]),
1015 }
1016 }
1017
1018 pub fn new_multiple(app_ids: Vec<ApplicationId>) -> Self {
1021 Self {
1022 execute_operations: Some(app_ids.clone()),
1023 mandatory_applications: app_ids.clone(),
1024 close_chain: app_ids.clone(),
1025 change_application_permissions: app_ids.clone(),
1026 call_service_as_oracle: Some(app_ids.clone()),
1027 make_http_requests: Some(app_ids),
1028 }
1029 }
1030
1031 pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
1033 match (app_id, &self.execute_operations) {
1034 (_, None) => true,
1035 (GenericApplicationId::System, Some(_)) => false,
1036 (GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
1037 }
1038 }
1039
1040 pub fn can_close_chain(&self, app_id: &ApplicationId) -> bool {
1042 self.close_chain.contains(app_id)
1043 }
1044
1045 pub fn can_change_application_permissions(&self, app_id: &ApplicationId) -> bool {
1048 self.change_application_permissions.contains(app_id)
1049 }
1050
1051 pub fn can_call_services(&self, app_id: &ApplicationId) -> bool {
1053 self.call_service_as_oracle
1054 .as_ref()
1055 .is_none_or(|app_ids| app_ids.contains(app_id))
1056 }
1057
1058 pub fn can_make_http_requests(&self, app_id: &ApplicationId) -> bool {
1060 self.make_http_requests
1061 .as_ref()
1062 .is_none_or(|app_ids| app_ids.contains(app_id))
1063 }
1064}
1065
1066#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
1068pub enum OracleResponse {
1069 Service(
1071 #[debug(with = "hex_debug")]
1072 #[serde(with = "serde_bytes")]
1073 Vec<u8>,
1074 ),
1075 Http(http::Response),
1077 Blob(BlobId),
1079 Assert,
1081 Round(Option<u32>),
1083 Event(
1085 EventId,
1086 #[debug(with = "hex_debug")]
1087 #[serde(with = "serde_bytes")]
1088 Vec<u8>,
1089 ),
1090 EventExists(EventId),
1092}
1093
1094impl BcsHashable<'_> for OracleResponse {}
1095
1096#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
1098pub struct ApplicationDescription {
1099 pub module_id: ModuleId,
1101 pub creator_chain_id: ChainId,
1103 pub block_height: BlockHeight,
1105 pub application_index: u32,
1107 #[serde(with = "serde_bytes")]
1109 #[debug(with = "hex_debug")]
1110 pub parameters: Vec<u8>,
1111 pub required_application_ids: Vec<ApplicationId>,
1113}
1114
1115impl From<&ApplicationDescription> for ApplicationId {
1116 fn from(description: &ApplicationDescription) -> Self {
1117 let mut hash = CryptoHash::new(&BlobContent::new_application_description(description));
1118 if matches!(description.module_id.vm_runtime, VmRuntime::Evm) {
1119 hash.make_evm_compatible();
1120 }
1121 ApplicationId::new(hash)
1122 }
1123}
1124
1125impl BcsHashable<'_> for ApplicationDescription {}
1126
1127impl ApplicationDescription {
1128 pub fn to_bytes(&self) -> Vec<u8> {
1130 bcs::to_bytes(self).expect("Serializing blob bytes should not fail!")
1131 }
1132
1133 pub fn contract_bytecode_blob_id(&self) -> BlobId {
1135 self.module_id.contract_bytecode_blob_id()
1136 }
1137
1138 pub fn service_bytecode_blob_id(&self) -> BlobId {
1140 self.module_id.service_bytecode_blob_id()
1141 }
1142}
1143
1144#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, WitType, WitLoad, WitStore)]
1146pub struct Bytecode {
1147 #[serde(with = "serde_bytes")]
1149 #[debug(with = "hex_debug")]
1150 pub bytes: Vec<u8>,
1151}
1152
1153impl Bytecode {
1154 pub fn new(bytes: Vec<u8>) -> Self {
1156 Bytecode { bytes }
1157 }
1158
1159 pub fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
1161 let bytes = fs::read(path)?;
1162 Ok(Bytecode { bytes })
1163 }
1164
1165 #[cfg(not(target_arch = "wasm32"))]
1167 pub fn compress(&self) -> CompressedBytecode {
1168 #[cfg(with_metrics)]
1169 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1170 let compressed_bytes_vec = zstd::stream::encode_all(&*self.bytes, 19)
1171 .expect("Compressing bytes in memory should not fail");
1172
1173 CompressedBytecode {
1174 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1175 }
1176 }
1177
1178 #[cfg(target_arch = "wasm32")]
1180 pub fn compress(&self) -> CompressedBytecode {
1181 use ruzstd::encoding::{CompressionLevel, FrameCompressor};
1182
1183 #[cfg(with_metrics)]
1184 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1185
1186 let mut compressed_bytes_vec = Vec::new();
1187 let mut compressor = FrameCompressor::new(CompressionLevel::Fastest);
1188 compressor.set_source(&*self.bytes);
1189 compressor.set_drain(&mut compressed_bytes_vec);
1190 compressor.compress();
1191
1192 CompressedBytecode {
1193 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1194 }
1195 }
1196}
1197
1198impl AsRef<[u8]> for Bytecode {
1199 fn as_ref(&self) -> &[u8] {
1200 self.bytes.as_ref()
1201 }
1202}
1203
1204#[derive(Error, Debug)]
1206pub enum DecompressionError {
1207 #[error("Bytecode could not be decompressed: {0}")]
1209 InvalidCompressedBytecode(#[from] io::Error),
1210}
1211
1212#[serde_as]
1214#[derive(Clone, Debug, Deserialize, Hash, Serialize, WitType, WitStore)]
1215#[cfg_attr(with_testing, derive(Eq, PartialEq))]
1216pub struct CompressedBytecode {
1217 #[serde_as(as = "Arc<Bytes>")]
1219 #[debug(skip)]
1220 pub compressed_bytes: Arc<Box<[u8]>>,
1221}
1222
1223#[cfg(not(target_arch = "wasm32"))]
1224impl CompressedBytecode {
1225 pub fn decompressed_size_at_most(
1227 compressed_bytes: &[u8],
1228 limit: u64,
1229 ) -> Result<bool, DecompressionError> {
1230 let mut decoder = zstd::stream::Decoder::new(compressed_bytes)?;
1231 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1232 let mut writer = LimitedWriter::new(io::sink(), limit);
1233 match io::copy(&mut decoder, &mut writer) {
1234 Ok(_) => Ok(true),
1235 Err(error) => {
1236 error.downcast::<LimitedWriterError>()?;
1237 Ok(false)
1238 }
1239 }
1240 }
1241
1242 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1244 #[cfg(with_metrics)]
1245 let _decompression_latency = metrics::BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1246 let bytes = zstd::stream::decode_all(&**self.compressed_bytes)?;
1247
1248 Ok(Bytecode { bytes })
1249 }
1250}
1251
1252#[cfg(target_arch = "wasm32")]
1253impl CompressedBytecode {
1254 pub fn decompressed_size_at_most(
1256 compressed_bytes: &[u8],
1257 limit: u64,
1258 ) -> Result<bool, DecompressionError> {
1259 use ruzstd::decoding::StreamingDecoder;
1260 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1261 let mut writer = LimitedWriter::new(io::sink(), limit);
1262 let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
1263
1264 match io::copy(&mut decoder, &mut writer) {
1266 Ok(_) => Ok(true),
1267 Err(error) => {
1268 error.downcast::<LimitedWriterError>()?;
1269 Ok(false)
1270 }
1271 }
1272 }
1273
1274 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1276 use ruzstd::{decoding::StreamingDecoder, io::Read};
1277
1278 #[cfg(with_metrics)]
1279 let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1280
1281 let compressed_bytes = &*self.compressed_bytes;
1282 let mut bytes = Vec::new();
1283 let mut decoder = StreamingDecoder::new(&**compressed_bytes).map_err(io::Error::other)?;
1284
1285 while !decoder.get_ref().is_empty() {
1287 decoder
1288 .read_to_end(&mut bytes)
1289 .expect("Reading from a slice in memory should not result in I/O errors");
1290 }
1291
1292 Ok(Bytecode { bytes })
1293 }
1294}
1295
1296impl BcsHashable<'_> for BlobContent {}
1297
1298#[serde_as]
1300#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Allocative)]
1301pub struct BlobContent {
1302 blob_type: BlobType,
1304 #[debug(skip)]
1306 #[serde_as(as = "Arc<Bytes>")]
1307 bytes: Arc<Box<[u8]>>,
1308}
1309
1310impl BlobContent {
1311 pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
1313 let bytes = bytes.into();
1314 BlobContent {
1315 blob_type,
1316 bytes: Arc::new(bytes),
1317 }
1318 }
1319
1320 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1322 BlobContent::new(BlobType::Data, bytes)
1323 }
1324
1325 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1327 BlobContent {
1328 blob_type: BlobType::ContractBytecode,
1329 bytes: compressed_bytecode.compressed_bytes,
1330 }
1331 }
1332
1333 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1335 BlobContent {
1336 blob_type: BlobType::EvmBytecode,
1337 bytes: compressed_bytecode.compressed_bytes,
1338 }
1339 }
1340
1341 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1343 BlobContent {
1344 blob_type: BlobType::ServiceBytecode,
1345 bytes: compressed_bytecode.compressed_bytes,
1346 }
1347 }
1348
1349 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1351 let bytes = application_description.to_bytes();
1352 BlobContent::new(BlobType::ApplicationDescription, bytes)
1353 }
1354
1355 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1357 BlobContent::new(BlobType::Committee, committee)
1358 }
1359
1360 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1362 let bytes = bcs::to_bytes(&chain_description)
1363 .expect("Serializing a ChainDescription should not fail!");
1364 BlobContent::new(BlobType::ChainDescription, bytes)
1365 }
1366
1367 pub fn bytes(&self) -> &[u8] {
1369 &self.bytes
1370 }
1371
1372 pub fn into_vec_or_clone(self) -> Vec<u8> {
1374 let bytes = Arc::unwrap_or_clone(self.bytes);
1375 bytes.into_vec()
1376 }
1377
1378 pub fn into_arc_bytes(self) -> Arc<Box<[u8]>> {
1380 self.bytes
1381 }
1382
1383 pub fn blob_type(&self) -> BlobType {
1385 self.blob_type
1386 }
1387}
1388
1389impl From<Blob> for BlobContent {
1390 fn from(blob: Blob) -> BlobContent {
1391 blob.content
1392 }
1393}
1394
1395#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
1397pub struct Blob {
1398 hash: CryptoHash,
1400 content: BlobContent,
1402}
1403
1404impl Blob {
1405 pub fn new(content: BlobContent) -> Self {
1407 let mut hash = CryptoHash::new(&content);
1408 if matches!(content.blob_type, BlobType::ApplicationDescription) {
1409 let application_description = bcs::from_bytes::<ApplicationDescription>(&content.bytes)
1410 .expect("to obtain an application description");
1411 if matches!(application_description.module_id.vm_runtime, VmRuntime::Evm) {
1412 hash.make_evm_compatible();
1413 }
1414 }
1415 Blob { hash, content }
1416 }
1417
1418 pub fn new_with_hash_unchecked(blob_id: BlobId, content: BlobContent) -> Self {
1420 Blob {
1421 hash: blob_id.hash,
1422 content,
1423 }
1424 }
1425
1426 pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1428 let bytes = bytes.into();
1429 Blob {
1430 hash: blob_id.hash,
1431 content: BlobContent {
1432 blob_type: blob_id.blob_type,
1433 bytes: Arc::new(bytes),
1434 },
1435 }
1436 }
1437
1438 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1440 Blob::new(BlobContent::new_data(bytes))
1441 }
1442
1443 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1445 Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode))
1446 }
1447
1448 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1450 Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode))
1451 }
1452
1453 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1455 Blob::new(BlobContent::new_service_bytecode(compressed_bytecode))
1456 }
1457
1458 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1460 Blob::new(BlobContent::new_application_description(
1461 application_description,
1462 ))
1463 }
1464
1465 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1467 Blob::new(BlobContent::new_committee(committee))
1468 }
1469
1470 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1472 Blob::new(BlobContent::new_chain_description(chain_description))
1473 }
1474
1475 pub fn id(&self) -> BlobId {
1477 BlobId {
1478 hash: self.hash,
1479 blob_type: self.content.blob_type,
1480 }
1481 }
1482
1483 pub fn content(&self) -> &BlobContent {
1485 &self.content
1486 }
1487
1488 pub fn into_content(self) -> BlobContent {
1490 self.content
1491 }
1492
1493 pub fn bytes(&self) -> &[u8] {
1495 self.content.bytes()
1496 }
1497
1498 pub fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
1500 Ok(Self::new_data(fs::read(path)?))
1501 }
1502
1503 pub fn is_committee_blob(&self) -> bool {
1505 self.content().blob_type().is_committee_blob()
1506 }
1507}
1508
1509impl Serialize for Blob {
1510 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1511 where
1512 S: Serializer,
1513 {
1514 if serializer.is_human_readable() {
1515 let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
1516 serializer.serialize_str(&hex::encode(blob_bytes))
1517 } else {
1518 BlobContent::serialize(self.content(), serializer)
1519 }
1520 }
1521}
1522
1523impl<'a> Deserialize<'a> for Blob {
1524 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1525 where
1526 D: Deserializer<'a>,
1527 {
1528 if deserializer.is_human_readable() {
1529 let s = String::deserialize(deserializer)?;
1530 let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1531 let content: BlobContent =
1532 bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
1533
1534 Ok(Blob::new(content))
1535 } else {
1536 let content = BlobContent::deserialize(deserializer)?;
1537 Ok(Blob::new(content))
1538 }
1539 }
1540}
1541
1542impl BcsHashable<'_> for Blob {}
1543
1544#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1546pub struct Event {
1547 pub stream_id: StreamId,
1549 pub index: u32,
1551 #[debug(with = "hex_debug")]
1553 #[serde(with = "serde_bytes")]
1554 pub value: Vec<u8>,
1555}
1556
1557impl Event {
1558 pub fn id(&self, chain_id: ChainId) -> EventId {
1560 EventId {
1561 chain_id,
1562 stream_id: self.stream_id.clone(),
1563 index: self.index,
1564 }
1565 }
1566}
1567
1568#[derive(Clone, Debug, Serialize, Deserialize, WitType, WitLoad, WitStore)]
1570pub struct StreamUpdate {
1571 pub chain_id: ChainId,
1573 pub stream_id: StreamId,
1575 pub previous_index: u32,
1577 pub next_index: u32,
1579}
1580
1581impl StreamUpdate {
1582 pub fn new_indices(&self) -> impl Iterator<Item = u32> {
1584 self.previous_index..self.next_index
1585 }
1586}
1587
1588impl BcsHashable<'_> for Event {}
1589
1590doc_scalar!(Bytecode, "A module bytecode (WebAssembly or EVM)");
1591doc_scalar!(Amount, "A non-negative amount of tokens.");
1592doc_scalar!(
1593 Epoch,
1594 "A number identifying the configuration of the chain (aka the committee)"
1595);
1596doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
1597doc_scalar!(
1598 Timestamp,
1599 "A timestamp, in microseconds since the Unix epoch"
1600);
1601doc_scalar!(TimeDelta, "A duration in microseconds");
1602doc_scalar!(
1603 Round,
1604 "A number to identify successive attempts to decide a value in a consensus protocol."
1605);
1606doc_scalar!(
1607 ChainDescription,
1608 "Initial chain configuration and chain origin."
1609);
1610doc_scalar!(OracleResponse, "A record of a single oracle response.");
1611doc_scalar!(BlobContent, "A blob of binary data.");
1612doc_scalar!(
1613 Blob,
1614 "A blob of binary data, with its content-addressed blob ID."
1615);
1616doc_scalar!(ApplicationDescription, "Description of a user application");
1617
1618#[cfg(with_metrics)]
1619mod metrics {
1620 use std::sync::LazyLock;
1621
1622 use prometheus::HistogramVec;
1623
1624 use crate::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
1625
1626 pub static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1628 register_histogram_vec(
1629 "bytecode_compression_latency",
1630 "Bytecode compression latency",
1631 &[],
1632 exponential_bucket_latencies(10.0),
1633 )
1634 });
1635
1636 pub static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1638 register_histogram_vec(
1639 "bytecode_decompression_latency",
1640 "Bytecode decompression latency",
1641 &[],
1642 exponential_bucket_latencies(10.0),
1643 )
1644 });
1645}
1646
1647#[cfg(test)]
1648mod tests {
1649 use std::str::FromStr;
1650
1651 use alloy_primitives::U256;
1652
1653 use super::{Amount, BlobContent};
1654 use crate::identifiers::BlobType;
1655
1656 #[test]
1657 fn display_amount() {
1658 assert_eq!("1.", Amount::ONE.to_string());
1659 assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
1660 assert_eq!(
1661 Amount(10_000_000_000_000_000_000),
1662 Amount::from_str("10").unwrap()
1663 );
1664 assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
1665 assert_eq!(
1666 "1001.3",
1667 (Amount::from_str("1.1")
1668 .unwrap()
1669 .saturating_add(Amount::from_str("1_000.2").unwrap()))
1670 .to_string()
1671 );
1672 assert_eq!(
1673 " 1.00000000000000000000",
1674 format!("{:25.20}", Amount::ONE)
1675 );
1676 assert_eq!(
1677 "~+12.34~~",
1678 format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
1679 );
1680 }
1681
1682 #[test]
1683 fn blob_content_serialization_deserialization() {
1684 let test_data = b"Hello, world!".as_slice();
1685 let original_blob = BlobContent::new(BlobType::Data, test_data);
1686
1687 let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1688 let deserialized: BlobContent =
1689 bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1690 assert_eq!(original_blob, deserialized);
1691
1692 let serialized =
1693 serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1694 let deserialized: BlobContent =
1695 serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1696 assert_eq!(original_blob, deserialized);
1697 }
1698
1699 #[test]
1700 fn blob_content_hash_consistency() {
1701 let test_data = b"Hello, world!";
1702 let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1703 let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1704
1705 let hash1 = crate::crypto::CryptoHash::new(&blob1);
1707 let hash2 = crate::crypto::CryptoHash::new(&blob2);
1708
1709 assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1710 assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1711 }
1712
1713 #[test]
1714 fn test_conversion_amount_u256() {
1715 let value_amount = Amount::from_tokens(15656565652209004332);
1716 let value_u256: U256 = value_amount.into();
1717 let value_amount_rev = Amount::try_from(value_u256).expect("Failed conversion");
1718 assert_eq!(value_amount, value_amount_rev);
1719 }
1720}