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