1use crate::{
6 EffectsAuxDataDigest, EpochId, ExecutionStatus, GasCostSummary, IdOperation, InputSharedObject,
7 ObjectChange, ObjectDigest, ObjectId, ObjectReference, ObjectRemoveKind, ObjectVersion,
8 OwnedObjectReference, Owner, TransactionDigest, TransactionEventsDigest, Version, WriteKind,
9};
10
11#[derive(Clone, Debug, Eq, PartialEq)]
31#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
32#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
33#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
34pub struct TransactionEffectsV1 {
35 pub status: ExecutionStatus,
37 #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
39 #[cfg_attr(feature = "bcs-schema", bcs_schema(as_type = "u64"))]
40 pub epoch: EpochId,
41 pub gas_cost_summary: GasCostSummary,
43 pub transaction_digest: TransactionDigest,
45 pub gas_object_index: Option<u32>,
49 pub events_digest: Option<TransactionEventsDigest>,
52 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=5).lift()))]
54 pub dependencies: Vec<TransactionDigest>,
55 pub lamport_version: Version,
57 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
59 pub changed_objects: Vec<ChangedObject>,
60 #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
66 pub unchanged_shared_objects: Vec<UnchangedSharedObject>,
67 pub auxiliary_data_digest: Option<EffectsAuxDataDigest>,
72}
73
74impl crate::TreeDisplay for TransactionEffectsV1 {
75 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
76 w.header("Transaction Effects V1")?;
77 w.child("Status", &self.status, false)?;
78 w.leaf("Epoch", &self.epoch, false)?;
79 w.child("Gas Cost Summary", &self.gas_cost_summary, false)?;
80 w.leaf("Transaction Digest", &self.transaction_digest, false)?;
81 w.option_leaf("Gas Object Index", &self.gas_object_index, false)?;
82 w.option_leaf("Events Digest", &self.events_digest, false)?;
83 w.leaves("Dependencies", &self.dependencies, false)?;
84 w.leaf("Lamport Version", &self.lamport_version, false)?;
85 w.children("Changed Objects", &self.changed_objects, false)?;
86 w.children(
87 "Unchanged Shared Objects",
88 &self.unchanged_shared_objects,
89 false,
90 )?;
91 w.option_leaf("Auxiliary Data Digest", &self.auxiliary_data_digest, true)
92 }
93}
94
95#[derive(Clone, Debug, Eq, PartialEq)]
105#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
106#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
107#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
108pub struct ChangedObject {
109 pub object_id: ObjectId,
111 pub input_state: ObjectIn,
113 pub output_state: ObjectOut,
115 pub id_operation: IdOperation,
119}
120
121impl crate::TreeDisplay for ChangedObject {
122 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
123 w.header("Changed Object")?;
124 w.leaf("Object ID", &self.object_id, false)?;
125 w.child("Input State", &self.input_state, false)?;
126 w.child("Output State", &self.output_state, false)?;
127 w.leaf("ID Operation", &self.id_operation, true)
128 }
129}
130
131#[derive(Clone, Debug, Eq, PartialEq)]
142#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
143#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
144#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
145pub struct UnchangedSharedObject {
146 pub object_id: ObjectId,
147 pub kind: UnchangedSharedKind,
148}
149
150impl crate::TreeDisplay for UnchangedSharedObject {
151 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
152 w.header("Unchanged Shared Object")?;
153 w.leaf("Object ID", &self.object_id, false)?;
154 w.child("Kind", &self.kind, true)
155 }
156}
157
158crate::impl_tree_display!(
159 TransactionEffectsV1,
160 ChangedObject,
161 UnchangedSharedObject,
162 UnchangedSharedKind,
163 ObjectIn,
164 ObjectOut
165);
166
167#[derive(Clone, Debug, Eq, PartialEq)]
181#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
182#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
183#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
184#[non_exhaustive]
185pub enum UnchangedSharedKind {
186 ReadOnlyRoot {
190 version: Version,
191 digest: ObjectDigest,
192 },
193 MutateDeleted { version: Version },
195 ReadDeleted { version: Version },
197 Canceled { version: Version },
200 PerEpochConfig,
203}
204
205impl UnchangedSharedKind {
206 crate::def_is!(
207 ReadOnlyRoot,
208 MutateDeleted,
209 ReadDeleted,
210 Canceled,
211 PerEpochConfig
212 );
213}
214
215impl crate::TreeDisplay for UnchangedSharedKind {
216 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
217 w.enum_name("Unchanged Shared Kind");
218 match self {
219 UnchangedSharedKind::ReadOnlyRoot { version, digest } => {
220 w.header("Read Only Root")?;
221 w.leaf("Version", version, false)?;
222 w.leaf("Digest", digest, true)
223 }
224 UnchangedSharedKind::MutateDeleted { version } => {
225 w.header("Mutate Deleted")?;
226 w.leaf("Version", version, true)
227 }
228 UnchangedSharedKind::ReadDeleted { version } => {
229 w.header("Read Deleted")?;
230 w.leaf("Version", version, true)
231 }
232 UnchangedSharedKind::Canceled { version } => {
233 w.header("Canceled")?;
234 w.leaf("Version", version, true)
235 }
236 UnchangedSharedKind::PerEpochConfig => w.header("Per Epoch Config"),
237 }
238 }
239}
240
241#[derive(Clone, Debug, Eq, PartialEq)]
256#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
257#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
258#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
259#[non_exhaustive]
260pub enum ObjectIn {
261 Missing,
262 Data {
264 version: Version,
265 digest: ObjectDigest,
266 owner: Owner,
267 },
268}
269
270impl ObjectIn {
271 crate::def_is!(Missing, Data);
272
273 pub fn opt_version(&self) -> Option<Version> {
274 if let Self::Data { version, .. } = self {
275 Some(*version)
276 } else {
277 None
278 }
279 }
280
281 pub fn version(&self) -> Version {
282 self.opt_version().expect("object does not exist")
283 }
284
285 pub fn opt_digest(&self) -> Option<ObjectDigest> {
286 if let Self::Data { digest, .. } = self {
287 Some(*digest)
288 } else {
289 None
290 }
291 }
292
293 pub fn digest(&self) -> ObjectDigest {
294 self.opt_digest().expect("object does not exist")
295 }
296
297 pub fn opt_owner(&self) -> Option<Owner> {
298 if let Self::Data { owner, .. } = self {
299 Some(*owner)
300 } else {
301 None
302 }
303 }
304
305 pub fn owner(&self) -> Owner {
306 self.opt_owner().expect("object does not exist")
307 }
308}
309
310impl crate::TreeDisplay for ObjectIn {
311 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
312 w.enum_name("Object In");
313 match self {
314 ObjectIn::Missing => w.header("Missing"),
315 ObjectIn::Data {
316 version,
317 digest,
318 owner,
319 } => {
320 w.header("Data")?;
321 w.leaf("Version", version, false)?;
322 w.leaf("Digest", digest, false)?;
323 w.leaf("Owner", owner, true)
324 }
325 }
326 }
327}
328
329#[derive(Clone, Debug, Eq, PartialEq)]
341#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
342#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
343#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
344#[non_exhaustive]
345pub enum ObjectOut {
346 Missing,
348 ObjectWrite { digest: ObjectDigest, owner: Owner },
350 PackageWrite {
353 version: Version,
354 digest: ObjectDigest,
355 },
356}
357
358impl ObjectOut {
359 crate::def_is!(Missing, ObjectWrite, PackageWrite);
360
361 pub fn opt_object_digest(&self) -> Option<ObjectDigest> {
362 if let Self::ObjectWrite { digest, .. } = self {
363 Some(*digest)
364 } else {
365 None
366 }
367 }
368
369 pub fn object_digest(&self) -> ObjectDigest {
370 self.opt_object_digest().expect("object does not exist")
371 }
372
373 pub fn opt_object_owner(&self) -> Option<Owner> {
374 if let Self::ObjectWrite { owner, .. } = self {
375 Some(*owner)
376 } else {
377 None
378 }
379 }
380
381 pub fn object_owner(&self) -> Owner {
382 self.opt_object_owner().expect("object does not exist")
383 }
384
385 pub fn opt_package_version(&self) -> Option<Version> {
386 if let Self::PackageWrite { version, .. } = self {
387 Some(*version)
388 } else {
389 None
390 }
391 }
392
393 pub fn package_version(&self) -> Version {
394 self.opt_package_version().expect("object does not exist")
395 }
396
397 pub fn opt_package_digest(&self) -> Option<ObjectDigest> {
398 if let Self::PackageWrite { digest, .. } = self {
399 Some(*digest)
400 } else {
401 None
402 }
403 }
404
405 pub fn package_digest(&self) -> ObjectDigest {
406 self.opt_package_digest().expect("package does not exist")
407 }
408}
409
410impl crate::TreeDisplay for ObjectOut {
411 fn fmt_tree(&self, w: &mut crate::TreeWriter<'_, '_>) -> std::fmt::Result {
412 w.enum_name("Object Out");
413 match self {
414 ObjectOut::Missing => w.header("Missing"),
415 ObjectOut::ObjectWrite { digest, owner } => {
416 w.header("Object Write")?;
417 w.leaf("Digest", digest, false)?;
418 w.leaf("Owner", owner, true)
419 }
420 ObjectOut::PackageWrite { version, digest } => {
421 w.header("Package Write")?;
422 w.leaf("Version", version, false)?;
423 w.leaf("Digest", digest, true)
424 }
425 }
426 }
427}
428
429impl TransactionEffectsV1 {
430 pub fn modified_at_versions(&self) -> Vec<ObjectVersion> {
433 self.changed_objects
434 .iter()
435 .filter_map(|changed| {
436 changed
437 .input_state
438 .opt_version()
439 .map(|version| ObjectVersion::new(changed.object_id, version))
440 })
441 .collect()
442 }
443
444 pub fn input_shared_objects(&self) -> Vec<InputSharedObject> {
448 self.changed_objects
449 .iter()
450 .filter_map(|changed| match changed.input_state {
451 ObjectIn::Data {
452 version,
453 digest,
454 owner: Owner::Shared { .. },
455 } => Some(InputSharedObject::Mutate(ObjectReference::new(
456 changed.object_id,
457 version,
458 digest,
459 ))),
460 _ => None,
461 })
462 .chain(
463 self.unchanged_shared_objects
464 .iter()
465 .filter_map(|unchanged| {
466 let object = |version| ObjectVersion::new(unchanged.object_id, version);
467 match unchanged.kind {
468 UnchangedSharedKind::ReadOnlyRoot { version, digest } => {
469 Some(InputSharedObject::ReadOnly(ObjectReference::new(
470 unchanged.object_id,
471 version,
472 digest,
473 )))
474 }
475 UnchangedSharedKind::ReadDeleted { version } => {
476 Some(InputSharedObject::ReadDeleted(object(version)))
477 }
478 UnchangedSharedKind::MutateDeleted { version } => {
479 Some(InputSharedObject::MutateDeleted(object(version)))
480 }
481 UnchangedSharedKind::Canceled { version } => {
482 Some(InputSharedObject::Canceled(object(version)))
483 }
484 UnchangedSharedKind::PerEpochConfig => None,
487 }
488 }),
489 )
490 .collect()
491 }
492
493 pub fn object_changes(&self) -> Vec<ObjectChange> {
496 self.changed_objects
497 .iter()
498 .map(|changed| {
499 let input = match changed.input_state {
500 ObjectIn::Data {
501 version, digest, ..
502 } => Some((version, digest)),
503 _ => None,
504 };
505 let output = match changed.output_state {
506 ObjectOut::ObjectWrite { digest, .. } => Some((self.lamport_version, digest)),
507 ObjectOut::PackageWrite { version, digest } => Some((version, digest)),
508 _ => None,
509 };
510 ObjectChange {
511 object_id: changed.object_id,
512 input_version: input.map(|(version, _)| version),
513 input_digest: input.map(|(_, digest)| digest),
514 output_version: output.map(|(version, _)| version),
515 output_digest: output.map(|(_, digest)| digest),
516 id_operation: changed.id_operation,
517 }
518 })
519 .collect()
520 }
521
522 pub fn old_object_metadata(&self) -> Vec<OwnedObjectReference> {
525 self.changed_objects
526 .iter()
527 .filter_map(|changed| match changed.input_state {
528 ObjectIn::Data {
529 version,
530 digest,
531 owner,
532 } => Some(OwnedObjectReference::new(
533 ObjectReference::new(changed.object_id, version, digest),
534 owner,
535 )),
536 _ => None,
537 })
538 .collect()
539 }
540
541 pub fn created(&self) -> Vec<OwnedObjectReference> {
545 self.changed_objects
546 .iter()
547 .filter(|changed| {
548 changed.input_state.is_missing() && changed.id_operation == IdOperation::Created
549 })
550 .filter_map(|changed| self.output_reference(changed))
551 .collect()
552 }
553
554 pub fn mutated(&self) -> Vec<OwnedObjectReference> {
558 self.changed_objects
559 .iter()
560 .filter(|changed| changed.input_state.is_data())
561 .filter_map(|changed| self.output_reference(changed))
562 .collect()
563 }
564
565 pub fn unwrapped(&self) -> Vec<OwnedObjectReference> {
568 self.changed_objects
569 .iter()
570 .filter(|changed| {
571 changed.input_state.is_missing()
572 && changed.id_operation == IdOperation::None
573 && changed.output_state.is_object_write()
575 })
576 .filter_map(|changed| self.output_reference(changed))
577 .collect()
578 }
579
580 pub fn deleted(&self) -> Vec<ObjectReference> {
584 self.removed_references(
585 |changed| changed.input_state.is_data() && changed.id_operation == IdOperation::Deleted,
586 ObjectDigest::OBJECT_DELETED,
587 )
588 }
589
590 pub fn unwrapped_then_deleted(&self) -> Vec<ObjectReference> {
595 self.removed_references(
596 |changed| {
597 changed.input_state.is_missing() && changed.id_operation == IdOperation::Deleted
598 },
599 ObjectDigest::OBJECT_DELETED,
600 )
601 }
602
603 pub fn wrapped(&self) -> Vec<ObjectReference> {
608 self.removed_references(
609 |changed| changed.input_state.is_data() && changed.id_operation == IdOperation::None,
610 ObjectDigest::OBJECT_WRAPPED,
611 )
612 }
613
614 pub fn all_changed_objects(&self) -> Vec<(OwnedObjectReference, WriteKind)> {
618 let tagged = |kind| move |object| (object, kind);
619 self.mutated()
620 .into_iter()
621 .map(tagged(WriteKind::Mutate))
622 .chain(self.created().into_iter().map(tagged(WriteKind::Create)))
623 .chain(self.unwrapped().into_iter().map(tagged(WriteKind::Unwrap)))
624 .collect()
625 }
626
627 pub fn all_removed_objects(&self) -> Vec<(ObjectReference, ObjectRemoveKind)> {
632 let tagged = |kind| move |reference| (reference, kind);
633 self.deleted()
634 .into_iter()
635 .map(tagged(ObjectRemoveKind::Delete))
636 .chain(
637 self.wrapped()
638 .into_iter()
639 .map(tagged(ObjectRemoveKind::Wrap)),
640 )
641 .collect()
642 }
643
644 pub fn gas_object(&self) -> Option<OwnedObjectReference> {
647 let changed = self.changed_objects.get(self.gas_object_index? as usize)?;
648 changed
650 .output_state
651 .is_object_write()
652 .then(|| self.output_reference(changed))?
653 }
654
655 fn output_reference(&self, changed: &ChangedObject) -> Option<OwnedObjectReference> {
659 match changed.output_state {
660 ObjectOut::ObjectWrite { digest, owner } => Some(OwnedObjectReference::new(
661 ObjectReference::new(changed.object_id, self.lamport_version, digest),
662 owner,
663 )),
664 ObjectOut::PackageWrite { version, digest } => Some(OwnedObjectReference::new(
665 ObjectReference::new(changed.object_id, version, digest),
666 Owner::Immutable,
667 )),
668 _ => None,
669 }
670 }
671
672 fn removed_references(
675 &self,
676 select: impl Fn(&ChangedObject) -> bool,
677 digest: ObjectDigest,
678 ) -> Vec<ObjectReference> {
679 self.changed_objects
680 .iter()
681 .filter(|changed| changed.output_state.is_missing() && select(changed))
682 .map(|changed| ObjectReference::new(changed.object_id, self.lamport_version, digest))
683 .collect()
684 }
685}
686
687#[cfg(all(feature = "proptest", test))]
688mod tests {
689 use test_strategy::proptest;
690
691 use super::TransactionEffectsV1;
692
693 #[proptest]
699 fn object_sets_report_each_changed_object_at_most_once(effects: TransactionEffectsV1) {
700 let reported = effects.created().len()
701 + effects.mutated().len()
702 + effects.unwrapped().len()
703 + effects.deleted().len()
704 + effects.unwrapped_then_deleted().len()
705 + effects.wrapped().len();
706
707 assert!(reported <= effects.changed_objects.len());
708 }
709}