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 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 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 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 fn move_ascii_str_layout() -> A::MoveStructLayout {
333 A::MoveStructLayout {
334 type_: struct_tag_sdk_to_core(&StructTag::new_ascii_string()),
335 fields: vec![A::MoveFieldLayout::new(
336 ident_str!("bytes").into(),
337 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
338 )],
339 }
340}
341
342pub fn move_utf8_str_layout() -> A::MoveStructLayout {
343 A::MoveStructLayout {
344 type_: struct_tag_sdk_to_core(&StructTag::new_string()),
345 fields: vec![A::MoveFieldLayout::new(
346 ident_str!("bytes").into(),
347 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
348 )],
349 }
350}
351
352pub fn url_layout() -> A::MoveStructLayout {
353 A::MoveStructLayout {
354 type_: struct_tag_sdk_to_core(&StructTag::new_url()),
355 fields: vec![A::MoveFieldLayout::new(
356 ident_str!("url").to_owned(),
357 A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
358 )],
359 }
360}
361
362#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
372pub struct MoveLegacyTxContext {
373 sender: AccountAddress,
375 digest: Vec<u8>,
377 epoch: EpochId,
379 epoch_timestamp_ms: CheckpointTimestamp,
381 ids_created: u64,
383}
384
385impl From<&TxContext> for MoveLegacyTxContext {
386 fn from(tx_context: &TxContext) -> Self {
387 Self {
388 sender: tx_context.sender,
389 digest: tx_context.digest.clone(),
390 epoch: tx_context.epoch,
391 epoch_timestamp_ms: tx_context.epoch_timestamp_ms,
392 ids_created: tx_context.ids_created,
393 }
394 }
395}
396
397#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
400pub struct TxContext {
401 sender: AccountAddress,
403 digest: Vec<u8>,
405 epoch: EpochId,
407 epoch_timestamp_ms: CheckpointTimestamp,
409 ids_created: u64,
412 rgp: u64,
414 gas_price: u64,
416 gas_budget: u64,
418 sponsor: Option<AccountAddress>,
420 is_native: bool,
423}
424
425#[derive(PartialEq, Eq, Clone, Copy)]
426pub enum TxContextKind {
427 None,
429 Mutable,
431 Immutable,
433}
434
435impl TxContext {
436 pub fn new(
437 sender: &Address,
438 digest: &TransactionDigest,
439 epoch_data: &EpochData,
440 rgp: u64,
441 gas_price: u64,
442 gas_budget: u64,
443 sponsor: Option<Address>,
444 protocol_config: &ProtocolConfig,
445 ) -> Self {
446 Self::new_from_components(
447 sender,
448 digest,
449 &epoch_data.epoch_id(),
450 epoch_data.epoch_start_timestamp(),
451 rgp,
452 gas_price,
453 gas_budget,
454 sponsor,
455 protocol_config,
456 )
457 }
458
459 pub fn new_from_components(
460 sender: &Address,
461 digest: &TransactionDigest,
462 epoch_id: &EpochId,
463 epoch_timestamp_ms: u64,
464 rgp: u64,
465 gas_price: u64,
466 gas_budget: u64,
467 sponsor: Option<Address>,
468 protocol_config: &ProtocolConfig,
469 ) -> Self {
470 Self {
471 sender: AccountAddress::new(sender.into_bytes()),
472 digest: digest.into_inner().to_vec(),
473 epoch: *epoch_id,
474 epoch_timestamp_ms,
475 ids_created: 0,
476 rgp,
477 gas_price,
478 gas_budget,
479 sponsor: sponsor.map(|s| AccountAddress::new(s.into_bytes())),
480 is_native: protocol_config.move_native_tx_context(),
481 }
482 }
483
484 pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
487 use SignatureToken as S;
488 let (kind, s) = match s {
489 S::MutableReference(s) => (TxContextKind::Mutable, s),
490 S::Reference(s) => (TxContextKind::Immutable, s),
491 _ => return TxContextKind::None,
492 };
493
494 let S::Datatype(idx) = &**s else {
495 return TxContextKind::None;
496 };
497
498 let (module_addr, module_name, struct_name) = resolve_struct(view, *idx);
499 let is_tx_context_type = module_name.as_str() == Identifier::TX_CONTEXT_MODULE.as_str()
500 && module_addr.as_ref() == Address::FRAMEWORK.as_bytes()
501 && struct_name.as_str() == Identifier::TX_CONTEXT.as_str();
502
503 if is_tx_context_type {
504 kind
505 } else {
506 TxContextKind::None
507 }
508 }
509
510 pub fn epoch(&self) -> EpochId {
511 self.epoch
512 }
513
514 pub fn epoch_timestamp_ms(&self) -> u64 {
515 self.epoch_timestamp_ms
516 }
517
518 pub fn digest(&self) -> TransactionDigest {
520 TransactionDigest::new(self.digest.clone().try_into().unwrap())
521 }
522
523 pub fn sponsor(&self) -> Option<Address> {
524 self.sponsor.map(|a| Address::from(a.into_bytes()))
525 }
526
527 pub fn rgp(&self) -> u64 {
528 self.rgp
529 }
530
531 pub fn gas_price(&self) -> u64 {
532 self.gas_price
533 }
534
535 pub fn gas_budget(&self) -> u64 {
536 self.gas_budget
537 }
538
539 pub fn ids_created(&self) -> u64 {
540 self.ids_created
541 }
542
543 pub fn fresh_id(&mut self) -> ObjectId {
546 let id = ObjectId::derive_id(self.digest(), self.ids_created);
547 self.ids_created += 1;
548 id
549 }
550
551 pub fn sender(&self) -> Address {
552 Address::new(self.sender.into_bytes())
553 }
554
555 pub fn to_vec(&self) -> Vec<u8> {
556 bcs::to_bytes(&self).unwrap()
557 }
558
559 pub fn to_bcs_legacy_context(&self) -> Vec<u8> {
563 let move_context: MoveLegacyTxContext = if self.is_native {
564 let tx_context = &TxContext {
565 sender: AccountAddress::ZERO,
566 digest: vec![],
567 epoch: 0,
568 epoch_timestamp_ms: 0,
569 ids_created: 0,
570 rgp: 0,
571 gas_price: 0,
572 gas_budget: 0,
573 sponsor: None,
574 is_native: true,
575 };
576 tx_context.into()
577 } else {
578 self.into()
579 };
580 bcs::to_bytes(&move_context).unwrap()
581 }
582
583 pub fn update_state(&mut self, other: MoveLegacyTxContext) -> Result<(), ExecutionError> {
588 if !self.is_native {
589 if self.sender != other.sender
590 || self.digest != other.digest
591 || other.ids_created < self.ids_created
592 {
593 return Err(ExecutionError::new_with_source(
594 ExecutionErrorKind::InvariantViolation,
595 "Immutable fields for TxContext changed",
596 ));
597 }
598 self.ids_created = other.ids_created;
599 }
600 Ok(())
601 }
602
603 pub fn replace(
605 &mut self,
606 sender: AccountAddress,
607 tx_hash: Vec<u8>,
608 epoch: u64,
609 epoch_timestamp_ms: u64,
610 ids_created: u64,
611 rgp: u64,
612 gas_price: u64,
613 gas_budget: u64,
614 sponsor: Option<AccountAddress>,
615 ) {
616 self.sender = sender;
617 self.digest = tx_hash;
618 self.epoch = epoch;
619 self.epoch_timestamp_ms = epoch_timestamp_ms;
620 self.ids_created = ids_created;
621 self.rgp = rgp;
622 self.gas_price = gas_price;
623 self.gas_budget = gas_budget;
624 self.sponsor = sponsor;
625 }
626
627 #[cfg(not(target_arch = "wasm32"))]
629 pub fn random_for_testing_only() -> Self {
630 Self::new(
631 &Address::random(),
632 &TransactionDigest::random(),
633 &EpochData::new_test(),
634 0,
635 0,
636 0,
637 None,
638 &ProtocolConfig::get_for_max_version_UNSAFE(),
639 )
640 }
641}
642
643pub fn dbg_object_id(name: u8) -> ObjectId {
645 ObjectId::new([name; ObjectId::LENGTH])
646}
647
648#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
649pub enum ObjectIdParseError {
650 #[error("ObjectId hex literal must start with 0x")]
651 HexLiteralPrefixMissing,
652
653 #[error("Could not convert from bytes slice")]
654 TryFromSlice,
655}
656
657impl fmt::Display for ObjectType {
658 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
659 match self {
660 ObjectType::Package => write!(f, "{PACKAGE}"),
661 ObjectType::Struct(t) => write!(
662 f,
663 "{}",
664 to_iota_struct_tag_string(t).map_err(fmt::Error::custom)?
665 ),
666 }
667 }
668}