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_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    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    /// The chain of the account.
145    pub chain_id: ChainId,
146    /// The owner of the account.
147    pub owner: AccountOwner,
148}
149
150impl Account {
151    /// Creates a new [`Account`] with the given chain ID and owner.
152    pub fn new(chain_id: ChainId, owner: AccountOwner) -> Self {
153        Self { chain_id, owner }
154    }
155
156    /// Creates an [`Account`] representing the balance shared by a chain's owners.
157    pub fn chain(chain_id: ChainId) -> Self {
158        Account {
159            chain_id,
160            owner: AccountOwner::CHAIN,
161        }
162    }
163
164    /// An address used exclusively for tests
165    #[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/// A pair of owner and spender accounts for managing allowances.
199#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Allocative)]
200pub struct OwnerSpender {
201    /// Account to withdraw from
202    pub owner: AccountOwner,
203    /// Account to do the withdrawing
204    pub spender: AccountOwner,
205}
206
207impl OwnerSpender {
208    /// Creates a new `OwnerSpender` pair.
209    /// Panics if owner and spender are the same.
210    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/// The unique identifier (UID) of a chain. This is currently computed as the hash value
219/// of a [`ChainDescription`].
220#[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/// The type of the blob.
241/// Should be a 1:1 mapping of the types in `Blob`.
242#[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    /// A generic data blob.
262    #[default]
263    Data,
264    /// A blob containing compressed contract Wasm bytecode.
265    ContractBytecode,
266    /// A blob containing compressed service Wasm bytecode.
267    ServiceBytecode,
268    /// A blob containing compressed EVM bytecode.
269    EvmBytecode,
270    /// A blob containing an application description.
271    ApplicationDescription,
272    /// A blob containing a committee of validators.
273    Committee,
274    /// A blob containing a chain description.
275    ChainDescription,
276    /// A blob containing the BCS-encoded `Formats` description published
277    /// alongside an application's contract and service blobs.
278    ApplicationFormats,
279    /// A blob containing one ordered chunk of a chain's execution-state dump at a
280    /// checkpoint, used to bootstrap a node without replaying the chain's history.
281    /// A single checkpoint produces a sequence of such blobs whose content hashes
282    /// are listed in `OracleResponse::Checkpoint`.
283    CheckpointExecutionState,
284}
285
286impl BlobType {
287    /// Returns whether the blob is of [`BlobType::Committee`] variant.
288    pub fn is_committee_blob(&self) -> bool {
289        matches!(self, BlobType::Committee)
290    }
291
292    /// Returns whether the blob carries a chunk of a checkpoint's execution-state dump.
293    /// Such blobs are produced by `ExecutionStateView::prepare_checkpoint` and exempt
294    /// from per-block published-blob counts and per-blob fees.
295    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/// A content-addressed blob ID i.e. the hash of the `BlobContent`.
315#[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    /// The type of the blob.
321    pub blob_type: BlobType,
322    /// The hash of the blob.
323    pub hash: CryptoHash,
324}
325
326impl BlobId {
327    /// Creates a new `BlobId` from a `CryptoHash`. This must be a hash of the blob's bytes!
328    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/// Hash of a data blob.
397#[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// TODO(#5166) we can be more specific here (and also more generic)
409#[cfg_attr(web, wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
410const _: &str = "export type ApplicationId = string;";
411
412/// A unique identifier for a user application from a blob.
413#[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    /// The hash of the `ApplicationDescription` this refers to.
418    pub application_description_hash: CryptoHash,
419    #[witty(skip)]
420    #[debug(skip)]
421    #[allocative(skip)]
422    phantom: PhantomData<A>,
423}
424
425/// A unique identifier for an application.
426#[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    /// The system application.
445    System,
446    /// A user application.
447    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/// A unique identifier for a module.
523#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
524#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
525pub struct ModuleId<Abi = (), Parameters = (), InstantiationArgument = ()> {
526    /// The hash of the blob containing the contract bytecode.
527    pub contract_blob_hash: CryptoHash,
528    /// The hash of the blob containing the service bytecode.
529    pub service_blob_hash: CryptoHash,
530    /// The virtual machine being used.
531    pub vm_runtime: VmRuntime,
532    /// The hash of an optional blob containing the BCS-encoded `Formats`
533    /// description for this module's application. Published alongside the
534    /// contract and service blobs when available.
535    pub formats_blob_hash: Option<CryptoHash>,
536    #[witty(skip)]
537    #[debug(skip)]
538    phantom: PhantomData<(Abi, Parameters, InstantiationArgument)>,
539}
540
541/// The name of an event stream.
542#[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/// An event stream ID.
588#[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    /// The application that can add events to this stream.
606    pub application_id: GenericApplicationId,
607    /// The name of this stream: an application can have multiple streams with different names.
608    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    /// Creates a system stream ID with the given name.
654    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/// The result of an `events_from_index`.
663#[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    /// The index of the found event.
680    pub index: u32,
681    /// The event being returned.
682    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/// An event identifier.
713#[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    /// The ID of the chain that generated this event.
729    pub chain_id: ChainId,
730    /// The ID of the stream this event belongs to.
731    pub stream_id: StreamId,
732    /// The event index, i.e. the number of events in the stream before this one.
733    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    /// Turns the stream name into bytes.
744    pub fn into_bytes(self) -> Vec<u8> {
745        self.0
746    }
747}
748
749// Cannot use #[derive(Clone)] because it requires `A: Clone`.
750impl<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    /// Creates a module ID from contract/service hashes and the VM runtime to use.
905    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    /// Creates a module ID from contract/service hashes, the VM runtime, and an
920    /// optional formats blob hash.
921    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    /// Specializes a module ID for a given ABI.
937    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    /// Gets the `BlobId` of the contract
950    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    /// Gets the `BlobId` of the service
958    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    /// Gets the `BlobId` of the application formats blob, if one was registered
966    /// at module publication.
967    pub fn formats_blob_id(&self) -> Option<BlobId> {
968        self.formats_blob_hash
969            .map(|hash| BlobId::new(hash, BlobType::ApplicationFormats))
970    }
971
972    /// Gets all bytecode `BlobId`s of the module, including the optional
973    /// application formats blob when present.
974    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    /// Forgets the ABI of a module ID (if any).
991    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
1002// Cannot use #[derive(Clone)] because it requires `A: Clone`.
1003impl<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    /// Creates an application ID from the application description hash.
1093    pub fn new(application_description_hash: CryptoHash) -> Self {
1094        ApplicationId {
1095            application_description_hash,
1096            phantom: PhantomData,
1097        }
1098    }
1099
1100    /// Converts the application ID to the ID of the blob containing the
1101    /// `ApplicationDescription`.
1102    pub fn description_blob_id(self) -> BlobId {
1103        BlobId::new(
1104            self.application_description_hash,
1105            BlobType::ApplicationDescription,
1106        )
1107    }
1108
1109    /// Specializes an application ID for a given ABI.
1110    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    /// Forgets the ABI of an application ID (if any).
1120    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    /// Returns whether the `ApplicationId` is the one of an EVM application.
1130    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    /// Converts the `ApplicationId` into an Ethereum Address.
1151    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    /// Converts the `ApplicationId` into an Ethereum-compatible 32-byte array.
1158    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    /// Verifies that the way of computing chain IDs doesn't change.
1331    #[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        // Chain-only account.
1405        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        // Account with owner.
1414        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        // Pins the entire derivation pipeline against silent drift, not just BCS.
1474        // The chain executed:
1475        //
1476        //   [u8; 32]
1477        //     -> Ed25519PublicKey                       (newtype wrap)
1478        //     -> AccountOwner::from(public_key)         (impl From, this file)
1479        //          -> CryptoHash::new(&public_key)
1480        //               -> Hashable::write into a Keccak256 hasher
1481        //                    -> BcsHashable blanket impl writes:
1482        //                         * type-name discriminator prefix
1483        //                         * BCS body (32 raw bytes for [u8; 32])
1484        //               -> Keccak256 finalize -> 32-byte hash
1485        //     -> AccountOwner::Address32(hash)
1486        //     -> Display: "0x" + lowercase hex
1487        //
1488        // Any change in any link breaks this test: BCS format, the
1489        // `BcsHashable` type-name discriminator, the hash function, the
1490        // `From<Ed25519PublicKey>` impl, the `Address32` carrier, or the
1491        // `Display` formatting.
1492        //
1493        // Fixed 32-byte public key (0x01..0x20).
1494        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        // The expected hex is the pinned output of `Keccak256(BCS(Ed25519PublicKey))`.
1502        // Do not update it without understanding why the derivation changed — the JS
1503        // test in `@linera/client` cross-checks this exact value.
1504        assert_eq!(
1505            owner.to_string(),
1506            "0xeacee5344cbec9569e836f95029d476c700f4f5bc007c71c0752c73fba149043",
1507            "Ed25519 owner derivation drifted; verify intentional before updating"
1508        );
1509    }
1510}