1use std::{
7 fmt,
8 hash::{Hash, Hasher},
9 marker::PhantomData,
10};
11
12use allocative::Allocative;
13#[cfg(with_revm)]
14use alloy_primitives::{Address, B256};
15use anyhow::{anyhow, Context};
16use async_graphql::{InputObject, SimpleObject};
17use custom_debug_derive::Debug;
18use derive_more::{Display, FromStr};
19use linera_witty::{WitLoad, WitStore, WitType};
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21
22use crate::{
23 bcs_scalar,
24 crypto::{
25 AccountPublicKey, CryptoError, CryptoHash, Ed25519PublicKey, EvmPublicKey,
26 Secp256k1PublicKey,
27 },
28 data_types::{BlobContent, ChainDescription},
29 doc_scalar, hex_debug,
30 vm::VmRuntime,
31};
32
33#[derive(
35 Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, WitLoad, WitStore, WitType, Allocative,
36)]
37#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
38#[cfg_attr(
40 web,
41 derive(tsify::Tsify),
42 tsify(from_wasm_abi, into_wasm_abi, type = "string")
43)]
44pub enum AccountOwner {
45 Reserved(u8),
47 Address32(CryptoHash),
49 Address20([u8; 20]),
51}
52
53impl fmt::Debug for AccountOwner {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 Self::Reserved(byte) => f.debug_tuple("Reserved").field(byte).finish(),
57 Self::Address32(hash) => write!(f, "Address32({hash:?})"),
58 Self::Address20(bytes) => write!(f, "Address20({})", hex::encode(bytes)),
59 }
60 }
61}
62
63impl AccountOwner {
64 pub const CHAIN: AccountOwner = AccountOwner::Reserved(0);
66
67 pub fn is_chain(&self) -> bool {
69 self == &AccountOwner::CHAIN
70 }
71
72 pub fn size(&self) -> u32 {
74 match self {
75 AccountOwner::Reserved(_) => 1,
76 AccountOwner::Address32(_) => 32,
77 AccountOwner::Address20(_) => 20,
78 }
79 }
80
81 #[cfg(with_revm)]
83 pub fn to_evm_address(&self) -> Option<Address> {
84 match self {
85 AccountOwner::Address20(address) => Some(Address::from(address)),
86 _ => None,
87 }
88 }
89}
90
91#[cfg(with_revm)]
92impl From<Address> for AccountOwner {
93 fn from(address: Address) -> Self {
94 let address = address.into_array();
95 AccountOwner::Address20(address)
96 }
97}
98
99impl From<[u8; 32]> for AccountOwner {
100 fn from(bytes: [u8; 32]) -> Self {
105 if bytes[..12].iter().all(|&b| b == 0) {
106 let mut addr = [0u8; 20];
107 addr.copy_from_slice(&bytes[12..]);
108 AccountOwner::Address20(addr)
109 } else {
110 AccountOwner::Address32(CryptoHash::from(bytes))
111 }
112 }
113}
114
115#[cfg(with_testing)]
116impl From<CryptoHash> for AccountOwner {
117 fn from(address: CryptoHash) -> Self {
118 AccountOwner::Address32(address)
119 }
120}
121
122#[derive(
124 Debug,
125 PartialEq,
126 Eq,
127 PartialOrd,
128 Ord,
129 Hash,
130 Copy,
131 Clone,
132 Serialize,
133 Deserialize,
134 WitLoad,
135 WitStore,
136 WitType,
137 SimpleObject,
138 InputObject,
139 Allocative,
140)]
141#[graphql(name = "AccountOutput", input_name = "Account")]
142#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
143pub struct Account {
144 pub chain_id: ChainId,
146 pub owner: AccountOwner,
148}
149
150impl Account {
151 pub fn new(chain_id: ChainId, owner: AccountOwner) -> Self {
153 Self { chain_id, owner }
154 }
155
156 pub fn chain(chain_id: ChainId) -> Self {
158 Account {
159 chain_id,
160 owner: AccountOwner::CHAIN,
161 }
162 }
163
164 #[cfg(with_testing)]
166 pub fn burn_address(chain_id: ChainId) -> Self {
167 let hash = CryptoHash::test_hash("burn");
168 Account {
169 chain_id,
170 owner: hash.into(),
171 }
172 }
173}
174
175impl fmt::Display for Account {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 write!(f, "{}@{}", self.owner, self.chain_id)
178 }
179}
180
181impl std::str::FromStr for Account {
182 type Err = anyhow::Error;
183
184 fn from_str(string: &str) -> Result<Self, Self::Err> {
185 if let Some((owner_string, chain_string)) = string.rsplit_once('@') {
186 let owner = owner_string.parse::<AccountOwner>()?;
187 let chain_id = chain_string.parse()?;
188 Ok(Account::new(chain_id, owner))
189 } else {
190 let chain_id = string
191 .parse()
192 .context("Expecting an account formatted as `chain-id` or `owner@chain-id`")?;
193 Ok(Account::chain(chain_id))
194 }
195 }
196}
197
198#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Allocative)]
200pub struct OwnerSpender {
201 pub owner: AccountOwner,
203 pub spender: AccountOwner,
205}
206
207impl OwnerSpender {
208 pub fn new(owner: AccountOwner, spender: AccountOwner) -> Self {
211 if owner == spender {
212 panic!("owner should be different from spender");
213 }
214 Self { owner, spender }
215 }
216}
217
218#[derive(
221 Eq,
222 PartialEq,
223 Ord,
224 PartialOrd,
225 Copy,
226 Clone,
227 Hash,
228 Serialize,
229 Deserialize,
230 WitLoad,
231 WitStore,
232 WitType,
233 Allocative,
234)]
235#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
236#[cfg_attr(with_testing, derive(Default))]
237#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
238pub struct ChainId(pub CryptoHash);
239
240#[derive(
243 Eq,
244 PartialEq,
245 Ord,
246 PartialOrd,
247 Clone,
248 Copy,
249 Hash,
250 Debug,
251 Serialize,
252 Deserialize,
253 WitType,
254 WitStore,
255 WitLoad,
256 Default,
257 Allocative,
258)]
259#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
260pub enum BlobType {
261 #[default]
263 Data,
264 ContractBytecode,
266 ServiceBytecode,
268 EvmBytecode,
270 ApplicationDescription,
272 Committee,
274 ChainDescription,
276 ApplicationFormats,
279 CheckpointExecutionState,
284}
285
286impl BlobType {
287 pub fn is_committee_blob(&self) -> bool {
289 matches!(self, BlobType::Committee)
290 }
291
292 pub fn is_checkpoint_blob(&self) -> bool {
296 matches!(self, BlobType::CheckpointExecutionState)
297 }
298}
299
300impl fmt::Display for BlobType {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 write!(f, "{self:?}")
303 }
304}
305
306impl std::str::FromStr for BlobType {
307 type Err = anyhow::Error;
308
309 fn from_str(s: &str) -> Result<Self, Self::Err> {
310 serde_json::from_str(&format!("\"{s}\"")).with_context(|| format!("Invalid BlobType: {s}"))
311 }
312}
313
314#[derive(
316 Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, WitType, WitStore, WitLoad, Allocative,
317)]
318#[cfg_attr(with_testing, derive(test_strategy::Arbitrary, Default))]
319pub struct BlobId {
320 pub blob_type: BlobType,
322 pub hash: CryptoHash,
324}
325
326impl BlobId {
327 pub fn new(hash: CryptoHash, blob_type: BlobType) -> Self {
329 Self { hash, blob_type }
330 }
331}
332
333impl fmt::Display for BlobId {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 write!(f, "{}:{}", self.blob_type, self.hash)?;
336 Ok(())
337 }
338}
339
340impl std::str::FromStr for BlobId {
341 type Err = anyhow::Error;
342
343 fn from_str(s: &str) -> Result<Self, Self::Err> {
344 let parts = s.split(':').collect::<Vec<_>>();
345 if parts.len() == 2 {
346 let blob_type = BlobType::from_str(parts[0]).context("Invalid BlobType!")?;
347 Ok(BlobId {
348 hash: CryptoHash::from_str(parts[1]).context("Invalid hash!")?,
349 blob_type,
350 })
351 } else {
352 Err(anyhow!("Invalid blob ID: {s}"))
353 }
354 }
355}
356
357#[derive(Serialize, Deserialize)]
358#[serde(rename = "BlobId")]
359struct BlobIdHelper {
360 hash: CryptoHash,
361 blob_type: BlobType,
362}
363
364impl Serialize for BlobId {
365 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
366 where
367 S: Serializer,
368 {
369 if serializer.is_human_readable() {
370 serializer.serialize_str(&self.to_string())
371 } else {
372 let helper = BlobIdHelper {
373 hash: self.hash,
374 blob_type: self.blob_type,
375 };
376 helper.serialize(serializer)
377 }
378 }
379}
380
381impl<'a> Deserialize<'a> for BlobId {
382 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
383 where
384 D: Deserializer<'a>,
385 {
386 if deserializer.is_human_readable() {
387 let s = String::deserialize(deserializer)?;
388 Self::from_str(&s).map_err(serde::de::Error::custom)
389 } else {
390 let helper = BlobIdHelper::deserialize(deserializer)?;
391 Ok(BlobId::new(helper.hash, helper.blob_type))
392 }
393 }
394}
395
396#[derive(
398 Eq, Hash, PartialEq, Debug, Serialize, Deserialize, Clone, Copy, WitType, WitLoad, WitStore,
399)]
400pub struct DataBlobHash(pub CryptoHash);
401
402impl From<DataBlobHash> for BlobId {
403 fn from(hash: DataBlobHash) -> BlobId {
404 BlobId::new(hash.0, BlobType::Data)
405 }
406}
407
408#[cfg_attr(web, wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
410const _: &str = "export type ApplicationId = string;";
411
412#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
414#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
415#[allocative(bound = "A")]
416pub struct ApplicationId<A = ()> {
417 pub application_description_hash: CryptoHash,
419 #[witty(skip)]
420 #[debug(skip)]
421 #[allocative(skip)]
422 phantom: PhantomData<A>,
423}
424
425#[derive(
427 Eq,
428 PartialEq,
429 Ord,
430 PartialOrd,
431 Copy,
432 Clone,
433 Hash,
434 Debug,
435 Serialize,
436 Deserialize,
437 WitLoad,
438 WitStore,
439 WitType,
440 Allocative,
441)]
442#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
443pub enum GenericApplicationId {
444 System,
446 User(ApplicationId),
448}
449
450impl fmt::Display for GenericApplicationId {
451 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452 match self {
453 GenericApplicationId::System => Display::fmt("System", f),
454 GenericApplicationId::User(application_id) => {
455 Display::fmt("User:", f)?;
456 Display::fmt(&application_id, f)
457 }
458 }
459 }
460}
461
462impl std::str::FromStr for GenericApplicationId {
463 type Err = anyhow::Error;
464
465 fn from_str(s: &str) -> Result<Self, Self::Err> {
466 if s == "System" {
467 return Ok(GenericApplicationId::System);
468 }
469 if let Some(result) = s.strip_prefix("User:") {
470 let application_id = ApplicationId::from_str(result)?;
471 return Ok(GenericApplicationId::User(application_id));
472 }
473 Err(anyhow!("Invalid parsing of GenericApplicationId"))
474 }
475}
476
477impl<A> From<ApplicationId<A>> for AccountOwner {
478 fn from(app_id: ApplicationId<A>) -> Self {
479 if app_id.is_evm() {
480 let hash_bytes = app_id.application_description_hash.as_bytes();
481 AccountOwner::Address20(hash_bytes[..20].try_into().unwrap())
482 } else {
483 AccountOwner::Address32(app_id.application_description_hash)
484 }
485 }
486}
487
488impl From<AccountPublicKey> for AccountOwner {
489 fn from(public_key: AccountPublicKey) -> Self {
490 match public_key {
491 AccountPublicKey::Ed25519(public_key) => public_key.into(),
492 AccountPublicKey::Secp256k1(public_key) => public_key.into(),
493 AccountPublicKey::EvmSecp256k1(public_key) => public_key.into(),
494 }
495 }
496}
497
498impl From<ApplicationId> for GenericApplicationId {
499 fn from(application_id: ApplicationId) -> Self {
500 GenericApplicationId::User(application_id)
501 }
502}
503
504impl From<Secp256k1PublicKey> for AccountOwner {
505 fn from(public_key: Secp256k1PublicKey) -> Self {
506 AccountOwner::Address32(CryptoHash::new(&public_key))
507 }
508}
509
510impl From<Ed25519PublicKey> for AccountOwner {
511 fn from(public_key: Ed25519PublicKey) -> Self {
512 AccountOwner::Address32(CryptoHash::new(&public_key))
513 }
514}
515
516impl From<EvmPublicKey> for AccountOwner {
517 fn from(public_key: EvmPublicKey) -> Self {
518 AccountOwner::Address20(alloy_primitives::Address::from_public_key(&public_key.0).into())
519 }
520}
521
522#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
524#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
525pub struct ModuleId<Abi = (), Parameters = (), InstantiationArgument = ()> {
526 pub contract_blob_hash: CryptoHash,
528 pub service_blob_hash: CryptoHash,
530 pub vm_runtime: VmRuntime,
532 pub formats_blob_hash: Option<CryptoHash>,
536 #[witty(skip)]
537 #[debug(skip)]
538 phantom: PhantomData<(Abi, Parameters, InstantiationArgument)>,
539}
540
541#[derive(
543 Clone,
544 Debug,
545 Eq,
546 Hash,
547 Ord,
548 PartialEq,
549 PartialOrd,
550 Serialize,
551 Deserialize,
552 WitLoad,
553 WitStore,
554 WitType,
555 Allocative,
556)]
557pub struct StreamName(
558 #[serde(with = "serde_bytes")]
559 #[debug(with = "hex_debug")]
560 pub Vec<u8>,
561);
562
563impl<T> From<T> for StreamName
564where
565 T: Into<Vec<u8>>,
566{
567 fn from(name: T) -> Self {
568 StreamName(name.into())
569 }
570}
571
572impl fmt::Display for StreamName {
573 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574 Display::fmt(&hex::encode(&self.0), f)
575 }
576}
577
578impl std::str::FromStr for StreamName {
579 type Err = anyhow::Error;
580
581 fn from_str(s: &str) -> Result<Self, Self::Err> {
582 let vec = hex::decode(s)?;
583 Ok(StreamName(vec))
584 }
585}
586
587#[derive(
589 Clone,
590 Debug,
591 Eq,
592 Hash,
593 Ord,
594 PartialEq,
595 PartialOrd,
596 WitLoad,
597 WitStore,
598 WitType,
599 SimpleObject,
600 InputObject,
601 Allocative,
602)]
603#[graphql(input_name = "StreamIdInput")]
604pub struct StreamId {
605 pub application_id: GenericApplicationId,
607 pub stream_name: StreamName,
609}
610
611impl serde::Serialize for StreamId {
612 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
613 where
614 S: serde::ser::Serializer,
615 {
616 if serializer.is_human_readable() {
617 serializer.serialize_str(&self.to_string())
618 } else {
619 use serde::ser::SerializeStruct;
620 let mut state = serializer.serialize_struct("StreamId", 2)?;
621 state.serialize_field("application_id", &self.application_id)?;
622 state.serialize_field("stream_name", &self.stream_name)?;
623 state.end()
624 }
625 }
626}
627
628impl<'de> serde::Deserialize<'de> for StreamId {
629 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
630 where
631 D: serde::de::Deserializer<'de>,
632 {
633 if deserializer.is_human_readable() {
634 let s = String::deserialize(deserializer)?;
635 Self::from_str(&s).map_err(serde::de::Error::custom)
636 } else {
637 #[derive(serde::Deserialize)]
638 #[serde(rename = "StreamId")]
639 struct StreamIdHelper {
640 application_id: GenericApplicationId,
641 stream_name: StreamName,
642 }
643 let helper = StreamIdHelper::deserialize(deserializer)?;
644 Ok(StreamId {
645 application_id: helper.application_id,
646 stream_name: helper.stream_name,
647 })
648 }
649 }
650}
651
652impl StreamId {
653 pub fn system(name: impl Into<StreamName>) -> Self {
655 StreamId {
656 application_id: GenericApplicationId::System,
657 stream_name: name.into(),
658 }
659 }
660}
661
662#[derive(
664 Debug,
665 Eq,
666 PartialEq,
667 Ord,
668 PartialOrd,
669 Clone,
670 Hash,
671 Serialize,
672 Deserialize,
673 WitLoad,
674 WitStore,
675 WitType,
676 SimpleObject,
677)]
678pub struct IndexAndEvent {
679 pub index: u32,
681 pub event: Vec<u8>,
683}
684
685impl fmt::Display for StreamId {
686 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687 Display::fmt(&self.application_id, f)?;
688 Display::fmt(":", f)?;
689 Display::fmt(&self.stream_name, f)
690 }
691}
692
693impl std::str::FromStr for StreamId {
694 type Err = anyhow::Error;
695
696 fn from_str(s: &str) -> Result<Self, Self::Err> {
697 let parts = s.rsplit_once(":");
698 if let Some((part0, part1)) = parts {
699 let application_id =
700 GenericApplicationId::from_str(part0).context("Invalid GenericApplicationId!")?;
701 let stream_name = StreamName::from_str(part1).context("Invalid StreamName!")?;
702 Ok(StreamId {
703 application_id,
704 stream_name,
705 })
706 } else {
707 Err(anyhow!("Invalid blob ID: {s}"))
708 }
709 }
710}
711
712#[derive(
714 Debug,
715 PartialEq,
716 Eq,
717 Hash,
718 Clone,
719 Serialize,
720 Deserialize,
721 WitLoad,
722 WitStore,
723 WitType,
724 SimpleObject,
725 Allocative,
726)]
727pub struct EventId {
728 pub chain_id: ChainId,
730 pub stream_id: StreamId,
732 pub index: u32,
734}
735
736impl fmt::Display for EventId {
737 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738 write!(f, "{}:{}:{}", self.chain_id, self.stream_id, self.index)
739 }
740}
741
742impl StreamName {
743 pub fn into_bytes(self) -> Vec<u8> {
745 self.0
746 }
747}
748
749impl<Abi, Parameters, InstantiationArgument> Clone
751 for ModuleId<Abi, Parameters, InstantiationArgument>
752{
753 fn clone(&self) -> Self {
754 *self
755 }
756}
757
758impl<Abi, Parameters, InstantiationArgument> Copy
759 for ModuleId<Abi, Parameters, InstantiationArgument>
760{
761}
762
763impl<Abi, Parameters, InstantiationArgument> PartialEq
764 for ModuleId<Abi, Parameters, InstantiationArgument>
765{
766 fn eq(&self, other: &Self) -> bool {
767 let ModuleId {
768 contract_blob_hash,
769 service_blob_hash,
770 vm_runtime,
771 formats_blob_hash,
772 phantom: _,
773 } = other;
774 self.contract_blob_hash == *contract_blob_hash
775 && self.service_blob_hash == *service_blob_hash
776 && self.vm_runtime == *vm_runtime
777 && self.formats_blob_hash == *formats_blob_hash
778 }
779}
780
781impl<Abi, Parameters, InstantiationArgument> Eq
782 for ModuleId<Abi, Parameters, InstantiationArgument>
783{
784}
785
786impl<Abi, Parameters, InstantiationArgument> PartialOrd
787 for ModuleId<Abi, Parameters, InstantiationArgument>
788{
789 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
790 Some(self.cmp(other))
791 }
792}
793
794impl<Abi, Parameters, InstantiationArgument> Ord
795 for ModuleId<Abi, Parameters, InstantiationArgument>
796{
797 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
798 let ModuleId {
799 contract_blob_hash,
800 service_blob_hash,
801 vm_runtime,
802 formats_blob_hash,
803 phantom: _,
804 } = other;
805 (
806 self.contract_blob_hash,
807 self.service_blob_hash,
808 self.vm_runtime,
809 self.formats_blob_hash,
810 )
811 .cmp(&(
812 *contract_blob_hash,
813 *service_blob_hash,
814 *vm_runtime,
815 *formats_blob_hash,
816 ))
817 }
818}
819
820impl<Abi, Parameters, InstantiationArgument> Hash
821 for ModuleId<Abi, Parameters, InstantiationArgument>
822{
823 fn hash<H: Hasher>(&self, state: &mut H) {
824 let ModuleId {
825 contract_blob_hash: contract_blob_id,
826 service_blob_hash: service_blob_id,
827 vm_runtime: vm_runtime_id,
828 formats_blob_hash,
829 phantom: _,
830 } = self;
831 contract_blob_id.hash(state);
832 service_blob_id.hash(state);
833 vm_runtime_id.hash(state);
834 formats_blob_hash.hash(state);
835 }
836}
837
838#[derive(Serialize, Deserialize)]
839#[serde(rename = "ModuleId")]
840struct SerializableModuleId {
841 contract_blob_hash: CryptoHash,
842 service_blob_hash: CryptoHash,
843 vm_runtime: VmRuntime,
844 #[serde(default)]
845 formats_blob_hash: Option<CryptoHash>,
846}
847
848impl<Abi, Parameters, InstantiationArgument> Serialize
849 for ModuleId<Abi, Parameters, InstantiationArgument>
850{
851 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
852 where
853 S: serde::ser::Serializer,
854 {
855 let serializable_module_id = SerializableModuleId {
856 contract_blob_hash: self.contract_blob_hash,
857 service_blob_hash: self.service_blob_hash,
858 vm_runtime: self.vm_runtime,
859 formats_blob_hash: self.formats_blob_hash,
860 };
861 if serializer.is_human_readable() {
862 let bytes =
863 bcs::to_bytes(&serializable_module_id).map_err(serde::ser::Error::custom)?;
864 serializer.serialize_str(&hex::encode(bytes))
865 } else {
866 SerializableModuleId::serialize(&serializable_module_id, serializer)
867 }
868 }
869}
870
871impl<'de, Abi, Parameters, InstantiationArgument> Deserialize<'de>
872 for ModuleId<Abi, Parameters, InstantiationArgument>
873{
874 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
875 where
876 D: serde::de::Deserializer<'de>,
877 {
878 if deserializer.is_human_readable() {
879 let s = String::deserialize(deserializer)?;
880 let module_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
881 let serializable_module_id: SerializableModuleId =
882 bcs::from_bytes(&module_id_bytes).map_err(serde::de::Error::custom)?;
883 Ok(ModuleId {
884 contract_blob_hash: serializable_module_id.contract_blob_hash,
885 service_blob_hash: serializable_module_id.service_blob_hash,
886 vm_runtime: serializable_module_id.vm_runtime,
887 formats_blob_hash: serializable_module_id.formats_blob_hash,
888 phantom: PhantomData,
889 })
890 } else {
891 let serializable_module_id = SerializableModuleId::deserialize(deserializer)?;
892 Ok(ModuleId {
893 contract_blob_hash: serializable_module_id.contract_blob_hash,
894 service_blob_hash: serializable_module_id.service_blob_hash,
895 vm_runtime: serializable_module_id.vm_runtime,
896 formats_blob_hash: serializable_module_id.formats_blob_hash,
897 phantom: PhantomData,
898 })
899 }
900 }
901}
902
903impl ModuleId {
904 pub fn new(
906 contract_blob_hash: CryptoHash,
907 service_blob_hash: CryptoHash,
908 vm_runtime: VmRuntime,
909 ) -> Self {
910 ModuleId {
911 contract_blob_hash,
912 service_blob_hash,
913 vm_runtime,
914 formats_blob_hash: None,
915 phantom: PhantomData,
916 }
917 }
918
919 pub fn new_with_formats(
922 contract_blob_hash: CryptoHash,
923 service_blob_hash: CryptoHash,
924 vm_runtime: VmRuntime,
925 formats_blob_hash: Option<CryptoHash>,
926 ) -> Self {
927 ModuleId {
928 contract_blob_hash,
929 service_blob_hash,
930 vm_runtime,
931 formats_blob_hash,
932 phantom: PhantomData,
933 }
934 }
935
936 pub fn with_abi<Abi, Parameters, InstantiationArgument>(
938 self,
939 ) -> ModuleId<Abi, Parameters, InstantiationArgument> {
940 ModuleId {
941 contract_blob_hash: self.contract_blob_hash,
942 service_blob_hash: self.service_blob_hash,
943 vm_runtime: self.vm_runtime,
944 formats_blob_hash: self.formats_blob_hash,
945 phantom: PhantomData,
946 }
947 }
948
949 pub fn contract_bytecode_blob_id(&self) -> BlobId {
951 match self.vm_runtime {
952 VmRuntime::Wasm => BlobId::new(self.contract_blob_hash, BlobType::ContractBytecode),
953 VmRuntime::Evm => BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode),
954 }
955 }
956
957 pub fn service_bytecode_blob_id(&self) -> BlobId {
959 match self.vm_runtime {
960 VmRuntime::Wasm => BlobId::new(self.service_blob_hash, BlobType::ServiceBytecode),
961 VmRuntime::Evm => BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode),
962 }
963 }
964
965 pub fn formats_blob_id(&self) -> Option<BlobId> {
968 self.formats_blob_hash
969 .map(|hash| BlobId::new(hash, BlobType::ApplicationFormats))
970 }
971
972 pub fn bytecode_blob_ids(&self) -> Vec<BlobId> {
975 let mut blobs = match self.vm_runtime {
976 VmRuntime::Wasm => vec![
977 BlobId::new(self.contract_blob_hash, BlobType::ContractBytecode),
978 BlobId::new(self.service_blob_hash, BlobType::ServiceBytecode),
979 ],
980 VmRuntime::Evm => vec![BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode)],
981 };
982 if let Some(blob_id) = self.formats_blob_id() {
983 blobs.push(blob_id);
984 }
985 blobs
986 }
987}
988
989impl<Abi, Parameters, InstantiationArgument> ModuleId<Abi, Parameters, InstantiationArgument> {
990 pub fn forget_abi(self) -> ModuleId {
992 ModuleId {
993 contract_blob_hash: self.contract_blob_hash,
994 service_blob_hash: self.service_blob_hash,
995 vm_runtime: self.vm_runtime,
996 formats_blob_hash: self.formats_blob_hash,
997 phantom: PhantomData,
998 }
999 }
1000}
1001
1002impl<A> Clone for ApplicationId<A> {
1004 fn clone(&self) -> Self {
1005 *self
1006 }
1007}
1008
1009impl<A> Copy for ApplicationId<A> {}
1010
1011impl<A: PartialEq> PartialEq for ApplicationId<A> {
1012 fn eq(&self, other: &Self) -> bool {
1013 self.application_description_hash == other.application_description_hash
1014 }
1015}
1016
1017impl<A: Eq> Eq for ApplicationId<A> {}
1018
1019impl<A: PartialOrd> PartialOrd for ApplicationId<A> {
1020 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1021 self.application_description_hash
1022 .partial_cmp(&other.application_description_hash)
1023 }
1024}
1025
1026impl<A: Ord> Ord for ApplicationId<A> {
1027 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1028 self.application_description_hash
1029 .cmp(&other.application_description_hash)
1030 }
1031}
1032
1033impl<A> Hash for ApplicationId<A> {
1034 fn hash<H: Hasher>(&self, state: &mut H) {
1035 self.application_description_hash.hash(state);
1036 }
1037}
1038
1039#[derive(Serialize, Deserialize)]
1040#[serde(rename = "ApplicationId")]
1041struct SerializableApplicationId {
1042 pub application_description_hash: CryptoHash,
1043}
1044
1045impl<A> Serialize for ApplicationId<A> {
1046 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1047 where
1048 S: serde::ser::Serializer,
1049 {
1050 if serializer.is_human_readable() {
1051 let bytes = bcs::to_bytes(&SerializableApplicationId {
1052 application_description_hash: self.application_description_hash,
1053 })
1054 .map_err(serde::ser::Error::custom)?;
1055 serializer.serialize_str(&hex::encode(bytes))
1056 } else {
1057 SerializableApplicationId::serialize(
1058 &SerializableApplicationId {
1059 application_description_hash: self.application_description_hash,
1060 },
1061 serializer,
1062 )
1063 }
1064 }
1065}
1066
1067impl<'de, A> Deserialize<'de> for ApplicationId<A> {
1068 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1069 where
1070 D: serde::de::Deserializer<'de>,
1071 {
1072 if deserializer.is_human_readable() {
1073 let s = String::deserialize(deserializer)?;
1074 let application_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1075 let application_id: SerializableApplicationId =
1076 bcs::from_bytes(&application_id_bytes).map_err(serde::de::Error::custom)?;
1077 Ok(ApplicationId {
1078 application_description_hash: application_id.application_description_hash,
1079 phantom: PhantomData,
1080 })
1081 } else {
1082 let value = SerializableApplicationId::deserialize(deserializer)?;
1083 Ok(ApplicationId {
1084 application_description_hash: value.application_description_hash,
1085 phantom: PhantomData,
1086 })
1087 }
1088 }
1089}
1090
1091impl ApplicationId {
1092 pub fn new(application_description_hash: CryptoHash) -> Self {
1094 ApplicationId {
1095 application_description_hash,
1096 phantom: PhantomData,
1097 }
1098 }
1099
1100 pub fn description_blob_id(self) -> BlobId {
1103 BlobId::new(
1104 self.application_description_hash,
1105 BlobType::ApplicationDescription,
1106 )
1107 }
1108
1109 pub fn with_abi<A>(self) -> ApplicationId<A> {
1111 ApplicationId {
1112 application_description_hash: self.application_description_hash,
1113 phantom: PhantomData,
1114 }
1115 }
1116}
1117
1118impl<A> ApplicationId<A> {
1119 pub fn forget_abi(self) -> ApplicationId {
1121 ApplicationId {
1122 application_description_hash: self.application_description_hash,
1123 phantom: PhantomData,
1124 }
1125 }
1126}
1127
1128impl<A> ApplicationId<A> {
1129 pub fn is_evm(&self) -> bool {
1131 let bytes = self.application_description_hash.as_bytes();
1132 bytes.0[20..] == [0; 12]
1133 }
1134}
1135
1136#[cfg(with_revm)]
1137impl From<Address> for ApplicationId {
1138 fn from(address: Address) -> ApplicationId {
1139 let mut arr = [0_u8; 32];
1140 arr[..20].copy_from_slice(address.as_slice());
1141 ApplicationId {
1142 application_description_hash: arr.into(),
1143 phantom: PhantomData,
1144 }
1145 }
1146}
1147
1148#[cfg(with_revm)]
1149impl<A> ApplicationId<A> {
1150 pub fn evm_address(&self) -> Address {
1152 let bytes = self.application_description_hash.as_bytes();
1153 let bytes = bytes.0.as_ref();
1154 Address::from_slice(&bytes[0..20])
1155 }
1156
1157 pub fn bytes32(&self) -> B256 {
1159 *self.application_description_hash.as_bytes()
1160 }
1161}
1162
1163#[derive(Serialize, Deserialize)]
1164#[serde(rename = "AccountOwner")]
1165enum SerializableAccountOwner {
1166 Reserved(u8),
1167 Address32(CryptoHash),
1168 Address20([u8; 20]),
1169}
1170
1171impl Serialize for AccountOwner {
1172 fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1173 if serializer.is_human_readable() {
1174 serializer.serialize_str(&self.to_string())
1175 } else {
1176 match self {
1177 AccountOwner::Reserved(value) => SerializableAccountOwner::Reserved(*value),
1178 AccountOwner::Address32(value) => SerializableAccountOwner::Address32(*value),
1179 AccountOwner::Address20(value) => SerializableAccountOwner::Address20(*value),
1180 }
1181 .serialize(serializer)
1182 }
1183 }
1184}
1185
1186impl<'de> Deserialize<'de> for AccountOwner {
1187 fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1188 if deserializer.is_human_readable() {
1189 let s = String::deserialize(deserializer)?;
1190 let value = Self::from_str(&s).map_err(serde::de::Error::custom)?;
1191 Ok(value)
1192 } else {
1193 let value = SerializableAccountOwner::deserialize(deserializer)?;
1194 match value {
1195 SerializableAccountOwner::Reserved(value) => Ok(AccountOwner::Reserved(value)),
1196 SerializableAccountOwner::Address32(value) => Ok(AccountOwner::Address32(value)),
1197 SerializableAccountOwner::Address20(value) => Ok(AccountOwner::Address20(value)),
1198 }
1199 }
1200 }
1201}
1202
1203impl fmt::Display for AccountOwner {
1204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1205 match self {
1206 AccountOwner::Reserved(value) => {
1207 write!(f, "0x{}", hex::encode(&value.to_be_bytes()[..]))?
1208 }
1209 AccountOwner::Address32(value) => write!(f, "0x{value}")?,
1210 AccountOwner::Address20(value) => write!(f, "0x{}", hex::encode(&value[..]))?,
1211 };
1212
1213 Ok(())
1214 }
1215}
1216
1217impl std::str::FromStr for AccountOwner {
1218 type Err = anyhow::Error;
1219
1220 fn from_str(s: &str) -> Result<Self, Self::Err> {
1221 if let Some(s) = s.strip_prefix("0x") {
1222 if s.len() == 64 {
1223 if let Ok(hash) = CryptoHash::from_str(s) {
1224 return Ok(AccountOwner::Address32(hash));
1225 }
1226 } else if s.len() == 40 {
1227 let address = hex::decode(s)?;
1228 if address.len() != 20 {
1229 anyhow::bail!("Invalid address length: {s}");
1230 }
1231 let address = <[u8; 20]>::try_from(address.as_slice()).unwrap();
1232 return Ok(AccountOwner::Address20(address));
1233 }
1234 if s.len() == 2 {
1235 let bytes = hex::decode(s)?;
1236 if bytes.len() == 1 {
1237 let value = u8::from_be_bytes(bytes.try_into().expect("one byte"));
1238 return Ok(AccountOwner::Reserved(value));
1239 }
1240 }
1241 }
1242 anyhow::bail!("Invalid address value: {s}");
1243 }
1244}
1245
1246impl fmt::Display for ChainId {
1247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1248 Display::fmt(&self.0, f)
1249 }
1250}
1251
1252impl std::str::FromStr for ChainId {
1253 type Err = CryptoError;
1254
1255 fn from_str(s: &str) -> Result<Self, Self::Err> {
1256 Ok(ChainId(CryptoHash::from_str(s)?))
1257 }
1258}
1259
1260impl TryFrom<&[u8]> for ChainId {
1261 type Error = CryptoError;
1262
1263 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
1264 Ok(ChainId(CryptoHash::try_from(value)?))
1265 }
1266}
1267
1268impl fmt::Debug for ChainId {
1269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1270 write!(f, "{:?}", self.0)
1271 }
1272}
1273
1274impl<'a> From<&'a ChainDescription> for ChainId {
1275 fn from(description: &'a ChainDescription) -> Self {
1276 Self(CryptoHash::new(&BlobContent::new_chain_description(
1277 description,
1278 )))
1279 }
1280}
1281
1282impl From<ChainDescription> for ChainId {
1283 fn from(description: ChainDescription) -> Self {
1284 From::from(&description)
1285 }
1286}
1287
1288bcs_scalar!(ApplicationId, "A unique identifier for a user application");
1289doc_scalar!(DataBlobHash, "Hash of a Data Blob");
1290doc_scalar!(
1291 GenericApplicationId,
1292 "A unique identifier for a user application or for the system application"
1293);
1294bcs_scalar!(ModuleId, "A unique identifier for an application module");
1295doc_scalar!(
1296 ChainId,
1297 "The unique identifier (UID) of a chain. This is currently computed as the hash value of a \
1298 ChainDescription."
1299);
1300doc_scalar!(StreamName, "The name of an event stream");
1301
1302doc_scalar!(
1303 AccountOwner,
1304 "A unique identifier for a user or an application."
1305);
1306doc_scalar!(
1307 BlobId,
1308 "A content-addressed blob ID i.e. the hash of the `BlobContent`"
1309);
1310bcs_scalar!(
1311 OwnerSpender,
1312 "A pair of owner and spender accounts for managing allowances"
1313);
1314
1315#[cfg(test)]
1316mod tests {
1317 #![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1318
1319 use std::str::FromStr as _;
1320
1321 use assert_matches::assert_matches;
1322
1323 use super::{AccountOwner, BlobType};
1324 use crate::{
1325 data_types::{Amount, ChainDescription, ChainOrigin, Epoch, InitialChainConfig, Timestamp},
1326 identifiers::{ApplicationId, CryptoHash, GenericApplicationId, StreamId, StreamName},
1327 ownership::ChainOwnership,
1328 };
1329
1330 #[test]
1332 fn chain_id_computing() {
1333 let example_chain_origin = ChainOrigin::Root(0);
1334 let example_chain_config = InitialChainConfig {
1335 epoch: Epoch::ZERO,
1336 ownership: ChainOwnership::single(AccountOwner::Reserved(0)),
1337 balance: Amount::ZERO,
1338 application_permissions: Default::default(),
1339 };
1340 let description = ChainDescription::new(
1341 example_chain_origin,
1342 example_chain_config,
1343 Timestamp::from(0),
1344 );
1345 assert_eq!(
1346 description.id().to_string(),
1347 "372e43034b962ee04f8242bce87fa9cd405dd824a31b6673cef4d24b937d0de5"
1348 );
1349 }
1350
1351 #[test]
1352 fn blob_types() {
1353 assert_eq!("ContractBytecode", BlobType::ContractBytecode.to_string());
1354 assert_eq!(
1355 BlobType::ContractBytecode,
1356 BlobType::from_str("ContractBytecode").unwrap()
1357 );
1358 }
1359
1360 #[test]
1361 fn addresses() {
1362 assert_eq!(&AccountOwner::Reserved(0).to_string(), "0x00");
1363 assert_eq!(AccountOwner::from_str("0x00").unwrap(), AccountOwner::CHAIN);
1364
1365 let address = AccountOwner::from_str("0x10").unwrap();
1366 assert_eq!(address, AccountOwner::Reserved(16));
1367 assert_eq!(address.to_string(), "0x10");
1368
1369 let address = AccountOwner::from_str(
1370 "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844",
1371 )
1372 .unwrap();
1373 assert_matches!(address, AccountOwner::Address32(_));
1374 assert_eq!(
1375 address.to_string(),
1376 "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844"
1377 );
1378
1379 let address = AccountOwner::from_str("0x6E0ab7F37b667b7228D3a03116Ca21Be83213823").unwrap();
1380 assert_matches!(address, AccountOwner::Address20(_));
1381 assert_eq!(
1382 address.to_string(),
1383 "0x6e0ab7f37b667b7228d3a03116ca21be83213823"
1384 );
1385
1386 assert!(AccountOwner::from_str("0x5487b7").is_err());
1387 assert!(AccountOwner::from_str("0").is_err());
1388 assert!(AccountOwner::from_str(
1389 "5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844"
1390 )
1391 .is_err());
1392 }
1393
1394 #[test]
1395 fn accounts() {
1396 use super::{Account, ChainId};
1397
1398 const CHAIN: &str = "76e3a8c7b2449e6bc238642ac68b4311a809cb57328bea0a1ef9122f08a0053d";
1399 const OWNER: &str = "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844";
1400
1401 let chain_id = ChainId::from_str(CHAIN).unwrap();
1402 let owner = AccountOwner::from_str(OWNER).unwrap();
1403
1404 let account = Account::from_str(CHAIN).unwrap();
1406 assert_eq!(
1407 account,
1408 Account::from_str(&format!("0x00@{CHAIN}")).unwrap()
1409 );
1410 assert_eq!(account, Account::chain(chain_id));
1411 assert_eq!(account.to_string(), format!("0x00@{CHAIN}"));
1412
1413 let account = Account::from_str(&format!("{OWNER}@{CHAIN}")).unwrap();
1415 assert_eq!(account, Account::new(chain_id, owner));
1416 assert_eq!(account.to_string(), format!("{OWNER}@{CHAIN}"));
1417 }
1418
1419 #[test]
1420 fn stream_name() {
1421 let vec = vec![32, 54, 120, 234];
1422 let stream_name1 = StreamName(vec);
1423 let stream_name2 = StreamName::from_str(&format!("{stream_name1}")).unwrap();
1424 assert_eq!(stream_name1, stream_name2);
1425 }
1426
1427 fn test_generic_application_id(application_id: GenericApplicationId) {
1428 let application_id2 = GenericApplicationId::from_str(&format!("{application_id}")).unwrap();
1429 assert_eq!(application_id, application_id2);
1430 }
1431
1432 #[test]
1433 fn generic_application_id() {
1434 test_generic_application_id(GenericApplicationId::System);
1435 let hash = CryptoHash::test_hash("test case");
1436 let application_id = ApplicationId::new(hash);
1437 test_generic_application_id(GenericApplicationId::User(application_id));
1438 }
1439
1440 #[test]
1441 fn stream_id() {
1442 let hash = CryptoHash::test_hash("test case");
1443 let application_id = ApplicationId::new(hash);
1444 let application_id = GenericApplicationId::User(application_id);
1445 let vec = vec![32, 54, 120, 234];
1446 let stream_name = StreamName(vec);
1447
1448 let stream_id1 = StreamId {
1449 application_id,
1450 stream_name,
1451 };
1452 let stream_id2 = StreamId::from_str(&format!("{stream_id1}")).unwrap();
1453 assert_eq!(stream_id1, stream_id2);
1454 }
1455
1456 #[cfg(with_revm)]
1457 #[test]
1458 fn test_address_account_owner() {
1459 use alloy_primitives::Address;
1460 let mut vec = Vec::new();
1461 for i in 0..20 {
1462 vec.push(i as u8);
1463 }
1464 let address1 = Address::from_slice(&vec);
1465 let account_owner = AccountOwner::from(address1);
1466 let address2 = account_owner.to_evm_address().unwrap();
1467 assert_eq!(address1, address2);
1468 }
1469
1470 #[test]
1471 fn ed25519_public_key_to_account_owner_known_vector() {
1472 use crate::crypto::Ed25519PublicKey;
1473 let pubkey_bytes: [u8; 32] = [
1495 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1496 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
1497 0x1d, 0x1e, 0x1f, 0x20,
1498 ];
1499 let pubkey = Ed25519PublicKey(pubkey_bytes);
1500 let owner = AccountOwner::from(pubkey);
1501 assert_eq!(
1505 owner.to_string(),
1506 "0xeacee5344cbec9569e836f95029d476c700f4f5bc007c71c0752c73fba149043",
1507 "Ed25519 owner derivation drifted; verify intentional before updating"
1508 );
1509 }
1510}