Skip to main content

linera_base/
identifiers.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Core identifiers used by the Linera protocol.
5
6use 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/// An account owner.
34#[derive(
35    Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, WitLoad, WitStore, WitType, Allocative,
36)]
37#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
38// TODO(#5166) we can be more specific here
39#[cfg_attr(
40    web,
41    derive(tsify::Tsify),
42    tsify(from_wasm_abi, into_wasm_abi, type = "string")
43)]
44pub enum AccountOwner {
45    /// Short addresses reserved for the protocol.
46    Reserved(u8),
47    /// 32-byte account address.
48    Address32(CryptoHash),
49    /// 20-byte account EVM-compatible address.
50    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    /// Returns the default chain address.
65    pub const CHAIN: AccountOwner = AccountOwner::Reserved(0);
66
67    /// Tests if the account is the chain address.
68    pub fn is_chain(&self) -> bool {
69        self == &AccountOwner::CHAIN
70    }
71
72    /// The size of the `AccountOwner`.
73    pub fn size(&self) -> u32 {
74        match self {
75            AccountOwner::Reserved(_) => 1,
76            AccountOwner::Address32(_) => 32,
77            AccountOwner::Address20(_) => 20,
78        }
79    }
80
81    /// Gets the EVM address if possible
82    #[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_testing)]
92impl From<CryptoHash> for AccountOwner {
93    fn from(address: CryptoHash) -> Self {
94        AccountOwner::Address32(address)
95    }
96}
97
98/// A system account.
99#[derive(
100    Allocative,
101    Debug,
102    PartialEq,
103    Eq,
104    Hash,
105    Copy,
106    Clone,
107    Serialize,
108    Deserialize,
109    WitLoad,
110    WitStore,
111    WitType,
112)]
113#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
114pub struct Account {
115    /// The chain of the account.
116    pub chain_id: ChainId,
117    /// The owner of the account, or `None` for the chain balance.
118    pub owner: AccountOwner,
119}
120
121impl Account {
122    /// Creates a new [`Account`] with the given chain ID and owner.
123    pub fn new(chain_id: ChainId, owner: AccountOwner) -> Self {
124        Self { chain_id, owner }
125    }
126
127    /// Creates an [`Account`] representing the balance shared by a chain's owners.
128    pub fn chain(chain_id: ChainId) -> Self {
129        Account {
130            chain_id,
131            owner: AccountOwner::CHAIN,
132        }
133    }
134
135    /// An address used exclusively for tests
136    #[cfg(with_testing)]
137    pub fn burn_address(chain_id: ChainId) -> Self {
138        let hash = CryptoHash::test_hash("burn");
139        Account {
140            chain_id,
141            owner: hash.into(),
142        }
143    }
144}
145
146impl fmt::Display for Account {
147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148        write!(f, "{}@{}", self.owner, self.chain_id)
149    }
150}
151
152impl std::str::FromStr for Account {
153    type Err = anyhow::Error;
154
155    fn from_str(string: &str) -> Result<Self, Self::Err> {
156        if let Some((owner_string, chain_string)) = string.rsplit_once('@') {
157            let owner = owner_string.parse::<AccountOwner>()?;
158            let chain_id = chain_string.parse()?;
159            Ok(Account::new(chain_id, owner))
160        } else {
161            let chain_id = string
162                .parse()
163                .context("Expecting an account formatted as `chain-id` or `owner@chain-id`")?;
164            Ok(Account::chain(chain_id))
165        }
166    }
167}
168
169/// The unique identifier (UID) of a chain. This is currently computed as the hash value
170/// of a [`ChainDescription`].
171#[derive(
172    Eq,
173    PartialEq,
174    Ord,
175    PartialOrd,
176    Copy,
177    Clone,
178    Hash,
179    Serialize,
180    Deserialize,
181    WitLoad,
182    WitStore,
183    WitType,
184    Allocative,
185)]
186#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
187#[cfg_attr(with_testing, derive(Default))]
188#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
189pub struct ChainId(pub CryptoHash);
190
191/// The type of the blob.
192/// Should be a 1:1 mapping of the types in `Blob`.
193#[derive(
194    Eq,
195    PartialEq,
196    Ord,
197    PartialOrd,
198    Clone,
199    Copy,
200    Hash,
201    Debug,
202    Serialize,
203    Deserialize,
204    WitType,
205    WitStore,
206    WitLoad,
207    Default,
208    Allocative,
209)]
210#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
211pub enum BlobType {
212    /// A generic data blob.
213    #[default]
214    Data,
215    /// A blob containing compressed contract Wasm bytecode.
216    ContractBytecode,
217    /// A blob containing compressed service Wasm bytecode.
218    ServiceBytecode,
219    /// A blob containing compressed EVM bytecode.
220    EvmBytecode,
221    /// A blob containing an application description.
222    ApplicationDescription,
223    /// A blob containing a committee of validators.
224    Committee,
225    /// A blob containing a chain description.
226    ChainDescription,
227}
228
229impl BlobType {
230    /// Returns whether the blob is of [`BlobType::Committee`] variant.
231    pub fn is_committee_blob(&self) -> bool {
232        match self {
233            BlobType::Data
234            | BlobType::ContractBytecode
235            | BlobType::ServiceBytecode
236            | BlobType::EvmBytecode
237            | BlobType::ApplicationDescription
238            | BlobType::ChainDescription => false,
239            BlobType::Committee => true,
240        }
241    }
242}
243
244impl fmt::Display for BlobType {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        write!(f, "{:?}", self)
247    }
248}
249
250impl std::str::FromStr for BlobType {
251    type Err = anyhow::Error;
252
253    fn from_str(s: &str) -> Result<Self, Self::Err> {
254        serde_json::from_str(&format!("\"{s}\""))
255            .with_context(|| format!("Invalid BlobType: {}", s))
256    }
257}
258
259/// A content-addressed blob ID i.e. the hash of the `BlobContent`.
260#[derive(
261    Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, WitType, WitStore, WitLoad, Allocative,
262)]
263#[cfg_attr(with_testing, derive(test_strategy::Arbitrary, Default))]
264pub struct BlobId {
265    /// The type of the blob.
266    pub blob_type: BlobType,
267    /// The hash of the blob.
268    pub hash: CryptoHash,
269}
270
271impl BlobId {
272    /// Creates a new `BlobId` from a `CryptoHash`. This must be a hash of the blob's bytes!
273    pub fn new(hash: CryptoHash, blob_type: BlobType) -> Self {
274        Self { hash, blob_type }
275    }
276}
277
278impl fmt::Display for BlobId {
279    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280        write!(f, "{}:{}", self.blob_type, self.hash)?;
281        Ok(())
282    }
283}
284
285impl std::str::FromStr for BlobId {
286    type Err = anyhow::Error;
287
288    fn from_str(s: &str) -> Result<Self, Self::Err> {
289        let parts = s.split(':').collect::<Vec<_>>();
290        if parts.len() == 2 {
291            let blob_type = BlobType::from_str(parts[0]).context("Invalid BlobType!")?;
292            Ok(BlobId {
293                hash: CryptoHash::from_str(parts[1]).context("Invalid hash!")?,
294                blob_type,
295            })
296        } else {
297            Err(anyhow!("Invalid blob ID: {}", s))
298        }
299    }
300}
301
302#[derive(Serialize, Deserialize)]
303#[serde(rename = "BlobId")]
304struct BlobIdHelper {
305    hash: CryptoHash,
306    blob_type: BlobType,
307}
308
309impl Serialize for BlobId {
310    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
311    where
312        S: Serializer,
313    {
314        if serializer.is_human_readable() {
315            serializer.serialize_str(&self.to_string())
316        } else {
317            let helper = BlobIdHelper {
318                hash: self.hash,
319                blob_type: self.blob_type,
320            };
321            helper.serialize(serializer)
322        }
323    }
324}
325
326impl<'a> Deserialize<'a> for BlobId {
327    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
328    where
329        D: Deserializer<'a>,
330    {
331        if deserializer.is_human_readable() {
332            let s = String::deserialize(deserializer)?;
333            Self::from_str(&s).map_err(serde::de::Error::custom)
334        } else {
335            let helper = BlobIdHelper::deserialize(deserializer)?;
336            Ok(BlobId::new(helper.hash, helper.blob_type))
337        }
338    }
339}
340
341/// Hash of a data blob.
342#[derive(
343    Eq, Hash, PartialEq, Debug, Serialize, Deserialize, Clone, Copy, WitType, WitLoad, WitStore,
344)]
345pub struct DataBlobHash(pub CryptoHash);
346
347impl From<DataBlobHash> for BlobId {
348    fn from(hash: DataBlobHash) -> BlobId {
349        BlobId::new(hash.0, BlobType::Data)
350    }
351}
352
353// TODO(#5166) we can be more specific here (and also more generic)
354#[cfg_attr(web, wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
355const _: &str = "export type ApplicationId = string;";
356
357/// A unique identifier for a user application from a blob.
358#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
359#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
360#[allocative(bound = "A")]
361pub struct ApplicationId<A = ()> {
362    /// The hash of the `ApplicationDescription` this refers to.
363    pub application_description_hash: CryptoHash,
364    #[witty(skip)]
365    #[debug(skip)]
366    #[allocative(skip)]
367    _phantom: PhantomData<A>,
368}
369
370/// A unique identifier for an application.
371#[derive(
372    Eq,
373    PartialEq,
374    Ord,
375    PartialOrd,
376    Copy,
377    Clone,
378    Hash,
379    Debug,
380    Serialize,
381    Deserialize,
382    WitLoad,
383    WitStore,
384    WitType,
385    Allocative,
386)]
387#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
388pub enum GenericApplicationId {
389    /// The system application.
390    System,
391    /// A user application.
392    User(ApplicationId),
393}
394
395impl fmt::Display for GenericApplicationId {
396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397        match self {
398            GenericApplicationId::System => Display::fmt("System", f),
399            GenericApplicationId::User(application_id) => {
400                Display::fmt("User:", f)?;
401                Display::fmt(&application_id, f)
402            }
403        }
404    }
405}
406
407impl std::str::FromStr for GenericApplicationId {
408    type Err = anyhow::Error;
409
410    fn from_str(s: &str) -> Result<Self, Self::Err> {
411        if s == "System" {
412            return Ok(GenericApplicationId::System);
413        }
414        if let Some(result) = s.strip_prefix("User:") {
415            let application_id = ApplicationId::from_str(result)?;
416            return Ok(GenericApplicationId::User(application_id));
417        }
418        Err(anyhow!("Invalid parsing of GenericApplicationId"))
419    }
420}
421
422impl GenericApplicationId {
423    /// Returns the `ApplicationId`, or `None` if it is `System`.
424    pub fn user_application_id(&self) -> Option<&ApplicationId> {
425        if let GenericApplicationId::User(app_id) = self {
426            Some(app_id)
427        } else {
428            None
429        }
430    }
431}
432
433impl<A> From<ApplicationId<A>> for AccountOwner {
434    fn from(app_id: ApplicationId<A>) -> Self {
435        AccountOwner::Address32(app_id.application_description_hash)
436    }
437}
438
439impl From<AccountPublicKey> for AccountOwner {
440    fn from(public_key: AccountPublicKey) -> Self {
441        match public_key {
442            AccountPublicKey::Ed25519(public_key) => public_key.into(),
443            AccountPublicKey::Secp256k1(public_key) => public_key.into(),
444            AccountPublicKey::EvmSecp256k1(public_key) => public_key.into(),
445        }
446    }
447}
448
449impl From<ApplicationId> for GenericApplicationId {
450    fn from(application_id: ApplicationId) -> Self {
451        GenericApplicationId::User(application_id)
452    }
453}
454
455impl From<Secp256k1PublicKey> for AccountOwner {
456    fn from(public_key: Secp256k1PublicKey) -> Self {
457        AccountOwner::Address32(CryptoHash::new(&public_key))
458    }
459}
460
461impl From<Ed25519PublicKey> for AccountOwner {
462    fn from(public_key: Ed25519PublicKey) -> Self {
463        AccountOwner::Address32(CryptoHash::new(&public_key))
464    }
465}
466
467impl From<EvmPublicKey> for AccountOwner {
468    fn from(public_key: EvmPublicKey) -> Self {
469        AccountOwner::Address20(alloy_primitives::Address::from_public_key(&public_key.0).into())
470    }
471}
472
473/// A unique identifier for a module.
474#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
475#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
476pub struct ModuleId<Abi = (), Parameters = (), InstantiationArgument = ()> {
477    /// The hash of the blob containing the contract bytecode.
478    pub contract_blob_hash: CryptoHash,
479    /// The hash of the blob containing the service bytecode.
480    pub service_blob_hash: CryptoHash,
481    /// The virtual machine being used.
482    pub vm_runtime: VmRuntime,
483    #[witty(skip)]
484    #[debug(skip)]
485    _phantom: PhantomData<(Abi, Parameters, InstantiationArgument)>,
486}
487
488/// The name of an event stream.
489#[derive(
490    Clone,
491    Debug,
492    Eq,
493    Hash,
494    Ord,
495    PartialEq,
496    PartialOrd,
497    Serialize,
498    Deserialize,
499    WitLoad,
500    WitStore,
501    WitType,
502    Allocative,
503)]
504pub struct StreamName(
505    #[serde(with = "serde_bytes")]
506    #[debug(with = "hex_debug")]
507    pub Vec<u8>,
508);
509
510impl<T> From<T> for StreamName
511where
512    T: Into<Vec<u8>>,
513{
514    fn from(name: T) -> Self {
515        StreamName(name.into())
516    }
517}
518
519impl fmt::Display for StreamName {
520    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521        Display::fmt(&hex::encode(&self.0), f)
522    }
523}
524
525impl std::str::FromStr for StreamName {
526    type Err = anyhow::Error;
527
528    fn from_str(s: &str) -> Result<Self, Self::Err> {
529        let vec = hex::decode(s)?;
530        Ok(StreamName(vec))
531    }
532}
533
534/// An event stream ID.
535#[derive(
536    Clone,
537    Debug,
538    Eq,
539    Hash,
540    Ord,
541    PartialEq,
542    PartialOrd,
543    Serialize,
544    Deserialize,
545    WitLoad,
546    WitStore,
547    WitType,
548    SimpleObject,
549    InputObject,
550    Allocative,
551)]
552#[graphql(input_name = "StreamIdInput")]
553pub struct StreamId {
554    /// The application that can add events to this stream.
555    pub application_id: GenericApplicationId,
556    /// The name of this stream: an application can have multiple streams with different names.
557    pub stream_name: StreamName,
558}
559
560impl StreamId {
561    /// Creates a system stream ID with the given name.
562    pub fn system(name: impl Into<StreamName>) -> Self {
563        StreamId {
564            application_id: GenericApplicationId::System,
565            stream_name: name.into(),
566        }
567    }
568}
569
570/// The result of an `events_from_index`.
571#[derive(
572    Debug,
573    Eq,
574    PartialEq,
575    Ord,
576    PartialOrd,
577    Clone,
578    Hash,
579    Serialize,
580    Deserialize,
581    WitLoad,
582    WitStore,
583    WitType,
584    SimpleObject,
585)]
586pub struct IndexAndEvent {
587    /// The index of the found event.
588    pub index: u32,
589    /// The event being returned.
590    pub event: Vec<u8>,
591}
592
593impl fmt::Display for StreamId {
594    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
595        Display::fmt(&self.application_id, f)?;
596        Display::fmt(":", f)?;
597        Display::fmt(&self.stream_name, f)
598    }
599}
600
601impl std::str::FromStr for StreamId {
602    type Err = anyhow::Error;
603
604    fn from_str(s: &str) -> Result<Self, Self::Err> {
605        let parts = s.rsplit_once(":");
606        if let Some((part0, part1)) = parts {
607            let application_id =
608                GenericApplicationId::from_str(part0).context("Invalid GenericApplicationId!")?;
609            let stream_name = StreamName::from_str(part1).context("Invalid StreamName!")?;
610            Ok(StreamId {
611                application_id,
612                stream_name,
613            })
614        } else {
615            Err(anyhow!("Invalid blob ID: {}", s))
616        }
617    }
618}
619
620/// An event identifier.
621#[derive(
622    Debug,
623    PartialEq,
624    Eq,
625    Hash,
626    Clone,
627    Serialize,
628    Deserialize,
629    WitLoad,
630    WitStore,
631    WitType,
632    SimpleObject,
633    Allocative,
634)]
635pub struct EventId {
636    /// The ID of the chain that generated this event.
637    pub chain_id: ChainId,
638    /// The ID of the stream this event belongs to.
639    pub stream_id: StreamId,
640    /// The event index, i.e. the number of events in the stream before this one.
641    pub index: u32,
642}
643
644impl StreamName {
645    /// Turns the stream name into bytes.
646    pub fn into_bytes(self) -> Vec<u8> {
647        self.0
648    }
649}
650
651// Cannot use #[derive(Clone)] because it requires `A: Clone`.
652impl<Abi, Parameters, InstantiationArgument> Clone
653    for ModuleId<Abi, Parameters, InstantiationArgument>
654{
655    fn clone(&self) -> Self {
656        *self
657    }
658}
659
660impl<Abi, Parameters, InstantiationArgument> Copy
661    for ModuleId<Abi, Parameters, InstantiationArgument>
662{
663}
664
665impl<Abi, Parameters, InstantiationArgument> PartialEq
666    for ModuleId<Abi, Parameters, InstantiationArgument>
667{
668    fn eq(&self, other: &Self) -> bool {
669        let ModuleId {
670            contract_blob_hash,
671            service_blob_hash,
672            vm_runtime,
673            _phantom,
674        } = other;
675        self.contract_blob_hash == *contract_blob_hash
676            && self.service_blob_hash == *service_blob_hash
677            && self.vm_runtime == *vm_runtime
678    }
679}
680
681impl<Abi, Parameters, InstantiationArgument> Eq
682    for ModuleId<Abi, Parameters, InstantiationArgument>
683{
684}
685
686impl<Abi, Parameters, InstantiationArgument> PartialOrd
687    for ModuleId<Abi, Parameters, InstantiationArgument>
688{
689    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
690        Some(self.cmp(other))
691    }
692}
693
694impl<Abi, Parameters, InstantiationArgument> Ord
695    for ModuleId<Abi, Parameters, InstantiationArgument>
696{
697    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
698        let ModuleId {
699            contract_blob_hash,
700            service_blob_hash,
701            vm_runtime,
702            _phantom,
703        } = other;
704        (
705            self.contract_blob_hash,
706            self.service_blob_hash,
707            self.vm_runtime,
708        )
709            .cmp(&(*contract_blob_hash, *service_blob_hash, *vm_runtime))
710    }
711}
712
713impl<Abi, Parameters, InstantiationArgument> Hash
714    for ModuleId<Abi, Parameters, InstantiationArgument>
715{
716    fn hash<H: Hasher>(&self, state: &mut H) {
717        let ModuleId {
718            contract_blob_hash: contract_blob_id,
719            service_blob_hash: service_blob_id,
720            vm_runtime: vm_runtime_id,
721            _phantom,
722        } = self;
723        contract_blob_id.hash(state);
724        service_blob_id.hash(state);
725        vm_runtime_id.hash(state);
726    }
727}
728
729#[derive(Serialize, Deserialize)]
730#[serde(rename = "ModuleId")]
731struct SerializableModuleId {
732    contract_blob_hash: CryptoHash,
733    service_blob_hash: CryptoHash,
734    vm_runtime: VmRuntime,
735}
736
737impl<Abi, Parameters, InstantiationArgument> Serialize
738    for ModuleId<Abi, Parameters, InstantiationArgument>
739{
740    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
741    where
742        S: serde::ser::Serializer,
743    {
744        let serializable_module_id = SerializableModuleId {
745            contract_blob_hash: self.contract_blob_hash,
746            service_blob_hash: self.service_blob_hash,
747            vm_runtime: self.vm_runtime,
748        };
749        if serializer.is_human_readable() {
750            let bytes =
751                bcs::to_bytes(&serializable_module_id).map_err(serde::ser::Error::custom)?;
752            serializer.serialize_str(&hex::encode(bytes))
753        } else {
754            SerializableModuleId::serialize(&serializable_module_id, serializer)
755        }
756    }
757}
758
759impl<'de, Abi, Parameters, InstantiationArgument> Deserialize<'de>
760    for ModuleId<Abi, Parameters, InstantiationArgument>
761{
762    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
763    where
764        D: serde::de::Deserializer<'de>,
765    {
766        if deserializer.is_human_readable() {
767            let s = String::deserialize(deserializer)?;
768            let module_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
769            let serializable_module_id: SerializableModuleId =
770                bcs::from_bytes(&module_id_bytes).map_err(serde::de::Error::custom)?;
771            Ok(ModuleId {
772                contract_blob_hash: serializable_module_id.contract_blob_hash,
773                service_blob_hash: serializable_module_id.service_blob_hash,
774                vm_runtime: serializable_module_id.vm_runtime,
775                _phantom: PhantomData,
776            })
777        } else {
778            let serializable_module_id = SerializableModuleId::deserialize(deserializer)?;
779            Ok(ModuleId {
780                contract_blob_hash: serializable_module_id.contract_blob_hash,
781                service_blob_hash: serializable_module_id.service_blob_hash,
782                vm_runtime: serializable_module_id.vm_runtime,
783                _phantom: PhantomData,
784            })
785        }
786    }
787}
788
789impl ModuleId {
790    /// Creates a module ID from contract/service hashes and the VM runtime to use.
791    pub fn new(
792        contract_blob_hash: CryptoHash,
793        service_blob_hash: CryptoHash,
794        vm_runtime: VmRuntime,
795    ) -> Self {
796        ModuleId {
797            contract_blob_hash,
798            service_blob_hash,
799            vm_runtime,
800            _phantom: PhantomData,
801        }
802    }
803
804    /// Specializes a module ID for a given ABI.
805    pub fn with_abi<Abi, Parameters, InstantiationArgument>(
806        self,
807    ) -> ModuleId<Abi, Parameters, InstantiationArgument> {
808        ModuleId {
809            contract_blob_hash: self.contract_blob_hash,
810            service_blob_hash: self.service_blob_hash,
811            vm_runtime: self.vm_runtime,
812            _phantom: PhantomData,
813        }
814    }
815
816    /// Gets the `BlobId` of the contract
817    pub fn contract_bytecode_blob_id(&self) -> BlobId {
818        match self.vm_runtime {
819            VmRuntime::Wasm => BlobId::new(self.contract_blob_hash, BlobType::ContractBytecode),
820            VmRuntime::Evm => BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode),
821        }
822    }
823
824    /// Gets the `BlobId` of the service
825    pub fn service_bytecode_blob_id(&self) -> BlobId {
826        match self.vm_runtime {
827            VmRuntime::Wasm => BlobId::new(self.service_blob_hash, BlobType::ServiceBytecode),
828            VmRuntime::Evm => BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode),
829        }
830    }
831
832    /// Gets all bytecode `BlobId`s of the module
833    pub fn bytecode_blob_ids(&self) -> Vec<BlobId> {
834        match self.vm_runtime {
835            VmRuntime::Wasm => vec![
836                BlobId::new(self.contract_blob_hash, BlobType::ContractBytecode),
837                BlobId::new(self.service_blob_hash, BlobType::ServiceBytecode),
838            ],
839            VmRuntime::Evm => vec![BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode)],
840        }
841    }
842}
843
844impl<Abi, Parameters, InstantiationArgument> ModuleId<Abi, Parameters, InstantiationArgument> {
845    /// Forgets the ABI of a module ID (if any).
846    pub fn forget_abi(self) -> ModuleId {
847        ModuleId {
848            contract_blob_hash: self.contract_blob_hash,
849            service_blob_hash: self.service_blob_hash,
850            vm_runtime: self.vm_runtime,
851            _phantom: PhantomData,
852        }
853    }
854}
855
856// Cannot use #[derive(Clone)] because it requires `A: Clone`.
857impl<A> Clone for ApplicationId<A> {
858    fn clone(&self) -> Self {
859        *self
860    }
861}
862
863impl<A> Copy for ApplicationId<A> {}
864
865impl<A: PartialEq> PartialEq for ApplicationId<A> {
866    fn eq(&self, other: &Self) -> bool {
867        self.application_description_hash == other.application_description_hash
868    }
869}
870
871impl<A: Eq> Eq for ApplicationId<A> {}
872
873impl<A: PartialOrd> PartialOrd for ApplicationId<A> {
874    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
875        self.application_description_hash
876            .partial_cmp(&other.application_description_hash)
877    }
878}
879
880impl<A: Ord> Ord for ApplicationId<A> {
881    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
882        self.application_description_hash
883            .cmp(&other.application_description_hash)
884    }
885}
886
887impl<A> Hash for ApplicationId<A> {
888    fn hash<H: Hasher>(&self, state: &mut H) {
889        self.application_description_hash.hash(state);
890    }
891}
892
893#[derive(Serialize, Deserialize)]
894#[serde(rename = "ApplicationId")]
895struct SerializableApplicationId {
896    pub application_description_hash: CryptoHash,
897}
898
899impl<A> Serialize for ApplicationId<A> {
900    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
901    where
902        S: serde::ser::Serializer,
903    {
904        if serializer.is_human_readable() {
905            let bytes = bcs::to_bytes(&SerializableApplicationId {
906                application_description_hash: self.application_description_hash,
907            })
908            .map_err(serde::ser::Error::custom)?;
909            serializer.serialize_str(&hex::encode(bytes))
910        } else {
911            SerializableApplicationId::serialize(
912                &SerializableApplicationId {
913                    application_description_hash: self.application_description_hash,
914                },
915                serializer,
916            )
917        }
918    }
919}
920
921impl<'de, A> Deserialize<'de> for ApplicationId<A> {
922    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
923    where
924        D: serde::de::Deserializer<'de>,
925    {
926        if deserializer.is_human_readable() {
927            let s = String::deserialize(deserializer)?;
928            let application_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
929            let application_id: SerializableApplicationId =
930                bcs::from_bytes(&application_id_bytes).map_err(serde::de::Error::custom)?;
931            Ok(ApplicationId {
932                application_description_hash: application_id.application_description_hash,
933                _phantom: PhantomData,
934            })
935        } else {
936            let value = SerializableApplicationId::deserialize(deserializer)?;
937            Ok(ApplicationId {
938                application_description_hash: value.application_description_hash,
939                _phantom: PhantomData,
940            })
941        }
942    }
943}
944
945impl ApplicationId {
946    /// Creates an application ID from the application description hash.
947    pub fn new(application_description_hash: CryptoHash) -> Self {
948        ApplicationId {
949            application_description_hash,
950            _phantom: PhantomData,
951        }
952    }
953
954    /// Converts the application ID to the ID of the blob containing the
955    /// `ApplicationDescription`.
956    pub fn description_blob_id(self) -> BlobId {
957        BlobId::new(
958            self.application_description_hash,
959            BlobType::ApplicationDescription,
960        )
961    }
962
963    /// Specializes an application ID for a given ABI.
964    pub fn with_abi<A>(self) -> ApplicationId<A> {
965        ApplicationId {
966            application_description_hash: self.application_description_hash,
967            _phantom: PhantomData,
968        }
969    }
970}
971
972impl<A> ApplicationId<A> {
973    /// Forgets the ABI of an application ID (if any).
974    pub fn forget_abi(self) -> ApplicationId {
975        ApplicationId {
976            application_description_hash: self.application_description_hash,
977            _phantom: PhantomData,
978        }
979    }
980}
981
982#[cfg(with_revm)]
983impl<A> ApplicationId<A> {
984    /// Converts the `ApplicationId` into an Ethereum Address.
985    pub fn evm_address(&self) -> Address {
986        let bytes = self.application_description_hash.as_bytes();
987        let bytes = bytes.0.as_ref();
988        Address::from_slice(&bytes[0..20])
989    }
990
991    /// Converts the `ApplicationId` into an Ethereum-compatible 32-byte array.
992    pub fn bytes32(&self) -> B256 {
993        *self.application_description_hash.as_bytes()
994    }
995
996    /// Returns whether the `ApplicationId` is the one of an EVM application.
997    pub fn is_evm(&self) -> bool {
998        let bytes = self.application_description_hash.as_bytes();
999        let bytes = bytes.0.as_ref();
1000        for byte in &bytes[20..] {
1001            if byte != &0 {
1002                return false;
1003            }
1004        }
1005        true
1006    }
1007}
1008
1009#[derive(Serialize, Deserialize)]
1010#[serde(rename = "AccountOwner")]
1011enum SerializableAccountOwner {
1012    Reserved(u8),
1013    Address32(CryptoHash),
1014    Address20([u8; 20]),
1015}
1016
1017impl Serialize for AccountOwner {
1018    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1019        if serializer.is_human_readable() {
1020            serializer.serialize_str(&self.to_string())
1021        } else {
1022            match self {
1023                AccountOwner::Reserved(value) => SerializableAccountOwner::Reserved(*value),
1024                AccountOwner::Address32(value) => SerializableAccountOwner::Address32(*value),
1025                AccountOwner::Address20(value) => SerializableAccountOwner::Address20(*value),
1026            }
1027            .serialize(serializer)
1028        }
1029    }
1030}
1031
1032impl<'de> Deserialize<'de> for AccountOwner {
1033    fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1034        if deserializer.is_human_readable() {
1035            let s = String::deserialize(deserializer)?;
1036            let value = Self::from_str(&s).map_err(serde::de::Error::custom)?;
1037            Ok(value)
1038        } else {
1039            let value = SerializableAccountOwner::deserialize(deserializer)?;
1040            match value {
1041                SerializableAccountOwner::Reserved(value) => Ok(AccountOwner::Reserved(value)),
1042                SerializableAccountOwner::Address32(value) => Ok(AccountOwner::Address32(value)),
1043                SerializableAccountOwner::Address20(value) => Ok(AccountOwner::Address20(value)),
1044            }
1045        }
1046    }
1047}
1048
1049impl fmt::Display for AccountOwner {
1050    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1051        match self {
1052            AccountOwner::Reserved(value) => {
1053                write!(f, "0x{}", hex::encode(&value.to_be_bytes()[..]))?
1054            }
1055            AccountOwner::Address32(value) => write!(f, "0x{}", value)?,
1056            AccountOwner::Address20(value) => write!(f, "0x{}", hex::encode(&value[..]))?,
1057        };
1058
1059        Ok(())
1060    }
1061}
1062
1063impl std::str::FromStr for AccountOwner {
1064    type Err = anyhow::Error;
1065
1066    fn from_str(s: &str) -> Result<Self, Self::Err> {
1067        if let Some(s) = s.strip_prefix("0x") {
1068            if s.len() == 64 {
1069                if let Ok(hash) = CryptoHash::from_str(s) {
1070                    return Ok(AccountOwner::Address32(hash));
1071                }
1072            } else if s.len() == 40 {
1073                let address = hex::decode(s)?;
1074                if address.len() != 20 {
1075                    anyhow::bail!("Invalid address length: {s}");
1076                }
1077                let address = <[u8; 20]>::try_from(address.as_slice()).unwrap();
1078                return Ok(AccountOwner::Address20(address));
1079            }
1080            if s.len() == 2 {
1081                let bytes = hex::decode(s)?;
1082                if bytes.len() == 1 {
1083                    let value = u8::from_be_bytes(bytes.try_into().expect("one byte"));
1084                    return Ok(AccountOwner::Reserved(value));
1085                }
1086            }
1087        }
1088        anyhow::bail!("Invalid address value: {s}");
1089    }
1090}
1091
1092impl fmt::Display for ChainId {
1093    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1094        Display::fmt(&self.0, f)
1095    }
1096}
1097
1098impl std::str::FromStr for ChainId {
1099    type Err = CryptoError;
1100
1101    fn from_str(s: &str) -> Result<Self, Self::Err> {
1102        Ok(ChainId(CryptoHash::from_str(s)?))
1103    }
1104}
1105
1106impl TryFrom<&[u8]> for ChainId {
1107    type Error = CryptoError;
1108
1109    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
1110        Ok(ChainId(CryptoHash::try_from(value)?))
1111    }
1112}
1113
1114impl fmt::Debug for ChainId {
1115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1116        write!(f, "{:?}", self.0)
1117    }
1118}
1119
1120impl<'a> From<&'a ChainDescription> for ChainId {
1121    fn from(description: &'a ChainDescription) -> Self {
1122        Self(CryptoHash::new(&BlobContent::new_chain_description(
1123            description,
1124        )))
1125    }
1126}
1127
1128impl From<ChainDescription> for ChainId {
1129    fn from(description: ChainDescription) -> Self {
1130        From::from(&description)
1131    }
1132}
1133
1134bcs_scalar!(ApplicationId, "A unique identifier for a user application");
1135doc_scalar!(DataBlobHash, "Hash of a Data Blob");
1136doc_scalar!(
1137    GenericApplicationId,
1138    "A unique identifier for a user application or for the system application"
1139);
1140bcs_scalar!(ModuleId, "A unique identifier for an application module");
1141doc_scalar!(
1142    ChainId,
1143    "The unique identifier (UID) of a chain. This is currently computed as the hash value of a \
1144    ChainDescription."
1145);
1146doc_scalar!(StreamName, "The name of an event stream");
1147
1148doc_scalar!(
1149    AccountOwner,
1150    "A unique identifier for a user or an application."
1151);
1152doc_scalar!(Account, "An account");
1153doc_scalar!(
1154    BlobId,
1155    "A content-addressed blob ID i.e. the hash of the `BlobContent`"
1156);
1157
1158#[cfg(test)]
1159mod tests {
1160    use std::str::FromStr as _;
1161
1162    use assert_matches::assert_matches;
1163
1164    use super::{AccountOwner, BlobType};
1165    use crate::{
1166        data_types::{Amount, ChainDescription, ChainOrigin, Epoch, InitialChainConfig, Timestamp},
1167        identifiers::{ApplicationId, CryptoHash, GenericApplicationId, StreamId, StreamName},
1168        ownership::ChainOwnership,
1169    };
1170
1171    /// Verifies that the way of computing chain IDs doesn't change.
1172    #[test]
1173    fn chain_id_computing() {
1174        let example_chain_origin = ChainOrigin::Root(0);
1175        let example_chain_config = InitialChainConfig {
1176            epoch: Epoch::ZERO,
1177            ownership: ChainOwnership::single(AccountOwner::Reserved(0)),
1178            balance: Amount::ZERO,
1179            min_active_epoch: Epoch::ZERO,
1180            max_active_epoch: Epoch::ZERO,
1181            application_permissions: Default::default(),
1182        };
1183        let description = ChainDescription::new(
1184            example_chain_origin,
1185            example_chain_config,
1186            Timestamp::from(0),
1187        );
1188        assert_eq!(
1189            description.id().to_string(),
1190            "fe947fddf2735224d01eb9d56580109f2d9d02397dc5ddd748ef9beeb38d9caa"
1191        );
1192    }
1193
1194    #[test]
1195    fn blob_types() {
1196        assert_eq!("ContractBytecode", BlobType::ContractBytecode.to_string());
1197        assert_eq!(
1198            BlobType::ContractBytecode,
1199            BlobType::from_str("ContractBytecode").unwrap()
1200        );
1201    }
1202
1203    #[test]
1204    fn addresses() {
1205        assert_eq!(&AccountOwner::Reserved(0).to_string(), "0x00");
1206        assert_eq!(AccountOwner::from_str("0x00").unwrap(), AccountOwner::CHAIN);
1207
1208        let address = AccountOwner::from_str("0x10").unwrap();
1209        assert_eq!(address, AccountOwner::Reserved(16));
1210        assert_eq!(address.to_string(), "0x10");
1211
1212        let address = AccountOwner::from_str(
1213            "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844",
1214        )
1215        .unwrap();
1216        assert_matches!(address, AccountOwner::Address32(_));
1217        assert_eq!(
1218            address.to_string(),
1219            "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844"
1220        );
1221
1222        let address = AccountOwner::from_str("0x6E0ab7F37b667b7228D3a03116Ca21Be83213823").unwrap();
1223        assert_matches!(address, AccountOwner::Address20(_));
1224        assert_eq!(
1225            address.to_string(),
1226            "0x6e0ab7f37b667b7228d3a03116ca21be83213823"
1227        );
1228
1229        assert!(AccountOwner::from_str("0x5487b7").is_err());
1230        assert!(AccountOwner::from_str("0").is_err());
1231        assert!(AccountOwner::from_str(
1232            "5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844"
1233        )
1234        .is_err());
1235    }
1236
1237    #[test]
1238    fn accounts() {
1239        use super::{Account, ChainId};
1240
1241        const CHAIN: &str = "76e3a8c7b2449e6bc238642ac68b4311a809cb57328bea0a1ef9122f08a0053d";
1242        const OWNER: &str = "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844";
1243
1244        let chain_id = ChainId::from_str(CHAIN).unwrap();
1245        let owner = AccountOwner::from_str(OWNER).unwrap();
1246
1247        // Chain-only account.
1248        let account = Account::from_str(CHAIN).unwrap();
1249        assert_eq!(
1250            account,
1251            Account::from_str(&format!("0x00@{CHAIN}")).unwrap()
1252        );
1253        assert_eq!(account, Account::chain(chain_id));
1254        assert_eq!(account.to_string(), format!("0x00@{CHAIN}"));
1255
1256        // Account with owner.
1257        let account = Account::from_str(&format!("{OWNER}@{CHAIN}")).unwrap();
1258        assert_eq!(account, Account::new(chain_id, owner));
1259        assert_eq!(account.to_string(), format!("{OWNER}@{CHAIN}"));
1260    }
1261
1262    #[test]
1263    fn stream_name() {
1264        let vec = vec![32, 54, 120, 234];
1265        let stream_name1 = StreamName(vec);
1266        let stream_name2 = StreamName::from_str(&format!("{stream_name1}")).unwrap();
1267        assert_eq!(stream_name1, stream_name2);
1268    }
1269
1270    fn test_generic_application_id(application_id: GenericApplicationId) {
1271        let application_id2 = GenericApplicationId::from_str(&format!("{application_id}")).unwrap();
1272        assert_eq!(application_id, application_id2);
1273    }
1274
1275    #[test]
1276    fn generic_application_id() {
1277        test_generic_application_id(GenericApplicationId::System);
1278        let hash = CryptoHash::test_hash("test case");
1279        let application_id = ApplicationId::new(hash);
1280        test_generic_application_id(GenericApplicationId::User(application_id));
1281    }
1282
1283    #[test]
1284    fn stream_id() {
1285        let hash = CryptoHash::test_hash("test case");
1286        let application_id = ApplicationId::new(hash);
1287        let application_id = GenericApplicationId::User(application_id);
1288        let vec = vec![32, 54, 120, 234];
1289        let stream_name = StreamName(vec);
1290
1291        let stream_id1 = StreamId {
1292            application_id,
1293            stream_name,
1294        };
1295        let stream_id2 = StreamId::from_str(&format!("{stream_id1}")).unwrap();
1296        assert_eq!(stream_id1, stream_id2);
1297    }
1298}