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