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