1use std::{
7 convert::{TryFrom, TryInto},
8 fmt,
9 str::FromStr,
10};
11
12use anyhow::anyhow;
13use fastcrypto::hash::HashFunction;
14use iota_protocol_config::ProtocolConfig;
15pub use iota_sdk_types::{
16 Address as IotaAddress, MoveObjectType, ObjectReference as ObjectRef, Version as SequenceNumber,
17};
18use iota_sdk_types::{Identifier, ObjectId, Owner, StructTag, TypeTag};
19use move_binary_format::{CompiledModule, file_format::SignatureToken};
20use move_bytecode_utils::resolve_struct;
21use move_core_types::{
22 account_address::AccountAddress, annotated_value as A, ident_str, identifier::IdentStr,
23};
24use serde::{
25 Deserialize, Serialize, Serializer,
26 ser::{Error, SerializeSeq},
27};
28
29use crate::{
30 MOVE_STDLIB_ADDRESS,
31 crypto::{AuthorityPublicKeyBytes, DefaultHash, IotaPublicKey, IotaSignature, PublicKey},
32 effects::{TransactionEffects, TransactionEffectsAPI, TransactionEffectsExt},
33 epoch_data::EpochData,
34 error::{ExecutionError, ExecutionErrorKind, IotaError, IotaResult},
35 id::RESOLVED_IOTA_ID,
36 iota_sdk_types_conversions::struct_tag_sdk_to_core,
37 iota_serde::to_iota_struct_tag_string,
38 messages_checkpoint::CheckpointTimestamp,
39 object::Object,
40 parse_iota_struct_tag,
41 signature::GenericSignature,
42 transaction::{Transaction, VerifiedTransaction},
43};
44pub use crate::{
45 committee::EpochId,
46 digests::{ObjectDigest, TransactionDigest, TransactionEffectsDigest},
47};
48
49#[cfg(test)]
50#[path = "unit_tests/base_types_tests.rs"]
51mod base_types_tests;
52
53pub type TxSequenceNumber = u64;
54
55pub type VersionNumber = SequenceNumber;
56
57pub type CommitRound = u64;
59
60pub type AuthorityName = AuthorityPublicKeyBytes;
61
62pub trait ConciseableName<'a> {
63 type ConciseTypeRef: std::fmt::Debug;
64 type ConciseType: std::fmt::Debug;
65
66 fn concise(&'a self) -> Self::ConciseTypeRef;
67 fn concise_owned(&self) -> Self::ConciseType;
68}
69
70pub type VersionDigest = (SequenceNumber, ObjectDigest);
71
72pub fn random_object_ref() -> ObjectRef {
73 ObjectRef::new(
74 ObjectId::random(),
75 SequenceNumber::default(),
76 ObjectDigest::new([0; 32]),
77 )
78}
79
80pub fn is_primitive_type_tag(t: &TypeTag) -> bool {
82 use TypeTag as T;
83
84 match t {
85 T::Bool | T::U8 | T::U16 | T::U32 | T::U64 | T::U128 | T::U256 | T::Address => true,
86 T::Vector(inner) => is_primitive_type_tag(inner),
87 T::Struct(st) => {
88 let resolved_struct = (
89 &AccountAddress::new(st.address().into_bytes()),
90 move_core_types::identifier::IdentStr::new(st.module().as_str()).unwrap(),
91 move_core_types::identifier::IdentStr::new(st.name().as_str()).unwrap(),
92 );
93 if resolved_struct == RESOLVED_IOTA_ID {
95 return true;
96 }
97 if resolved_struct == RESOLVED_UTF8_STR {
99 return true;
100 }
101 if resolved_struct == RESOLVED_ASCII_STR {
103 return true;
104 }
105 resolved_struct == RESOLVED_STD_OPTION
107 && st.type_params().len() == 1
108 && is_primitive_type_tag(&st.type_params()[0])
109 }
110 T::Signer => false,
111 }
112}
113
114#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
116pub enum ObjectType {
117 Package,
119 Struct(MoveObjectType),
121}
122
123const PACKAGE: &str = "package";
124
125impl ObjectType {
126 pub fn is_gas_coin(&self) -> bool {
127 matches!(self, ObjectType::Struct(s) if s.is_gas_coin())
128 }
129
130 pub fn is_coin(&self) -> bool {
131 matches!(self, ObjectType::Struct(s) if s.is_coin())
132 }
133
134 pub fn is_package(&self) -> bool {
135 matches!(self, ObjectType::Package)
136 }
137}
138
139impl From<&Object> for ObjectType {
140 fn from(o: &Object) -> Self {
141 o.data
142 .object_type()
143 .map(|t| ObjectType::Struct(t.clone()))
144 .unwrap_or(ObjectType::Package)
145 }
146}
147
148impl TryFrom<ObjectType> for StructTag {
149 type Error = anyhow::Error;
150
151 fn try_from(o: ObjectType) -> Result<Self, anyhow::Error> {
152 match o {
153 ObjectType::Package => Err(anyhow!("Cannot create StructTag from Package")),
154 ObjectType::Struct(s) => Ok(s.into()),
155 }
156 }
157}
158
159impl FromStr for ObjectType {
160 type Err = anyhow::Error;
161
162 fn from_str(s: &str) -> Result<Self, Self::Err> {
163 if s.to_lowercase() == PACKAGE {
164 Ok(ObjectType::Package)
165 } else {
166 let tag = parse_iota_struct_tag(s)?;
167 Ok(ObjectType::Struct(tag.into()))
168 }
169 }
170}
171
172#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
173pub struct ObjectInfo {
174 pub object_id: ObjectId,
175 pub version: SequenceNumber,
176 pub digest: ObjectDigest,
177 pub type_: ObjectType,
178 pub owner: Owner,
179 pub previous_transaction: TransactionDigest,
180}
181
182impl ObjectInfo {
183 pub fn new(oref: &ObjectRef, o: &Object) -> Self {
184 Self {
185 object_id: oref.object_id,
186 version: oref.version,
187 digest: oref.digest,
188 type_: o.into(),
189 owner: o.owner,
190 previous_transaction: o.previous_transaction,
191 }
192 }
193
194 pub fn from_object(object: &Object) -> Self {
195 Self {
196 object_id: object.id(),
197 version: object.version(),
198 digest: object.digest(),
199 type_: object.into(),
200 owner: object.owner,
201 previous_transaction: object.previous_transaction,
202 }
203 }
204}
205
206impl From<ObjectInfo> for ObjectRef {
207 fn from(info: ObjectInfo) -> Self {
208 ObjectRef::new(info.object_id, info.version, info.digest)
209 }
210}
211
212impl From<&ObjectInfo> for ObjectRef {
213 fn from(info: &ObjectInfo) -> Self {
214 ObjectRef::new(info.object_id, info.version, info.digest)
215 }
216}
217
218pub const IOTA_ADDRESS_LENGTH: usize = ObjectId::LENGTH;
219
220pub fn address_from_iota_pub_key<T: IotaPublicKey>(pk: &T) -> IotaAddress {
221 let mut hasher = DefaultHash::default();
222 T::SIGNATURE_SCHEME.update_hasher_with_flag(&mut hasher);
223 hasher.update(pk);
224 let g_arr = hasher.finalize();
225 IotaAddress::new(g_arr.digest)
226}
227
228impl From<&PublicKey> for IotaAddress {
229 fn from(pk: &PublicKey) -> Self {
230 let mut hasher = DefaultHash::default();
231 pk.scheme().update_hasher_with_flag(&mut hasher);
232 hasher.update(pk);
233 let g_arr = hasher.finalize();
234 IotaAddress::new(g_arr.digest)
235 }
236}
237
238impl TryFrom<&GenericSignature> for IotaAddress {
239 type Error = IotaError;
240 fn try_from(sig: &GenericSignature) -> IotaResult<Self> {
243 match sig {
244 GenericSignature::Signature(sig) => {
245 let scheme = sig.scheme();
246 let pub_key_bytes = sig.public_key_bytes();
247 let pub_key = PublicKey::try_from_bytes(scheme, pub_key_bytes).map_err(|_| {
248 IotaError::InvalidSignature {
249 error: "Cannot parse pubkey".to_string(),
250 }
251 })?;
252 Ok(IotaAddress::from(&pub_key))
253 }
254 GenericSignature::MultiSig(ms) => Ok(ms.committee().into()),
255 #[allow(deprecated)]
256 GenericSignature::ZkLoginAuthenticatorDeprecated(_) => {
257 Err(IotaError::UnsupportedFeature {
258 error: "zkLogin is not supported".to_string(),
259 })
260 }
261 GenericSignature::PasskeyAuthenticator(s) => Ok(IotaAddress::from(&s.get_pk()?)),
262 GenericSignature::MoveAuthenticator(move_authenticator) => move_authenticator.address(),
263 }
264 }
265}
266
267pub fn dbg_addr(name: u8) -> IotaAddress {
269 let addr = [name; IOTA_ADDRESS_LENGTH];
270 IotaAddress::new(addr)
271}
272
273#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, Debug)]
274pub struct ExecutionDigests {
275 pub transaction: TransactionDigest,
276 pub effects: TransactionEffectsDigest,
277}
278
279impl ExecutionDigests {
280 pub fn new(transaction: TransactionDigest, effects: TransactionEffectsDigest) -> Self {
281 Self {
282 transaction,
283 effects,
284 }
285 }
286
287 pub fn random() -> Self {
288 Self {
289 transaction: TransactionDigest::random(),
290 effects: TransactionEffectsDigest::random(),
291 }
292 }
293}
294
295#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
296pub struct ExecutionData {
297 pub transaction: Transaction,
298 pub effects: TransactionEffects,
299}
300
301impl ExecutionData {
302 pub fn new(transaction: Transaction, effects: TransactionEffects) -> ExecutionData {
303 debug_assert_eq!(transaction.digest(), effects.transaction_digest());
304 Self {
305 transaction,
306 effects,
307 }
308 }
309
310 pub fn digests(&self) -> ExecutionDigests {
311 self.effects.execution_digests()
312 }
313}
314
315#[derive(Clone, Eq, PartialEq, Debug)]
316pub struct VerifiedExecutionData {
317 pub transaction: VerifiedTransaction,
318 pub effects: TransactionEffects,
319}
320
321impl VerifiedExecutionData {
322 pub fn new(transaction: VerifiedTransaction, effects: TransactionEffects) -> Self {
323 debug_assert_eq!(transaction.digest(), effects.transaction_digest());
324 Self {
325 transaction,
326 effects,
327 }
328 }
329
330 pub fn new_unchecked(data: ExecutionData) -> Self {
331 Self {
332 transaction: VerifiedTransaction::new_unchecked(data.transaction),
333 effects: data.effects,
334 }
335 }
336
337 pub fn into_inner(self) -> ExecutionData {
338 ExecutionData {
339 transaction: self.transaction.into_inner(),
340 effects: self.effects,
341 }
342 }
343
344 pub fn digests(&self) -> ExecutionDigests {
345 self.effects.execution_digests()
346 }
347}
348
349pub const RESOLVED_STD_OPTION: (&AccountAddress, &IdentStr, &IdentStr) = (
350 &MOVE_STDLIB_ADDRESS,
351 ident_str!("option"),
352 ident_str!("Option"),
353);
354
355pub const RESOLVED_ASCII_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
356 &MOVE_STDLIB_ADDRESS,
357 ident_str!("ascii"),
358 ident_str!("String"),
359);
360
361pub const RESOLVED_UTF8_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
362 &MOVE_STDLIB_ADDRESS,
363 ident_str!("string"),
364 ident_str!("String"),
365);
366
367pub fn move_ascii_str_layout() -> A::MoveStructLayout {
368 A::MoveStructLayout {
369 type_: struct_tag_sdk_to_core(&StructTag::new_ascii_string()),
370 fields: vec![A::MoveFieldLayout::new(
371 ident_str!("bytes").into(),
372 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
373 )],
374 }
375}
376
377pub fn move_utf8_str_layout() -> A::MoveStructLayout {
378 A::MoveStructLayout {
379 type_: struct_tag_sdk_to_core(&StructTag::new_string()),
380 fields: vec![A::MoveFieldLayout::new(
381 ident_str!("bytes").into(),
382 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
383 )],
384 }
385}
386
387pub fn url_layout() -> A::MoveStructLayout {
388 A::MoveStructLayout {
389 type_: struct_tag_sdk_to_core(&StructTag::new_url()),
390 fields: vec![A::MoveFieldLayout::new(
391 ident_str!("url").to_owned(),
392 A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
393 )],
394 }
395}
396
397#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
407pub struct MoveLegacyTxContext {
408 sender: AccountAddress,
410 digest: Vec<u8>,
412 epoch: EpochId,
414 epoch_timestamp_ms: CheckpointTimestamp,
416 ids_created: u64,
418}
419
420impl From<&TxContext> for MoveLegacyTxContext {
421 fn from(tx_context: &TxContext) -> Self {
422 Self {
423 sender: tx_context.sender,
424 digest: tx_context.digest.clone(),
425 epoch: tx_context.epoch,
426 epoch_timestamp_ms: tx_context.epoch_timestamp_ms,
427 ids_created: tx_context.ids_created,
428 }
429 }
430}
431
432#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
435pub struct TxContext {
436 sender: AccountAddress,
438 digest: Vec<u8>,
440 epoch: EpochId,
442 epoch_timestamp_ms: CheckpointTimestamp,
444 ids_created: u64,
447 rgp: u64,
449 gas_price: u64,
451 gas_budget: u64,
453 sponsor: Option<AccountAddress>,
455 is_native: bool,
458}
459
460#[derive(PartialEq, Eq, Clone, Copy)]
461pub enum TxContextKind {
462 None,
464 Mutable,
466 Immutable,
468}
469
470impl TxContext {
471 pub fn new(
472 sender: &IotaAddress,
473 digest: &TransactionDigest,
474 epoch_data: &EpochData,
475 rgp: u64,
476 gas_price: u64,
477 gas_budget: u64,
478 sponsor: Option<IotaAddress>,
479 protocol_config: &ProtocolConfig,
480 ) -> Self {
481 Self::new_from_components(
482 sender,
483 digest,
484 &epoch_data.epoch_id(),
485 epoch_data.epoch_start_timestamp(),
486 rgp,
487 gas_price,
488 gas_budget,
489 sponsor,
490 protocol_config,
491 )
492 }
493
494 pub fn new_from_components(
495 sender: &IotaAddress,
496 digest: &TransactionDigest,
497 epoch_id: &EpochId,
498 epoch_timestamp_ms: u64,
499 rgp: u64,
500 gas_price: u64,
501 gas_budget: u64,
502 sponsor: Option<IotaAddress>,
503 protocol_config: &ProtocolConfig,
504 ) -> Self {
505 Self {
506 sender: AccountAddress::new(sender.into_bytes()),
507 digest: digest.into_inner().to_vec(),
508 epoch: *epoch_id,
509 epoch_timestamp_ms,
510 ids_created: 0,
511 rgp,
512 gas_price,
513 gas_budget,
514 sponsor: sponsor.map(|s| AccountAddress::new(s.into_bytes())),
515 is_native: protocol_config.move_native_tx_context(),
516 }
517 }
518
519 pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
522 use SignatureToken as S;
523 let (kind, s) = match s {
524 S::MutableReference(s) => (TxContextKind::Mutable, s),
525 S::Reference(s) => (TxContextKind::Immutable, s),
526 _ => return TxContextKind::None,
527 };
528
529 let S::Datatype(idx) = &**s else {
530 return TxContextKind::None;
531 };
532
533 let (module_addr, module_name, struct_name) = resolve_struct(view, *idx);
534 let is_tx_context_type = module_name.as_str() == Identifier::TX_CONTEXT_MODULE.as_str()
535 && module_addr.as_ref() == IotaAddress::FRAMEWORK.as_bytes()
536 && struct_name.as_str() == Identifier::TX_CONTEXT.as_str();
537
538 if is_tx_context_type {
539 kind
540 } else {
541 TxContextKind::None
542 }
543 }
544
545 pub fn epoch(&self) -> EpochId {
546 self.epoch
547 }
548
549 pub fn epoch_timestamp_ms(&self) -> u64 {
550 self.epoch_timestamp_ms
551 }
552
553 pub fn digest(&self) -> TransactionDigest {
555 TransactionDigest::new(self.digest.clone().try_into().unwrap())
556 }
557
558 pub fn sponsor(&self) -> Option<IotaAddress> {
559 self.sponsor.map(|a| IotaAddress::from(a.into_bytes()))
560 }
561
562 pub fn rgp(&self) -> u64 {
563 self.rgp
564 }
565
566 pub fn gas_price(&self) -> u64 {
567 self.gas_price
568 }
569
570 pub fn gas_budget(&self) -> u64 {
571 self.gas_budget
572 }
573
574 pub fn ids_created(&self) -> u64 {
575 self.ids_created
576 }
577
578 pub fn fresh_id(&mut self) -> ObjectId {
581 let id = ObjectId::derive_id(self.digest(), self.ids_created);
582 self.ids_created += 1;
583 id
584 }
585
586 pub fn sender(&self) -> IotaAddress {
587 IotaAddress::new(self.sender.into_bytes())
588 }
589
590 pub fn to_vec(&self) -> Vec<u8> {
591 bcs::to_bytes(&self).unwrap()
592 }
593
594 pub fn to_bcs_legacy_context(&self) -> Vec<u8> {
598 let move_context: MoveLegacyTxContext = if self.is_native {
599 let tx_context = &TxContext {
600 sender: AccountAddress::ZERO,
601 digest: vec![],
602 epoch: 0,
603 epoch_timestamp_ms: 0,
604 ids_created: 0,
605 rgp: 0,
606 gas_price: 0,
607 gas_budget: 0,
608 sponsor: None,
609 is_native: true,
610 };
611 tx_context.into()
612 } else {
613 self.into()
614 };
615 bcs::to_bytes(&move_context).unwrap()
616 }
617
618 pub fn update_state(&mut self, other: MoveLegacyTxContext) -> Result<(), ExecutionError> {
623 if !self.is_native {
624 if self.sender != other.sender
625 || self.digest != other.digest
626 || other.ids_created < self.ids_created
627 {
628 return Err(ExecutionError::new_with_source(
629 ExecutionErrorKind::InvariantViolation,
630 "Immutable fields for TxContext changed",
631 ));
632 }
633 self.ids_created = other.ids_created;
634 }
635 Ok(())
636 }
637
638 pub fn replace(
640 &mut self,
641 sender: AccountAddress,
642 tx_hash: Vec<u8>,
643 epoch: u64,
644 epoch_timestamp_ms: u64,
645 ids_created: u64,
646 rgp: u64,
647 gas_price: u64,
648 gas_budget: u64,
649 sponsor: Option<AccountAddress>,
650 ) {
651 self.sender = sender;
652 self.digest = tx_hash;
653 self.epoch = epoch;
654 self.epoch_timestamp_ms = epoch_timestamp_ms;
655 self.ids_created = ids_created;
656 self.rgp = rgp;
657 self.gas_price = gas_price;
658 self.gas_budget = gas_budget;
659 self.sponsor = sponsor;
660 }
661
662 #[cfg(not(target_arch = "wasm32"))]
664 pub fn random_for_testing_only() -> Self {
665 Self::new(
666 &IotaAddress::random(),
667 &TransactionDigest::random(),
668 &EpochData::new_test(),
669 0,
670 0,
671 0,
672 None,
673 &ProtocolConfig::get_for_max_version_UNSAFE(),
674 )
675 }
676}
677
678pub fn dbg_object_id(name: u8) -> ObjectId {
680 ObjectId::new([name; ObjectId::LENGTH])
681}
682
683#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
684pub enum ObjectIdParseError {
685 #[error("ObjectId hex literal must start with 0x")]
686 HexLiteralPrefixMissing,
687
688 #[error("Could not convert from bytes slice")]
689 TryFromSlice,
690}
691
692impl fmt::Display for ObjectType {
693 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
694 match self {
695 ObjectType::Package => write!(f, "{PACKAGE}"),
696 ObjectType::Struct(t) => write!(
697 f,
698 "{}",
699 to_iota_struct_tag_string(t).map_err(fmt::Error::custom)?
700 ),
701 }
702 }
703}
704
705#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
711#[serde(try_from = "Vec<T>")]
712pub struct SizeOneVec<T> {
713 e: T,
714}
715
716impl<T> SizeOneVec<T> {
717 pub fn new(e: T) -> Self {
718 Self { e }
719 }
720
721 pub fn element(&self) -> &T {
722 &self.e
723 }
724
725 pub fn element_mut(&mut self) -> &mut T {
726 &mut self.e
727 }
728
729 pub fn into_inner(self) -> T {
730 self.e
731 }
732
733 pub fn iter(&self) -> std::iter::Once<&T> {
734 std::iter::once(&self.e)
735 }
736}
737
738impl<T> Serialize for SizeOneVec<T>
739where
740 T: Serialize,
741{
742 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
743 where
744 S: Serializer,
745 {
746 let mut seq = serializer.serialize_seq(Some(1))?;
747 seq.serialize_element(&self.e)?;
748 seq.end()
749 }
750}
751
752impl<T> TryFrom<Vec<T>> for SizeOneVec<T> {
753 type Error = anyhow::Error;
754
755 fn try_from(mut v: Vec<T>) -> Result<Self, Self::Error> {
756 if v.len() != 1 {
757 Err(anyhow!("Expected a vec of size 1"))
758 } else {
759 Ok(SizeOneVec {
760 e: v.pop().unwrap(),
761 })
762 }
763 }
764}
765
766#[test]
767fn test_size_one_vec_is_transparent() {
768 let regular = vec![42u8];
769 let size_one = SizeOneVec::new(42u8);
770
771 let regular_ser = bcs::to_bytes(®ular).unwrap();
773 let size_one_deser = bcs::from_bytes::<SizeOneVec<u8>>(®ular_ser).unwrap();
774 assert_eq!(size_one, size_one_deser);
775
776 let size_one_ser = bcs::to_bytes(&SizeOneVec::new(43u8)).unwrap();
778 let regular_deser = bcs::from_bytes::<Vec<u8>>(&size_one_ser).unwrap();
779 assert_eq!(regular_deser, vec![43u8]);
780
781 let empty_ser = bcs::to_bytes(&Vec::<u8>::new()).unwrap();
783 bcs::from_bytes::<SizeOneVec<u8>>(&empty_ser).unwrap_err();
784
785 let size_greater_than_one_ser = bcs::to_bytes(&vec![1u8, 2u8]).unwrap();
786 bcs::from_bytes::<SizeOneVec<u8>>(&size_greater_than_one_ser).unwrap_err();
787}