sui_types/effects/
effects_v2.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::object_change::{AccumulatorWriteV1, ObjectIn, ObjectOut};
5use super::{EffectsObjectChange, IDOperation, ObjectChange};
6use crate::accumulator_event::AccumulatorEvent;
7use crate::accumulator_root::AccumulatorObjId;
8use crate::base_types::{
9    EpochId, ObjectDigest, ObjectID, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest,
10    VersionDigest,
11};
12use crate::digests::{EffectsAuxDataDigest, TransactionEventsDigest};
13use crate::effects::{InputConsensusObject, TransactionEffectsAPI};
14use crate::execution::SharedInput;
15use crate::execution_status::{
16    ExecutionErrorKind, ExecutionFailure, ExecutionStatus, MoveLocation,
17};
18use crate::gas::GasCostSummary;
19#[cfg(debug_assertions)]
20use crate::is_system_package;
21use crate::object::{OBJECT_START_VERSION, Owner};
22use crate::transaction::SharedObjectMutability;
23use serde::{Deserialize, Serialize};
24#[cfg(debug_assertions)]
25use std::collections::HashSet;
26use std::collections::{BTreeMap, BTreeSet};
27
28/// The response from processing a transaction or a certified transaction
29#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
30pub struct TransactionEffectsV2 {
31    /// The status of the execution
32    pub(crate) status: ExecutionStatus,
33    /// The epoch when this transaction was executed.
34    pub(crate) executed_epoch: EpochId,
35    pub(crate) gas_used: GasCostSummary,
36    /// The transaction digest
37    pub(crate) transaction_digest: TransactionDigest,
38    /// The updated gas object reference, as an index into the `changed_objects` vector.
39    /// Having a dedicated field for convenient access.
40    /// System transaction that don't require gas will leave this as None.
41    pub(crate) gas_object_index: Option<u32>,
42    /// The digest of the events emitted during execution,
43    /// can be None if the transaction does not emit any event.
44    pub(crate) events_digest: Option<TransactionEventsDigest>,
45    /// The set of transaction digests this transaction depends on.
46    pub(crate) dependencies: Vec<TransactionDigest>,
47
48    /// The version number of all the written Move objects by this transaction.
49    pub(crate) lamport_version: SequenceNumber,
50    /// Objects whose state are changed in the object store.
51    /// This field should not be exposed to the public API.
52    /// Otherwise it will make it harder to use effects of different versions.
53    /// Note that for accumulator writes, the ObjectID here will be the dynamic field object ID
54    /// that stores the accumulator value. However this object is not really mutated
55    /// in this transaction. We just have to use an ObjectID that is unique so that
56    /// it does not conflict with any other object IDs in the changed_objects.
57    pub(crate) changed_objects: Vec<(ObjectID, EffectsObjectChange)>,
58    /// Consensus objects that are not mutated in this transaction. Unlike owned objects,
59    /// read-only consensus objects' version are not committed in the transaction,
60    /// and in order for a node to catch up and execute it without consensus sequencing,
61    /// the version needs to be committed in the effects.
62    pub(crate) unchanged_consensus_objects: Vec<(ObjectID, UnchangedConsensusKind)>,
63    /// Auxiliary data that are not protocol-critical, generated as part of the effects but are stored separately.
64    /// Storing it separately allows us to avoid bloating the effects with data that are not critical.
65    /// It also provides more flexibility on the format and type of the data.
66    pub(crate) aux_data_digest: Option<EffectsAuxDataDigest>,
67}
68
69impl TransactionEffectsAPI for TransactionEffectsV2 {
70    fn status(&self) -> &ExecutionStatus {
71        &self.status
72    }
73
74    fn into_status(self) -> ExecutionStatus {
75        self.status
76    }
77
78    fn executed_epoch(&self) -> EpochId {
79        self.executed_epoch
80    }
81
82    fn modified_at_versions(&self) -> Vec<(ObjectID, SequenceNumber)> {
83        self.changed_objects
84            .iter()
85            .filter_map(|(id, change)| {
86                if let ObjectIn::Exist(((version, _digest), _owner)) = &change.input_state {
87                    Some((*id, *version))
88                } else {
89                    None
90                }
91            })
92            .collect()
93    }
94
95    fn move_abort(&self) -> Option<(MoveLocation, u64)> {
96        let ExecutionStatus::Failure(ExecutionFailure {
97            error: ExecutionErrorKind::MoveAbort(move_location, code),
98            ..
99        }) = self.status()
100        else {
101            return None;
102        };
103        Some((move_location.clone(), *code))
104    }
105
106    fn lamport_version(&self) -> SequenceNumber {
107        self.lamport_version
108    }
109
110    fn old_object_metadata(&self) -> Vec<(ObjectRef, Owner)> {
111        self.changed_objects
112            .iter()
113            .filter_map(|(id, change)| {
114                if let ObjectIn::Exist(((version, digest), owner)) = &change.input_state {
115                    Some(((*id, *version, *digest), owner.clone()))
116                } else {
117                    None
118                }
119            })
120            .collect()
121    }
122
123    fn input_consensus_objects(&self) -> Vec<InputConsensusObject> {
124        self.changed_objects
125            .iter()
126            .filter_map(|(id, change)| match &change.input_state {
127                ObjectIn::Exist(((version, digest), owner)) if owner.is_consensus() => {
128                    Some(InputConsensusObject::Mutate((*id, *version, *digest)))
129                }
130                _ => None,
131            })
132            .chain(
133                self.unchanged_consensus_objects
134                    .iter()
135                    .filter_map(|(id, change_kind)| match change_kind {
136                        UnchangedConsensusKind::ReadOnlyRoot((version, digest)) => {
137                            Some(InputConsensusObject::ReadOnly((*id, *version, *digest)))
138                        }
139                        UnchangedConsensusKind::MutateConsensusStreamEnded(seqno) => Some(
140                            InputConsensusObject::MutateConsensusStreamEnded(*id, *seqno),
141                        ),
142                        UnchangedConsensusKind::ReadConsensusStreamEnded(seqno) => {
143                            Some(InputConsensusObject::ReadConsensusStreamEnded(*id, *seqno))
144                        }
145                        UnchangedConsensusKind::Cancelled(seqno) => {
146                            Some(InputConsensusObject::Cancelled(*id, *seqno))
147                        }
148                        // We can not expose the per epoch config object as input consensus object,
149                        // since it does not require sequencing, and hence shall not be considered
150                        // as a normal input consensus object.
151                        UnchangedConsensusKind::PerEpochConfig => None,
152                    }),
153            )
154            .collect()
155    }
156
157    fn created(&self) -> Vec<(ObjectRef, Owner)> {
158        self.changed_objects
159            .iter()
160            .filter_map(|(id, change)| {
161                match (
162                    &change.input_state,
163                    &change.output_state,
164                    &change.id_operation,
165                ) {
166                    (
167                        ObjectIn::NotExist,
168                        ObjectOut::ObjectWrite((digest, owner)),
169                        IDOperation::Created,
170                    ) => Some(((*id, self.lamport_version, *digest), owner.clone())),
171                    (
172                        ObjectIn::NotExist,
173                        ObjectOut::PackageWrite((version, digest)),
174                        IDOperation::Created,
175                    ) => Some(((*id, *version, *digest), Owner::Immutable)),
176                    _ => None,
177                }
178            })
179            .collect()
180    }
181
182    fn mutated(&self) -> Vec<(ObjectRef, Owner)> {
183        self.changed_objects
184            .iter()
185            .filter_map(
186                |(id, change)| match (&change.input_state, &change.output_state) {
187                    (ObjectIn::Exist(_), ObjectOut::ObjectWrite((digest, owner))) => {
188                        Some(((*id, self.lamport_version, *digest), owner.clone()))
189                    }
190                    (ObjectIn::Exist(_), ObjectOut::PackageWrite((version, digest))) => {
191                        Some(((*id, *version, *digest), Owner::Immutable))
192                    }
193                    _ => None,
194                },
195            )
196            .collect()
197    }
198
199    fn unwrapped(&self) -> Vec<(ObjectRef, Owner)> {
200        self.changed_objects
201            .iter()
202            .filter_map(|(id, change)| {
203                match (
204                    &change.input_state,
205                    &change.output_state,
206                    &change.id_operation,
207                ) {
208                    (
209                        ObjectIn::NotExist,
210                        ObjectOut::ObjectWrite((digest, owner)),
211                        IDOperation::None,
212                    ) => Some(((*id, self.lamport_version, *digest), owner.clone())),
213                    _ => None,
214                }
215            })
216            .collect()
217    }
218
219    fn deleted(&self) -> Vec<ObjectRef> {
220        self.changed_objects
221            .iter()
222            .filter_map(|(id, change)| {
223                match (
224                    &change.input_state,
225                    &change.output_state,
226                    &change.id_operation,
227                ) {
228                    (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::Deleted) => Some((
229                        *id,
230                        self.lamport_version,
231                        ObjectDigest::OBJECT_DIGEST_DELETED,
232                    )),
233                    _ => None,
234                }
235            })
236            .collect()
237    }
238
239    fn unwrapped_then_deleted(&self) -> Vec<ObjectRef> {
240        self.changed_objects
241            .iter()
242            .filter_map(|(id, change)| {
243                match (
244                    &change.input_state,
245                    &change.output_state,
246                    &change.id_operation,
247                ) {
248                    (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => Some((
249                        *id,
250                        self.lamport_version,
251                        ObjectDigest::OBJECT_DIGEST_DELETED,
252                    )),
253                    _ => None,
254                }
255            })
256            .collect()
257    }
258
259    fn wrapped(&self) -> Vec<ObjectRef> {
260        self.changed_objects
261            .iter()
262            .filter_map(|(id, change)| {
263                match (
264                    &change.input_state,
265                    &change.output_state,
266                    &change.id_operation,
267                ) {
268                    (ObjectIn::Exist(_), ObjectOut::NotExist, IDOperation::None) => Some((
269                        *id,
270                        self.lamport_version,
271                        ObjectDigest::OBJECT_DIGEST_WRAPPED,
272                    )),
273                    _ => None,
274                }
275            })
276            .collect()
277    }
278
279    fn written(&self) -> Vec<ObjectRef> {
280        self.changed_objects
281            .iter()
282            .filter_map(
283                |(id, change)| match (&change.output_state, &change.id_operation) {
284                    (ObjectOut::NotExist, IDOperation::Deleted) => Some((
285                        *id,
286                        self.lamport_version,
287                        ObjectDigest::OBJECT_DIGEST_DELETED,
288                    )),
289                    (ObjectOut::NotExist, IDOperation::None) => Some((
290                        *id,
291                        self.lamport_version,
292                        ObjectDigest::OBJECT_DIGEST_WRAPPED,
293                    )),
294                    (ObjectOut::ObjectWrite((d, _)), _) => Some((*id, self.lamport_version, *d)),
295                    (ObjectOut::PackageWrite(vd), _) => Some((*id, vd.0, vd.1)),
296                    (ObjectOut::AccumulatorWriteV1(_), _) => None,
297                    _ => None,
298                },
299            )
300            .collect()
301    }
302
303    fn transferred_from_consensus(&self) -> Vec<ObjectRef> {
304        self.changed_objects
305            .iter()
306            .filter_map(|(id, change)| {
307                match (
308                    &change.input_state,
309                    &change.output_state,
310                    &change.id_operation,
311                ) {
312                    (
313                        ObjectIn::Exist((_, Owner::ConsensusAddressOwner { .. })),
314                        ObjectOut::ObjectWrite((
315                            object_digest,
316                            Owner::AddressOwner(_) | Owner::ObjectOwner(_) | Owner::Immutable,
317                        )),
318                        IDOperation::None,
319                    ) => Some((*id, self.lamport_version, *object_digest)),
320                    _ => None,
321                }
322            })
323            .collect()
324    }
325
326    fn transferred_to_consensus(&self) -> Vec<ObjectRef> {
327        self.changed_objects
328            .iter()
329            .filter_map(|(id, change)| {
330                match (
331                    &change.input_state,
332                    &change.output_state,
333                    &change.id_operation,
334                ) {
335                    (
336                        ObjectIn::Exist((_, Owner::AddressOwner(_) | Owner::ObjectOwner(_))),
337                        ObjectOut::ObjectWrite((
338                            object_digest,
339                            Owner::ConsensusAddressOwner { .. },
340                        )),
341                        IDOperation::None,
342                    ) => Some((*id, self.lamport_version, *object_digest)),
343                    _ => None,
344                }
345            })
346            .collect()
347    }
348
349    fn consensus_owner_changed(&self) -> Vec<ObjectRef> {
350        self.changed_objects
351            .iter()
352            .filter_map(|(id, change)| {
353                match (
354                    &change.input_state,
355                    &change.output_state,
356                    &change.id_operation,
357                ) {
358                    (
359                        ObjectIn::Exist((
360                            _,
361                            Owner::ConsensusAddressOwner {
362                                owner: old_owner, ..
363                            },
364                        )),
365                        ObjectOut::ObjectWrite((
366                            object_digest,
367                            Owner::ConsensusAddressOwner {
368                                owner: new_owner, ..
369                            },
370                        )),
371                        IDOperation::None,
372                    ) if old_owner != new_owner => {
373                        Some((*id, self.lamport_version, *object_digest))
374                    }
375                    _ => None,
376                }
377            })
378            .collect()
379    }
380
381    fn object_changes(&self) -> Vec<ObjectChange> {
382        self.changed_objects
383            .iter()
384            .filter_map(|(id, change)| {
385                let input_version_digest = match &change.input_state {
386                    ObjectIn::NotExist => None,
387                    ObjectIn::Exist((vd, _)) => Some(*vd),
388                };
389
390                let output_version_digest = match &change.output_state {
391                    ObjectOut::NotExist => None,
392                    ObjectOut::ObjectWrite((d, _)) => Some((self.lamport_version, *d)),
393                    ObjectOut::PackageWrite(vd) => Some(*vd),
394                    ObjectOut::AccumulatorWriteV1(_) => {
395                        return None;
396                    }
397                };
398
399                Some(ObjectChange {
400                    id: *id,
401
402                    input_version: input_version_digest.map(|k| k.0),
403                    input_digest: input_version_digest.map(|k| k.1),
404
405                    output_version: output_version_digest.map(|k| k.0),
406                    output_digest: output_version_digest.map(|k| k.1),
407
408                    id_operation: change.id_operation,
409                })
410            })
411            .collect()
412    }
413
414    fn published_packages(&self) -> Vec<ObjectID> {
415        self.changed_objects
416            .iter()
417            .filter_map(|(id, change)| {
418                if matches!(&change.output_state, ObjectOut::PackageWrite(_)) {
419                    Some(*id)
420                } else {
421                    None
422                }
423            })
424            .collect()
425    }
426
427    fn accumulator_events(&self) -> Vec<AccumulatorEvent> {
428        self.changed_objects
429            .iter()
430            .filter_map(|(id, change)| match &change.output_state {
431                ObjectOut::AccumulatorWriteV1(write) => Some(AccumulatorEvent::new(
432                    AccumulatorObjId::new_unchecked(*id),
433                    write.clone(),
434                )),
435                _ => None,
436            })
437            .collect()
438    }
439
440    fn gas_object(&self) -> Option<(ObjectRef, Owner)> {
441        self.gas_object_index.map(|index| {
442            let entry = &self.changed_objects[index as usize];
443            match &entry.1.output_state {
444                ObjectOut::ObjectWrite((digest, owner)) => {
445                    ((entry.0, self.lamport_version, *digest), owner.clone())
446                }
447                ObjectOut::NotExist => {
448                    // Gas coin was deleted. Preserve the ID but return the marker digest and a
449                    // dummy owner.
450                    (
451                        (
452                            entry.0,
453                            self.lamport_version,
454                            ObjectDigest::OBJECT_DIGEST_DELETED,
455                        ),
456                        Owner::AddressOwner(SuiAddress::default()),
457                    )
458                }
459                _ => panic!("Gas object must be an ObjectWrite or Deleted in changed_objects"),
460            }
461        })
462    }
463
464    fn events_digest(&self) -> Option<&TransactionEventsDigest> {
465        self.events_digest.as_ref()
466    }
467
468    fn dependencies(&self) -> &[TransactionDigest] {
469        &self.dependencies
470    }
471
472    fn transaction_digest(&self) -> &TransactionDigest {
473        &self.transaction_digest
474    }
475
476    fn gas_cost_summary(&self) -> &GasCostSummary {
477        &self.gas_used
478    }
479
480    fn unchanged_consensus_objects(&self) -> Vec<(ObjectID, UnchangedConsensusKind)> {
481        self.unchanged_consensus_objects.clone()
482    }
483
484    fn accumulator_updates(&self) -> Vec<(ObjectID, AccumulatorWriteV1)> {
485        self.changed_objects
486            .iter()
487            .filter_map(|(id, change)| match &change.output_state {
488                ObjectOut::AccumulatorWriteV1(update) => Some((*id, update.clone())),
489                _ => None,
490            })
491            .collect()
492    }
493
494    fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
495        &mut self.status
496    }
497
498    fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
499        &mut self.gas_used
500    }
501
502    fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
503        &mut self.transaction_digest
504    }
505
506    fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
507        &mut self.dependencies
508    }
509
510    fn unsafe_add_input_consensus_object_for_testing(&mut self, kind: InputConsensusObject) {
511        match kind {
512            InputConsensusObject::Mutate(obj_ref) => self.changed_objects.push((
513                obj_ref.0,
514                EffectsObjectChange {
515                    input_state: ObjectIn::Exist((
516                        (obj_ref.1, obj_ref.2),
517                        Owner::Shared {
518                            initial_shared_version: OBJECT_START_VERSION,
519                        },
520                    )),
521                    output_state: ObjectOut::ObjectWrite((
522                        obj_ref.2,
523                        Owner::Shared {
524                            initial_shared_version: obj_ref.1,
525                        },
526                    )),
527                    id_operation: IDOperation::None,
528                },
529            )),
530            InputConsensusObject::ReadOnly(obj_ref) => self.unchanged_consensus_objects.push((
531                obj_ref.0,
532                UnchangedConsensusKind::ReadOnlyRoot((obj_ref.1, obj_ref.2)),
533            )),
534            InputConsensusObject::ReadConsensusStreamEnded(obj_id, seqno) => {
535                self.unchanged_consensus_objects.push((
536                    obj_id,
537                    UnchangedConsensusKind::ReadConsensusStreamEnded(seqno),
538                ))
539            }
540            InputConsensusObject::MutateConsensusStreamEnded(obj_id, seqno) => {
541                self.unchanged_consensus_objects.push((
542                    obj_id,
543                    UnchangedConsensusKind::MutateConsensusStreamEnded(seqno),
544                ))
545            }
546            InputConsensusObject::Cancelled(obj_id, seqno) => self
547                .unchanged_consensus_objects
548                .push((obj_id, UnchangedConsensusKind::Cancelled(seqno))),
549        }
550    }
551
552    fn unsafe_add_deleted_live_object_for_testing(&mut self, obj_ref: ObjectRef) {
553        self.changed_objects.push((
554            obj_ref.0,
555            EffectsObjectChange {
556                input_state: ObjectIn::Exist((
557                    (obj_ref.1, obj_ref.2),
558                    Owner::AddressOwner(SuiAddress::default()),
559                )),
560                output_state: ObjectOut::ObjectWrite((
561                    obj_ref.2,
562                    Owner::AddressOwner(SuiAddress::default()),
563                )),
564                id_operation: IDOperation::None,
565            },
566        ))
567    }
568
569    fn unsafe_add_object_tombstone_for_testing(&mut self, obj_ref: ObjectRef) {
570        self.changed_objects.push((
571            obj_ref.0,
572            EffectsObjectChange {
573                input_state: ObjectIn::Exist((
574                    (obj_ref.1, obj_ref.2),
575                    Owner::AddressOwner(SuiAddress::default()),
576                )),
577                output_state: ObjectOut::NotExist,
578                id_operation: IDOperation::Deleted,
579            },
580        ))
581    }
582}
583
584impl TransactionEffectsV2 {
585    /// Derive the unchanged consensus objects of a transaction from its shared inputs and the
586    /// per-epoch config objects it read, given the set of objects it changed. Callers compute
587    /// this before constructing the effects and pass the result to [`Self::new`], so additional
588    /// unchanged consensus objects can be appended before construction if needed.
589    pub fn compute_unchanged_consensus_objects(
590        shared_objects: Vec<SharedInput>,
591        loaded_per_epoch_config_objects: BTreeSet<ObjectID>,
592        changed_objects: &BTreeMap<ObjectID, EffectsObjectChange>,
593    ) -> Vec<(ObjectID, UnchangedConsensusKind)> {
594        shared_objects
595            .into_iter()
596            .filter_map(|shared_input| match shared_input {
597                SharedInput::Existing((id, version, digest)) => {
598                    if changed_objects.contains_key(&id) {
599                        None
600                    } else {
601                        Some((id, UnchangedConsensusKind::ReadOnlyRoot((version, digest))))
602                    }
603                }
604                SharedInput::ConsensusStreamEnded((id, version, mutability, _)) => {
605                    debug_assert!(!changed_objects.contains_key(&id));
606                    match mutability {
607                        SharedObjectMutability::Mutable => Some((
608                            id,
609                            UnchangedConsensusKind::MutateConsensusStreamEnded(version),
610                        )),
611                        SharedObjectMutability::Immutable => Some((
612                            id,
613                            UnchangedConsensusKind::ReadConsensusStreamEnded(version),
614                        )),
615                        // This is current unreachable, because non exclusive writes are not exposed to
616                        // user transactions yet, and so there is no way for their inputs to be deleted.
617                        SharedObjectMutability::NonExclusiveWrite => Some((
618                            id,
619                            UnchangedConsensusKind::MutateConsensusStreamEnded(version),
620                        )),
621                    }
622                }
623                SharedInput::Cancelled((id, version)) => {
624                    debug_assert!(!changed_objects.contains_key(&id));
625                    Some((id, UnchangedConsensusKind::Cancelled(version)))
626                }
627            })
628            .chain(
629                loaded_per_epoch_config_objects
630                    .into_iter()
631                    .map(|id| (id, UnchangedConsensusKind::PerEpochConfig)),
632            )
633            .collect()
634    }
635
636    pub fn new(
637        status: ExecutionStatus,
638        executed_epoch: EpochId,
639        gas_used: GasCostSummary,
640        unchanged_consensus_objects: Vec<(ObjectID, UnchangedConsensusKind)>,
641        transaction_digest: TransactionDigest,
642        lamport_version: SequenceNumber,
643        changed_objects: BTreeMap<ObjectID, EffectsObjectChange>,
644        gas_object: Option<ObjectID>,
645        events_digest: Option<TransactionEventsDigest>,
646        dependencies: Vec<TransactionDigest>,
647    ) -> Self {
648        let changed_objects: Vec<_> = changed_objects.into_iter().collect();
649
650        let gas_object_index = gas_object.map(|gas_id| {
651            changed_objects
652                .iter()
653                .position(|(id, _)| id == &gas_id)
654                .unwrap() as u32
655        });
656
657        let result = Self {
658            status,
659            executed_epoch,
660            gas_used,
661            transaction_digest,
662            lamport_version,
663            changed_objects,
664            unchanged_consensus_objects,
665            gas_object_index,
666            events_digest,
667            dependencies,
668            aux_data_digest: None,
669        };
670        #[cfg(debug_assertions)]
671        result.check_invariant();
672
673        result
674    }
675
676    /// This function demonstrates what's the invariant of the effects.
677    /// It also documents the semantics of different combinations in object changes.
678    #[cfg(debug_assertions)]
679    fn check_invariant(&self) {
680        let mut unique_ids = HashSet::new();
681        for (id, change) in &self.changed_objects {
682            assert!(unique_ids.insert(*id));
683            match (
684                &change.input_state,
685                &change.output_state,
686                &change.id_operation,
687            ) {
688                (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Created) => {
689                    // created and then wrapped Move object.
690                }
691                (ObjectIn::NotExist, ObjectOut::NotExist, IDOperation::Deleted) => {
692                    // unwrapped and then deleted Move object.
693                }
694                (ObjectIn::NotExist, ObjectOut::ObjectWrite((_, owner)), IDOperation::None) => {
695                    // unwrapped Move object.
696                    // It's not allowed to make an object shared after unwrapping.
697                    assert!(!owner.is_shared());
698                }
699                (ObjectIn::NotExist, ObjectOut::ObjectWrite(..), IDOperation::Created) => {
700                    // created Move object.
701                }
702                (ObjectIn::NotExist, ObjectOut::PackageWrite(_), IDOperation::Created) => {
703                    // created Move package or user Move package upgrade.
704                }
705                (
706                    ObjectIn::Exist(((old_version, _), old_owner)),
707                    ObjectOut::NotExist,
708                    IDOperation::None,
709                ) => {
710                    // wrapped.
711                    assert!(old_version.value() < self.lamport_version.value());
712                    assert!(
713                        !old_owner.is_shared() && !old_owner.is_immutable(),
714                        "Cannot wrap shared or immutable object"
715                    );
716                }
717                (
718                    ObjectIn::Exist(((old_version, _), old_owner)),
719                    ObjectOut::NotExist,
720                    IDOperation::Deleted,
721                ) => {
722                    // deleted.
723                    assert!(old_version.value() < self.lamport_version.value());
724                    assert!(!old_owner.is_immutable(), "Cannot delete immutable object");
725                }
726                (
727                    ObjectIn::Exist(((old_version, old_digest), old_owner)),
728                    ObjectOut::ObjectWrite((new_digest, new_owner)),
729                    IDOperation::None,
730                ) => {
731                    // mutated.
732                    assert!(old_version.value() < self.lamport_version.value());
733                    assert_ne!(old_digest, new_digest);
734                    assert!(!old_owner.is_immutable(), "Cannot mutate immutable object");
735                    if old_owner.is_shared() {
736                        assert!(new_owner.is_shared(), "Cannot un-share an object");
737                    } else {
738                        assert!(!new_owner.is_shared(), "Cannot share an existing object");
739                    }
740                }
741                (
742                    ObjectIn::Exist(((old_version, old_digest), old_owner)),
743                    ObjectOut::PackageWrite((new_version, new_digest)),
744                    IDOperation::None,
745                ) => {
746                    // system package upgrade.
747                    assert!(
748                        old_owner.is_immutable() && is_system_package(*id),
749                        "Must be a system package"
750                    );
751                    assert_eq!(old_version.value() + 1, new_version.value());
752                    assert_ne!(old_digest, new_digest);
753                }
754                (ObjectIn::NotExist, ObjectOut::AccumulatorWriteV1(_), IDOperation::None) => {
755                    // This is an accumulator write.
756                }
757                _ => {
758                    panic!("Impossible object change: {:?}, {:?}", id, change);
759                }
760            }
761        }
762        // Make sure that gas object, if present, has an address owner.
763        if let Some((_, owner)) = self.gas_object() {
764            assert!(matches!(owner, Owner::AddressOwner(_)));
765        }
766
767        for (id, _) in &self.unchanged_consensus_objects {
768            assert!(
769                unique_ids.insert(*id),
770                "Duplicate object id: {:?}\n{:#?}",
771                id,
772                self
773            );
774        }
775    }
776}
777
778impl Default for TransactionEffectsV2 {
779    fn default() -> Self {
780        Self {
781            status: ExecutionStatus::Success,
782            executed_epoch: 0,
783            gas_used: GasCostSummary::default(),
784            transaction_digest: TransactionDigest::default(),
785            lamport_version: SequenceNumber::default(),
786            changed_objects: vec![],
787            unchanged_consensus_objects: vec![],
788            gas_object_index: None,
789            events_digest: None,
790            dependencies: vec![],
791            aux_data_digest: None,
792        }
793    }
794}
795
796#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
797pub enum UnchangedConsensusKind {
798    /// Read-only consensus objects from the input. We don't really need ObjectDigest
799    /// for protocol correctness, but it will make it easier to verify untrusted read.
800    ReadOnlyRoot(VersionDigest),
801    /// Objects with ended consensus streams that appear mutably/owned in the input.
802    MutateConsensusStreamEnded(SequenceNumber),
803    /// Objects with ended consensus streams objects that appear as read-only in the input.
804    ReadConsensusStreamEnded(SequenceNumber),
805    /// Consensus objects in cancelled transaction. The sequence number embed cancellation reason.
806    Cancelled(SequenceNumber),
807    /// Read of a per-epoch config object that should remain the same during an epoch.
808    PerEpochConfig,
809}