Skip to main content

iota_sdk_types/effects/
v1.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2025 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use 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/// Version 1 of TransactionEffects
12///
13/// # BCS
14///
15/// The BCS serialized form for this type is defined by the following ABNF:
16///
17/// ```text
18/// transaction-effects-v1 = execution-status                    ; status
19///                          u64                                 ; epoch
20///                          gas-cost-summary                    ; gas-used
21///                          transaction-digest                  ; transaction-digest
22///                          (option u32)                        ; gas-object-index
23///                          (option transaction-events-digest)  ; events-digest
24///                          (vector transaction-digest)         ; dependencies
25///                          u64                                 ; lamport-version
26///                          (vector changed-object)             ; changed-objects
27///                          (vector unchanged-shared-object)    ; unchanged-shared-objects
28///                          (option effects-aux-data-digest)    ; auxiliary-data-digest
29/// ```
30#[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    /// The status of the execution
36    pub status: ExecutionStatus,
37    /// The epoch when this transaction was executed.
38    #[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    /// The gas used by this transaction
42    pub gas_cost_summary: GasCostSummary,
43    /// The transaction digest
44    pub transaction_digest: TransactionDigest,
45    /// The updated gas object reference, as an index into the `changed_objects`
46    /// vector. Having a dedicated field for convenient access.
47    /// System transaction that don't require gas will leave this as None.
48    pub gas_object_index: Option<u32>,
49    /// The digest of the events emitted during execution,
50    /// can be None if the transaction does not emit any event.
51    pub events_digest: Option<TransactionEventsDigest>,
52    /// The set of transaction digests this transaction depends on.
53    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=5).lift()))]
54    pub dependencies: Vec<TransactionDigest>,
55    /// The version number of all the written Move objects by this transaction.
56    pub lamport_version: Version,
57    /// Objects whose state are changed in the object store.
58    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
59    pub changed_objects: Vec<ChangedObject>,
60    /// Shared objects that are not mutated in this transaction. Unlike owned
61    /// objects, read-only shared objects' version are not committed in the
62    /// transaction, and in order for a node to catch up and execute it
63    /// without consensus sequencing, the version needs to be committed in
64    /// the effects.
65    #[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
66    pub unchanged_shared_objects: Vec<UnchangedSharedObject>,
67    /// Auxiliary data that are not protocol-critical, generated as part of the
68    /// effects but are stored separately. Storing it separately allows us
69    /// to avoid bloating the effects with data that are not critical.
70    /// It also provides more flexibility on the format and type of the data.
71    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/// Input/output state of an object that was changed during execution
96///
97/// # BCS
98///
99/// The BCS serialized form for this type is defined by the following ABNF:
100///
101/// ```text
102/// changed-object = object-id object-in object-out id-operation
103/// ```
104#[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    /// Id of the object
110    pub object_id: ObjectId,
111    /// State of the object in the store prior to this transaction.
112    pub input_state: ObjectIn,
113    /// State of the object in the store after this transaction.
114    pub output_state: ObjectOut,
115    /// Whether this object ID is created or deleted in this transaction.
116    /// This information isn't required by the protocol but is useful for
117    /// providing more detailed semantics on object changes.
118    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/// A shared object that wasn't changed during execution
132///
133/// # BCS
134///
135/// The BCS serialized form for this type is defined by the following ABNF:
136///
137/// ```text
138/// unchanged-shared-object = object-id               ; object-id
139///                           unchanged-shared-kind   ; kind
140/// ```
141#[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/// Type of unchanged shared object
168///
169/// # BCS
170///
171/// The BCS serialized form for this type is defined by the following ABNF:
172///
173/// ```text
174/// unchanged-shared-kind = %d00 u64 object-digest   ; ReadOnlyRoot
175///                       / %d01 u64                  ; MutateDeleted
176///                       / %d02 u64                  ; ReadDeleted
177///                       / %d03 u64                  ; Canceled
178///                       / %d04                       ; PerEpochConfig
179/// ```
180#[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    /// Read-only shared objects from the input. We don't really need
187    /// ObjectDigest for protocol correctness, but it will make it easier to
188    /// verify untrusted read.
189    ReadOnlyRoot {
190        version: Version,
191        digest: ObjectDigest,
192    },
193    /// Deleted shared objects that appear mutably/owned in the input.
194    MutateDeleted { version: Version },
195    /// Deleted shared objects that appear as read-only in the input.
196    ReadDeleted { version: Version },
197    /// Shared objects in canceled transaction. The sequence number embed
198    /// cancellation reason.
199    Canceled { version: Version },
200    /// Read of a per-epoch config object that should remain the same during an
201    /// epoch.
202    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/// State of an object prior to execution
242///
243/// If an object exists (at root-level) in the store prior to this transaction,
244/// it should be Data, otherwise it's Missing, e.g. wrapped objects should be
245/// Missing.
246///
247/// # BCS
248///
249/// The BCS serialized form for this type is defined by the following ABNF:
250///
251/// ```text
252/// object-in = %d00                          ; Missing
253///           / %d01 u64 object-digest owner   ; Data
254/// ```
255#[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    /// The old version, digest and owner.
263    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/// State of an object after execution
330///
331/// # BCS
332///
333/// The BCS serialized form for this type is defined by the following ABNF:
334///
335/// ```text
336/// object-out = %d00                       ; Missing
337///            / %d01 object-digest owner   ; ObjectWrite
338///            / %d02 u64 object-digest     ; PackageWrite
339/// ```
340#[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    /// Same definition as in ObjectIn.
347    Missing,
348    /// Any written object, including all of mutated, created, unwrapped today.
349    ObjectWrite { digest: ObjectDigest, owner: Owner },
350    /// Packages writes need to be tracked separately with version because
351    /// we don't use lamport version for package publish and upgrades.
352    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    /// The id and pre-transaction version of every object that existed before
431    /// this transaction and was modified by it (mutated, wrapped or deleted).
432    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    /// The shared objects this transaction was sequenced against, whether or
445    /// not it changed them. Excludes per-epoch config objects, which need no
446    /// sequencing.
447    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                            // A per-epoch config object is read without being
485                            // sequenced, so it is not an input in this sense.
486                            UnchangedSharedKind::PerEpochConfig => None,
487                        }
488                    }),
489            )
490            .collect()
491    }
492
493    /// What this transaction did to each object it changed, with the version
494    /// and digest each side is at resolved.
495    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    /// The reference and owner, before this transaction, of every object it
523    /// modified.
524    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    /// Objects (Move objects and packages) newly created by this transaction,
542    /// paired with their owner. Excludes objects created and then wrapped
543    /// within the same transaction.
544    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    /// Objects that existed before this transaction and whose contents it
555    /// updated (in-place mutations and system package upgrades), at their
556    /// post-transaction reference and owner.
557    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    /// Objects that were wrapped inside another object before this transaction
566    /// and that it promoted back to top-level objects in the store.
567    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                    // A package is never wrapped, so never unwrapped either.
574                    && changed.output_state.is_object_write()
575            })
576            .filter_map(|changed| self.output_reference(changed))
577            .collect()
578    }
579
580    /// Objects that existed before this transaction and that it deleted.
581    /// References carry the version this transaction assigned and the
582    /// [`ObjectDigest::OBJECT_DELETED`] tombstone digest.
583    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    /// Objects unwrapped and then deleted within this same transaction, so
591    /// that they existed as top-level objects neither before nor after it.
592    /// References carry the version this transaction assigned and the
593    /// [`ObjectDigest::OBJECT_DELETED`] tombstone digest.
594    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    /// Objects that existed as top-level objects before this transaction and
604    /// that it wrapped inside another object, so they are no longer visible in
605    /// the object store. References carry the version this transaction assigned
606    /// and the [`ObjectDigest::OBJECT_WRAPPED`] tombstone digest.
607    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    /// Every object still in the store after this transaction, tagged with how
615    /// it got there: the created, mutated and unwrapped objects together.
616    /// Excludes the objects the transaction removed.
617    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    /// Every object that was in the store before this transaction and is not
628    /// after it, tagged with why: the deleted and wrapped objects together.
629    /// Excludes an object the transaction unwrapped and then deleted, which was
630    /// not in the store to begin with.
631    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    /// The post-transaction reference and owner of the gas object, or `None`
645    /// for a system transaction, which pays no gas and so names none.
646    pub fn gas_object(&self) -> Option<OwnedObjectReference> {
647        let changed = self.changed_objects.get(self.gas_object_index? as usize)?;
648        // Gas is paid in coins, so a gas object is never a package.
649        changed
650            .output_state
651            .is_object_write()
652            .then(|| self.output_reference(changed))?
653    }
654
655    /// The post-transaction reference and owner of a changed object, or `None`
656    /// if this transaction removed it from the store. A package carries its own
657    /// version; every other object takes the version this transaction assigned.
658    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    /// References, carrying `digest` as their tombstone, to the objects this
673    /// transaction removed from the store that `select` accepts.
674    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    /// The six object sets are selected by mutually exclusive combinations of
694    /// input state, output state and id operation, so together they report each
695    /// changed object at most once — for any effects, not only well-formed
696    /// ones. The fixtures cover the other half, that real effects leave none
697    /// out.
698    #[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}