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::{
16 Address, Identifier, MoveObjectType, ObjectDigest, ObjectId, ObjectReference, Owner,
17 SignatureScheme, StructTag, TransactionDigest, TransactionEffects, TransactionEffectsDigest,
18 TypeTag, Version,
19};
20use move_binary_format::{CompiledModule, file_format::SignatureToken};
21use move_bytecode_utils::resolve_struct;
22use move_core_types::{
23 account_address::AccountAddress, annotated_value as A, ident_str, identifier::IdentStr,
24};
25use serde::{Deserialize, Serialize, ser::Error};
26
27pub use crate::committee::EpochId;
28use crate::{
29 IOTA_FRAMEWORK_ADDRESS, MOVE_STDLIB_ADDRESS,
30 crypto::{AuthorityPublicKeyBytes, DefaultHash, PublicKey},
31 effects::{TransactionEffectsAPI, TransactionEffectsExt},
32 epoch_data::EpochData,
33 error::{ExecutionError, ExecutionErrorKind},
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 transaction::{TransactionEnvelope, VerifiedTransaction},
40};
41
42#[cfg(test)]
43#[path = "unit_tests/base_types_tests.rs"]
44mod base_types_tests;
45
46pub type TxSequenceNumber = u64;
47
48pub type VersionNumber = Version;
49
50pub type CommitRound = u64;
52
53pub type AuthorityName = AuthorityPublicKeyBytes;
54
55pub trait ConciseableName<'a> {
56 type ConciseTypeRef: std::fmt::Debug;
57 type ConciseType: std::fmt::Debug;
58
59 fn concise(&'a self) -> Self::ConciseTypeRef;
60 fn concise_owned(&self) -> Self::ConciseType;
61}
62
63pub type VersionDigest = (Version, ObjectDigest);
64
65pub fn random_object_ref() -> ObjectReference {
66 ObjectReference::new(
67 ObjectId::random(),
68 Version::default(),
69 ObjectDigest::new([0; 32]),
70 )
71}
72
73pub fn is_primitive_type_tag(t: &TypeTag) -> bool {
75 use TypeTag as T;
76
77 match t {
78 T::Bool | T::U8 | T::U16 | T::U32 | T::U64 | T::U128 | T::U256 | T::Address => true,
79 T::Vector(inner) => is_primitive_type_tag(inner),
80 T::Struct(st) => {
81 let resolved_struct = (
82 &AccountAddress::new(st.address().into_bytes()),
83 move_core_types::identifier::IdentStr::new(st.module().as_str()).unwrap(),
84 move_core_types::identifier::IdentStr::new(st.name().as_str()).unwrap(),
85 );
86 if resolved_struct == RESOLVED_IOTA_ID {
88 return true;
89 }
90 if resolved_struct == RESOLVED_UTF8_STR {
92 return true;
93 }
94 if resolved_struct == RESOLVED_ASCII_STR {
96 return true;
97 }
98 resolved_struct == RESOLVED_STD_OPTION
100 && st.type_params().len() == 1
101 && is_primitive_type_tag(&st.type_params()[0])
102 }
103 T::Signer => false,
104 }
105}
106
107#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
109pub enum ObjectType {
110 Package,
112 Struct(MoveObjectType),
114}
115
116const PACKAGE: &str = "package";
117
118impl ObjectType {
119 pub fn is_gas_coin(&self) -> bool {
120 matches!(self, ObjectType::Struct(s) if s.is_gas_coin())
121 }
122
123 pub fn is_coin(&self) -> bool {
124 matches!(self, ObjectType::Struct(s) if s.is_coin())
125 }
126
127 pub fn is_package(&self) -> bool {
128 matches!(self, ObjectType::Package)
129 }
130}
131
132impl From<&Object> for ObjectType {
133 fn from(o: &Object) -> Self {
134 o.data
135 .opt_object_type()
136 .map(|t| ObjectType::Struct(t.clone()))
137 .unwrap_or(ObjectType::Package)
138 }
139}
140
141impl TryFrom<ObjectType> for StructTag {
142 type Error = anyhow::Error;
143
144 fn try_from(o: ObjectType) -> Result<Self, anyhow::Error> {
145 match o {
146 ObjectType::Package => Err(anyhow!("Cannot create StructTag from Package")),
147 ObjectType::Struct(s) => Ok(s.into()),
148 }
149 }
150}
151
152impl FromStr for ObjectType {
153 type Err = anyhow::Error;
154
155 fn from_str(s: &str) -> Result<Self, Self::Err> {
156 if s.to_lowercase() == PACKAGE {
157 Ok(ObjectType::Package)
158 } else {
159 let tag = parse_iota_struct_tag(s)?;
160 Ok(ObjectType::Struct(tag.into()))
161 }
162 }
163}
164
165#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
166pub struct ObjectInfo {
167 pub object_id: ObjectId,
168 pub version: Version,
169 pub digest: ObjectDigest,
170 pub object_type: ObjectType,
171 pub owner: Owner,
172 pub previous_transaction: TransactionDigest,
173}
174
175impl ObjectInfo {
176 pub fn new(oref: &ObjectReference, o: &Object) -> Self {
177 Self {
178 object_id: oref.object_id,
179 version: oref.version,
180 digest: oref.digest,
181 object_type: o.into(),
182 owner: o.owner,
183 previous_transaction: o.previous_transaction,
184 }
185 }
186
187 pub fn from_object(object: &Object) -> Self {
188 Self {
189 object_id: object.id(),
190 version: object.version(),
191 digest: object.digest(),
192 object_type: object.into(),
193 owner: object.owner,
194 previous_transaction: object.previous_transaction,
195 }
196 }
197}
198
199impl From<ObjectInfo> for ObjectReference {
200 fn from(info: ObjectInfo) -> Self {
201 ObjectReference::new(info.object_id, info.version, info.digest)
202 }
203}
204
205impl From<&ObjectInfo> for ObjectReference {
206 fn from(info: &ObjectInfo) -> Self {
207 ObjectReference::new(info.object_id, info.version, info.digest)
208 }
209}
210
211pub const IOTA_ADDRESS_LENGTH: usize = ObjectId::LENGTH;
212
213fn update_hasher_with_flag(hasher: &mut DefaultHash, scheme: SignatureScheme) {
216 if scheme != SignatureScheme::Ed25519 {
217 hasher.update([scheme.to_u8()]);
218 }
219}
220
221impl From<&PublicKey> for Address {
222 fn from(pk: &PublicKey) -> Self {
223 let mut hasher = DefaultHash::default();
224 update_hasher_with_flag(&mut hasher, pk.scheme());
225 hasher.update(pk);
226 let g_arr = hasher.finalize();
227 Address::new(g_arr.digest)
228 }
229}
230
231pub fn dbg_addr(name: u8) -> Address {
233 let addr = [name; IOTA_ADDRESS_LENGTH];
234 Address::new(addr)
235}
236
237#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, Debug)]
238pub struct ExecutionDigests {
239 pub transaction: TransactionDigest,
240 pub effects: TransactionEffectsDigest,
241}
242
243impl ExecutionDigests {
244 pub fn new(transaction: TransactionDigest, effects: TransactionEffectsDigest) -> Self {
245 Self {
246 transaction,
247 effects,
248 }
249 }
250
251 pub fn random() -> Self {
252 Self {
253 transaction: TransactionDigest::random(),
254 effects: TransactionEffectsDigest::random(),
255 }
256 }
257}
258
259#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
260pub struct ExecutionData {
261 pub transaction: TransactionEnvelope,
262 pub effects: TransactionEffects,
263}
264
265impl ExecutionData {
266 pub fn new(transaction: TransactionEnvelope, effects: TransactionEffects) -> ExecutionData {
267 debug_assert_eq!(transaction.digest(), effects.transaction_digest());
268 Self {
269 transaction,
270 effects,
271 }
272 }
273
274 pub fn digests(&self) -> ExecutionDigests {
275 self.effects.execution_digests()
276 }
277}
278
279#[derive(Clone, Eq, PartialEq, Debug)]
280pub struct VerifiedExecutionData {
281 pub transaction: VerifiedTransaction,
282 pub effects: TransactionEffects,
283}
284
285impl VerifiedExecutionData {
286 pub fn new(transaction: VerifiedTransaction, effects: TransactionEffects) -> Self {
287 debug_assert_eq!(transaction.digest(), effects.transaction_digest());
288 Self {
289 transaction,
290 effects,
291 }
292 }
293
294 pub fn new_unchecked(data: ExecutionData) -> Self {
295 Self {
296 transaction: VerifiedTransaction::new_unchecked(data.transaction),
297 effects: data.effects,
298 }
299 }
300
301 pub fn into_inner(self) -> ExecutionData {
302 ExecutionData {
303 transaction: self.transaction.into_inner(),
304 effects: self.effects,
305 }
306 }
307
308 pub fn digests(&self) -> ExecutionDigests {
309 self.effects.execution_digests()
310 }
311}
312
313pub const RESOLVED_STD_OPTION: (&AccountAddress, &IdentStr, &IdentStr) = (
314 &MOVE_STDLIB_ADDRESS,
315 ident_str!("option"),
316 ident_str!("Option"),
317);
318
319pub const RESOLVED_ASCII_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
320 &MOVE_STDLIB_ADDRESS,
321 ident_str!("ascii"),
322 ident_str!("String"),
323);
324
325pub const RESOLVED_UTF8_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
326 &MOVE_STDLIB_ADDRESS,
327 ident_str!("string"),
328 ident_str!("String"),
329);
330
331pub const RESOLVED_TX_CONTEXT: (&AccountAddress, &IdentStr, &IdentStr) = (
332 &crate::IOTA_FRAMEWORK_ADDRESS,
333 ident_str!("tx_context"),
334 ident_str!("TxContext"),
335);
336
337pub const RESOLVED_IOTA_ID: (&AccountAddress, &IdentStr, &IdentStr) = (
338 &IOTA_FRAMEWORK_ADDRESS,
339 ident_str!("object"),
340 ident_str!("ID"),
341);
342
343pub fn move_ascii_str_layout() -> A::MoveStructLayout {
344 A::MoveStructLayout {
345 type_: struct_tag_sdk_to_core(&StructTag::new_ascii_string()),
346 fields: vec![A::MoveFieldLayout::new(
347 ident_str!("bytes").into(),
348 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
349 )],
350 }
351}
352
353pub fn move_utf8_str_layout() -> A::MoveStructLayout {
354 A::MoveStructLayout {
355 type_: struct_tag_sdk_to_core(&StructTag::new_string()),
356 fields: vec![A::MoveFieldLayout::new(
357 ident_str!("bytes").into(),
358 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
359 )],
360 }
361}
362
363pub fn url_layout() -> A::MoveStructLayout {
364 A::MoveStructLayout {
365 type_: struct_tag_sdk_to_core(&StructTag::new_url()),
366 fields: vec![A::MoveFieldLayout::new(
367 ident_str!("url").to_owned(),
368 A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
369 )],
370 }
371}
372
373#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
383pub struct MoveLegacyTxContext {
384 sender: AccountAddress,
386 digest: Vec<u8>,
388 epoch: EpochId,
390 epoch_timestamp_ms: CheckpointTimestamp,
392 ids_created: u64,
394}
395
396impl From<&TxContext> for MoveLegacyTxContext {
397 fn from(tx_context: &TxContext) -> Self {
398 Self {
399 sender: tx_context.sender,
400 digest: tx_context.digest.clone(),
401 epoch: tx_context.epoch,
402 epoch_timestamp_ms: tx_context.epoch_timestamp_ms,
403 ids_created: tx_context.ids_created,
404 }
405 }
406}
407
408#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
411pub struct TxContext {
412 sender: AccountAddress,
414 digest: Vec<u8>,
416 epoch: EpochId,
418 epoch_timestamp_ms: CheckpointTimestamp,
420 ids_created: u64,
423 rgp: u64,
425 gas_price: u64,
427 gas_budget: u64,
429 sponsor: Option<AccountAddress>,
431 is_native: bool,
434}
435
436#[derive(PartialEq, Eq, Clone, Copy)]
437pub enum TxContextKind {
438 None,
440 Mutable,
442 Immutable,
444}
445
446impl TxContext {
447 pub fn new(
448 sender: &Address,
449 digest: &TransactionDigest,
450 epoch_data: &EpochData,
451 rgp: u64,
452 gas_price: u64,
453 gas_budget: u64,
454 sponsor: Option<Address>,
455 protocol_config: &ProtocolConfig,
456 ) -> Self {
457 Self::new_from_components(
458 sender,
459 digest,
460 &epoch_data.epoch_id(),
461 epoch_data.epoch_start_timestamp(),
462 rgp,
463 gas_price,
464 gas_budget,
465 sponsor,
466 protocol_config,
467 )
468 }
469
470 pub fn new_from_components(
471 sender: &Address,
472 digest: &TransactionDigest,
473 epoch_id: &EpochId,
474 epoch_timestamp_ms: u64,
475 rgp: u64,
476 gas_price: u64,
477 gas_budget: u64,
478 sponsor: Option<Address>,
479 protocol_config: &ProtocolConfig,
480 ) -> Self {
481 Self {
482 sender: AccountAddress::new(sender.into_bytes()),
483 digest: digest.into_bytes().to_vec(),
484 epoch: *epoch_id,
485 epoch_timestamp_ms,
486 ids_created: 0,
487 rgp,
488 gas_price,
489 gas_budget,
490 sponsor: sponsor.map(|s| AccountAddress::new(s.into_bytes())),
491 is_native: protocol_config.move_native_tx_context(),
492 }
493 }
494
495 pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
498 use SignatureToken as S;
499 let (kind, s) = match s {
500 S::MutableReference(s) => (TxContextKind::Mutable, s),
501 S::Reference(s) => (TxContextKind::Immutable, s),
502 _ => return TxContextKind::None,
503 };
504
505 let S::Datatype(idx) = &**s else {
506 return TxContextKind::None;
507 };
508
509 let (module_addr, module_name, struct_name) = resolve_struct(view, *idx);
510 let is_tx_context_type = module_name.as_str() == Identifier::TX_CONTEXT_MODULE.as_str()
511 && module_addr.as_ref() == Address::FRAMEWORK.as_bytes()
512 && struct_name.as_str() == Identifier::TX_CONTEXT.as_str();
513
514 if is_tx_context_type {
515 kind
516 } else {
517 TxContextKind::None
518 }
519 }
520
521 pub fn epoch(&self) -> EpochId {
522 self.epoch
523 }
524
525 pub fn epoch_timestamp_ms(&self) -> u64 {
526 self.epoch_timestamp_ms
527 }
528
529 pub fn digest(&self) -> TransactionDigest {
531 TransactionDigest::new(self.digest.clone().try_into().unwrap())
532 }
533
534 pub fn sponsor(&self) -> Option<Address> {
535 self.sponsor.map(|a| Address::from(a.into_bytes()))
536 }
537
538 pub fn rgp(&self) -> u64 {
539 self.rgp
540 }
541
542 pub fn gas_price(&self) -> u64 {
543 self.gas_price
544 }
545
546 pub fn gas_budget(&self) -> u64 {
547 self.gas_budget
548 }
549
550 pub fn ids_created(&self) -> u64 {
551 self.ids_created
552 }
553
554 pub fn fresh_id(&mut self) -> ObjectId {
557 let id = ObjectId::derive_id(self.digest(), self.ids_created);
558 self.ids_created += 1;
559 id
560 }
561
562 pub fn sender(&self) -> Address {
563 Address::new(self.sender.into_bytes())
564 }
565
566 pub fn to_vec(&self) -> Vec<u8> {
567 bcs::to_bytes(&self).unwrap()
568 }
569
570 pub fn to_bcs_legacy_context(&self) -> Vec<u8> {
574 let move_context: MoveLegacyTxContext = if self.is_native {
575 let tx_context = &TxContext {
576 sender: AccountAddress::ZERO,
577 digest: vec![],
578 epoch: 0,
579 epoch_timestamp_ms: 0,
580 ids_created: 0,
581 rgp: 0,
582 gas_price: 0,
583 gas_budget: 0,
584 sponsor: None,
585 is_native: true,
586 };
587 tx_context.into()
588 } else {
589 self.into()
590 };
591 bcs::to_bytes(&move_context).unwrap()
592 }
593
594 pub fn update_state(&mut self, other: MoveLegacyTxContext) -> Result<(), ExecutionError> {
599 if !self.is_native {
600 if self.sender != other.sender
601 || self.digest != other.digest
602 || other.ids_created < self.ids_created
603 {
604 return Err(ExecutionError::new_with_source(
605 ExecutionErrorKind::InvariantViolation,
606 "Immutable fields for TxContext changed",
607 ));
608 }
609 self.ids_created = other.ids_created;
610 }
611 Ok(())
612 }
613
614 pub fn replace(
616 &mut self,
617 sender: AccountAddress,
618 tx_hash: Vec<u8>,
619 epoch: u64,
620 epoch_timestamp_ms: u64,
621 ids_created: u64,
622 rgp: u64,
623 gas_price: u64,
624 gas_budget: u64,
625 sponsor: Option<AccountAddress>,
626 ) {
627 self.sender = sender;
628 self.digest = tx_hash;
629 self.epoch = epoch;
630 self.epoch_timestamp_ms = epoch_timestamp_ms;
631 self.ids_created = ids_created;
632 self.rgp = rgp;
633 self.gas_price = gas_price;
634 self.gas_budget = gas_budget;
635 self.sponsor = sponsor;
636 }
637
638 #[cfg(not(target_arch = "wasm32"))]
640 pub fn random_for_testing_only() -> Self {
641 Self::new(
642 &Address::random(),
643 &TransactionDigest::random(),
644 &EpochData::new_test(),
645 0,
646 0,
647 0,
648 None,
649 &ProtocolConfig::get_for_max_version_UNSAFE(),
650 )
651 }
652}
653
654pub fn dbg_object_id(name: u8) -> ObjectId {
656 ObjectId::new([name; ObjectId::LENGTH])
657}
658
659#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
660pub enum ObjectIdParseError {
661 #[error("ObjectId hex literal must start with 0x")]
662 HexLiteralPrefixMissing,
663
664 #[error("Could not convert from bytes slice")]
665 TryFromSlice,
666}
667
668impl fmt::Display for ObjectType {
669 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
670 match self {
671 ObjectType::Package => write!(f, "{PACKAGE}"),
672 ObjectType::Struct(t) => write!(
673 f,
674 "{}",
675 to_iota_struct_tag_string(t).map_err(fmt::Error::custom)?
676 ),
677 }
678 }
679}