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