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    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    /// A blob containing the JSON-encoded `Formats` description published
275    /// alongside an application's contract and service blobs.
276    ApplicationFormats,
277    /// A blob containing one ordered chunk of a chain's execution-state dump at a
278    /// checkpoint, used to bootstrap a node without replaying the chain's history.
279    /// A single checkpoint produces a sequence of such blobs whose content hashes
280    /// are listed in `OracleResponse::Checkpoint`.
281    CheckpointExecutionState,
282}
283
284impl BlobType {
285    /// Returns whether the blob is of [`BlobType::Committee`] variant.
286    pub fn is_committee_blob(&self) -> bool {
287        matches!(self, BlobType::Committee)
288    }
289
290    /// Returns whether the blob carries a chunk of a checkpoint's execution-state dump.
291    /// Such blobs are produced by `ExecutionStateView::prepare_checkpoint` and exempt
292    /// from per-block published-blob counts and per-blob fees.
293    pub fn is_checkpoint_blob(&self) -> bool {
294        matches!(self, BlobType::CheckpointExecutionState)
295    }
296}
297
298impl fmt::Display for BlobType {
299    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300        write!(f, "{self:?}")
301    }
302}
303
304impl std::str::FromStr for BlobType {
305    type Err = anyhow::Error;
306
307    fn from_str(s: &str) -> Result<Self, Self::Err> {
308        serde_json::from_str(&format!("\"{s}\"")).with_context(|| format!("Invalid BlobType: {s}"))
309    }
310}
311
312/// A content-addressed blob ID i.e. the hash of the `BlobContent`.
313#[derive(
314    Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash, Debug, WitType, WitStore, WitLoad, Allocative,
315)]
316#[cfg_attr(with_testing, derive(test_strategy::Arbitrary, Default))]
317pub struct BlobId {
318    /// The type of the blob.
319    pub blob_type: BlobType,
320    /// The hash of the blob.
321    pub hash: CryptoHash,
322}
323
324impl BlobId {
325    /// Creates a new `BlobId` from a `CryptoHash`. This must be a hash of the blob's bytes!
326    pub fn new(hash: CryptoHash, blob_type: BlobType) -> Self {
327        Self { hash, blob_type }
328    }
329}
330
331impl fmt::Display for BlobId {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        write!(f, "{}:{}", self.blob_type, self.hash)?;
334        Ok(())
335    }
336}
337
338impl std::str::FromStr for BlobId {
339    type Err = anyhow::Error;
340
341    fn from_str(s: &str) -> Result<Self, Self::Err> {
342        let parts = s.split(':').collect::<Vec<_>>();
343        if parts.len() == 2 {
344            let blob_type = BlobType::from_str(parts[0]).context("Invalid BlobType!")?;
345            Ok(BlobId {
346                hash: CryptoHash::from_str(parts[1]).context("Invalid hash!")?,
347                blob_type,
348            })
349        } else {
350            Err(anyhow!("Invalid blob ID: {s}"))
351        }
352    }
353}
354
355#[derive(Serialize, Deserialize)]
356#[serde(rename = "BlobId")]
357struct BlobIdHelper {
358    hash: CryptoHash,
359    blob_type: BlobType,
360}
361
362impl Serialize for BlobId {
363    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
364    where
365        S: Serializer,
366    {
367        if serializer.is_human_readable() {
368            serializer.serialize_str(&self.to_string())
369        } else {
370            let helper = BlobIdHelper {
371                hash: self.hash,
372                blob_type: self.blob_type,
373            };
374            helper.serialize(serializer)
375        }
376    }
377}
378
379impl<'a> Deserialize<'a> for BlobId {
380    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
381    where
382        D: Deserializer<'a>,
383    {
384        if deserializer.is_human_readable() {
385            let s = String::deserialize(deserializer)?;
386            Self::from_str(&s).map_err(serde::de::Error::custom)
387        } else {
388            let helper = BlobIdHelper::deserialize(deserializer)?;
389            Ok(BlobId::new(helper.hash, helper.blob_type))
390        }
391    }
392}
393
394/// Hash of a data blob.
395#[derive(
396    Eq, Hash, PartialEq, Debug, Serialize, Deserialize, Clone, Copy, WitType, WitLoad, WitStore,
397)]
398pub struct DataBlobHash(pub CryptoHash);
399
400impl From<DataBlobHash> for BlobId {
401    fn from(hash: DataBlobHash) -> BlobId {
402        BlobId::new(hash.0, BlobType::Data)
403    }
404}
405
406// TODO(#5166) we can be more specific here (and also more generic)
407#[cfg_attr(web, wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
408const _: &str = "export type ApplicationId = string;";
409
410/// A unique identifier for a user application from a blob.
411#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
412#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
413#[allocative(bound = "A")]
414pub struct ApplicationId<A = ()> {
415    /// The hash of the `ApplicationDescription` this refers to.
416    pub application_description_hash: CryptoHash,
417    #[witty(skip)]
418    #[debug(skip)]
419    #[allocative(skip)]
420    phantom: PhantomData<A>,
421}
422
423/// A unique identifier for an application.
424#[derive(
425    Eq,
426    PartialEq,
427    Ord,
428    PartialOrd,
429    Copy,
430    Clone,
431    Hash,
432    Debug,
433    Serialize,
434    Deserialize,
435    WitLoad,
436    WitStore,
437    WitType,
438    Allocative,
439)]
440#[cfg_attr(web, derive(tsify::Tsify), tsify(from_wasm_abi, into_wasm_abi))]
441pub enum GenericApplicationId {
442    /// The system application.
443    System,
444    /// A user application.
445    User(ApplicationId),
446}
447
448impl fmt::Display for GenericApplicationId {
449    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450        match self {
451            GenericApplicationId::System => Display::fmt("System", f),
452            GenericApplicationId::User(application_id) => {
453                Display::fmt("User:", f)?;
454                Display::fmt(&application_id, f)
455            }
456        }
457    }
458}
459
460impl std::str::FromStr for GenericApplicationId {
461    type Err = anyhow::Error;
462
463    fn from_str(s: &str) -> Result<Self, Self::Err> {
464        if s == "System" {
465            return Ok(GenericApplicationId::System);
466        }
467        if let Some(result) = s.strip_prefix("User:") {
468            let application_id = ApplicationId::from_str(result)?;
469            return Ok(GenericApplicationId::User(application_id));
470        }
471        Err(anyhow!("Invalid parsing of GenericApplicationId"))
472    }
473}
474
475impl<A> From<ApplicationId<A>> for AccountOwner {
476    fn from(app_id: ApplicationId<A>) -> Self {
477        if app_id.is_evm() {
478            let hash_bytes = app_id.application_description_hash.as_bytes();
479            AccountOwner::Address20(hash_bytes[..20].try_into().unwrap())
480        } else {
481            AccountOwner::Address32(app_id.application_description_hash)
482        }
483    }
484}
485
486impl From<AccountPublicKey> for AccountOwner {
487    fn from(public_key: AccountPublicKey) -> Self {
488        match public_key {
489            AccountPublicKey::Ed25519(public_key) => public_key.into(),
490            AccountPublicKey::Secp256k1(public_key) => public_key.into(),
491            AccountPublicKey::EvmSecp256k1(public_key) => public_key.into(),
492        }
493    }
494}
495
496impl From<ApplicationId> for GenericApplicationId {
497    fn from(application_id: ApplicationId) -> Self {
498        GenericApplicationId::User(application_id)
499    }
500}
501
502impl From<Secp256k1PublicKey> for AccountOwner {
503    fn from(public_key: Secp256k1PublicKey) -> Self {
504        AccountOwner::Address32(CryptoHash::new(&public_key))
505    }
506}
507
508impl From<Ed25519PublicKey> for AccountOwner {
509    fn from(public_key: Ed25519PublicKey) -> Self {
510        AccountOwner::Address32(CryptoHash::new(&public_key))
511    }
512}
513
514impl From<EvmPublicKey> for AccountOwner {
515    fn from(public_key: EvmPublicKey) -> Self {
516        AccountOwner::Address20(alloy_primitives::Address::from_public_key(&public_key.0).into())
517    }
518}
519
520/// A unique identifier for a module.
521#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
522#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
523pub struct ModuleId<Abi = (), Parameters = (), InstantiationArgument = ()> {
524    /// The hash of the blob containing the contract bytecode.
525    pub contract_blob_hash: CryptoHash,
526    /// The hash of the blob containing the service bytecode.
527    pub service_blob_hash: CryptoHash,
528    /// The virtual machine being used.
529    pub vm_runtime: VmRuntime,
530    /// The hash of an optional blob containing the JSON-encoded `Formats`
531    /// description for this module's application. Published alongside the
532    /// contract and service blobs when available.
533    pub formats_blob_hash: Option<CryptoHash>,
534    #[witty(skip)]
535    #[debug(skip)]
536    phantom: PhantomData<(Abi, Parameters, InstantiationArgument)>,
537}
538
539/// The name of an event stream.
540#[derive(
541    Clone,
542    Debug,
543    Eq,
544    Hash,
545    Ord,
546    PartialEq,
547    PartialOrd,
548    Serialize,
549    Deserialize,
550    WitLoad,
551    WitStore,
552    WitType,
553    Allocative,
554)]
555pub struct StreamName(
556    #[serde(with = "serde_bytes")]
557    #[debug(with = "hex_debug")]
558    pub Vec<u8>,
559);
560
561impl<T> From<T> for StreamName
562where
563    T: Into<Vec<u8>>,
564{
565    fn from(name: T) -> Self {
566        StreamName(name.into())
567    }
568}
569
570impl fmt::Display for StreamName {
571    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
572        Display::fmt(&hex::encode(&self.0), f)
573    }
574}
575
576impl std::str::FromStr for StreamName {
577    type Err = anyhow::Error;
578
579    fn from_str(s: &str) -> Result<Self, Self::Err> {
580        let vec = hex::decode(s)?;
581        Ok(StreamName(vec))
582    }
583}
584
585/// An event stream ID.
586#[derive(
587    Clone,
588    Debug,
589    Eq,
590    Hash,
591    Ord,
592    PartialEq,
593    PartialOrd,
594    WitLoad,
595    WitStore,
596    WitType,
597    SimpleObject,
598    InputObject,
599    Allocative,
600)]
601#[graphql(input_name = "StreamIdInput")]
602pub struct StreamId {
603    /// The application that can add events to this stream.
604    pub application_id: GenericApplicationId,
605    /// The name of this stream: an application can have multiple streams with different names.
606    pub stream_name: StreamName,
607}
608
609impl serde::Serialize for StreamId {
610    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
611    where
612        S: serde::ser::Serializer,
613    {
614        if serializer.is_human_readable() {
615            serializer.serialize_str(&self.to_string())
616        } else {
617            use serde::ser::SerializeStruct;
618            let mut state = serializer.serialize_struct("StreamId", 2)?;
619            state.serialize_field("application_id", &self.application_id)?;
620            state.serialize_field("stream_name", &self.stream_name)?;
621            state.end()
622        }
623    }
624}
625
626impl<'de> serde::Deserialize<'de> for StreamId {
627    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
628    where
629        D: serde::de::Deserializer<'de>,
630    {
631        if deserializer.is_human_readable() {
632            let s = String::deserialize(deserializer)?;
633            Self::from_str(&s).map_err(serde::de::Error::custom)
634        } else {
635            #[derive(serde::Deserialize)]
636            #[serde(rename = "StreamId")]
637            struct StreamIdHelper {
638                application_id: GenericApplicationId,
639                stream_name: StreamName,
640            }
641            let helper = StreamIdHelper::deserialize(deserializer)?;
642            Ok(StreamId {
643                application_id: helper.application_id,
644                stream_name: helper.stream_name,
645            })
646        }
647    }
648}
649
650impl StreamId {
651    /// Creates a system stream ID with the given name.
652    pub fn system(name: impl Into<StreamName>) -> Self {
653        StreamId {
654            application_id: GenericApplicationId::System,
655            stream_name: name.into(),
656        }
657    }
658}
659
660/// The result of an `events_from_index`.
661#[derive(
662    Debug,
663    Eq,
664    PartialEq,
665    Ord,
666    PartialOrd,
667    Clone,
668    Hash,
669    Serialize,
670    Deserialize,
671    WitLoad,
672    WitStore,
673    WitType,
674    SimpleObject,
675)]
676pub struct IndexAndEvent {
677    /// The index of the found event.
678    pub index: u32,
679    /// The event being returned.
680    pub event: Vec<u8>,
681}
682
683impl fmt::Display for StreamId {
684    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
685        Display::fmt(&self.application_id, f)?;
686        Display::fmt(":", f)?;
687        Display::fmt(&self.stream_name, f)
688    }
689}
690
691impl std::str::FromStr for StreamId {
692    type Err = anyhow::Error;
693
694    fn from_str(s: &str) -> Result<Self, Self::Err> {
695        let parts = s.rsplit_once(":");
696        if let Some((part0, part1)) = parts {
697            let application_id =
698                GenericApplicationId::from_str(part0).context("Invalid GenericApplicationId!")?;
699            let stream_name = StreamName::from_str(part1).context("Invalid StreamName!")?;
700            Ok(StreamId {
701                application_id,
702                stream_name,
703            })
704        } else {
705            Err(anyhow!("Invalid blob ID: {s}"))
706        }
707    }
708}
709
710/// An event identifier.
711#[derive(
712    Debug,
713    PartialEq,
714    Eq,
715    Hash,
716    Clone,
717    Serialize,
718    Deserialize,
719    WitLoad,
720    WitStore,
721    WitType,
722    SimpleObject,
723    Allocative,
724)]
725pub struct EventId {
726    /// The ID of the chain that generated this event.
727    pub chain_id: ChainId,
728    /// The ID of the stream this event belongs to.
729    pub stream_id: StreamId,
730    /// The event index, i.e. the number of events in the stream before this one.
731    pub index: u32,
732}
733
734impl fmt::Display for EventId {
735    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736        write!(f, "{}:{}:{}", self.chain_id, self.stream_id, self.index)
737    }
738}
739
740impl StreamName {
741    /// Turns the stream name into bytes.
742    pub fn into_bytes(self) -> Vec<u8> {
743        self.0
744    }
745}
746
747// Cannot use #[derive(Clone)] because it requires `A: Clone`.
748impl<Abi, Parameters, InstantiationArgument> Clone
749    for ModuleId<Abi, Parameters, InstantiationArgument>
750{
751    fn clone(&self) -> Self {
752        *self
753    }
754}
755
756impl<Abi, Parameters, InstantiationArgument> Copy
757    for ModuleId<Abi, Parameters, InstantiationArgument>
758{
759}
760
761impl<Abi, Parameters, InstantiationArgument> PartialEq
762    for ModuleId<Abi, Parameters, InstantiationArgument>
763{
764    fn eq(&self, other: &Self) -> bool {
765        let ModuleId {
766            contract_blob_hash,
767            service_blob_hash,
768            vm_runtime,
769            formats_blob_hash,
770            phantom: _,
771        } = other;
772        self.contract_blob_hash == *contract_blob_hash
773            && self.service_blob_hash == *service_blob_hash
774            && self.vm_runtime == *vm_runtime
775            && self.formats_blob_hash == *formats_blob_hash
776    }
777}
778
779impl<Abi, Parameters, InstantiationArgument> Eq
780    for ModuleId<Abi, Parameters, InstantiationArgument>
781{
782}
783
784impl<Abi, Parameters, InstantiationArgument> PartialOrd
785    for ModuleId<Abi, Parameters, InstantiationArgument>
786{
787    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
788        Some(self.cmp(other))
789    }
790}
791
792impl<Abi, Parameters, InstantiationArgument> Ord
793    for ModuleId<Abi, Parameters, InstantiationArgument>
794{
795    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
796        let ModuleId {
797            contract_blob_hash,
798            service_blob_hash,
799            vm_runtime,
800            formats_blob_hash,
801            phantom: _,
802        } = other;
803        (
804            self.contract_blob_hash,
805            self.service_blob_hash,
806            self.vm_runtime,
807            self.formats_blob_hash,
808        )
809            .cmp(&(
810                *contract_blob_hash,
811                *service_blob_hash,
812                *vm_runtime,
813                *formats_blob_hash,
814            ))
815    }
816}
817
818impl<Abi, Parameters, InstantiationArgument> Hash
819    for ModuleId<Abi, Parameters, InstantiationArgument>
820{
821    fn hash<H: Hasher>(&self, state: &mut H) {
822        let ModuleId {
823            contract_blob_hash: contract_blob_id,
824            service_blob_hash: service_blob_id,
825            vm_runtime: vm_runtime_id,
826            formats_blob_hash,
827            phantom: _,
828        } = self;
829        contract_blob_id.hash(state);
830        service_blob_id.hash(state);
831        vm_runtime_id.hash(state);
832        formats_blob_hash.hash(state);
833    }
834}
835
836#[derive(Serialize, Deserialize)]
837#[serde(rename = "ModuleId")]
838struct SerializableModuleId {
839    contract_blob_hash: CryptoHash,
840    service_blob_hash: CryptoHash,
841    vm_runtime: VmRuntime,
842    #[serde(default)]
843    formats_blob_hash: Option<CryptoHash>,
844}
845
846impl<Abi, Parameters, InstantiationArgument> Serialize
847    for ModuleId<Abi, Parameters, InstantiationArgument>
848{
849    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
850    where
851        S: serde::ser::Serializer,
852    {
853        let serializable_module_id = SerializableModuleId {
854            contract_blob_hash: self.contract_blob_hash,
855            service_blob_hash: self.service_blob_hash,
856            vm_runtime: self.vm_runtime,
857            formats_blob_hash: self.formats_blob_hash,
858        };
859        if serializer.is_human_readable() {
860            let bytes =
861                bcs::to_bytes(&serializable_module_id).map_err(serde::ser::Error::custom)?;
862            serializer.serialize_str(&hex::encode(bytes))
863        } else {
864            SerializableModuleId::serialize(&serializable_module_id, serializer)
865        }
866    }
867}
868
869impl<'de, Abi, Parameters, InstantiationArgument> Deserialize<'de>
870    for ModuleId<Abi, Parameters, InstantiationArgument>
871{
872    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
873    where
874        D: serde::de::Deserializer<'de>,
875    {
876        if deserializer.is_human_readable() {
877            let s = String::deserialize(deserializer)?;
878            let module_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
879            let serializable_module_id: SerializableModuleId =
880                bcs::from_bytes(&module_id_bytes).map_err(serde::de::Error::custom)?;
881            Ok(ModuleId {
882                contract_blob_hash: serializable_module_id.contract_blob_hash,
883                service_blob_hash: serializable_module_id.service_blob_hash,
884                vm_runtime: serializable_module_id.vm_runtime,
885                formats_blob_hash: serializable_module_id.formats_blob_hash,
886                phantom: PhantomData,
887            })
888        } else {
889            let serializable_module_id = SerializableModuleId::deserialize(deserializer)?;
890            Ok(ModuleId {
891                contract_blob_hash: serializable_module_id.contract_blob_hash,
892                service_blob_hash: serializable_module_id.service_blob_hash,
893                vm_runtime: serializable_module_id.vm_runtime,
894                formats_blob_hash: serializable_module_id.formats_blob_hash,
895                phantom: PhantomData,
896            })
897        }
898    }
899}
900
901impl ModuleId {
902    /// Creates a module ID from contract/service hashes and the VM runtime to use.
903    pub fn new(
904        contract_blob_hash: CryptoHash,
905        service_blob_hash: CryptoHash,
906        vm_runtime: VmRuntime,
907    ) -> Self {
908        ModuleId {
909            contract_blob_hash,
910            service_blob_hash,
911            vm_runtime,
912            formats_blob_hash: None,
913            phantom: PhantomData,
914        }
915    }
916
917    /// Creates a module ID from contract/service hashes, the VM runtime, and an
918    /// optional formats blob hash.
919    pub fn new_with_formats(
920        contract_blob_hash: CryptoHash,
921        service_blob_hash: CryptoHash,
922        vm_runtime: VmRuntime,
923        formats_blob_hash: Option<CryptoHash>,
924    ) -> Self {
925        ModuleId {
926            contract_blob_hash,
927            service_blob_hash,
928            vm_runtime,
929            formats_blob_hash,
930            phantom: PhantomData,
931        }
932    }
933
934    /// Specializes a module ID for a given ABI.
935    pub fn with_abi<Abi, Parameters, InstantiationArgument>(
936        self,
937    ) -> ModuleId<Abi, Parameters, InstantiationArgument> {
938        ModuleId {
939            contract_blob_hash: self.contract_blob_hash,
940            service_blob_hash: self.service_blob_hash,
941            vm_runtime: self.vm_runtime,
942            formats_blob_hash: self.formats_blob_hash,
943            phantom: PhantomData,
944        }
945    }
946
947    /// Gets the `BlobId` of the contract
948    pub fn contract_bytecode_blob_id(&self) -> BlobId {
949        match self.vm_runtime {
950            VmRuntime::Wasm => BlobId::new(self.contract_blob_hash, BlobType::ContractBytecode),
951            VmRuntime::Evm => BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode),
952        }
953    }
954
955    /// Gets the `BlobId` of the service
956    pub fn service_bytecode_blob_id(&self) -> BlobId {
957        match self.vm_runtime {
958            VmRuntime::Wasm => BlobId::new(self.service_blob_hash, BlobType::ServiceBytecode),
959            VmRuntime::Evm => BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode),
960        }
961    }
962
963    /// Gets the `BlobId` of the application formats blob, if one was registered
964    /// at module publication.
965    pub fn formats_blob_id(&self) -> Option<BlobId> {
966        self.formats_blob_hash
967            .map(|hash| BlobId::new(hash, BlobType::ApplicationFormats))
968    }
969
970    /// Gets all bytecode `BlobId`s of the module, including the optional
971    /// application formats blob when present.
972    pub fn bytecode_blob_ids(&self) -> Vec<BlobId> {
973        let mut blobs = match self.vm_runtime {
974            VmRuntime::Wasm => vec![
975                BlobId::new(self.contract_blob_hash, BlobType::ContractBytecode),
976                BlobId::new(self.service_blob_hash, BlobType::ServiceBytecode),
977            ],
978            VmRuntime::Evm => vec![BlobId::new(self.contract_blob_hash, BlobType::EvmBytecode)],
979        };
980        if let Some(blob_id) = self.formats_blob_id() {
981            blobs.push(blob_id);
982        }
983        blobs
984    }
985}
986
987impl<Abi, Parameters, InstantiationArgument> ModuleId<Abi, Parameters, InstantiationArgument> {
988    /// Forgets the ABI of a module ID (if any).
989    pub fn forget_abi(self) -> ModuleId {
990        ModuleId {
991            contract_blob_hash: self.contract_blob_hash,
992            service_blob_hash: self.service_blob_hash,
993            vm_runtime: self.vm_runtime,
994            formats_blob_hash: self.formats_blob_hash,
995            phantom: PhantomData,
996        }
997    }
998}
999
1000// Cannot use #[derive(Clone)] because it requires `A: Clone`.
1001impl<A> Clone for ApplicationId<A> {
1002    fn clone(&self) -> Self {
1003        *self
1004    }
1005}
1006
1007impl<A> Copy for ApplicationId<A> {}
1008
1009impl<A: PartialEq> PartialEq for ApplicationId<A> {
1010    fn eq(&self, other: &Self) -> bool {
1011        self.application_description_hash == other.application_description_hash
1012    }
1013}
1014
1015impl<A: Eq> Eq for ApplicationId<A> {}
1016
1017impl<A: PartialOrd> PartialOrd for ApplicationId<A> {
1018    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1019        self.application_description_hash
1020            .partial_cmp(&other.application_description_hash)
1021    }
1022}
1023
1024impl<A: Ord> Ord for ApplicationId<A> {
1025    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1026        self.application_description_hash
1027            .cmp(&other.application_description_hash)
1028    }
1029}
1030
1031impl<A> Hash for ApplicationId<A> {
1032    fn hash<H: Hasher>(&self, state: &mut H) {
1033        self.application_description_hash.hash(state);
1034    }
1035}
1036
1037#[derive(Serialize, Deserialize)]
1038#[serde(rename = "ApplicationId")]
1039struct SerializableApplicationId {
1040    pub application_description_hash: CryptoHash,
1041}
1042
1043impl<A> Serialize for ApplicationId<A> {
1044    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1045    where
1046        S: serde::ser::Serializer,
1047    {
1048        if serializer.is_human_readable() {
1049            let bytes = bcs::to_bytes(&SerializableApplicationId {
1050                application_description_hash: self.application_description_hash,
1051            })
1052            .map_err(serde::ser::Error::custom)?;
1053            serializer.serialize_str(&hex::encode(bytes))
1054        } else {
1055            SerializableApplicationId::serialize(
1056                &SerializableApplicationId {
1057                    application_description_hash: self.application_description_hash,
1058                },
1059                serializer,
1060            )
1061        }
1062    }
1063}
1064
1065impl<'de, A> Deserialize<'de> for ApplicationId<A> {
1066    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1067    where
1068        D: serde::de::Deserializer<'de>,
1069    {
1070        if deserializer.is_human_readable() {
1071            let s = String::deserialize(deserializer)?;
1072            let application_id_bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
1073            let application_id: SerializableApplicationId =
1074                bcs::from_bytes(&application_id_bytes).map_err(serde::de::Error::custom)?;
1075            Ok(ApplicationId {
1076                application_description_hash: application_id.application_description_hash,
1077                phantom: PhantomData,
1078            })
1079        } else {
1080            let value = SerializableApplicationId::deserialize(deserializer)?;
1081            Ok(ApplicationId {
1082                application_description_hash: value.application_description_hash,
1083                phantom: PhantomData,
1084            })
1085        }
1086    }
1087}
1088
1089impl ApplicationId {
1090    /// Creates an application ID from the application description hash.
1091    pub fn new(application_description_hash: CryptoHash) -> Self {
1092        ApplicationId {
1093            application_description_hash,
1094            phantom: PhantomData,
1095        }
1096    }
1097
1098    /// Converts the application ID to the ID of the blob containing the
1099    /// `ApplicationDescription`.
1100    pub fn description_blob_id(self) -> BlobId {
1101        BlobId::new(
1102            self.application_description_hash,
1103            BlobType::ApplicationDescription,
1104        )
1105    }
1106
1107    /// Specializes an application ID for a given ABI.
1108    pub fn with_abi<A>(self) -> ApplicationId<A> {
1109        ApplicationId {
1110            application_description_hash: self.application_description_hash,
1111            phantom: PhantomData,
1112        }
1113    }
1114}
1115
1116impl<A> ApplicationId<A> {
1117    /// Forgets the ABI of an application ID (if any).
1118    pub fn forget_abi(self) -> ApplicationId {
1119        ApplicationId {
1120            application_description_hash: self.application_description_hash,
1121            phantom: PhantomData,
1122        }
1123    }
1124}
1125
1126impl<A> ApplicationId<A> {
1127    /// Returns whether the `ApplicationId` is the one of an EVM application.
1128    pub fn is_evm(&self) -> bool {
1129        let bytes = self.application_description_hash.as_bytes();
1130        bytes.0[20..] == [0; 12]
1131    }
1132}
1133
1134#[cfg(with_revm)]
1135impl From<Address> for ApplicationId {
1136    fn from(address: Address) -> ApplicationId {
1137        let mut arr = [0_u8; 32];
1138        arr[..20].copy_from_slice(address.as_slice());
1139        ApplicationId {
1140            application_description_hash: arr.into(),
1141            phantom: PhantomData,
1142        }
1143    }
1144}
1145
1146#[cfg(with_revm)]
1147impl<A> ApplicationId<A> {
1148    /// Converts the `ApplicationId` into an Ethereum Address.
1149    pub fn evm_address(&self) -> Address {
1150        let bytes = self.application_description_hash.as_bytes();
1151        let bytes = bytes.0.as_ref();
1152        Address::from_slice(&bytes[0..20])
1153    }
1154
1155    /// Converts the `ApplicationId` into an Ethereum-compatible 32-byte array.
1156    pub fn bytes32(&self) -> B256 {
1157        *self.application_description_hash.as_bytes()
1158    }
1159}
1160
1161#[derive(Serialize, Deserialize)]
1162#[serde(rename = "AccountOwner")]
1163enum SerializableAccountOwner {
1164    Reserved(u8),
1165    Address32(CryptoHash),
1166    Address20([u8; 20]),
1167}
1168
1169impl Serialize for AccountOwner {
1170    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1171        if serializer.is_human_readable() {
1172            serializer.serialize_str(&self.to_string())
1173        } else {
1174            match self {
1175                AccountOwner::Reserved(value) => SerializableAccountOwner::Reserved(*value),
1176                AccountOwner::Address32(value) => SerializableAccountOwner::Address32(*value),
1177                AccountOwner::Address20(value) => SerializableAccountOwner::Address20(*value),
1178            }
1179            .serialize(serializer)
1180        }
1181    }
1182}
1183
1184impl<'de> Deserialize<'de> for AccountOwner {
1185    fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1186        if deserializer.is_human_readable() {
1187            let s = String::deserialize(deserializer)?;
1188            let value = Self::from_str(&s).map_err(serde::de::Error::custom)?;
1189            Ok(value)
1190        } else {
1191            let value = SerializableAccountOwner::deserialize(deserializer)?;
1192            match value {
1193                SerializableAccountOwner::Reserved(value) => Ok(AccountOwner::Reserved(value)),
1194                SerializableAccountOwner::Address32(value) => Ok(AccountOwner::Address32(value)),
1195                SerializableAccountOwner::Address20(value) => Ok(AccountOwner::Address20(value)),
1196            }
1197        }
1198    }
1199}
1200
1201impl fmt::Display for AccountOwner {
1202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1203        match self {
1204            AccountOwner::Reserved(value) => {
1205                write!(f, "0x{}", hex::encode(&value.to_be_bytes()[..]))?
1206            }
1207            AccountOwner::Address32(value) => write!(f, "0x{value}")?,
1208            AccountOwner::Address20(value) => write!(f, "0x{}", hex::encode(&value[..]))?,
1209        };
1210
1211        Ok(())
1212    }
1213}
1214
1215impl std::str::FromStr for AccountOwner {
1216    type Err = anyhow::Error;
1217
1218    fn from_str(s: &str) -> Result<Self, Self::Err> {
1219        if let Some(s) = s.strip_prefix("0x") {
1220            if s.len() == 64 {
1221                if let Ok(hash) = CryptoHash::from_str(s) {
1222                    return Ok(AccountOwner::Address32(hash));
1223                }
1224            } else if s.len() == 40 {
1225                let address = hex::decode(s)?;
1226                if address.len() != 20 {
1227                    anyhow::bail!("Invalid address length: {s}");
1228                }
1229                let address = <[u8; 20]>::try_from(address.as_slice()).unwrap();
1230                return Ok(AccountOwner::Address20(address));
1231            }
1232            if s.len() == 2 {
1233                let bytes = hex::decode(s)?;
1234                if bytes.len() == 1 {
1235                    let value = u8::from_be_bytes(bytes.try_into().expect("one byte"));
1236                    return Ok(AccountOwner::Reserved(value));
1237                }
1238            }
1239        }
1240        anyhow::bail!("Invalid address value: {s}");
1241    }
1242}
1243
1244impl fmt::Display for ChainId {
1245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1246        Display::fmt(&self.0, f)
1247    }
1248}
1249
1250impl std::str::FromStr for ChainId {
1251    type Err = CryptoError;
1252
1253    fn from_str(s: &str) -> Result<Self, Self::Err> {
1254        Ok(ChainId(CryptoHash::from_str(s)?))
1255    }
1256}
1257
1258impl TryFrom<&[u8]> for ChainId {
1259    type Error = CryptoError;
1260
1261    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
1262        Ok(ChainId(CryptoHash::try_from(value)?))
1263    }
1264}
1265
1266impl fmt::Debug for ChainId {
1267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1268        write!(f, "{:?}", self.0)
1269    }
1270}
1271
1272impl<'a> From<&'a ChainDescription> for ChainId {
1273    fn from(description: &'a ChainDescription) -> Self {
1274        Self(CryptoHash::new(&BlobContent::new_chain_description(
1275            description,
1276        )))
1277    }
1278}
1279
1280impl From<ChainDescription> for ChainId {
1281    fn from(description: ChainDescription) -> Self {
1282        From::from(&description)
1283    }
1284}
1285
1286bcs_scalar!(ApplicationId, "A unique identifier for a user application");
1287doc_scalar!(DataBlobHash, "Hash of a Data Blob");
1288doc_scalar!(
1289    GenericApplicationId,
1290    "A unique identifier for a user application or for the system application"
1291);
1292bcs_scalar!(ModuleId, "A unique identifier for an application module");
1293doc_scalar!(
1294    ChainId,
1295    "The unique identifier (UID) of a chain. This is currently computed as the hash value of a \
1296    ChainDescription."
1297);
1298doc_scalar!(StreamName, "The name of an event stream");
1299
1300doc_scalar!(
1301    AccountOwner,
1302    "A unique identifier for a user or an application."
1303);
1304doc_scalar!(
1305    BlobId,
1306    "A content-addressed blob ID i.e. the hash of the `BlobContent`"
1307);
1308bcs_scalar!(
1309    OwnerSpender,
1310    "A pair of owner and spender accounts for managing allowances"
1311);
1312
1313#[cfg(test)]
1314mod tests {
1315    #![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1316
1317    use std::str::FromStr as _;
1318
1319    use assert_matches::assert_matches;
1320
1321    use super::{AccountOwner, BlobType};
1322    use crate::{
1323        data_types::{Amount, ChainDescription, ChainOrigin, Epoch, InitialChainConfig, Timestamp},
1324        identifiers::{ApplicationId, CryptoHash, GenericApplicationId, StreamId, StreamName},
1325        ownership::ChainOwnership,
1326    };
1327
1328    /// Verifies that the way of computing chain IDs doesn't change.
1329    #[test]
1330    fn chain_id_computing() {
1331        let example_chain_origin = ChainOrigin::Root(0);
1332        let example_chain_config = InitialChainConfig {
1333            epoch: Epoch::ZERO,
1334            ownership: ChainOwnership::single(AccountOwner::Reserved(0)),
1335            balance: Amount::ZERO,
1336            application_permissions: Default::default(),
1337        };
1338        let description = ChainDescription::new(
1339            example_chain_origin,
1340            example_chain_config,
1341            Timestamp::from(0),
1342        );
1343        assert_eq!(
1344            description.id().to_string(),
1345            "372e43034b962ee04f8242bce87fa9cd405dd824a31b6673cef4d24b937d0de5"
1346        );
1347    }
1348
1349    #[test]
1350    fn blob_types() {
1351        assert_eq!("ContractBytecode", BlobType::ContractBytecode.to_string());
1352        assert_eq!(
1353            BlobType::ContractBytecode,
1354            BlobType::from_str("ContractBytecode").unwrap()
1355        );
1356    }
1357
1358    #[test]
1359    fn addresses() {
1360        assert_eq!(&AccountOwner::Reserved(0).to_string(), "0x00");
1361        assert_eq!(AccountOwner::from_str("0x00").unwrap(), AccountOwner::CHAIN);
1362
1363        let address = AccountOwner::from_str("0x10").unwrap();
1364        assert_eq!(address, AccountOwner::Reserved(16));
1365        assert_eq!(address.to_string(), "0x10");
1366
1367        let address = AccountOwner::from_str(
1368            "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844",
1369        )
1370        .unwrap();
1371        assert_matches!(address, AccountOwner::Address32(_));
1372        assert_eq!(
1373            address.to_string(),
1374            "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844"
1375        );
1376
1377        let address = AccountOwner::from_str("0x6E0ab7F37b667b7228D3a03116Ca21Be83213823").unwrap();
1378        assert_matches!(address, AccountOwner::Address20(_));
1379        assert_eq!(
1380            address.to_string(),
1381            "0x6e0ab7f37b667b7228d3a03116ca21be83213823"
1382        );
1383
1384        assert!(AccountOwner::from_str("0x5487b7").is_err());
1385        assert!(AccountOwner::from_str("0").is_err());
1386        assert!(AccountOwner::from_str(
1387            "5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844"
1388        )
1389        .is_err());
1390    }
1391
1392    #[test]
1393    fn accounts() {
1394        use super::{Account, ChainId};
1395
1396        const CHAIN: &str = "76e3a8c7b2449e6bc238642ac68b4311a809cb57328bea0a1ef9122f08a0053d";
1397        const OWNER: &str = "0x5487b70625ce71f7ee29154ad32aefa1c526cb483bdb783dea2e1d17bc497844";
1398
1399        let chain_id = ChainId::from_str(CHAIN).unwrap();
1400        let owner = AccountOwner::from_str(OWNER).unwrap();
1401
1402        // Chain-only account.
1403        let account = Account::from_str(CHAIN).unwrap();
1404        assert_eq!(
1405            account,
1406            Account::from_str(&format!("0x00@{CHAIN}")).unwrap()
1407        );
1408        assert_eq!(account, Account::chain(chain_id));
1409        assert_eq!(account.to_string(), format!("0x00@{CHAIN}"));
1410
1411        // Account with owner.
1412        let account = Account::from_str(&format!("{OWNER}@{CHAIN}")).unwrap();
1413        assert_eq!(account, Account::new(chain_id, owner));
1414        assert_eq!(account.to_string(), format!("{OWNER}@{CHAIN}"));
1415    }
1416
1417    #[test]
1418    fn stream_name() {
1419        let vec = vec![32, 54, 120, 234];
1420        let stream_name1 = StreamName(vec);
1421        let stream_name2 = StreamName::from_str(&format!("{stream_name1}")).unwrap();
1422        assert_eq!(stream_name1, stream_name2);
1423    }
1424
1425    fn test_generic_application_id(application_id: GenericApplicationId) {
1426        let application_id2 = GenericApplicationId::from_str(&format!("{application_id}")).unwrap();
1427        assert_eq!(application_id, application_id2);
1428    }
1429
1430    #[test]
1431    fn generic_application_id() {
1432        test_generic_application_id(GenericApplicationId::System);
1433        let hash = CryptoHash::test_hash("test case");
1434        let application_id = ApplicationId::new(hash);
1435        test_generic_application_id(GenericApplicationId::User(application_id));
1436    }
1437
1438    #[test]
1439    fn stream_id() {
1440        let hash = CryptoHash::test_hash("test case");
1441        let application_id = ApplicationId::new(hash);
1442        let application_id = GenericApplicationId::User(application_id);
1443        let vec = vec![32, 54, 120, 234];
1444        let stream_name = StreamName(vec);
1445
1446        let stream_id1 = StreamId {
1447            application_id,
1448            stream_name,
1449        };
1450        let stream_id2 = StreamId::from_str(&format!("{stream_id1}")).unwrap();
1451        assert_eq!(stream_id1, stream_id2);
1452    }
1453
1454    #[cfg(with_revm)]
1455    #[test]
1456    fn test_address_account_owner() {
1457        use alloy_primitives::Address;
1458        let mut vec = Vec::new();
1459        for i in 0..20 {
1460            vec.push(i as u8);
1461        }
1462        let address1 = Address::from_slice(&vec);
1463        let account_owner = AccountOwner::from(address1);
1464        let address2 = account_owner.to_evm_address().unwrap();
1465        assert_eq!(address1, address2);
1466    }
1467
1468    #[test]
1469    fn ed25519_public_key_to_account_owner_known_vector() {
1470        use crate::crypto::Ed25519PublicKey;
1471        // Pins the entire derivation pipeline against silent drift, not just BCS.
1472        // The chain executed:
1473        //
1474        //   [u8; 32]
1475        //     -> Ed25519PublicKey                       (newtype wrap)
1476        //     -> AccountOwner::from(public_key)         (impl From, this file)
1477        //          -> CryptoHash::new(&public_key)
1478        //               -> Hashable::write into a Keccak256 hasher
1479        //                    -> BcsHashable blanket impl writes:
1480        //                         * type-name discriminator prefix
1481        //                         * BCS body (32 raw bytes for [u8; 32])
1482        //               -> Keccak256 finalize -> 32-byte hash
1483        //     -> AccountOwner::Address32(hash)
1484        //     -> Display: "0x" + lowercase hex
1485        //
1486        // Any change in any link breaks this test: BCS format, the
1487        // `BcsHashable` type-name discriminator, the hash function, the
1488        // `From<Ed25519PublicKey>` impl, the `Address32` carrier, or the
1489        // `Display` formatting.
1490        //
1491        // Fixed 32-byte public key (0x01..0x20).
1492        let pubkey_bytes: [u8; 32] = [
1493            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1494            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
1495            0x1d, 0x1e, 0x1f, 0x20,
1496        ];
1497        let pubkey = Ed25519PublicKey(pubkey_bytes);
1498        let owner = AccountOwner::from(pubkey);
1499        // The expected hex is the pinned output of `Keccak256(BCS(Ed25519PublicKey))`.
1500        // Do not update it without understanding why the derivation changed — the JS
1501        // test in `@linera/client` cross-checks this exact value.
1502        assert_eq!(
1503            owner.to_string(),
1504            "0xeacee5344cbec9569e836f95029d476c700f4f5bc007c71c0752c73fba149043",
1505            "Ed25519 owner derivation drifted; verify intentional before updating"
1506        );
1507    }
1508}