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, TransactionEffectsDigest, TypeTag, Version,
18};
19use move_binary_format::{CompiledModule, file_format::SignatureToken};
20use move_bytecode_utils::resolve_struct;
21use move_core_types::{
22 account_address::AccountAddress, annotated_value as A, ident_str, identifier::IdentStr,
23};
24use serde::{Deserialize, Serialize, ser::Error};
25
26pub use crate::committee::EpochId;
27use crate::{
28 MOVE_STDLIB_ADDRESS,
29 crypto::{AuthorityPublicKeyBytes, DefaultHash, IotaPublicKey, PublicKey},
30 effects::{TransactionEffects, TransactionEffectsAPI, TransactionEffectsExt},
31 epoch_data::EpochData,
32 error::{ExecutionError, ExecutionErrorKind},
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 transaction::{Transaction, 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 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 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 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
221pub fn address_from_iota_pub_key<T: IotaPublicKey>(pk: &T) -> Address {
222 let mut hasher = DefaultHash::default();
223 update_hasher_with_flag(&mut hasher, T::SIGNATURE_SCHEME);
224 hasher.update(pk);
225 let g_arr = hasher.finalize();
226 Address::new(g_arr.digest)
227}
228
229impl From<&PublicKey> for Address {
230 fn from(pk: &PublicKey) -> Self {
231 let mut hasher = DefaultHash::default();
232 update_hasher_with_flag(&mut hasher, pk.scheme());
233 hasher.update(pk);
234 let g_arr = hasher.finalize();
235 Address::new(g_arr.digest)
236 }
237}
238
239pub fn dbg_addr(name: u8) -> Address {
241 let addr = [name; IOTA_ADDRESS_LENGTH];
242 Address::new(addr)
243}
244
245#[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, Debug)]
246pub struct ExecutionDigests {
247 pub transaction: TransactionDigest,
248 pub effects: TransactionEffectsDigest,
249}
250
251impl ExecutionDigests {
252 pub fn new(transaction: TransactionDigest, effects: TransactionEffectsDigest) -> Self {
253 Self {
254 transaction,
255 effects,
256 }
257 }
258
259 pub fn random() -> Self {
260 Self {
261 transaction: TransactionDigest::random(),
262 effects: TransactionEffectsDigest::random(),
263 }
264 }
265}
266
267#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
268pub struct ExecutionData {
269 pub transaction: Transaction,
270 pub effects: TransactionEffects,
271}
272
273impl ExecutionData {
274 pub fn new(transaction: Transaction, effects: TransactionEffects) -> ExecutionData {
275 debug_assert_eq!(transaction.digest(), effects.transaction_digest());
276 Self {
277 transaction,
278 effects,
279 }
280 }
281
282 pub fn digests(&self) -> ExecutionDigests {
283 self.effects.execution_digests()
284 }
285}
286
287#[derive(Clone, Eq, PartialEq, Debug)]
288pub struct VerifiedExecutionData {
289 pub transaction: VerifiedTransaction,
290 pub effects: TransactionEffects,
291}
292
293impl VerifiedExecutionData {
294 pub fn new(transaction: VerifiedTransaction, effects: TransactionEffects) -> Self {
295 debug_assert_eq!(transaction.digest(), effects.transaction_digest());
296 Self {
297 transaction,
298 effects,
299 }
300 }
301
302 pub fn new_unchecked(data: ExecutionData) -> Self {
303 Self {
304 transaction: VerifiedTransaction::new_unchecked(data.transaction),
305 effects: data.effects,
306 }
307 }
308
309 pub fn into_inner(self) -> ExecutionData {
310 ExecutionData {
311 transaction: self.transaction.into_inner(),
312 effects: self.effects,
313 }
314 }
315
316 pub fn digests(&self) -> ExecutionDigests {
317 self.effects.execution_digests()
318 }
319}
320
321pub const RESOLVED_STD_OPTION: (&AccountAddress, &IdentStr, &IdentStr) = (
322 &MOVE_STDLIB_ADDRESS,
323 ident_str!("option"),
324 ident_str!("Option"),
325);
326
327pub const RESOLVED_ASCII_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
328 &MOVE_STDLIB_ADDRESS,
329 ident_str!("ascii"),
330 ident_str!("String"),
331);
332
333pub const RESOLVED_UTF8_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
334 &MOVE_STDLIB_ADDRESS,
335 ident_str!("string"),
336 ident_str!("String"),
337);
338
339pub fn move_ascii_str_layout() -> A::MoveStructLayout {
340 A::MoveStructLayout {
341 type_: struct_tag_sdk_to_core(&StructTag::new_ascii_string()),
342 fields: vec![A::MoveFieldLayout::new(
343 ident_str!("bytes").into(),
344 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
345 )],
346 }
347}
348
349pub fn move_utf8_str_layout() -> A::MoveStructLayout {
350 A::MoveStructLayout {
351 type_: struct_tag_sdk_to_core(&StructTag::new_string()),
352 fields: vec![A::MoveFieldLayout::new(
353 ident_str!("bytes").into(),
354 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
355 )],
356 }
357}
358
359pub fn url_layout() -> A::MoveStructLayout {
360 A::MoveStructLayout {
361 type_: struct_tag_sdk_to_core(&StructTag::new_url()),
362 fields: vec![A::MoveFieldLayout::new(
363 ident_str!("url").to_owned(),
364 A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
365 )],
366 }
367}
368
369#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
379pub struct MoveLegacyTxContext {
380 sender: AccountAddress,
382 digest: Vec<u8>,
384 epoch: EpochId,
386 epoch_timestamp_ms: CheckpointTimestamp,
388 ids_created: u64,
390}
391
392impl From<&TxContext> for MoveLegacyTxContext {
393 fn from(tx_context: &TxContext) -> Self {
394 Self {
395 sender: tx_context.sender,
396 digest: tx_context.digest.clone(),
397 epoch: tx_context.epoch,
398 epoch_timestamp_ms: tx_context.epoch_timestamp_ms,
399 ids_created: tx_context.ids_created,
400 }
401 }
402}
403
404#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
407pub struct TxContext {
408 sender: AccountAddress,
410 digest: Vec<u8>,
412 epoch: EpochId,
414 epoch_timestamp_ms: CheckpointTimestamp,
416 ids_created: u64,
419 rgp: u64,
421 gas_price: u64,
423 gas_budget: u64,
425 sponsor: Option<AccountAddress>,
427 is_native: bool,
430}
431
432#[derive(PartialEq, Eq, Clone, Copy)]
433pub enum TxContextKind {
434 None,
436 Mutable,
438 Immutable,
440}
441
442impl TxContext {
443 pub fn new(
444 sender: &Address,
445 digest: &TransactionDigest,
446 epoch_data: &EpochData,
447 rgp: u64,
448 gas_price: u64,
449 gas_budget: u64,
450 sponsor: Option<Address>,
451 protocol_config: &ProtocolConfig,
452 ) -> Self {
453 Self::new_from_components(
454 sender,
455 digest,
456 &epoch_data.epoch_id(),
457 epoch_data.epoch_start_timestamp(),
458 rgp,
459 gas_price,
460 gas_budget,
461 sponsor,
462 protocol_config,
463 )
464 }
465
466 pub fn new_from_components(
467 sender: &Address,
468 digest: &TransactionDigest,
469 epoch_id: &EpochId,
470 epoch_timestamp_ms: u64,
471 rgp: u64,
472 gas_price: u64,
473 gas_budget: u64,
474 sponsor: Option<Address>,
475 protocol_config: &ProtocolConfig,
476 ) -> Self {
477 Self {
478 sender: AccountAddress::new(sender.into_bytes()),
479 digest: digest.into_inner().to_vec(),
480 epoch: *epoch_id,
481 epoch_timestamp_ms,
482 ids_created: 0,
483 rgp,
484 gas_price,
485 gas_budget,
486 sponsor: sponsor.map(|s| AccountAddress::new(s.into_bytes())),
487 is_native: protocol_config.move_native_tx_context(),
488 }
489 }
490
491 pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
494 use SignatureToken as S;
495 let (kind, s) = match s {
496 S::MutableReference(s) => (TxContextKind::Mutable, s),
497 S::Reference(s) => (TxContextKind::Immutable, s),
498 _ => return TxContextKind::None,
499 };
500
501 let S::Datatype(idx) = &**s else {
502 return TxContextKind::None;
503 };
504
505 let (module_addr, module_name, struct_name) = resolve_struct(view, *idx);
506 let is_tx_context_type = module_name.as_str() == Identifier::TX_CONTEXT_MODULE.as_str()
507 && module_addr.as_ref() == Address::FRAMEWORK.as_bytes()
508 && struct_name.as_str() == Identifier::TX_CONTEXT.as_str();
509
510 if is_tx_context_type {
511 kind
512 } else {
513 TxContextKind::None
514 }
515 }
516
517 pub fn epoch(&self) -> EpochId {
518 self.epoch
519 }
520
521 pub fn epoch_timestamp_ms(&self) -> u64 {
522 self.epoch_timestamp_ms
523 }
524
525 pub fn digest(&self) -> TransactionDigest {
527 TransactionDigest::new(self.digest.clone().try_into().unwrap())
528 }
529
530 pub fn sponsor(&self) -> Option<Address> {
531 self.sponsor.map(|a| Address::from(a.into_bytes()))
532 }
533
534 pub fn rgp(&self) -> u64 {
535 self.rgp
536 }
537
538 pub fn gas_price(&self) -> u64 {
539 self.gas_price
540 }
541
542 pub fn gas_budget(&self) -> u64 {
543 self.gas_budget
544 }
545
546 pub fn ids_created(&self) -> u64 {
547 self.ids_created
548 }
549
550 pub fn fresh_id(&mut self) -> ObjectId {
553 let id = ObjectId::derive_id(self.digest(), self.ids_created);
554 self.ids_created += 1;
555 id
556 }
557
558 pub fn sender(&self) -> Address {
559 Address::new(self.sender.into_bytes())
560 }
561
562 pub fn to_vec(&self) -> Vec<u8> {
563 bcs::to_bytes(&self).unwrap()
564 }
565
566 pub fn to_bcs_legacy_context(&self) -> Vec<u8> {
570 let move_context: MoveLegacyTxContext = if self.is_native {
571 let tx_context = &TxContext {
572 sender: AccountAddress::ZERO,
573 digest: vec![],
574 epoch: 0,
575 epoch_timestamp_ms: 0,
576 ids_created: 0,
577 rgp: 0,
578 gas_price: 0,
579 gas_budget: 0,
580 sponsor: None,
581 is_native: true,
582 };
583 tx_context.into()
584 } else {
585 self.into()
586 };
587 bcs::to_bytes(&move_context).unwrap()
588 }
589
590 pub fn update_state(&mut self, other: MoveLegacyTxContext) -> Result<(), ExecutionError> {
595 if !self.is_native {
596 if self.sender != other.sender
597 || self.digest != other.digest
598 || other.ids_created < self.ids_created
599 {
600 return Err(ExecutionError::new_with_source(
601 ExecutionErrorKind::InvariantViolation,
602 "Immutable fields for TxContext changed",
603 ));
604 }
605 self.ids_created = other.ids_created;
606 }
607 Ok(())
608 }
609
610 pub fn replace(
612 &mut self,
613 sender: AccountAddress,
614 tx_hash: Vec<u8>,
615 epoch: u64,
616 epoch_timestamp_ms: u64,
617 ids_created: u64,
618 rgp: u64,
619 gas_price: u64,
620 gas_budget: u64,
621 sponsor: Option<AccountAddress>,
622 ) {
623 self.sender = sender;
624 self.digest = tx_hash;
625 self.epoch = epoch;
626 self.epoch_timestamp_ms = epoch_timestamp_ms;
627 self.ids_created = ids_created;
628 self.rgp = rgp;
629 self.gas_price = gas_price;
630 self.gas_budget = gas_budget;
631 self.sponsor = sponsor;
632 }
633
634 #[cfg(not(target_arch = "wasm32"))]
636 pub fn random_for_testing_only() -> Self {
637 Self::new(
638 &Address::random(),
639 &TransactionDigest::random(),
640 &EpochData::new_test(),
641 0,
642 0,
643 0,
644 None,
645 &ProtocolConfig::get_for_max_version_UNSAFE(),
646 )
647 }
648}
649
650pub fn dbg_object_id(name: u8) -> ObjectId {
652 ObjectId::new([name; ObjectId::LENGTH])
653}
654
655#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
656pub enum ObjectIdParseError {
657 #[error("ObjectId hex literal must start with 0x")]
658 HexLiteralPrefixMissing,
659
660 #[error("Could not convert from bytes slice")]
661 TryFromSlice,
662}
663
664impl fmt::Display for ObjectType {
665 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
666 match self {
667 ObjectType::Package => write!(f, "{PACKAGE}"),
668 ObjectType::Struct(t) => write!(
669 f,
670 "{}",
671 to_iota_struct_tag_string(t).map_err(fmt::Error::custom)?
672 ),
673 }
674 }
675}