1use std::{
7 fmt,
8 hash::{Hash, Hasher},
9 marker::PhantomData,
10};
11
12use allocative::Allocative;
13#[cfg(with_revm)]
14use alloy_primitives::{Address, B256};
15use anyhow::{anyhow, Context};
16use async_graphql::{InputObject, SimpleObject};
17use custom_debug_derive::Debug;
18use derive_more::{Display, FromStr};
19use linera_witty::{WitLoad, WitStore, WitType};
20use serde::{Deserialize, Deserializer, Serialize, Serializer};
21
22use crate::{
23 bcs_scalar,
24 crypto::{
25 AccountPublicKey, CryptoError, CryptoHash, Ed25519PublicKey, EvmPublicKey,
26 Secp256k1PublicKey,
27 },
28 data_types::{BlobContent, ChainDescription},
29 doc_scalar, hex_debug,
30 vm::VmRuntime,
31};
32
33#[derive(
35 Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, WitLoad, WitStore, WitType, Allocative,
36)]
37#[cfg_attr(with_testing, derive(test_strategy::Arbitrary))]
38#[cfg_attr(
40 web,
41 derive(tsify::Tsify),
42 tsify(from_wasm_abi, into_wasm_abi, type = "string")
43)]
44pub enum AccountOwner {
45 Reserved(u8),
47 Address32(CryptoHash),
49 Address20([u8; 20]),
51}
52
53impl fmt::Debug for AccountOwner {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 Self::Reserved(byte) => f.debug_tuple("Reserved").field(byte).finish(),
57 Self::Address32(hash) => write!(f, "Address32({hash:?})"),
58 Self::Address20(bytes) => write!(f, "Address20({})", hex::encode(bytes)),
59 }
60 }
61}
62
63impl AccountOwner {
64 pub const CHAIN: AccountOwner = AccountOwner::Reserved(0);
66
67 pub fn is_chain(&self) -> bool {
69 self == &AccountOwner::CHAIN
70 }
71
72 pub fn size(&self) -> u32 {
74 match self {
75 AccountOwner::Reserved(_) => 1,
76 AccountOwner::Address32(_) => 32,
77 AccountOwner::Address20(_) => 20,
78 }
79 }
80
81 #[cfg(with_revm)]
83 pub fn to_evm_address(&self) -> Option<Address> {
84 match self {
85 AccountOwner::Address20(address) => Some(Address::from(address)),
86 _ => None,
87 }
88 }
89}
90
91#[cfg(with_revm)]
92impl From<Address> for AccountOwner {
93 fn from(address: Address) -> Self {
94 let address = address.into_array();
95 AccountOwner::Address20(address)
96 }
97}
98
99impl From<[u8; 32]> for AccountOwner {
100 fn from(bytes: [u8; 32]) -> Self {
105 if bytes[..12].iter().all(|&b| b == 0) {
106 let mut addr = [0u8; 20];
107 addr.copy_from_slice(&bytes[12..]);
108 AccountOwner::Address20(addr)
109 } else {
110 AccountOwner::Address32(CryptoHash::from(bytes))
111 }
112 }
113}
114
115#[cfg(with_testing)]
116impl From<CryptoHash> for AccountOwner {
117 fn from(address: CryptoHash) -> Self {
118 AccountOwner::Address32(address)
119 }
120}
121
122#[derive(
124 Debug,
125 PartialEq,
126 Eq,
127 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 pub chain_id: ChainId,
144 pub owner: AccountOwner,
146}
147
148impl Account {
149 pub fn new(chain_id: ChainId, owner: AccountOwner) -> Self {
151 Self { chain_id, owner }
152 }
153
154 pub fn chain(chain_id: ChainId) -> Self {
156 Account {
157 chain_id,
158 owner: AccountOwner::CHAIN,
159 }
160 }
161
162 #[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#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Allocative)]
198pub struct OwnerSpender {
199 pub owner: AccountOwner,
201 pub spender: AccountOwner,
203}
204
205impl OwnerSpender {
206 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#[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#[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 #[default]
261 Data,
262 ContractBytecode,
264 ServiceBytecode,
266 EvmBytecode,
268 ApplicationDescription,
270 Committee,
272 ChainDescription,
274 ApplicationFormats,
277 CheckpointExecutionState,
282}
283
284impl BlobType {
285 pub fn is_committee_blob(&self) -> bool {
287 matches!(self, BlobType::Committee)
288 }
289
290 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#[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 pub blob_type: BlobType,
320 pub hash: CryptoHash,
322}
323
324impl BlobId {
325 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#[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#[cfg_attr(web, wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section))]
408const _: &str = "export type ApplicationId = string;";
409
410#[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 pub application_description_hash: CryptoHash,
417 #[witty(skip)]
418 #[debug(skip)]
419 #[allocative(skip)]
420 phantom: PhantomData<A>,
421}
422
423#[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 System,
444 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#[derive(Debug, WitLoad, WitStore, WitType, Allocative)]
522#[cfg_attr(with_testing, derive(Default, test_strategy::Arbitrary))]
523pub struct ModuleId<Abi = (), Parameters = (), InstantiationArgument = ()> {
524 pub contract_blob_hash: CryptoHash,
526 pub service_blob_hash: CryptoHash,
528 pub vm_runtime: VmRuntime,
530 pub formats_blob_hash: Option<CryptoHash>,
534 #[witty(skip)]
535 #[debug(skip)]
536 phantom: PhantomData<(Abi, Parameters, InstantiationArgument)>,
537}
538
539#[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#[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 pub application_id: GenericApplicationId,
605 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 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#[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 pub index: u32,
679 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#[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 pub chain_id: ChainId,
728 pub stream_id: StreamId,
730 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 pub fn into_bytes(self) -> Vec<u8> {
743 self.0
744 }
745}
746
747impl<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 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 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 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 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 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 pub fn formats_blob_id(&self) -> Option<BlobId> {
966 self.formats_blob_hash
967 .map(|hash| BlobId::new(hash, BlobType::ApplicationFormats))
968 }
969
970 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 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
1000impl<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 pub fn new(application_description_hash: CryptoHash) -> Self {
1092 ApplicationId {
1093 application_description_hash,
1094 phantom: PhantomData,
1095 }
1096 }
1097
1098 pub fn description_blob_id(self) -> BlobId {
1101 BlobId::new(
1102 self.application_description_hash,
1103 BlobType::ApplicationDescription,
1104 )
1105 }
1106
1107 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 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 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 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 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 #[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 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 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 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 assert_eq!(
1503 owner.to_string(),
1504 "0xeacee5344cbec9569e836f95029d476c700f4f5bc007c71c0752c73fba149043",
1505 "Ed25519 owner derivation drifted; verify intentional before updating"
1506 );
1507 }
1508}