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_fast(&self) -> bool {
669 matches!(self, Round::Fast)
670 }
671
672 pub fn number(&self) -> u32 {
674 match self {
675 Round::Fast => 0,
676 Round::MultiLeader(r) | Round::SingleLeader(r) | Round::Validator(r) => *r,
677 }
678 }
679
680 pub fn type_name(&self) -> &'static str {
682 match self {
683 Round::Fast => "fast",
684 Round::MultiLeader(_) => "multi",
685 Round::SingleLeader(_) => "single",
686 Round::Validator(_) => "validator",
687 }
688 }
689}
690
691impl<'a> iter::Sum<&'a Amount> for Amount {
692 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
693 iter.fold(Self::ZERO, |a, b| a.saturating_add(*b))
694 }
695}
696
697impl Amount {
698 pub const DECIMAL_PLACES: u8 = 18;
700
701 pub const ONE: Amount = Amount(10u128.pow(Amount::DECIMAL_PLACES as u32));
703
704 pub const fn from_tokens(tokens: u128) -> Amount {
706 Self::ONE.saturating_mul(tokens)
707 }
708
709 pub const fn from_millis(millitokens: u128) -> Amount {
711 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 3)).saturating_mul(millitokens)
712 }
713
714 pub const fn from_micros(microtokens: u128) -> Amount {
716 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 6)).saturating_mul(microtokens)
717 }
718
719 pub const fn from_nanos(nanotokens: u128) -> Amount {
721 Amount(10u128.pow(Amount::DECIMAL_PLACES as u32 - 9)).saturating_mul(nanotokens)
722 }
723
724 pub const fn from_attos(attotokens: u128) -> Amount {
726 Amount(attotokens)
727 }
728
729 pub const fn to_attos(self) -> u128 {
731 self.0
732 }
733
734 pub const fn upper_half(self) -> u64 {
736 (self.0 >> 64) as u64
737 }
738
739 pub const fn lower_half(self) -> u64 {
741 self.0 as u64
742 }
743
744 pub fn saturating_ratio(self, other: Amount) -> u128 {
746 self.0.checked_div(other.0).unwrap_or(u128::MAX)
747 }
748
749 pub fn is_zero(&self) -> bool {
751 *self == Amount::ZERO
752 }
753}
754
755#[derive(
757 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Debug, Serialize, Deserialize, Allocative,
758)]
759pub enum ChainOrigin {
760 Root(u32),
762 Child {
764 parent: ChainId,
766 block_height: BlockHeight,
768 chain_index: u32,
771 },
772}
773
774impl ChainOrigin {
775 pub fn is_child(&self) -> bool {
777 matches!(self, ChainOrigin::Child { .. })
778 }
779
780 pub fn root(&self) -> Option<u32> {
782 match self {
783 ChainOrigin::Root(i) => Some(*i),
784 ChainOrigin::Child { .. } => None,
785 }
786 }
787}
788
789#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Default, Debug, Allocative)]
791pub struct Epoch(pub u32);
792
793impl Epoch {
794 pub const ZERO: Epoch = Epoch(0);
796}
797
798impl Serialize for Epoch {
799 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
800 where
801 S: serde::ser::Serializer,
802 {
803 if serializer.is_human_readable() {
804 serializer.serialize_str(&self.0.to_string())
805 } else {
806 serializer.serialize_newtype_struct("Epoch", &self.0)
807 }
808 }
809}
810
811impl<'de> Deserialize<'de> for Epoch {
812 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
813 where
814 D: serde::de::Deserializer<'de>,
815 {
816 if deserializer.is_human_readable() {
817 let s = String::deserialize(deserializer)?;
818 Ok(Epoch(u32::from_str(&s).map_err(serde::de::Error::custom)?))
819 } else {
820 #[derive(Deserialize)]
821 #[serde(rename = "Epoch")]
822 struct EpochDerived(u32);
823
824 let value = EpochDerived::deserialize(deserializer)?;
825 Ok(Self(value.0))
826 }
827 }
828}
829
830impl std::fmt::Display for Epoch {
831 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
832 write!(f, "{}", self.0)
833 }
834}
835
836impl std::str::FromStr for Epoch {
837 type Err = CryptoError;
838
839 fn from_str(s: &str) -> Result<Self, Self::Err> {
840 Ok(Epoch(s.parse()?))
841 }
842}
843
844impl From<u32> for Epoch {
845 fn from(value: u32) -> Self {
846 Epoch(value)
847 }
848}
849
850impl Epoch {
851 #[inline]
854 pub fn try_add_one(self) -> Result<Self, ArithmeticError> {
855 let val = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
856 Ok(Self(val))
857 }
858
859 pub fn try_sub_one(self) -> Result<Self, ArithmeticError> {
862 let val = self.0.checked_sub(1).ok_or(ArithmeticError::Underflow)?;
863 Ok(Self(val))
864 }
865
866 #[inline]
868 pub fn try_add_assign_one(&mut self) -> Result<(), ArithmeticError> {
869 self.0 = self.0.checked_add(1).ok_or(ArithmeticError::Overflow)?;
870 Ok(())
871 }
872}
873
874#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
876pub struct InitialChainConfig {
877 pub ownership: ChainOwnership,
879 pub epoch: Epoch,
881 pub min_active_epoch: Epoch,
883 pub max_active_epoch: Epoch,
885 pub balance: Amount,
887 pub application_permissions: ApplicationPermissions,
889}
890
891#[derive(Eq, PartialEq, Clone, Hash, Debug, Serialize, Deserialize, Allocative)]
893pub struct ChainDescription {
894 origin: ChainOrigin,
895 timestamp: Timestamp,
896 config: InitialChainConfig,
897}
898
899impl ChainDescription {
900 pub fn new(origin: ChainOrigin, config: InitialChainConfig, timestamp: Timestamp) -> Self {
902 Self {
903 origin,
904 config,
905 timestamp,
906 }
907 }
908
909 pub fn id(&self) -> ChainId {
911 ChainId::from(self)
912 }
913
914 pub fn origin(&self) -> ChainOrigin {
916 self.origin
917 }
918
919 pub fn config(&self) -> &InitialChainConfig {
921 &self.config
922 }
923
924 pub fn timestamp(&self) -> Timestamp {
926 self.timestamp
927 }
928
929 pub fn is_child(&self) -> bool {
931 self.origin.is_child()
932 }
933}
934
935impl BcsHashable<'_> for ChainDescription {}
936
937#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
939pub struct NetworkDescription {
940 pub name: String,
942 pub genesis_config_hash: CryptoHash,
944 pub genesis_timestamp: Timestamp,
946 pub genesis_committee_blob_hash: CryptoHash,
948 pub admin_chain_id: ChainId,
950}
951
952#[derive(
954 Default,
955 Debug,
956 PartialEq,
957 Eq,
958 PartialOrd,
959 Ord,
960 Hash,
961 Clone,
962 Serialize,
963 Deserialize,
964 WitType,
965 WitLoad,
966 WitStore,
967 InputObject,
968 Allocative,
969)]
970pub struct ApplicationPermissions {
971 #[debug(skip_if = Option::is_none)]
975 pub execute_operations: Option<Vec<ApplicationId>>,
976 #[graphql(default)]
979 #[debug(skip_if = Vec::is_empty)]
980 pub mandatory_applications: Vec<ApplicationId>,
981 #[graphql(default)]
983 #[debug(skip_if = Vec::is_empty)]
984 pub close_chain: Vec<ApplicationId>,
985 #[graphql(default)]
987 #[debug(skip_if = Vec::is_empty)]
988 pub change_application_permissions: Vec<ApplicationId>,
989 #[graphql(default)]
991 #[debug(skip_if = Option::is_none)]
992 pub call_service_as_oracle: Option<Vec<ApplicationId>>,
993 #[graphql(default)]
995 #[debug(skip_if = Option::is_none)]
996 pub make_http_requests: Option<Vec<ApplicationId>>,
997}
998
999impl ApplicationPermissions {
1000 pub fn new_single(app_id: ApplicationId) -> Self {
1003 Self {
1004 execute_operations: Some(vec![app_id]),
1005 mandatory_applications: vec![app_id],
1006 close_chain: vec![app_id],
1007 change_application_permissions: vec![app_id],
1008 call_service_as_oracle: Some(vec![app_id]),
1009 make_http_requests: Some(vec![app_id]),
1010 }
1011 }
1012
1013 pub fn new_multiple(app_ids: Vec<ApplicationId>) -> Self {
1016 Self {
1017 execute_operations: Some(app_ids.clone()),
1018 mandatory_applications: app_ids.clone(),
1019 close_chain: app_ids.clone(),
1020 change_application_permissions: app_ids.clone(),
1021 call_service_as_oracle: Some(app_ids.clone()),
1022 make_http_requests: Some(app_ids),
1023 }
1024 }
1025
1026 pub fn can_execute_operations(&self, app_id: &GenericApplicationId) -> bool {
1028 match (app_id, &self.execute_operations) {
1029 (_, None) => true,
1030 (GenericApplicationId::System, Some(_)) => false,
1031 (GenericApplicationId::User(app_id), Some(app_ids)) => app_ids.contains(app_id),
1032 }
1033 }
1034
1035 pub fn can_close_chain(&self, app_id: &ApplicationId) -> bool {
1037 self.close_chain.contains(app_id)
1038 }
1039
1040 pub fn can_change_application_permissions(&self, app_id: &ApplicationId) -> bool {
1043 self.change_application_permissions.contains(app_id)
1044 }
1045
1046 pub fn can_call_services(&self, app_id: &ApplicationId) -> bool {
1048 self.call_service_as_oracle
1049 .as_ref()
1050 .is_none_or(|app_ids| app_ids.contains(app_id))
1051 }
1052
1053 pub fn can_make_http_requests(&self, app_id: &ApplicationId) -> bool {
1055 self.make_http_requests
1056 .as_ref()
1057 .is_none_or(|app_ids| app_ids.contains(app_id))
1058 }
1059}
1060
1061#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Allocative)]
1063pub enum OracleResponse {
1064 Service(
1066 #[debug(with = "hex_debug")]
1067 #[serde(with = "serde_bytes")]
1068 Vec<u8>,
1069 ),
1070 Http(http::Response),
1072 Blob(BlobId),
1074 Assert,
1076 Round(Option<u32>),
1078 Event(
1080 EventId,
1081 #[debug(with = "hex_debug")]
1082 #[serde(with = "serde_bytes")]
1083 Vec<u8>,
1084 ),
1085 EventExists(EventId),
1087}
1088
1089impl BcsHashable<'_> for OracleResponse {}
1090
1091#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
1093pub struct ApplicationDescription {
1094 pub module_id: ModuleId,
1096 pub creator_chain_id: ChainId,
1098 pub block_height: BlockHeight,
1100 pub application_index: u32,
1102 #[serde(with = "serde_bytes")]
1104 #[debug(with = "hex_debug")]
1105 pub parameters: Vec<u8>,
1106 pub required_application_ids: Vec<ApplicationId>,
1108}
1109
1110impl From<&ApplicationDescription> for ApplicationId {
1111 fn from(description: &ApplicationDescription) -> Self {
1112 let mut hash = CryptoHash::new(&BlobContent::new_application_description(description));
1113 if matches!(description.module_id.vm_runtime, VmRuntime::Evm) {
1114 hash.make_evm_compatible();
1115 }
1116 ApplicationId::new(hash)
1117 }
1118}
1119
1120impl BcsHashable<'_> for ApplicationDescription {}
1121
1122impl ApplicationDescription {
1123 pub fn to_bytes(&self) -> Vec<u8> {
1125 bcs::to_bytes(self).expect("Serializing blob bytes should not fail!")
1126 }
1127
1128 pub fn contract_bytecode_blob_id(&self) -> BlobId {
1130 self.module_id.contract_bytecode_blob_id()
1131 }
1132
1133 pub fn service_bytecode_blob_id(&self) -> BlobId {
1135 self.module_id.service_bytecode_blob_id()
1136 }
1137}
1138
1139#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, WitType, WitLoad, WitStore)]
1141pub struct Bytecode {
1142 #[serde(with = "serde_bytes")]
1144 #[debug(with = "hex_debug")]
1145 pub bytes: Vec<u8>,
1146}
1147
1148impl Bytecode {
1149 pub fn new(bytes: Vec<u8>) -> Self {
1151 Bytecode { bytes }
1152 }
1153
1154 pub fn load_from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
1156 let bytes = fs::read(path)?;
1157 Ok(Bytecode { bytes })
1158 }
1159
1160 #[cfg(not(target_arch = "wasm32"))]
1162 pub fn compress(&self) -> CompressedBytecode {
1163 #[cfg(with_metrics)]
1164 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1165 let compressed_bytes_vec = zstd::stream::encode_all(&*self.bytes, 19)
1166 .expect("Compressing bytes in memory should not fail");
1167
1168 CompressedBytecode {
1169 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1170 }
1171 }
1172
1173 #[cfg(target_arch = "wasm32")]
1175 pub fn compress(&self) -> CompressedBytecode {
1176 use ruzstd::encoding::{CompressionLevel, FrameCompressor};
1177
1178 #[cfg(with_metrics)]
1179 let _compression_latency = metrics::BYTECODE_COMPRESSION_LATENCY.measure_latency();
1180
1181 let mut compressed_bytes_vec = Vec::new();
1182 let mut compressor = FrameCompressor::new(CompressionLevel::Fastest);
1183 compressor.set_source(&*self.bytes);
1184 compressor.set_drain(&mut compressed_bytes_vec);
1185 compressor.compress();
1186
1187 CompressedBytecode {
1188 compressed_bytes: Arc::new(compressed_bytes_vec.into_boxed_slice()),
1189 }
1190 }
1191}
1192
1193impl AsRef<[u8]> for Bytecode {
1194 fn as_ref(&self) -> &[u8] {
1195 self.bytes.as_ref()
1196 }
1197}
1198
1199#[derive(Error, Debug)]
1201pub enum DecompressionError {
1202 #[error("Bytecode could not be decompressed: {0}")]
1204 InvalidCompressedBytecode(#[from] io::Error),
1205}
1206
1207#[serde_as]
1209#[derive(Clone, Debug, Deserialize, Hash, Serialize, WitType, WitStore)]
1210#[cfg_attr(with_testing, derive(Eq, PartialEq))]
1211pub struct CompressedBytecode {
1212 #[serde_as(as = "Arc<Bytes>")]
1214 #[debug(skip)]
1215 pub compressed_bytes: Arc<Box<[u8]>>,
1216}
1217
1218#[cfg(not(target_arch = "wasm32"))]
1219impl CompressedBytecode {
1220 pub fn decompressed_size_at_most(
1222 compressed_bytes: &[u8],
1223 limit: u64,
1224 ) -> Result<bool, DecompressionError> {
1225 let mut decoder = zstd::stream::Decoder::new(compressed_bytes)?;
1226 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1227 let mut writer = LimitedWriter::new(io::sink(), limit);
1228 match io::copy(&mut decoder, &mut writer) {
1229 Ok(_) => Ok(true),
1230 Err(error) => {
1231 error.downcast::<LimitedWriterError>()?;
1232 Ok(false)
1233 }
1234 }
1235 }
1236
1237 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1239 #[cfg(with_metrics)]
1240 let _decompression_latency = metrics::BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1241 let bytes = zstd::stream::decode_all(&**self.compressed_bytes)?;
1242
1243 Ok(Bytecode { bytes })
1244 }
1245}
1246
1247#[cfg(target_arch = "wasm32")]
1248impl CompressedBytecode {
1249 pub fn decompressed_size_at_most(
1251 compressed_bytes: &[u8],
1252 limit: u64,
1253 ) -> Result<bool, DecompressionError> {
1254 use ruzstd::decoding::StreamingDecoder;
1255 let limit = usize::try_from(limit).unwrap_or(usize::MAX);
1256 let mut writer = LimitedWriter::new(io::sink(), limit);
1257 let mut decoder = StreamingDecoder::new(compressed_bytes).map_err(io::Error::other)?;
1258
1259 match io::copy(&mut decoder, &mut writer) {
1261 Ok(_) => Ok(true),
1262 Err(error) => {
1263 error.downcast::<LimitedWriterError>()?;
1264 Ok(false)
1265 }
1266 }
1267 }
1268
1269 pub fn decompress(&self) -> Result<Bytecode, DecompressionError> {
1271 use ruzstd::{decoding::StreamingDecoder, io::Read};
1272
1273 #[cfg(with_metrics)]
1274 let _decompression_latency = BYTECODE_DECOMPRESSION_LATENCY.measure_latency();
1275
1276 let compressed_bytes = &*self.compressed_bytes;
1277 let mut bytes = Vec::new();
1278 let mut decoder = StreamingDecoder::new(&**compressed_bytes).map_err(io::Error::other)?;
1279
1280 while !decoder.get_ref().is_empty() {
1282 decoder
1283 .read_to_end(&mut bytes)
1284 .expect("Reading from a slice in memory should not result in I/O errors");
1285 }
1286
1287 Ok(Bytecode { bytes })
1288 }
1289}
1290
1291impl BcsHashable<'_> for BlobContent {}
1292
1293#[serde_as]
1295#[derive(Hash, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Allocative)]
1296pub struct BlobContent {
1297 blob_type: BlobType,
1299 #[debug(skip)]
1301 #[serde_as(as = "Arc<Bytes>")]
1302 bytes: Arc<Box<[u8]>>,
1303}
1304
1305impl BlobContent {
1306 pub fn new(blob_type: BlobType, bytes: impl Into<Box<[u8]>>) -> Self {
1308 let bytes = bytes.into();
1309 BlobContent {
1310 blob_type,
1311 bytes: Arc::new(bytes),
1312 }
1313 }
1314
1315 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1317 BlobContent::new(BlobType::Data, bytes)
1318 }
1319
1320 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1322 BlobContent {
1323 blob_type: BlobType::ContractBytecode,
1324 bytes: compressed_bytecode.compressed_bytes,
1325 }
1326 }
1327
1328 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1330 BlobContent {
1331 blob_type: BlobType::EvmBytecode,
1332 bytes: compressed_bytecode.compressed_bytes,
1333 }
1334 }
1335
1336 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1338 BlobContent {
1339 blob_type: BlobType::ServiceBytecode,
1340 bytes: compressed_bytecode.compressed_bytes,
1341 }
1342 }
1343
1344 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1346 let bytes = application_description.to_bytes();
1347 BlobContent::new(BlobType::ApplicationDescription, bytes)
1348 }
1349
1350 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1352 BlobContent::new(BlobType::Committee, committee)
1353 }
1354
1355 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1357 let bytes = bcs::to_bytes(&chain_description)
1358 .expect("Serializing a ChainDescription should not fail!");
1359 BlobContent::new(BlobType::ChainDescription, bytes)
1360 }
1361
1362 pub fn bytes(&self) -> &[u8] {
1364 &self.bytes
1365 }
1366
1367 pub fn into_vec_or_clone(self) -> Vec<u8> {
1369 let bytes = Arc::unwrap_or_clone(self.bytes);
1370 bytes.into_vec()
1371 }
1372
1373 pub fn into_arc_bytes(self) -> Arc<Box<[u8]>> {
1375 self.bytes
1376 }
1377
1378 pub fn blob_type(&self) -> BlobType {
1380 self.blob_type
1381 }
1382}
1383
1384impl From<Blob> for BlobContent {
1385 fn from(blob: Blob) -> BlobContent {
1386 blob.content
1387 }
1388}
1389
1390#[derive(Debug, Hash, PartialEq, Eq, Clone, Allocative)]
1392pub struct Blob {
1393 hash: CryptoHash,
1395 content: BlobContent,
1397}
1398
1399impl Blob {
1400 pub fn new(content: BlobContent) -> Self {
1402 let mut hash = CryptoHash::new(&content);
1403 if matches!(content.blob_type, BlobType::ApplicationDescription) {
1404 let application_description = bcs::from_bytes::<ApplicationDescription>(&content.bytes)
1405 .expect("to obtain an application description");
1406 if matches!(application_description.module_id.vm_runtime, VmRuntime::Evm) {
1407 hash.make_evm_compatible();
1408 }
1409 }
1410 Blob { hash, content }
1411 }
1412
1413 pub fn new_with_hash_unchecked(blob_id: BlobId, content: BlobContent) -> Self {
1415 Blob {
1416 hash: blob_id.hash,
1417 content,
1418 }
1419 }
1420
1421 pub fn new_with_id_unchecked(blob_id: BlobId, bytes: impl Into<Box<[u8]>>) -> Self {
1423 let bytes = bytes.into();
1424 Blob {
1425 hash: blob_id.hash,
1426 content: BlobContent {
1427 blob_type: blob_id.blob_type,
1428 bytes: Arc::new(bytes),
1429 },
1430 }
1431 }
1432
1433 pub fn new_data(bytes: impl Into<Box<[u8]>>) -> Self {
1435 Blob::new(BlobContent::new_data(bytes))
1436 }
1437
1438 pub fn new_contract_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1440 Blob::new(BlobContent::new_contract_bytecode(compressed_bytecode))
1441 }
1442
1443 pub fn new_evm_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1445 Blob::new(BlobContent::new_evm_bytecode(compressed_bytecode))
1446 }
1447
1448 pub fn new_service_bytecode(compressed_bytecode: CompressedBytecode) -> Self {
1450 Blob::new(BlobContent::new_service_bytecode(compressed_bytecode))
1451 }
1452
1453 pub fn new_application_description(application_description: &ApplicationDescription) -> Self {
1455 Blob::new(BlobContent::new_application_description(
1456 application_description,
1457 ))
1458 }
1459
1460 pub fn new_committee(committee: impl Into<Box<[u8]>>) -> Self {
1462 Blob::new(BlobContent::new_committee(committee))
1463 }
1464
1465 pub fn new_chain_description(chain_description: &ChainDescription) -> Self {
1467 Blob::new(BlobContent::new_chain_description(chain_description))
1468 }
1469
1470 pub fn id(&self) -> BlobId {
1472 BlobId {
1473 hash: self.hash,
1474 blob_type: self.content.blob_type,
1475 }
1476 }
1477
1478 pub fn content(&self) -> &BlobContent {
1480 &self.content
1481 }
1482
1483 pub fn into_content(self) -> BlobContent {
1485 self.content
1486 }
1487
1488 pub fn bytes(&self) -> &[u8] {
1490 self.content.bytes()
1491 }
1492
1493 pub fn load_data_blob_from_file(path: impl AsRef<Path>) -> io::Result<Self> {
1495 Ok(Self::new_data(fs::read(path)?))
1496 }
1497
1498 pub fn is_committee_blob(&self) -> bool {
1500 self.content().blob_type().is_committee_blob()
1501 }
1502}
1503
1504impl Serialize for Blob {
1505 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1506 where
1507 S: Serializer,
1508 {
1509 if serializer.is_human_readable() {
1510 let blob_bytes = bcs::to_bytes(&self.content).map_err(serde::ser::Error::custom)?;
1511 serializer.serialize_str(&hex::encode(blob_bytes))
1512 } else {
1513 BlobContent::serialize(self.content(), serializer)
1514 }
1515 }
1516}
1517
1518impl<'a> Deserialize<'a> for Blob {
1519 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1520 where
1521 D: Deserializer<'a>,
1522 {
1523 if deserializer.is_human_readable() {
1524 let s = String::deserialize(deserializer)?;
1525 let content_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1526 let content: BlobContent =
1527 bcs::from_bytes(&content_bytes).map_err(serde::de::Error::custom)?;
1528
1529 Ok(Blob::new(content))
1530 } else {
1531 let content = BlobContent::deserialize(deserializer)?;
1532 Ok(Blob::new(content))
1533 }
1534 }
1535}
1536
1537impl BcsHashable<'_> for Blob {}
1538
1539#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject, Allocative)]
1541pub struct Event {
1542 pub stream_id: StreamId,
1544 pub index: u32,
1546 #[debug(with = "hex_debug")]
1548 #[serde(with = "serde_bytes")]
1549 pub value: Vec<u8>,
1550}
1551
1552impl Event {
1553 pub fn id(&self, chain_id: ChainId) -> EventId {
1555 EventId {
1556 chain_id,
1557 stream_id: self.stream_id.clone(),
1558 index: self.index,
1559 }
1560 }
1561}
1562
1563#[derive(Clone, Debug, Serialize, Deserialize, WitType, WitLoad, WitStore)]
1565pub struct StreamUpdate {
1566 pub chain_id: ChainId,
1568 pub stream_id: StreamId,
1570 pub previous_index: u32,
1572 pub next_index: u32,
1574}
1575
1576impl StreamUpdate {
1577 pub fn new_indices(&self) -> impl Iterator<Item = u32> {
1579 self.previous_index..self.next_index
1580 }
1581}
1582
1583impl BcsHashable<'_> for Event {}
1584
1585doc_scalar!(Bytecode, "A module bytecode (WebAssembly or EVM)");
1586doc_scalar!(Amount, "A non-negative amount of tokens.");
1587doc_scalar!(
1588 Epoch,
1589 "A number identifying the configuration of the chain (aka the committee)"
1590);
1591doc_scalar!(BlockHeight, "A block height to identify blocks in a chain");
1592doc_scalar!(
1593 Timestamp,
1594 "A timestamp, in microseconds since the Unix epoch"
1595);
1596doc_scalar!(TimeDelta, "A duration in microseconds");
1597doc_scalar!(
1598 Round,
1599 "A number to identify successive attempts to decide a value in a consensus protocol."
1600);
1601doc_scalar!(
1602 ChainDescription,
1603 "Initial chain configuration and chain origin."
1604);
1605doc_scalar!(OracleResponse, "A record of a single oracle response.");
1606doc_scalar!(BlobContent, "A blob of binary data.");
1607doc_scalar!(
1608 Blob,
1609 "A blob of binary data, with its content-addressed blob ID."
1610);
1611doc_scalar!(ApplicationDescription, "Description of a user application");
1612
1613#[cfg(with_metrics)]
1614mod metrics {
1615 use std::sync::LazyLock;
1616
1617 use prometheus::HistogramVec;
1618
1619 use crate::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
1620
1621 pub static BYTECODE_COMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1623 register_histogram_vec(
1624 "bytecode_compression_latency",
1625 "Bytecode compression latency",
1626 &[],
1627 exponential_bucket_latencies(10.0),
1628 )
1629 });
1630
1631 pub static BYTECODE_DECOMPRESSION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| {
1633 register_histogram_vec(
1634 "bytecode_decompression_latency",
1635 "Bytecode decompression latency",
1636 &[],
1637 exponential_bucket_latencies(10.0),
1638 )
1639 });
1640}
1641
1642#[cfg(test)]
1643mod tests {
1644 use std::str::FromStr;
1645
1646 use alloy_primitives::U256;
1647
1648 use super::{Amount, BlobContent};
1649 use crate::identifiers::BlobType;
1650
1651 #[test]
1652 fn display_amount() {
1653 assert_eq!("1.", Amount::ONE.to_string());
1654 assert_eq!("1.", Amount::from_str("1.").unwrap().to_string());
1655 assert_eq!(
1656 Amount(10_000_000_000_000_000_000),
1657 Amount::from_str("10").unwrap()
1658 );
1659 assert_eq!("10.", Amount(10_000_000_000_000_000_000).to_string());
1660 assert_eq!(
1661 "1001.3",
1662 (Amount::from_str("1.1")
1663 .unwrap()
1664 .saturating_add(Amount::from_str("1_000.2").unwrap()))
1665 .to_string()
1666 );
1667 assert_eq!(
1668 " 1.00000000000000000000",
1669 format!("{:25.20}", Amount::ONE)
1670 );
1671 assert_eq!(
1672 "~+12.34~~",
1673 format!("{:~^+9.1}", Amount::from_str("12.34").unwrap())
1674 );
1675 }
1676
1677 #[test]
1678 fn blob_content_serialization_deserialization() {
1679 let test_data = b"Hello, world!".as_slice();
1680 let original_blob = BlobContent::new(BlobType::Data, test_data);
1681
1682 let serialized = bcs::to_bytes(&original_blob).expect("Failed to serialize BlobContent");
1683 let deserialized: BlobContent =
1684 bcs::from_bytes(&serialized).expect("Failed to deserialize BlobContent");
1685 assert_eq!(original_blob, deserialized);
1686
1687 let serialized =
1688 serde_json::to_vec(&original_blob).expect("Failed to serialize BlobContent");
1689 let deserialized: BlobContent =
1690 serde_json::from_slice(&serialized).expect("Failed to deserialize BlobContent");
1691 assert_eq!(original_blob, deserialized);
1692 }
1693
1694 #[test]
1695 fn blob_content_hash_consistency() {
1696 let test_data = b"Hello, world!";
1697 let blob1 = BlobContent::new(BlobType::Data, test_data.as_slice());
1698 let blob2 = BlobContent::new(BlobType::Data, Vec::from(test_data.as_slice()));
1699
1700 let hash1 = crate::crypto::CryptoHash::new(&blob1);
1702 let hash2 = crate::crypto::CryptoHash::new(&blob2);
1703
1704 assert_eq!(hash1, hash2, "Hashes should be equal for same content");
1705 assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal");
1706 }
1707
1708 #[test]
1709 fn test_conversion_amount_u256() {
1710 let value_amount = Amount::from_tokens(15656565652209004332);
1711 let value_u256: U256 = value_amount.into();
1712 let value_amount_rev = Amount::try_from(value_u256).expect("Failed conversion");
1713 assert_eq!(value_amount, value_amount_rev);
1714 }
1715}