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