1use std::{
7 cmp::max,
8 convert::{TryFrom, TryInto},
9 fmt,
10 str::FromStr,
11};
12
13use anyhow::{anyhow, bail};
14use fastcrypto::{
15 encoding::{Encoding, Hex, decode_bytes_hex},
16 hash::HashFunction,
17 traits::AllowedRng,
18};
19use fastcrypto_zkp::bn254::zk_login::ZkLoginInputs;
20use iota_sdk_types::crypto::HashingIntentScope;
21use move_binary_format::{CompiledModule, file_format::SignatureToken};
22use move_bytecode_utils::resolve_struct;
23use move_core_types::{
24 account_address::AccountAddress,
25 annotated_value as A, ident_str,
26 identifier::IdentStr,
27 language_storage::{ModuleId, StructTag, TypeTag},
28};
29use rand::Rng;
30use schemars::JsonSchema;
31use serde::{
32 Deserialize, Serialize, Serializer,
33 ser::{Error, SerializeSeq},
34};
35use serde_with::serde_as;
36
37use crate::{
38 IOTA_CLOCK_OBJECT_ID, IOTA_FRAMEWORK_ADDRESS, IOTA_SYSTEM_ADDRESS, MOVE_STDLIB_ADDRESS,
39 balance::Balance,
40 coin::{COIN_MODULE_NAME, COIN_STRUCT_NAME, Coin, CoinMetadata, TreasuryCap},
41 coin_manager::CoinManager,
42 crypto::{
43 AuthorityPublicKeyBytes, DefaultHash, IotaPublicKey, IotaSignature, PublicKey,
44 SignatureScheme,
45 },
46 dynamic_field::{DynamicFieldInfo, DynamicFieldType},
47 effects::{TransactionEffects, TransactionEffectsAPI},
48 epoch_data::EpochData,
49 error::{ExecutionError, ExecutionErrorKind, IotaError, IotaResult},
50 gas_coin::{GAS, GasCoin},
51 governance::{STAKED_IOTA_STRUCT_NAME, STAKING_POOL_MODULE_NAME, StakedIota},
52 id::RESOLVED_IOTA_ID,
53 iota_serde::{HexAccountAddress, Readable, to_iota_struct_tag_string},
54 messages_checkpoint::CheckpointTimestamp,
55 multisig::MultiSigPublicKey,
56 object::{Object, Owner},
57 parse_iota_struct_tag,
58 signature::GenericSignature,
59 stardust::output::{AliasOutput, BasicOutput, Nft, NftOutput},
60 timelock::{
61 timelock::{self, TimeLock},
62 timelocked_staked_iota::TimelockedStakedIota,
63 },
64 transaction::{Transaction, VerifiedTransaction},
65 zk_login_authenticator::ZkLoginAuthenticator,
66};
67pub use crate::{
68 committee::EpochId,
69 digests::{ObjectDigest, TransactionDigest, TransactionEffectsDigest},
70};
71
72#[cfg(test)]
73#[path = "unit_tests/base_types_tests.rs"]
74mod base_types_tests;
75
76#[derive(
77 Eq,
78 PartialEq,
79 Ord,
80 PartialOrd,
81 Copy,
82 Clone,
83 Hash,
84 Default,
85 Debug,
86 Serialize,
87 Deserialize,
88 JsonSchema,
89)]
90#[cfg_attr(feature = "fuzzing", derive(proptest_derive::Arbitrary))]
91pub struct SequenceNumber(u64);
92
93impl SequenceNumber {
94 pub fn one_before(&self) -> Option<SequenceNumber> {
95 if self.0 == 0 {
96 None
97 } else {
98 Some(SequenceNumber(self.0 - 1))
99 }
100 }
101
102 pub fn next(&self) -> SequenceNumber {
103 SequenceNumber(self.0 + 1)
104 }
105}
106
107pub type TxSequenceNumber = u64;
108
109impl fmt::Display for SequenceNumber {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{:#x}", self.0)
112 }
113}
114
115pub type VersionNumber = SequenceNumber;
116
117pub type CommitRound = u64;
119
120pub type AuthorityName = AuthorityPublicKeyBytes;
121
122pub trait ConciseableName<'a> {
123 type ConciseTypeRef: std::fmt::Debug;
124 type ConciseType: std::fmt::Debug;
125
126 fn concise(&'a self) -> Self::ConciseTypeRef;
127 fn concise_owned(&self) -> Self::ConciseType;
128}
129
130#[serde_as]
131#[derive(Eq, PartialEq, Clone, Copy, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema)]
132pub struct ObjectID(
133 #[schemars(with = "Hex")]
134 #[serde_as(as = "Readable<HexAccountAddress, _>")]
135 AccountAddress,
136);
137
138pub type VersionDigest = (SequenceNumber, ObjectDigest);
139
140pub type ObjectRef = (ObjectID, SequenceNumber, ObjectDigest);
141
142pub fn random_object_ref() -> ObjectRef {
143 (
144 ObjectID::random(),
145 SequenceNumber::new(),
146 ObjectDigest::new([0; 32]),
147 )
148}
149
150#[derive(Eq, PartialEq, PartialOrd, Ord, Debug, Clone, Deserialize, Serialize, Hash)]
156pub struct MoveObjectType(MoveObjectType_);
157
158#[derive(Eq, PartialEq, PartialOrd, Ord, Debug, Clone, Deserialize, Serialize, Hash)]
161pub enum MoveObjectType_ {
162 Other(StructTag),
164 GasCoin,
166 StakedIota,
168 Coin(TypeTag),
171 }
175
176impl MoveObjectType {
177 pub fn gas_coin() -> Self {
178 Self(MoveObjectType_::GasCoin)
179 }
180
181 pub fn coin(coin_type: TypeTag) -> Self {
182 Self(if GAS::is_gas_type(&coin_type) {
183 MoveObjectType_::GasCoin
184 } else {
185 MoveObjectType_::Coin(coin_type)
186 })
187 }
188
189 pub fn staked_iota() -> Self {
190 Self(MoveObjectType_::StakedIota)
191 }
192
193 pub fn timelocked_iota_balance() -> Self {
194 Self(MoveObjectType_::Other(TimeLock::<Balance>::type_(
195 Balance::type_(GAS::type_().into()).into(),
196 )))
197 }
198
199 pub fn timelocked_staked_iota() -> Self {
200 Self(MoveObjectType_::Other(TimelockedStakedIota::type_()))
201 }
202
203 pub fn stardust_nft() -> Self {
204 Self(MoveObjectType_::Other(Nft::tag()))
205 }
206
207 pub fn address(&self) -> AccountAddress {
208 match &self.0 {
209 MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) => IOTA_FRAMEWORK_ADDRESS,
210 MoveObjectType_::StakedIota => IOTA_SYSTEM_ADDRESS,
211 MoveObjectType_::Other(s) => s.address,
212 }
213 }
214
215 pub fn module(&self) -> &IdentStr {
216 match &self.0 {
217 MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) => COIN_MODULE_NAME,
218 MoveObjectType_::StakedIota => STAKING_POOL_MODULE_NAME,
219 MoveObjectType_::Other(s) => &s.module,
220 }
221 }
222
223 pub fn name(&self) -> &IdentStr {
224 match &self.0 {
225 MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) => COIN_STRUCT_NAME,
226 MoveObjectType_::StakedIota => STAKED_IOTA_STRUCT_NAME,
227 MoveObjectType_::Other(s) => &s.name,
228 }
229 }
230
231 pub fn type_params(&self) -> Vec<TypeTag> {
232 match &self.0 {
233 MoveObjectType_::GasCoin => vec![GAS::type_tag()],
234 MoveObjectType_::StakedIota => vec![],
235 MoveObjectType_::Coin(inner) => vec![inner.clone()],
236 MoveObjectType_::Other(s) => s.type_params.clone(),
237 }
238 }
239
240 pub fn into_type_params(self) -> Vec<TypeTag> {
241 match self.0 {
242 MoveObjectType_::GasCoin => vec![GAS::type_tag()],
243 MoveObjectType_::StakedIota => vec![],
244 MoveObjectType_::Coin(inner) => vec![inner],
245 MoveObjectType_::Other(s) => s.type_params,
246 }
247 }
248
249 pub fn coin_type_maybe(&self) -> Option<TypeTag> {
250 match &self.0 {
251 MoveObjectType_::GasCoin => Some(GAS::type_tag()),
252 MoveObjectType_::Coin(inner) => Some(inner.clone()),
253 MoveObjectType_::StakedIota => None,
254 MoveObjectType_::Other(_) => None,
255 }
256 }
257
258 pub fn module_id(&self) -> ModuleId {
259 ModuleId::new(self.address(), self.module().to_owned())
260 }
261
262 pub fn size_for_gas_metering(&self) -> usize {
263 match &self.0 {
265 MoveObjectType_::GasCoin => 1,
266 MoveObjectType_::StakedIota => 1,
267 MoveObjectType_::Coin(inner) => bcs::serialized_size(inner).unwrap() + 1,
268 MoveObjectType_::Other(s) => bcs::serialized_size(s).unwrap() + 1,
269 }
270 }
271
272 pub fn is_coin(&self) -> bool {
275 match &self.0 {
276 MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) => true,
277 MoveObjectType_::StakedIota | MoveObjectType_::Other(_) => false,
278 }
279 }
280
281 pub fn is_gas_coin(&self) -> bool {
283 match &self.0 {
284 MoveObjectType_::GasCoin => true,
285 MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) | MoveObjectType_::Other(_) => {
286 false
287 }
288 }
289 }
290
291 pub fn is_coin_t(&self, t: &TypeTag) -> bool {
293 match &self.0 {
294 MoveObjectType_::GasCoin => GAS::is_gas_type(t),
295 MoveObjectType_::Coin(c) => t == c,
296 MoveObjectType_::StakedIota | MoveObjectType_::Other(_) => false,
297 }
298 }
299
300 pub fn is_staked_iota(&self) -> bool {
301 match &self.0 {
302 MoveObjectType_::StakedIota => true,
303 MoveObjectType_::GasCoin | MoveObjectType_::Coin(_) | MoveObjectType_::Other(_) => {
304 false
305 }
306 }
307 }
308
309 pub fn is_coin_metadata(&self) -> bool {
310 match &self.0 {
311 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
312 false
313 }
314 MoveObjectType_::Other(s) => CoinMetadata::is_coin_metadata(s),
315 }
316 }
317
318 pub fn is_coin_manager(&self) -> bool {
319 matches!(&self.0, MoveObjectType_::Other(struct_tag) if CoinManager::is_coin_manager(struct_tag))
320 }
321
322 pub fn is_treasury_cap(&self) -> bool {
323 match &self.0 {
324 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
325 false
326 }
327 MoveObjectType_::Other(s) => TreasuryCap::is_treasury_type(s),
328 }
329 }
330
331 pub fn is_regulated_coin_metadata(&self) -> bool {
332 self.address() == IOTA_FRAMEWORK_ADDRESS
333 && self.module().as_str() == "coin"
334 && self.name().as_str() == "RegulatedCoinMetadata"
335 }
336
337 pub fn is_coin_deny_cap_v1(&self) -> bool {
338 self.address() == IOTA_FRAMEWORK_ADDRESS
339 && self.module().as_str() == "coin"
340 && self.name().as_str() == "DenyCapV1"
341 }
342
343 pub fn is_dynamic_field(&self) -> bool {
344 match &self.0 {
345 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
346 false
347 }
348 MoveObjectType_::Other(s) => DynamicFieldInfo::is_dynamic_field(s),
349 }
350 }
351
352 pub fn is_timelock(&self) -> bool {
353 match &self.0 {
354 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
355 false
356 }
357 MoveObjectType_::Other(s) => timelock::is_timelock(s),
358 }
359 }
360
361 pub fn is_timelocked_balance(&self) -> bool {
362 match &self.0 {
363 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
364 false
365 }
366 MoveObjectType_::Other(s) => timelock::is_timelocked_balance(s),
367 }
368 }
369
370 pub fn is_timelocked_staked_iota(&self) -> bool {
371 match &self.0 {
372 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
373 false
374 }
375 MoveObjectType_::Other(s) => TimelockedStakedIota::is_timelocked_staked_iota(s),
376 }
377 }
378
379 pub fn is_alias_output(&self) -> bool {
380 match &self.0 {
381 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
382 false
383 }
384 MoveObjectType_::Other(s) => AliasOutput::is_alias_output(s),
385 }
386 }
387
388 pub fn is_basic_output(&self) -> bool {
389 match &self.0 {
390 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
391 false
392 }
393 MoveObjectType_::Other(s) => BasicOutput::is_basic_output(s),
394 }
395 }
396
397 pub fn is_nft_output(&self) -> bool {
398 match &self.0 {
399 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
400 false
401 }
402 MoveObjectType_::Other(s) => NftOutput::is_nft_output(s),
403 }
404 }
405
406 pub fn try_extract_field_name(&self, type_: &DynamicFieldType) -> IotaResult<TypeTag> {
407 match &self.0 {
408 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
409 Err(IotaError::ObjectDeserialization {
410 error: "Error extracting dynamic object name from Coin object".to_string(),
411 })
412 }
413 MoveObjectType_::Other(s) => DynamicFieldInfo::try_extract_field_name(s, type_),
414 }
415 }
416
417 pub fn try_extract_field_value(&self) -> IotaResult<TypeTag> {
418 match &self.0 {
419 MoveObjectType_::GasCoin | MoveObjectType_::StakedIota | MoveObjectType_::Coin(_) => {
420 Err(IotaError::ObjectDeserialization {
421 error: "Error extracting dynamic object value from Coin object".to_string(),
422 })
423 }
424 MoveObjectType_::Other(s) => DynamicFieldInfo::try_extract_field_value(s),
425 }
426 }
427
428 pub fn is(&self, s: &StructTag) -> bool {
429 match &self.0 {
430 MoveObjectType_::GasCoin => GasCoin::is_gas_coin(s),
431 MoveObjectType_::StakedIota => StakedIota::is_staked_iota(s),
432 MoveObjectType_::Coin(inner) => {
433 Coin::is_coin(s) && s.type_params.len() == 1 && inner == &s.type_params[0]
434 }
435 MoveObjectType_::Other(o) => s == o,
436 }
437 }
438
439 pub fn other(&self) -> Option<&StructTag> {
440 if let MoveObjectType_::Other(s) = &self.0 {
441 Some(s)
442 } else {
443 None
444 }
445 }
446
447 pub fn to_canonical_string(&self, with_prefix: bool) -> String {
450 StructTag::from(self.clone()).to_canonical_string(with_prefix)
451 }
452}
453
454impl From<StructTag> for MoveObjectType {
455 fn from(mut s: StructTag) -> Self {
456 Self(if GasCoin::is_gas_coin(&s) {
457 MoveObjectType_::GasCoin
458 } else if Coin::is_coin(&s) {
459 MoveObjectType_::Coin(s.type_params.pop().unwrap())
461 } else if StakedIota::is_staked_iota(&s) {
462 MoveObjectType_::StakedIota
463 } else {
464 MoveObjectType_::Other(s)
465 })
466 }
467}
468
469impl From<MoveObjectType> for StructTag {
470 fn from(t: MoveObjectType) -> Self {
471 match t.0 {
472 MoveObjectType_::GasCoin => GasCoin::type_(),
473 MoveObjectType_::StakedIota => StakedIota::type_(),
474 MoveObjectType_::Coin(inner) => Coin::type_(inner),
475 MoveObjectType_::Other(s) => s,
476 }
477 }
478}
479
480impl From<MoveObjectType> for TypeTag {
481 fn from(o: MoveObjectType) -> TypeTag {
482 let s: StructTag = o.into();
483 TypeTag::Struct(Box::new(s))
484 }
485}
486
487pub fn is_primitive_type_tag(t: &TypeTag) -> bool {
489 use TypeTag as T;
490
491 match t {
492 T::Bool | T::U8 | T::U16 | T::U32 | T::U64 | T::U128 | T::U256 | T::Address => true,
493 T::Vector(inner) => is_primitive_type_tag(inner),
494 T::Struct(st) => {
495 let StructTag {
496 address,
497 module,
498 name,
499 type_params: type_args,
500 } = &**st;
501 let resolved_struct = (address, module.as_ident_str(), name.as_ident_str());
502 if resolved_struct == RESOLVED_IOTA_ID {
504 return true;
505 }
506 if resolved_struct == RESOLVED_UTF8_STR {
508 return true;
509 }
510 if resolved_struct == RESOLVED_ASCII_STR {
512 return true;
513 }
514 resolved_struct == RESOLVED_STD_OPTION
516 && type_args.len() == 1
517 && is_primitive_type_tag(&type_args[0])
518 }
519 T::Signer => false,
520 }
521}
522
523#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
525pub enum ObjectType {
526 Package,
528 Struct(MoveObjectType),
530}
531
532impl From<&Object> for ObjectType {
533 fn from(o: &Object) -> Self {
534 o.data
535 .type_()
536 .map(|t| ObjectType::Struct(t.clone()))
537 .unwrap_or(ObjectType::Package)
538 }
539}
540
541impl TryFrom<ObjectType> for StructTag {
542 type Error = anyhow::Error;
543
544 fn try_from(o: ObjectType) -> Result<Self, anyhow::Error> {
545 match o {
546 ObjectType::Package => Err(anyhow!("Cannot create StructTag from Package")),
547 ObjectType::Struct(move_object_type) => Ok(move_object_type.into()),
548 }
549 }
550}
551
552impl FromStr for ObjectType {
553 type Err = anyhow::Error;
554
555 fn from_str(s: &str) -> Result<Self, Self::Err> {
556 if s.to_lowercase() == PACKAGE {
557 Ok(ObjectType::Package)
558 } else {
559 let tag = parse_iota_struct_tag(s)?;
560 Ok(ObjectType::Struct(MoveObjectType::from(tag)))
561 }
562 }
563}
564
565#[derive(Clone, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug)]
566pub struct ObjectInfo {
567 pub object_id: ObjectID,
568 pub version: SequenceNumber,
569 pub digest: ObjectDigest,
570 pub type_: ObjectType,
571 pub owner: Owner,
572 pub previous_transaction: TransactionDigest,
573}
574
575impl ObjectInfo {
576 pub fn new(oref: &ObjectRef, o: &Object) -> Self {
577 let (object_id, version, digest) = *oref;
578 Self {
579 object_id,
580 version,
581 digest,
582 type_: o.into(),
583 owner: o.owner,
584 previous_transaction: o.previous_transaction,
585 }
586 }
587
588 pub fn from_object(object: &Object) -> Self {
589 Self {
590 object_id: object.id(),
591 version: object.version(),
592 digest: object.digest(),
593 type_: object.into(),
594 owner: object.owner,
595 previous_transaction: object.previous_transaction,
596 }
597 }
598}
599const PACKAGE: &str = "package";
600impl ObjectType {
601 pub fn is_gas_coin(&self) -> bool {
602 matches!(self, ObjectType::Struct(s) if s.is_gas_coin())
603 }
604
605 pub fn is_coin(&self) -> bool {
606 matches!(self, ObjectType::Struct(s) if s.is_coin())
607 }
608
609 pub fn is_coin_t(&self, t: &TypeTag) -> bool {
611 matches!(self, ObjectType::Struct(s) if s.is_coin_t(t))
612 }
613
614 pub fn is_package(&self) -> bool {
615 matches!(self, ObjectType::Package)
616 }
617}
618
619impl From<ObjectInfo> for ObjectRef {
620 fn from(info: ObjectInfo) -> Self {
621 (info.object_id, info.version, info.digest)
622 }
623}
624
625impl From<&ObjectInfo> for ObjectRef {
626 fn from(info: &ObjectInfo) -> Self {
627 (info.object_id, info.version, info.digest)
628 }
629}
630
631pub const IOTA_ADDRESS_LENGTH: usize = ObjectID::LENGTH;
632
633#[serde_as]
634#[derive(
635 Eq, Default, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema,
636)]
637#[cfg_attr(feature = "fuzzing", derive(proptest_derive::Arbitrary))]
638pub struct IotaAddress(
639 #[schemars(with = "Hex")]
640 #[serde_as(as = "Readable<Hex, _>")]
641 [u8; IOTA_ADDRESS_LENGTH],
642);
643
644impl IotaAddress {
645 pub const ZERO: Self = Self([0u8; IOTA_ADDRESS_LENGTH]);
646
647 pub fn to_vec(&self) -> Vec<u8> {
649 self.0.to_vec()
650 }
651
652 pub fn random_for_testing_only() -> Self {
654 AccountAddress::random().into()
655 }
656
657 pub fn generate<R: rand::RngCore + rand::CryptoRng>(mut rng: R) -> Self {
658 let buf: [u8; IOTA_ADDRESS_LENGTH] = rng.gen();
659 Self(buf)
660 }
661
662 pub fn to_inner(self) -> [u8; IOTA_ADDRESS_LENGTH] {
664 self.0
665 }
666
667 pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, IotaError> {
669 <[u8; IOTA_ADDRESS_LENGTH]>::try_from(bytes.as_ref())
670 .map_err(|_| IotaError::InvalidAddress)
671 .map(IotaAddress)
672 }
673
674 pub fn try_from_padded(inputs: &ZkLoginInputs) -> IotaResult<Self> {
679 Ok((&PublicKey::from_zklogin_inputs(inputs)?).into())
680 }
681
682 pub fn try_from_unpadded(inputs: &ZkLoginInputs) -> IotaResult<Self> {
684 let mut hasher = DefaultHash::default();
685 hasher.update([SignatureScheme::ZkLoginAuthenticator.flag()]);
686 let iss_bytes = inputs.get_iss().as_bytes();
687 hasher.update([iss_bytes.len() as u8]);
688 hasher.update(iss_bytes);
689 hasher.update(inputs.get_address_seed().unpadded());
690 Ok(IotaAddress(hasher.finalize().digest))
691 }
692}
693
694impl From<ObjectID> for IotaAddress {
695 fn from(object_id: ObjectID) -> IotaAddress {
696 Self(object_id.into_bytes())
697 }
698}
699
700impl From<AccountAddress> for IotaAddress {
701 fn from(address: AccountAddress) -> IotaAddress {
702 Self(address.into_bytes())
703 }
704}
705
706impl TryFrom<&[u8]> for IotaAddress {
707 type Error = IotaError;
708
709 fn try_from(bytes: &[u8]) -> Result<Self, IotaError> {
711 Self::from_bytes(bytes)
712 }
713}
714
715impl TryFrom<Vec<u8>> for IotaAddress {
716 type Error = IotaError;
717
718 fn try_from(bytes: Vec<u8>) -> Result<Self, IotaError> {
720 Self::from_bytes(bytes)
721 }
722}
723
724impl AsRef<[u8]> for IotaAddress {
725 fn as_ref(&self) -> &[u8] {
726 &self.0[..]
727 }
728}
729
730impl FromStr for IotaAddress {
731 type Err = anyhow::Error;
732 fn from_str(s: &str) -> Result<Self, Self::Err> {
733 decode_bytes_hex(s).map_err(|e| anyhow!(e))
734 }
735}
736
737impl<T: IotaPublicKey> From<&T> for IotaAddress {
738 fn from(pk: &T) -> Self {
739 let mut hasher = DefaultHash::default();
740 T::SIGNATURE_SCHEME.update_hasher_with_flag(&mut hasher);
741 hasher.update(pk);
742 let g_arr = hasher.finalize();
743 IotaAddress(g_arr.digest)
744 }
745}
746
747impl From<&PublicKey> for IotaAddress {
748 fn from(pk: &PublicKey) -> Self {
749 let mut hasher = DefaultHash::default();
750 pk.scheme().update_hasher_with_flag(&mut hasher);
751 hasher.update(pk);
752 let g_arr = hasher.finalize();
753 IotaAddress(g_arr.digest)
754 }
755}
756
757impl From<&MultiSigPublicKey> for IotaAddress {
758 fn from(multisig_pk: &MultiSigPublicKey) -> Self {
767 let mut hasher = DefaultHash::default();
768 hasher.update([SignatureScheme::MultiSig.flag()]);
769 hasher.update(multisig_pk.threshold().to_le_bytes());
770 multisig_pk.pubkeys().iter().for_each(|(pk, w)| {
771 pk.scheme().update_hasher_with_flag(&mut hasher);
772 hasher.update(pk.as_ref());
773 hasher.update(w.to_le_bytes());
774 });
775 IotaAddress(hasher.finalize().digest)
776 }
777}
778
779impl TryFrom<&ZkLoginAuthenticator> for IotaAddress {
783 type Error = IotaError;
784 fn try_from(authenticator: &ZkLoginAuthenticator) -> IotaResult<Self> {
785 IotaAddress::try_from_unpadded(&authenticator.inputs)
786 }
787}
788
789impl TryFrom<&GenericSignature> for IotaAddress {
790 type Error = IotaError;
791 fn try_from(sig: &GenericSignature) -> IotaResult<Self> {
794 match sig {
795 GenericSignature::Signature(sig) => {
796 let scheme = sig.scheme();
797 let pub_key_bytes = sig.public_key_bytes();
798 let pub_key = PublicKey::try_from_bytes(scheme, pub_key_bytes).map_err(|_| {
799 IotaError::InvalidSignature {
800 error: "Cannot parse pubkey".to_string(),
801 }
802 })?;
803 Ok(IotaAddress::from(&pub_key))
804 }
805 GenericSignature::MultiSig(ms) => Ok(ms.get_pk().into()),
806 GenericSignature::ZkLoginAuthenticator(zklogin) => {
807 IotaAddress::try_from_unpadded(&zklogin.inputs)
808 }
809 GenericSignature::PasskeyAuthenticator(s) => Ok(IotaAddress::from(&s.get_pk()?)),
810 }
811 }
812}
813
814impl fmt::Display for IotaAddress {
815 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
816 write!(f, "0x{}", Hex::encode(self.0))
817 }
818}
819
820impl fmt::Debug for IotaAddress {
821 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
822 write!(f, "0x{}", Hex::encode(self.0))
823 }
824}
825
826pub fn dbg_addr(name: u8) -> IotaAddress {
828 let addr = [name; IOTA_ADDRESS_LENGTH];
829 IotaAddress(addr)
830}
831
832#[derive(
833 Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema, Debug,
834)]
835pub struct ExecutionDigests {
836 pub transaction: TransactionDigest,
837 pub effects: TransactionEffectsDigest,
838}
839
840impl ExecutionDigests {
841 pub fn new(transaction: TransactionDigest, effects: TransactionEffectsDigest) -> Self {
842 Self {
843 transaction,
844 effects,
845 }
846 }
847
848 pub fn random() -> Self {
849 Self {
850 transaction: TransactionDigest::random(),
851 effects: TransactionEffectsDigest::random(),
852 }
853 }
854}
855
856#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
857pub struct ExecutionData {
858 pub transaction: Transaction,
859 pub effects: TransactionEffects,
860}
861
862impl ExecutionData {
863 pub fn new(transaction: Transaction, effects: TransactionEffects) -> ExecutionData {
864 debug_assert_eq!(transaction.digest(), effects.transaction_digest());
865 Self {
866 transaction,
867 effects,
868 }
869 }
870
871 pub fn digests(&self) -> ExecutionDigests {
872 self.effects.execution_digests()
873 }
874}
875
876#[derive(Clone, Eq, PartialEq, Debug)]
877pub struct VerifiedExecutionData {
878 pub transaction: VerifiedTransaction,
879 pub effects: TransactionEffects,
880}
881
882impl VerifiedExecutionData {
883 pub fn new(transaction: VerifiedTransaction, effects: TransactionEffects) -> Self {
884 debug_assert_eq!(transaction.digest(), effects.transaction_digest());
885 Self {
886 transaction,
887 effects,
888 }
889 }
890
891 pub fn new_unchecked(data: ExecutionData) -> Self {
892 Self {
893 transaction: VerifiedTransaction::new_unchecked(data.transaction),
894 effects: data.effects,
895 }
896 }
897
898 pub fn into_inner(self) -> ExecutionData {
899 ExecutionData {
900 transaction: self.transaction.into_inner(),
901 effects: self.effects,
902 }
903 }
904
905 pub fn digests(&self) -> ExecutionDigests {
906 self.effects.execution_digests()
907 }
908}
909
910pub const STD_OPTION_MODULE_NAME: &IdentStr = ident_str!("option");
911pub const STD_OPTION_STRUCT_NAME: &IdentStr = ident_str!("Option");
912pub const RESOLVED_STD_OPTION: (&AccountAddress, &IdentStr, &IdentStr) = (
913 &MOVE_STDLIB_ADDRESS,
914 STD_OPTION_MODULE_NAME,
915 STD_OPTION_STRUCT_NAME,
916);
917
918pub const STD_ASCII_MODULE_NAME: &IdentStr = ident_str!("ascii");
919pub const STD_ASCII_STRUCT_NAME: &IdentStr = ident_str!("String");
920pub const RESOLVED_ASCII_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
921 &MOVE_STDLIB_ADDRESS,
922 STD_ASCII_MODULE_NAME,
923 STD_ASCII_STRUCT_NAME,
924);
925
926pub const STD_UTF8_MODULE_NAME: &IdentStr = ident_str!("string");
927pub const STD_UTF8_STRUCT_NAME: &IdentStr = ident_str!("String");
928pub const RESOLVED_UTF8_STR: (&AccountAddress, &IdentStr, &IdentStr) = (
929 &MOVE_STDLIB_ADDRESS,
930 STD_UTF8_MODULE_NAME,
931 STD_UTF8_STRUCT_NAME,
932);
933
934pub const TX_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("tx_context");
935pub const TX_CONTEXT_STRUCT_NAME: &IdentStr = ident_str!("TxContext");
936
937pub const URL_MODULE_NAME: &IdentStr = ident_str!("url");
938pub const URL_STRUCT_NAME: &IdentStr = ident_str!("Url");
939
940pub fn move_ascii_str_layout() -> A::MoveStructLayout {
941 A::MoveStructLayout {
942 type_: StructTag {
943 address: MOVE_STDLIB_ADDRESS,
944 module: STD_ASCII_MODULE_NAME.to_owned(),
945 name: STD_ASCII_STRUCT_NAME.to_owned(),
946 type_params: vec![],
947 },
948 fields: vec![A::MoveFieldLayout::new(
949 ident_str!("bytes").into(),
950 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
951 )],
952 }
953}
954
955pub fn move_utf8_str_layout() -> A::MoveStructLayout {
956 A::MoveStructLayout {
957 type_: StructTag {
958 address: MOVE_STDLIB_ADDRESS,
959 module: STD_UTF8_MODULE_NAME.to_owned(),
960 name: STD_UTF8_STRUCT_NAME.to_owned(),
961 type_params: vec![],
962 },
963 fields: vec![A::MoveFieldLayout::new(
964 ident_str!("bytes").into(),
965 A::MoveTypeLayout::Vector(Box::new(A::MoveTypeLayout::U8)),
966 )],
967 }
968}
969
970pub fn url_layout() -> A::MoveStructLayout {
971 A::MoveStructLayout {
972 type_: StructTag {
973 address: IOTA_FRAMEWORK_ADDRESS,
974 module: URL_MODULE_NAME.to_owned(),
975 name: URL_STRUCT_NAME.to_owned(),
976 type_params: vec![],
977 },
978 fields: vec![A::MoveFieldLayout::new(
979 ident_str!("url").to_owned(),
980 A::MoveTypeLayout::Struct(Box::new(move_ascii_str_layout())),
981 )],
982 }
983}
984
985#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
986pub struct TxContext {
987 sender: AccountAddress,
989 digest: Vec<u8>,
991 epoch: EpochId,
993 epoch_timestamp_ms: CheckpointTimestamp,
995 ids_created: u64,
998}
999
1000#[derive(PartialEq, Eq, Clone, Copy)]
1001pub enum TxContextKind {
1002 None,
1004 Mutable,
1006 Immutable,
1008}
1009
1010impl TxContext {
1011 pub fn new(sender: &IotaAddress, digest: &TransactionDigest, epoch_data: &EpochData) -> Self {
1012 Self::new_from_components(
1013 sender,
1014 digest,
1015 &epoch_data.epoch_id(),
1016 epoch_data.epoch_start_timestamp(),
1017 )
1018 }
1019
1020 pub fn new_from_components(
1021 sender: &IotaAddress,
1022 digest: &TransactionDigest,
1023 epoch_id: &EpochId,
1024 epoch_timestamp_ms: u64,
1025 ) -> Self {
1026 Self {
1027 sender: AccountAddress::new(sender.0),
1028 digest: digest.into_inner().to_vec(),
1029 epoch: *epoch_id,
1030 epoch_timestamp_ms,
1031 ids_created: 0,
1032 }
1033 }
1034
1035 pub fn kind(view: &CompiledModule, s: &SignatureToken) -> TxContextKind {
1038 use SignatureToken as S;
1039 let (kind, s) = match s {
1040 S::MutableReference(s) => (TxContextKind::Mutable, s),
1041 S::Reference(s) => (TxContextKind::Immutable, s),
1042 _ => return TxContextKind::None,
1043 };
1044
1045 let S::Datatype(idx) = &**s else {
1046 return TxContextKind::None;
1047 };
1048
1049 let (module_addr, module_name, struct_name) = resolve_struct(view, *idx);
1050 let is_tx_context_type = module_name == TX_CONTEXT_MODULE_NAME
1051 && module_addr == &IOTA_FRAMEWORK_ADDRESS
1052 && struct_name == TX_CONTEXT_STRUCT_NAME;
1053
1054 if is_tx_context_type {
1055 kind
1056 } else {
1057 TxContextKind::None
1058 }
1059 }
1060
1061 pub fn epoch(&self) -> EpochId {
1062 self.epoch
1063 }
1064
1065 pub fn fresh_id(&mut self) -> ObjectID {
1068 let id = ObjectID::derive_id(self.digest(), self.ids_created);
1069
1070 self.ids_created += 1;
1071 id
1072 }
1073
1074 pub fn digest(&self) -> TransactionDigest {
1076 TransactionDigest::new(self.digest.clone().try_into().unwrap())
1077 }
1078
1079 pub fn sender(&self) -> IotaAddress {
1080 IotaAddress::from(ObjectID(self.sender))
1081 }
1082
1083 pub fn to_vec(&self) -> Vec<u8> {
1084 bcs::to_bytes(&self).unwrap()
1085 }
1086
1087 pub fn update_state(&mut self, other: TxContext) -> Result<(), ExecutionError> {
1092 if self.sender != other.sender
1093 || self.digest != other.digest
1094 || other.ids_created < self.ids_created
1095 {
1096 return Err(ExecutionError::new_with_source(
1097 ExecutionErrorKind::InvariantViolation,
1098 "Immutable fields for TxContext changed",
1099 ));
1100 }
1101 self.ids_created = other.ids_created;
1102 Ok(())
1103 }
1104
1105 pub fn random_for_testing_only() -> Self {
1107 Self::new(
1108 &IotaAddress::random_for_testing_only(),
1109 &TransactionDigest::random(),
1110 &EpochData::new_test(),
1111 )
1112 }
1113
1114 pub fn with_sender_for_testing_only(sender: &IotaAddress) -> Self {
1116 Self::new(sender, &TransactionDigest::random(), &EpochData::new_test())
1117 }
1118}
1119
1120impl SequenceNumber {
1122 pub const MIN_VALID_INCL: SequenceNumber = SequenceNumber(u64::MIN);
1127
1128 pub const MAX_VALID_EXCL: SequenceNumber = SequenceNumber(0x7fff_ffff_ffff_ffff);
1136
1137 pub const CANCELLED_READ: SequenceNumber =
1140 SequenceNumber(SequenceNumber::MAX_VALID_EXCL.value() + 1);
1141
1142 pub const CONGESTED_PRIOR_TO_GAS_PRICE_FEEDBACK: SequenceNumber =
1147 SequenceNumber(SequenceNumber::MAX_VALID_EXCL.value() + 2);
1148
1149 pub const RANDOMNESS_UNAVAILABLE: SequenceNumber =
1152 SequenceNumber(SequenceNumber::MAX_VALID_EXCL.value() + 3);
1153
1154 const CONGESTED_BASE_OFFSET_FOR_GAS_PRICE_FEEDBACK: u64 = 1_000;
1176
1177 const MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK: SequenceNumber = SequenceNumber(
1181 SequenceNumber::MAX_VALID_EXCL.value() + Self::CONGESTED_BASE_OFFSET_FOR_GAS_PRICE_FEEDBACK,
1182 );
1183
1184 pub const fn new() -> Self {
1185 SequenceNumber(0)
1186 }
1187
1188 pub const fn value(&self) -> u64 {
1189 self.0
1190 }
1191
1192 pub const fn from_u64(u: u64) -> Self {
1193 SequenceNumber(u)
1194 }
1195
1196 pub fn new_congested_with_suggested_gas_price(suggested_gas_price: u64) -> Self {
1202 let (version, overflows) = Self::MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK
1203 .value()
1204 .overflowing_add(suggested_gas_price);
1205 debug_assert!(
1206 !overflows,
1207 "the calculated version for a congested shared objects overflows"
1208 );
1209
1210 Self(version)
1211 }
1212
1213 pub fn is_congested(&self) -> bool {
1216 *self == Self::CONGESTED_PRIOR_TO_GAS_PRICE_FEEDBACK
1217 || self >= &Self::MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK
1218 }
1219
1220 pub fn get_congested_version_suggested_gas_price(&self) -> u64 {
1225 assert!(
1226 *self >= Self::MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK,
1227 "this is not a version used for congested shared objects in the gas price feedback \
1228 mechanism"
1229 );
1230
1231 self.value() - Self::MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK.value()
1232 }
1233
1234 pub fn increment(&mut self) {
1235 assert!(
1236 self.is_valid(),
1237 "cannot increment a sequence number: \
1238 maximum valid sequence number has already been reached"
1239 );
1240 self.0 += 1;
1241 }
1242
1243 pub fn increment_to(&mut self, next: SequenceNumber) {
1244 debug_assert!(*self < next, "Not an increment: {self} to {next}");
1245 *self = next;
1246 }
1247
1248 pub fn decrement(&mut self) {
1249 assert_ne!(
1250 *self,
1251 Self::MIN_VALID_INCL,
1252 "cannot decrement a sequence number: \
1253 minimum valid sequence number has already been reached"
1254 );
1255 self.0 -= 1;
1256 }
1257
1258 pub fn decrement_to(&mut self, prev: SequenceNumber) {
1259 debug_assert!(prev < *self, "Not a decrement: {self} to {prev}");
1260 *self = prev;
1261 }
1262
1263 #[must_use]
1266 pub fn lamport_increment(inputs: impl IntoIterator<Item = SequenceNumber>) -> SequenceNumber {
1267 let max_input = inputs.into_iter().fold(SequenceNumber::new(), max);
1268
1269 assert!(
1274 max_input.is_valid(),
1275 "cannot increment a sequence number: \
1276 maximum valid sequence number has already been reached"
1277 );
1278
1279 SequenceNumber(max_input.0 + 1)
1280 }
1281
1282 pub fn is_cancelled(&self) -> bool {
1285 self == &SequenceNumber::CANCELLED_READ
1286 || self == &SequenceNumber::RANDOMNESS_UNAVAILABLE
1287 || self.is_congested()
1288 }
1289
1290 pub fn is_valid(&self) -> bool {
1293 self < &SequenceNumber::MAX_VALID_EXCL
1294 }
1295}
1296
1297impl From<SequenceNumber> for u64 {
1298 fn from(val: SequenceNumber) -> Self {
1299 val.0
1300 }
1301}
1302
1303impl From<u64> for SequenceNumber {
1304 fn from(value: u64) -> Self {
1305 SequenceNumber(value)
1306 }
1307}
1308
1309impl From<SequenceNumber> for usize {
1310 fn from(value: SequenceNumber) -> Self {
1311 value.0 as usize
1312 }
1313}
1314
1315impl ObjectID {
1316 pub const LENGTH: usize = AccountAddress::LENGTH;
1318 pub const ZERO: Self = Self::new([0u8; Self::LENGTH]);
1320 pub const MAX: Self = Self::new([0xff; Self::LENGTH]);
1321 pub const fn new(obj_id: [u8; Self::LENGTH]) -> Self {
1323 Self(AccountAddress::new(obj_id))
1324 }
1325
1326 pub const fn from_address(addr: AccountAddress) -> Self {
1328 Self(addr)
1329 }
1330
1331 pub fn random() -> Self {
1333 Self::from(AccountAddress::random())
1334 }
1335
1336 pub fn random_from_rng<R>(rng: &mut R) -> Self
1338 where
1339 R: AllowedRng,
1340 {
1341 let buf: [u8; Self::LENGTH] = rng.gen();
1342 ObjectID::new(buf)
1343 }
1344
1345 pub fn to_vec(&self) -> Vec<u8> {
1347 self.0.to_vec()
1348 }
1349
1350 pub fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, ObjectIDParseError> {
1352 <[u8; Self::LENGTH]>::try_from(bytes.as_ref())
1353 .map_err(|_| ObjectIDParseError::TryFromSlice)
1354 .map(ObjectID::new)
1355 }
1356
1357 pub fn into_bytes(self) -> [u8; Self::LENGTH] {
1359 self.0.into_bytes()
1360 }
1361
1362 pub const fn from_single_byte(byte: u8) -> ObjectID {
1364 let mut bytes = [0u8; Self::LENGTH];
1365 bytes[Self::LENGTH - 1] = byte;
1366 ObjectID::new(bytes)
1367 }
1368
1369 pub fn from_hex_literal(literal: &str) -> Result<Self, ObjectIDParseError> {
1372 if !literal.starts_with("0x") {
1373 return Err(ObjectIDParseError::HexLiteralPrefixMissing);
1374 }
1375
1376 let hex_len = literal.len() - 2;
1377
1378 if hex_len < Self::LENGTH * 2 {
1380 let mut hex_str = String::with_capacity(Self::LENGTH * 2);
1381 for _ in 0..Self::LENGTH * 2 - hex_len {
1382 hex_str.push('0');
1383 }
1384 hex_str.push_str(&literal[2..]);
1385 Self::from_str(&hex_str)
1386 } else {
1387 Self::from_str(&literal[2..])
1388 }
1389 }
1390
1391 pub fn derive_id(digest: TransactionDigest, creation_num: u64) -> Self {
1394 let mut hasher = DefaultHash::default();
1395 hasher.update([HashingIntentScope::RegularObjectId as u8]);
1396 hasher.update(digest);
1397 hasher.update(creation_num.to_le_bytes());
1398 let hash = hasher.finalize();
1399
1400 ObjectID::try_from(&hash.as_ref()[0..ObjectID::LENGTH]).unwrap()
1404 }
1405
1406 pub fn next_increment(&self) -> Result<ObjectID, anyhow::Error> {
1409 let mut prev_val = self.to_vec();
1410 let mx = [0xFF; Self::LENGTH];
1411
1412 if prev_val == mx {
1413 bail!("Increment will cause overflow");
1414 }
1415
1416 for idx in (0..Self::LENGTH).rev() {
1418 if prev_val[idx] == 0xFF {
1419 prev_val[idx] = 0;
1420 } else {
1421 prev_val[idx] += 1;
1422 break;
1423 };
1424 }
1425 ObjectID::try_from(prev_val.clone()).map_err(|w| w.into())
1426 }
1427
1428 pub fn in_range(offset: ObjectID, count: u64) -> Result<Vec<ObjectID>, anyhow::Error> {
1430 let mut ret = Vec::new();
1431 let mut prev = offset;
1432 for o in 0..count {
1433 if o != 0 {
1434 prev = prev.next_increment()?;
1435 }
1436 ret.push(prev);
1437 }
1438 Ok(ret)
1439 }
1440
1441 pub fn to_hex_uncompressed(&self) -> String {
1445 format!("{self}")
1446 }
1447
1448 pub fn is_clock(&self) -> bool {
1449 *self == IOTA_CLOCK_OBJECT_ID
1450 }
1451}
1452
1453impl From<IotaAddress> for ObjectID {
1454 fn from(address: IotaAddress) -> ObjectID {
1455 let tmp: AccountAddress = address.into();
1456 tmp.into()
1457 }
1458}
1459
1460impl From<AccountAddress> for ObjectID {
1461 fn from(address: AccountAddress) -> Self {
1462 Self(address)
1463 }
1464}
1465
1466impl fmt::Display for ObjectID {
1467 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1468 write!(f, "0x{}", Hex::encode(self.0))
1469 }
1470}
1471
1472impl fmt::Debug for ObjectID {
1473 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
1474 write!(f, "0x{}", Hex::encode(self.0))
1475 }
1476}
1477
1478impl AsRef<[u8]> for ObjectID {
1479 fn as_ref(&self) -> &[u8] {
1480 self.0.as_slice()
1481 }
1482}
1483
1484impl TryFrom<&[u8]> for ObjectID {
1485 type Error = ObjectIDParseError;
1486
1487 fn try_from(bytes: &[u8]) -> Result<ObjectID, ObjectIDParseError> {
1489 Self::from_bytes(bytes)
1490 }
1491}
1492
1493impl TryFrom<Vec<u8>> for ObjectID {
1494 type Error = ObjectIDParseError;
1495
1496 fn try_from(bytes: Vec<u8>) -> Result<ObjectID, ObjectIDParseError> {
1498 Self::from_bytes(bytes)
1499 }
1500}
1501
1502impl FromStr for ObjectID {
1503 type Err = ObjectIDParseError;
1504
1505 fn from_str(s: &str) -> Result<Self, ObjectIDParseError> {
1508 decode_bytes_hex(s).or_else(|_| Self::from_hex_literal(s))
1509 }
1510}
1511
1512impl std::ops::Deref for ObjectID {
1513 type Target = AccountAddress;
1514
1515 fn deref(&self) -> &Self::Target {
1516 &self.0
1517 }
1518}
1519
1520pub fn dbg_object_id(name: u8) -> ObjectID {
1522 ObjectID::new([name; ObjectID::LENGTH])
1523}
1524
1525#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
1526pub enum ObjectIDParseError {
1527 #[error("ObjectID hex literal must start with 0x")]
1528 HexLiteralPrefixMissing,
1529
1530 #[error("Could not convert from bytes slice")]
1531 TryFromSlice,
1532}
1533
1534impl From<ObjectID> for AccountAddress {
1535 fn from(obj_id: ObjectID) -> Self {
1536 obj_id.0
1537 }
1538}
1539
1540impl From<IotaAddress> for AccountAddress {
1541 fn from(address: IotaAddress) -> Self {
1542 Self::new(address.0)
1543 }
1544}
1545
1546impl fmt::Display for MoveObjectType {
1547 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1548 let s: StructTag = self.clone().into();
1549 write!(
1550 f,
1551 "{}",
1552 to_iota_struct_tag_string(&s).map_err(fmt::Error::custom)?
1553 )
1554 }
1555}
1556
1557impl fmt::Display for ObjectType {
1558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
1559 match self {
1560 ObjectType::Package => write!(f, "{PACKAGE}"),
1561 ObjectType::Struct(t) => write!(f, "{t}"),
1562 }
1563 }
1564}
1565
1566#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
1572#[serde(try_from = "Vec<T>")]
1573pub struct SizeOneVec<T> {
1574 e: T,
1575}
1576
1577impl<T> SizeOneVec<T> {
1578 pub fn new(e: T) -> Self {
1579 Self { e }
1580 }
1581
1582 pub fn element(&self) -> &T {
1583 &self.e
1584 }
1585
1586 pub fn element_mut(&mut self) -> &mut T {
1587 &mut self.e
1588 }
1589
1590 pub fn into_inner(self) -> T {
1591 self.e
1592 }
1593
1594 pub fn iter(&self) -> std::iter::Once<&T> {
1595 std::iter::once(&self.e)
1596 }
1597}
1598
1599impl<T> Serialize for SizeOneVec<T>
1600where
1601 T: Serialize,
1602{
1603 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1604 where
1605 S: Serializer,
1606 {
1607 let mut seq = serializer.serialize_seq(Some(1))?;
1608 seq.serialize_element(&self.e)?;
1609 seq.end()
1610 }
1611}
1612
1613impl<T> TryFrom<Vec<T>> for SizeOneVec<T> {
1614 type Error = anyhow::Error;
1615
1616 fn try_from(mut v: Vec<T>) -> Result<Self, Self::Error> {
1617 if v.len() != 1 {
1618 Err(anyhow!("Expected a vec of size 1"))
1619 } else {
1620 Ok(SizeOneVec {
1621 e: v.pop().unwrap(),
1622 })
1623 }
1624 }
1625}
1626
1627#[test]
1628fn test_size_one_vec_is_transparent() {
1629 let regular = vec![42u8];
1630 let size_one = SizeOneVec::new(42u8);
1631
1632 let regular_ser = bcs::to_bytes(®ular).unwrap();
1634 let size_one_deser = bcs::from_bytes::<SizeOneVec<u8>>(®ular_ser).unwrap();
1635 assert_eq!(size_one, size_one_deser);
1636
1637 let size_one_ser = bcs::to_bytes(&SizeOneVec::new(43u8)).unwrap();
1639 let regular_deser = bcs::from_bytes::<Vec<u8>>(&size_one_ser).unwrap();
1640 assert_eq!(regular_deser, vec![43u8]);
1641
1642 let empty_ser = bcs::to_bytes(&Vec::<u8>::new()).unwrap();
1644 bcs::from_bytes::<SizeOneVec<u8>>(&empty_ser).unwrap_err();
1645
1646 let size_greater_than_one_ser = bcs::to_bytes(&vec![1u8, 2u8]).unwrap();
1647 bcs::from_bytes::<SizeOneVec<u8>>(&size_greater_than_one_ser).unwrap_err();
1648}