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