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 try_add_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
439 self.0 = self
440 .0
441 .checked_add(other.0)
442 .ok_or(ArithmeticError::Overflow)?;
443 Ok(())
444 }
445
446 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
448 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
449 Ok(())
450 }
451
452 pub const fn saturating_add_assign(&mut self, other: Self) {
454 self.0 = self.0.saturating_add(other.0);
455 }
456
457 pub fn try_sub_assign(&mut self, other: Self) -> Result<(), ArithmeticError> {
459 self.0 = self
460 .0
461 .checked_sub(other.0)
462 .ok_or(ArithmeticError::Underflow)?;
463 Ok(())
464 }
465
466 pub fn saturating_div(&self, other: $wrapped) -> Self {
468 Self(self.0.checked_div(other).unwrap_or($wrapped::MAX))
469 }
470
471 pub const fn saturating_mul(&self, other: $wrapped) -> Self {
473 Self(self.0.saturating_mul(other))
474 }
475
476 pub fn try_mul(self, other: $wrapped) -> Result<Self, ArithmeticError> {
478 let val = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
479 Ok(Self(val))
480 }
481
482 pub fn try_mul_assign(&mut self, other: $wrapped) -> Result<(), ArithmeticError> {
484 self.0 = self.0.checked_mul(other).ok_or(ArithmeticError::Overflow)?;
485 Ok(())
486 }
487 }
488
489 impl From<$name> for $wrapped {
490 fn from(value: $name) -> Self {
491 value.0
492 }
493 }
494
495 #[cfg(with_testing)]
497 impl From<$wrapped> for $name {
498 fn from(value: $wrapped) -> Self {
499 Self(value)
500 }
501 }
502
503 #[cfg(with_testing)]
504 impl ops::Add for $name {
505 type Output = Self;
506
507 fn add(self, other: Self) -> Self {
508 Self(self.0 + other.0)
509 }
510 }
511
512 #[cfg(with_testing)]
513 impl ops::Sub for $name {
514 type Output = Self;
515
516 fn sub(self, other: Self) -> Self {
517 Self(self.0 - other.0)
518 }
519 }
520
521 #[cfg(with_testing)]
522 impl ops::Mul<$wrapped> for $name {
523 type Output = Self;
524
525 fn mul(self, other: $wrapped) -> Self {
526 Self(self.0 * other)
527 }
528 }
529 };
530}
531
532impl TryFrom<BlockHeight> for usize {
533 type Error = ArithmeticError;
534
535 fn try_from(height: BlockHeight) -> Result<usize, ArithmeticError> {
536 usize::try_from(height.0).map_err(|_| ArithmeticError::Overflow)
537 }
538}
539
540impl_wrapped_number!(Amount, u128);
541impl_wrapped_number!(BlockHeight, u64);
542impl_wrapped_number!(TimeDelta, u64);
543
544impl Display for Amount {
545 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
546 let places = Amount::DECIMAL_PLACES as usize;
548 let min_digits = places + 1;
549 let decimals = format!("{:0min_digits$}", self.0);
550 let integer_part = &decimals[..(decimals.len() - places)];
551 let fractional_part = decimals[(decimals.len() - places)..].trim_end_matches('0');
552
553 let precision = f.precision().unwrap_or(0).max(fractional_part.len());
555 let sign = if f.sign_plus() && self.0 > 0 { "+" } else { "" };
556 let pad_width = f.width().map_or(0, |w| {
558 w.saturating_sub(precision)
559 .saturating_sub(sign.len() + integer_part.len() + 1)
560 });
561 let left_pad = match f.align() {
562 None | Some(fmt::Alignment::Right) => pad_width,
563 Some(fmt::Alignment::Center) => pad_width / 2,
564 Some(fmt::Alignment::Left) => 0,
565 };
566
567 for _ in 0..left_pad {
568 write!(f, "{}", f.fill())?;
569 }
570 write!(f, "{sign}{integer_part}.{fractional_part:0<precision$}")?;
571 for _ in left_pad..pad_width {
572 write!(f, "{}", f.fill())?;
573 }
574 Ok(())
575 }
576}
577
578#[derive(Error, Debug)]
579#[allow(missing_docs)]
580pub enum ParseAmountError {
581 #[error("cannot parse amount")]
582 Parse,
583 #[error("cannot represent amount: number too high")]
584 TooHigh,
585 #[error("cannot represent amount: too many decimal places after the point")]
586 TooManyDigits,
587}
588
589impl FromStr for Amount {
590 type Err = ParseAmountError;
591
592 fn from_str(src: &str) -> Result<Self, Self::Err> {
593 let mut result: u128 = 0;
594 let mut decimals: Option<u8> = None;
595 let mut chars = src.trim().chars().peekable();
596 if chars.peek() == Some(&'+') {
597 chars.next();
598 }
599 for char in chars {
600 match char {
601 '_' => {}
602 '.' if decimals.is_some() => return Err(ParseAmountError::Parse),
603 '.' => decimals = Some(Amount::DECIMAL_PLACES),
604 char => {
605 let digit = u128::from(char.to_digit(10).ok_or(ParseAmountError::Parse)?);
606 if let Some(d) = &mut decimals {
607 *d = d.checked_sub(1).ok_or(ParseAmountError::TooManyDigits)?;
608 }
609 result = result
610 .checked_mul(10)
611 .and_then(|r| r.checked_add(digit))
612 .ok_or(ParseAmountError::TooHigh)?;
613 }
614 }
615 }
616 result = result
617 .checked_mul(10u128.pow(decimals.unwrap_or(Amount::DECIMAL_PLACES) as u32))
618 .ok_or(ParseAmountError::TooHigh)?;
619 Ok(Amount(result))
620 }
621}
622
623impl Display for BlockHeight {
624 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625 self.0.fmt(f)
626 }
627}
628
629impl FromStr for BlockHeight {
630 type Err = ParseIntError;
631
632 fn from_str(src: &str) -> Result<Self, Self::Err> {
633 Ok(Self(u64::from_str(src)?))
634 }
635}
636
637impl Display for Round {
638 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
639 match self {
640 Round::Fast => write!(f, "fast round"),
641 Round::MultiLeader(r) => write!(f, "multi-leader round {}", r),
642 Round::SingleLeader(r) => write!(f, "single-leader round {}", r),
643 Round::Validator(r) => write!(f, "validator round {}", r),
644 }
645 }
646}
647
648impl Round {
649 pub fn is_multi_leader(&self) -> bool {
651 matches!(self, Round::MultiLeader(_))
652 }
653
654 pub fn multi_leader(&self) -> Option<u32> {
656 match self {
657 Round::MultiLeader(number) => Some(*number),
658 _ => None,
659 }
660 }
661
662 pub fn is_fast(&self) -> bool {
664 matches!(self, Round::Fast)
665 }
666
667 pub fn number(&self) -> u32 {
669 match self {
670 Round::Fast => 0,
671 Round::MultiLeader(r) | Round::SingleLeader(r) | Round::Validator(r) => *r,
672 }
673 }
674
675 pub fn type_name(&self) -> &'static str {
677 match self {
678 Round::Fast => "fast",
679 Round::MultiLeader(_) => "multi",
680 Round::SingleLeader(_) => "single",
681 Round::Validator(_) => "validator",
682 }
683 }
684}
685
686impl<'a> iter::Sum<&'a Amount> for Amount {
687 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
688 iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
689 }
690}
691
692impl Amount {
693 pub const DECIMAL_PLACES: u8 = 18;
695
696 pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
698
699 pub const fn from_tokens(tokens: u128) -> Amount {
701 Self::ONE.saturating_mul(tokens)
702 }
703
704 pub const fn from_millis(millitokens: u128) -> Amount {
706 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
707 }
708
709 pub const fn from_micros(microtokens: u128) -> Amount {
711 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
712 }
713
714 pub const fn from_nanos(nanotokens: u128) -> Amount {
716 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
717 }
718
719 pub const fn from_attos(attotokens: u128) -> Amount {
721 Amount(attotokens)
722 }
723
724 pub const fn upper_half(self) -> u64 {
726 (self.0 >> 64) as u64
727 }
728
729 pub const fn lower_half(self) -> u64 {
731 self.0 as u64
732 }
733
734 pub fn saturating_ratio(self, other: Amount) -> u128 {
736 self.0.checked_div(other.0).unwrap_or(u128::MAX)
737 }
738
739 pub fn is_zero(&self) -> bool {
741 *self == Amount::ZERO
742 }
743}
744
745#[derive(
747 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize, Allocative,
748)]
749pub enum ChainOrigin {
750 Root(u32),
752 Child {
754 parent: ChainId,
756 block_height: BlockHeight,
758 chain_index: u32,
761 },
762}
763
764impl ChainOrigin {
765 pub fn is_child(&self) -> bool {
767 matches!(self, ChainOrigin::Child { .. })
768 }
769
770 pub fn root(&self) -> Option<u32> {
772 match self {
773 ChainOrigin::Root(i) => Some(*i),
774 ChainOrigin::Child { .. } => None,
775 }
776 }
777}
778
779#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Allocative)]
781pub struct Epoch(pub u32);
782
783impl Epoch {
784 pub const ZERO: Epoch = Epoch(0);
786}
787
788impl Serialize for Epoch {
789 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
790 where
791 S: serde::ser::Serializer,
792 {
793 if serializer.is_human_readable() {
794 serializer.serialize_str(&self.0.to_string())
795 } else {
796 serializer.serialize_newtype_struct("Epoch", &self.0)
797 }
798 }
799}
800
801impl<'de> Deserialize<'de> for Epoch {
802 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
803 where
804 D: serde::de::Deserializer<'de>,
805 {
806 if deserializer.is_human_readable() {
807 let s = String::deserialize(deserializer)?;
808 Ok(Epoch(u32::from_str(&s).map_err(serde::de::Error::custom)?))
809 } else {
810 #[derive(Deserialize)]
811 #[serde(rename = "Epoch")]
812 struct EpochDerived(u32);
813
814 let value = EpochDerived::deserialize(deserializer)?;
815 Ok(Self(value.0))
816 }
817 }
818}
819
820impl std::fmt::Display for Epoch {
821 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
822 write!(f, "{}", self.0)
823 }
824}
825
826impl std::str::FromStr for Epoch {
827 type Err = CryptoError;
828
829 fn from_str(s: &str) -> Result<Self, Self::Err> {
830 Ok(Epoch(s.parse()?))
831 }
832}
833
834impl From<u32> for Epoch {
835 fn from(value: u32) -> Self {
836 Epoch(value)
837 }
838}
839
840impl Epoch {
841 #[inline]
844 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
845 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
846 Ok(Self(val))
847 }
848
849 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
852 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
853 Ok(Self(val))
854 }
855
856 #[inline]
858 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
859 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
860 Ok(())
861 }
862}
863
864#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
866pub struct InitialChainConfig {
867 pub ownership: ChainOwnership,
869 pub epoch: Epoch,
871 pub min_active_epoch: Epoch,
873 pub max_active_epoch: Epoch,
875 pub balance: Amount,
877 pub application_permissions: ApplicationPermissions,
879}
880
881#[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize, Allocative)]
883pub struct ChainDescription {
884 origin: ChainOrigin,
885 timestamp: Timestamp,
886 config: InitialChainConfig,
887}
888
889impl ChainDescription {
890 pub fn new(origin: ChainOrigin, config: InitialChainConfig, timestamp: Timestamp) -> Self {
892 Self {
893 origin,
894 config,
895 timestamp,
896 }
897 }
898
899 pub fn id(&self) -> ChainId {
901 ChainId::from(self)
902 }
903
904 pub fn origin(&self) -> ChainOrigin {
906 self.origin
907 }
908
909 pub fn config(&self) -> &InitialChainConfig {
911 &self.config
912 }
913
914 pub fn timestamp(&self) -> Timestamp {
916 self.timestamp
917 }
918
919 pub fn is_child(&self) -> bool {
921 self.origin.is_child()
922 }
923}
924
925impl BcsHashable<'_> for ChainDescription {}
926
927#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
929pub struct NetworkDescription {
930 pub name: String,
932 pub genesis_config_hash: CryptoHash,
934 pub genesis_timestamp: Timestamp,
936 pub genesis_committee_blob_hash: CryptoHash,
938 pub admin_chain_id: ChainId,
940}
941
942#[derive(
944 Default,
945 Debug,
946 PartialEq,
947 Eq,
948 PartialOrd,
949 Ord,
950 Hash,
951 Clone,
952 Serialize,
953 Deserialize,
954 WitType,
955 WitLoad,
956 WitStore,
957 InputObject,
958 Allocative,
959)]
960pub struct ApplicationPermissions {
961 #[debug(skip_if = Option::is_none)]
965 pub execute_operations: Option<Vec<ApplicationId>>,
966 #[graphql(default)]
969 #[debug(skip_if = Vec::is_empty)]
970 pub mandatory_applications: Vec<ApplicationId>,
971 #[graphql(default)]
973 #[debug(skip_if = Vec::is_empty)]
974 pub close_chain: Vec<ApplicationId>,
975 #[graphql(default)]
977 #[debug(skip_if = Vec::is_empty)]
978 pub change_application_permissions: Vec<ApplicationId>,
979 #[graphql(default)]
981 #[debug(skip_if = Option::is_none)]
982 pub call_service_as_oracle: Option<Vec<ApplicationId>>,
983 #[graphql(default)]
985 #[debug(skip_if = Option::is_none)]
986 pub make_http_requests: Option<Vec<ApplicationId>>,
987}
988
989impl ApplicationPermissions {
990 pub fn new_single(app_id: ApplicationId) -> Self {
993 Self {
994 execute_operations: Some(vec![app_id]),
995 mandatory_applications: vec![app_id],
996 close_chain: vec![app_id],
997 change_application_permissions: vec![app_id],
998 call_service_as_oracle: Some(vec![app_id]),
999 make_http_requests: Some(vec![app_id]),
1000 }
1001 }
1002
1003 pub fn new_multiple(app_ids: Vec<ApplicationId>) -> Self {
1006 Self {
1007 execute_operations: Some(app_ids.clone()),
1008 mandatory_applications: app_ids.clone(),
1009 close_chain: app_ids.clone(),
1010 change_application_permissions: app_ids.clone(),
1011 call_service_as_oracle: Some(app_ids.clone()),
1012 make_http_requests: Some(app_ids),
1013 }
1014 }
1015
1016 pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
1018 match (app_id, &self.execute_operations) {
1019 (_, None) => true,
1020 (GenericApplicationId::System, Some(_)) => false,
1021 (GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
1022 }
1023 }
1024
1025 pub fn can_close_chain(&self, app_id: &ApplicationId) -> bool {
1027 self.close_chain.contains(app_id)
1028 }
1029
1030 pub fn can_change_application_permissions(&self, app_id: &ApplicationId) -> bool {
1033 self.change_application_permissions.contains(app_id)
1034 }
1035
1036 pub fn can_call_services(&self, app_id: &ApplicationId) -> bool {
1038 self.call_service_as_oracle
1039 .as_ref()
1040 .is_none_or(|app_ids| app_ids.contains(app_id))
1041 }
1042
1043 pub fn can_make_http_requests(&self, app_id: &ApplicationId) -> bool {
1045 self.make_http_requests
1046 .as_ref()
1047 .is_none_or(|app_ids| app_ids.contains(app_id))
1048 }
1049}
1050
1051#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
1053pub enum OracleResponse {
1054 Service(
1056 #[debug(with = "hex_debug")]
1057 #[serde(with = "serde_bytes")]
1058 Vec<u8>,
1059 ),
1060 Http(http::Response),
1062 Blob(BlobId),
1064 Assert,
1066 Round(Option<u32>),
1068 Event(
1070 EventId,
1071 #[debug(with = "hex_debug")]
1072 #[serde(with = "serde_bytes")]
1073 Vec<u8>,
1074 ),
1075 EventExists(EventId),
1077}
1078
1079impl BcsHashable<'_> for OracleResponse {}
1080
1081#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
1083pub struct ApplicationDescription {
1084 pub module_id: ModuleId,
1086 pub creator_chain_id: ChainId,
1088 pub block_height: BlockHeight,
1090 pub application_index: u32,
1092 #[serde(with = "serde_bytes")]
1094 #[debug(with = "hex_debug")]
1095 pub parameters: Vec<u8>,
1096 pub required_application_ids: Vec<ApplicationId>,
1098}
1099
1100impl From<&ApplicationDescription> for ApplicationId {
1101 fn from(description: &ApplicationDescription) -> Self {
1102 let mut hash = CryptoHash::new(&BlobContent::new_application_description(description));
1103 if matches!(description.module_id.vm_runtime, VmRuntime::Evm) {
1104 hash.make_evm_compatible();
1105 }
1106 ApplicationId::new(hash)
1107 }
1108}
1109
1110impl BcsHashable<'_> for ApplicationDescription {}
1111
1112impl ApplicationDescription {
1113 pub fn to_bytes(&self) -> Vec<u8> {
1115 bcs::to_bytes(self).expect("Serializing blob bytes should not fail!")
1116 }
1117
1118 pub fn contract_bytecode_blob_id(&self) -> BlobId {
1120 self.module_id.contract_bytecode_blob_id()
1121 }
1122
1123 pub fn service_bytecode_blob_id(&self) -> BlobId {
1125 self.module_id.service_bytecode_blob_id()
1126 }
1127}
1128
1129#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, WitType, WitLoad, WitStore)]
1131pub struct Bytecode {
1132 #[serde(with = "serde_bytes")]
1134 #[debug(with = "hex_debug")]
1135 pub bytes: Vec<u8>,
1136}
1137
1138impl Bytecode {
1139 pub fn new(bytes: Vec<u8>) -> Self {
1141 Bytecode { bytes }
1142 }
1143
1144 pub fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
1146 let bytes = fs::read(path)?;
1147 Ok(Bytecode { bytes })
1148 }
1149
1150 #[cfg(not(target_arch = "wasm32"))]
1152 pub fn compress(&self) -> CompressedBytecode {
1153 #[cfg(with_metrics)]
1154 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1155 let compressed_bytes_vec = zstd::stream::encode_all(&*self.bytes, 19)
1156 .expect("Compressing bytes in memory should not fail");
1157
1158 CompressedBytecode {
1159 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1160 }
1161 }
1162
1163 #[cfg(target_arch = "wasm32")]
1165 pub fn compress(&self) -> CompressedBytecode {
1166 use ruzstd::encoding::{CompressionLevel, FrameCompressor};
1167
1168 #[cfg(with_metrics)]
1169 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1170
1171 let mut compressed_bytes_vec = Vec::new();
1172 let mut compressor = FrameCompressor::new(CompressionLevel::Fastest);
1173 compressor.set_source(&*self.bytes);
1174 compressor.set_drain(&mut compressed_bytes_vec);
1175 compressor.compress();
1176
1177 CompressedBytecode {
1178 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1179 }
1180 }
1181}
1182
1183impl AsRef<[u8]> for Bytecode {
1184 fn as_ref(&self) -> &[u8] {
1185 self.bytes.as_ref()
1186 }
1187}
1188
1189#[derive(Error, Debug)]
1191pub enum DecompressionError {
1192 #[error("Bytecode could not be decompressed: {0}")]
1194 InvalidCompressedBytecode(#[from] io::Error),
1195}
1196
1197#[serde_as]
1199#[derive(Clone, Debug, Deserialize, Hash, Serialize, WitType, WitStore)]
1200#[cfg_attr(with_testing, derive(Eq, PartialEq))]
1201pub struct CompressedBytecode {
1202 #[serde_as(as = "Arc<Bytes>")]
1204 #[debug(skip)]
1205 pub compressed_bytes: Arc<Box<[u8]>>,
1206}
1207
1208#[cfg(not(target_arch = "wasm32"))]
1209impl CompressedBytecode {
1210 pub fn decompressed_size_at_most(
1212 compressed_bytes: &[u8],
1213 limit: u64,
1214 ) -> Result<bool, DecompressionError> {
1215 let mut decoder = zstd::stream::Decoder::new(compressed_bytes)?;
1216 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1217 let mut writer = LimitedWriter::new(io::sink(), limit);
1218 match io::copy(&mut decoder, &mut writer) {
1219 Ok(_) => Ok(true),
1220 Err(error) => {
1221 error.downcast::<LimitedWriterError>()?;
1222 Ok(false)
1223 }
1224 }
1225 }
1226
1227 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1229 #[cfg(with_metrics)]
1230 let _decompression_latency = metrics::BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1231 let bytes = zstd::stream::decode_all(&**self.compressed_bytes)?;
1232
1233 Ok(Bytecode { bytes })
1234 }
1235}
1236
1237#[cfg(target_arch = "wasm32")]
1238impl CompressedBytecode {
1239 pub fn decompressed_size_at_most(
1241 compressed_bytes: &[u8],
1242 limit: u64,
1243 ) -> Result<bool, DecompressionError> {
1244 use ruzstd::decoding::StreamingDecoder;
1245 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1246 let mut writer = LimitedWriter::new(io::sink(), limit);
1247 let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
1248
1249 match io::copy(&mut decoder, &mut writer) {
1251 Ok(_) => Ok(true),
1252 Err(error) => {
1253 error.downcast::<LimitedWriterError>()?;
1254 Ok(false)
1255 }
1256 }
1257 }
1258
1259 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1261 use ruzstd::{decoding::StreamingDecoder, io::Read};
1262
1263 #[cfg(with_metrics)]
1264 let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1265
1266 let compressed_bytes = &*self.compressed_bytes;
1267 let mut bytes = Vec::new();
1268 let mut decoder = StreamingDecoder::new(&**compressed_bytes).map_err(io::Error::other)?;
1269
1270 while !decoder.get_ref().is_empty() {
1272 decoder
1273 .read_to_end(&mut bytes)
1274 .expect("Reading from a slice in memory should not result in I/O errors");
1275 }
1276
1277 Ok(Bytecode { bytes })
1278 }
1279}
1280
1281impl BcsHashable<'_> for BlobContent {}
1282
1283#[serde_as]
1285#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Allocative)]
1286pub struct BlobContent {
1287 blob_type: BlobType,
1289 #[debug(skip)]
1291 #[serde_as(as = "Arc<Bytes>")]
1292 bytes: Arc<Box<[u8]>>,
1293}
1294
1295impl BlobContent {
1296 pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
1298 let bytes = bytes.into();
1299 BlobContent {
1300 blob_type,
1301 bytes: Arc::new(bytes),
1302 }
1303 }
1304
1305 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1307 BlobContent::new(BlobType::Data, bytes)
1308 }
1309
1310 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1312 BlobContent {
1313 blob_type: BlobType::ContractBytecode,
1314 bytes: compressed_bytecode.compressed_bytes,
1315 }
1316 }
1317
1318 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1320 BlobContent {
1321 blob_type: BlobType::EvmBytecode,
1322 bytes: compressed_bytecode.compressed_bytes,
1323 }
1324 }
1325
1326 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1328 BlobContent {
1329 blob_type: BlobType::ServiceBytecode,
1330 bytes: compressed_bytecode.compressed_bytes,
1331 }
1332 }
1333
1334 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1336 let bytes = application_description.to_bytes();
1337 BlobContent::new(BlobType::ApplicationDescription, bytes)
1338 }
1339
1340 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1342 BlobContent::new(BlobType::Committee, committee)
1343 }
1344
1345 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1347 let bytes = bcs::to_bytes(&chain_description)
1348 .expect("Serializing a ChainDescription should not fail!");
1349 BlobContent::new(BlobType::ChainDescription, bytes)
1350 }
1351
1352 pub fn bytes(&self) -> &[u8] {
1354 &self.bytes
1355 }
1356
1357 pub fn into_vec_or_clone(self) -> Vec<u8> {
1359 let bytes = Arc::unwrap_or_clone(self.bytes);
1360 bytes.into_vec()
1361 }
1362
1363 pub fn into_arc_bytes(self) -> Arc<Box<[u8]>> {
1365 self.bytes
1366 }
1367
1368 pub fn blob_type(&self) -> BlobType {
1370 self.blob_type
1371 }
1372}
1373
1374impl From<Blob> for BlobContent {
1375 fn from(blob: Blob) -> BlobContent {
1376 blob.content
1377 }
1378}
1379
1380#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
1382pub struct Blob {
1383 hash: CryptoHash,
1385 content: BlobContent,
1387}
1388
1389impl Blob {
1390 pub fn new(content: BlobContent) -> Self {
1392 let mut hash = CryptoHash::new(&content);
1393 if matches!(content.blob_type, BlobType::ApplicationDescription) {
1394 let application_description = bcs::from_bytes::<ApplicationDescription>(&content.bytes)
1395 .expect("to obtain an application description");
1396 if matches!(application_description.module_id.vm_runtime, VmRuntime::Evm) {
1397 hash.make_evm_compatible();
1398 }
1399 }
1400 Blob { hash, content }
1401 }
1402
1403 pub fn new_with_hash_unchecked(blob_id: BlobId, content: BlobContent) -> Self {
1405 Blob {
1406 hash: blob_id.hash,
1407 content,
1408 }
1409 }
1410
1411 pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1413 let bytes = bytes.into();
1414 Blob {
1415 hash: blob_id.hash,
1416 content: BlobContent {
1417 blob_type: blob_id.blob_type,
1418 bytes: Arc::new(bytes),
1419 },
1420 }
1421 }
1422
1423 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1425 Blob::new(BlobContent::new_data(bytes))
1426 }
1427
1428 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1430 Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode))
1431 }
1432
1433 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1435 Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode))
1436 }
1437
1438 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1440 Blob::new(BlobContent::new_service_bytecode(compressed_bytecode))
1441 }
1442
1443 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1445 Blob::new(BlobContent::new_application_description(
1446 application_description,
1447 ))
1448 }
1449
1450 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1452 Blob::new(BlobContent::new_committee(committee))
1453 }
1454
1455 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1457 Blob::new(BlobContent::new_chain_description(chain_description))
1458 }
1459
1460 pub fn id(&self) -> BlobId {
1462 BlobId {
1463 hash: self.hash,
1464 blob_type: self.content.blob_type,
1465 }
1466 }
1467
1468 pub fn content(&self) -> &BlobContent {
1470 &self.content
1471 }
1472
1473 pub fn into_content(self) -> BlobContent {
1475 self.content
1476 }
1477
1478 pub fn bytes(&self) -> &[u8] {
1480 self.content.bytes()
1481 }
1482
1483 pub fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
1485 Ok(Self::new_data(fs::read(path)?))
1486 }
1487
1488 pub fn is_committee_blob(&self) -> bool {
1490 self.content().blob_type().is_committee_blob()
1491 }
1492}
1493
1494impl Serialize for Blob {
1495 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1496 where
1497 S: Serializer,
1498 {
1499 if serializer.is_human_readable() {
1500 let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
1501 serializer.serialize_str(&hex::encode(blob_bytes))
1502 } else {
1503 BlobContent::serialize(self.content(), serializer)
1504 }
1505 }
1506}
1507
1508impl<'a> Deserialize<'a> for Blob {
1509 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1510 where
1511 D: Deserializer<'a>,
1512 {
1513 if deserializer.is_human_readable() {
1514 let s = String::deserialize(deserializer)?;
1515 let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1516 let content: BlobContent =
1517 bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
1518
1519 Ok(Blob::new(content))
1520 } else {
1521 let content = BlobContent::deserialize(deserializer)?;
1522 Ok(Blob::new(content))
1523 }
1524 }
1525}
1526
1527impl BcsHashable<'_> for Blob {}
1528
1529#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1531pub struct Event {
1532 pub stream_id: StreamId,
1534 pub index: u32,
1536 #[debug(with = "hex_debug")]
1538 #[serde(with = "serde_bytes")]
1539 pub value: Vec<u8>,
1540}
1541
1542impl Event {
1543 pub fn id(&self, chain_id: ChainId) -> EventId {
1545 EventId {
1546 chain_id,
1547 stream_id: self.stream_id.clone(),
1548 index: self.index,
1549 }
1550 }
1551}
1552
1553#[derive(Clone, Debug, Serialize, Deserialize, WitType, WitLoad, WitStore)]
1555pub struct StreamUpdate {
1556 pub chain_id: ChainId,
1558 pub stream_id: StreamId,
1560 pub previous_index: u32,
1562 pub next_index: u32,
1564}
1565
1566impl StreamUpdate {
1567 pub fn new_indices(&self) -> impl Iterator<Item = u32> {
1569 self.previous_index..self.next_index
1570 }
1571}
1572
1573impl BcsHashable<'_> for Event {}
1574
1575doc_scalar!(Bytecode, "A module bytecode (WebAssembly or EVM)");
1576doc_scalar!(Amount, "A non-negative amount of tokens.");
1577doc_scalar!(
1578 Epoch,
1579 "A number identifying the configuration of the chain (aka the committee)"
1580);
1581doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
1582doc_scalar!(
1583 Timestamp,
1584 "A timestamp, in microseconds since the Unix epoch"
1585);
1586doc_scalar!(TimeDelta, "A duration in microseconds");
1587doc_scalar!(
1588 Round,
1589 "A number to identify successive attempts to decide a value in a consensus protocol."
1590);
1591doc_scalar!(
1592 ChainDescription,
1593 "Initial chain configuration and chain origin."
1594);
1595doc_scalar!(OracleResponse, "A record of a single oracle response.");
1596doc_scalar!(BlobContent, "A blob of binary data.");
1597doc_scalar!(
1598 Blob,
1599 "A blob of binary data, with its content-addressed blob ID."
1600);
1601doc_scalar!(ApplicationDescription, "Description of a user application");
1602
1603#[cfg(with_metrics)]
1604mod metrics {
1605 use std::sync::LazyLock;
1606
1607 use prometheus::HistogramVec;
1608
1609 use crate::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
1610
1611 pub static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1613 register_histogram_vec(
1614 "bytecode_compression_latency",
1615 "Bytecode compression latency",
1616 &[],
1617 exponential_bucket_latencies(10.0),
1618 )
1619 });
1620
1621 pub static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1623 register_histogram_vec(
1624 "bytecode_decompression_latency",
1625 "Bytecode decompression latency",
1626 &[],
1627 exponential_bucket_latencies(10.0),
1628 )
1629 });
1630}
1631
1632#[cfg(test)]
1633mod tests {
1634 use std::str::FromStr;
1635
1636 use alloy_primitives::U256;
1637
1638 use super::{Amount, BlobContent};
1639 use crate::identifiers::BlobType;
1640
1641 #[test]
1642 fn display_amount() {
1643 assert_eq!("1.", Amount::ONE.to_string());
1644 assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
1645 assert_eq!(
1646 Amount(10_000_000_000_000_000_000),
1647 Amount::from_str("10").unwrap()
1648 );
1649 assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
1650 assert_eq!(
1651 "1001.3",
1652 (Amount::from_str("1.1")
1653 .unwrap()
1654 .saturating_add(Amount::from_str("1_000.2").unwrap()))
1655 .to_string()
1656 );
1657 assert_eq!(
1658 " 1.00000000000000000000",
1659 format!("{:25.20}", Amount::ONE)
1660 );
1661 assert_eq!(
1662 "~+12.34~~",
1663 format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
1664 );
1665 }
1666
1667 #[test]
1668 fn blob_content_serialization_deserialization() {
1669 let test_data = b"Hello, world!".as_slice();
1670 let original_blob = BlobContent::new(BlobType::Data, test_data);
1671
1672 let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1673 let deserialized: BlobContent =
1674 bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1675 assert_eq!(original_blob, deserialized);
1676
1677 let serialized =
1678 serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1679 let deserialized: BlobContent =
1680 serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1681 assert_eq!(original_blob, deserialized);
1682 }
1683
1684 #[test]
1685 fn blob_content_hash_consistency() {
1686 let test_data = b"Hello, world!";
1687 let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1688 let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1689
1690 let hash1 = crate::crypto::CryptoHash::new(&blob1);
1692 let hash2 = crate::crypto::CryptoHash::new(&blob2);
1693
1694 assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1695 assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1696 }
1697
1698 #[test]
1699 fn test_conversion_amount_u256() {
1700 let value_amount = Amount::from_tokens(15656565652209004332);
1701 let value_u256: U256 = value_amount.into();
1702 let value_amount_rev = Amount::try_from(value_u256).expect("Failed conversion");
1703 assert_eq!(value_amount, value_amount_rev);
1704 }
1705}