sui_types/effects/
test_effects_builder.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::accumulator_event::AccumulatorEvent;
5use crate::base_types::{ObjectID, SequenceNumber};
6use crate::digests::{ObjectDigest, TransactionEventsDigest};
7use crate::effects::{
8    EffectsObjectChange, IDOperation, ObjectIn, ObjectOut, TransactionEffects, TransactionEffectsV2,
9};
10use crate::execution::SharedInput;
11use crate::execution_status::ExecutionStatus;
12use crate::gas::GasCostSummary;
13use crate::message_envelope::Message;
14use crate::object::Owner;
15use crate::transaction::{
16    InputObjectKind, SenderSignedData, SharedObjectMutability, TransactionDataAPI,
17};
18use std::collections::{BTreeMap, BTreeSet};
19
20#[derive(Clone)]
21pub struct TestEffectsBuilder {
22    transaction: SenderSignedData,
23    /// Override the execution status if provided.
24    status: Option<ExecutionStatus>,
25    /// Provide the assigned versions for all shared objects.
26    shared_input_versions: BTreeMap<ObjectID, SequenceNumber>,
27    events_digest: Option<TransactionEventsDigest>,
28    created_objects: Vec<(ObjectID, Owner)>,
29    /// Objects that are mutated: (ID, old version, new owner).
30    mutated_objects: Vec<(ObjectID, SequenceNumber, Owner)>,
31    /// Objects that are deleted: (ID, old version).
32    deleted_objects: Vec<(ObjectID, SequenceNumber)>,
33    /// Objects that are wrapped: (ID, old version).
34    wrapped_objects: Vec<(ObjectID, SequenceNumber)>,
35    /// Objects that are unwrapped: (ID, new owner).
36    unwrapped_objects: Vec<(ObjectID, Owner)>,
37    /// Immutable objects that are read.
38    frozen_objects: BTreeSet<ObjectID>,
39    /// Accumulator events.
40    accumulator_events: Vec<AccumulatorEvent>,
41    /// Move package writes that create a new on-chain id
42    package_writes: Vec<(ObjectID, SequenceNumber)>,
43}
44
45impl TestEffectsBuilder {
46    pub fn new(transaction: &SenderSignedData) -> Self {
47        Self {
48            transaction: transaction.clone(),
49            status: None,
50            shared_input_versions: BTreeMap::new(),
51            events_digest: None,
52            created_objects: vec![],
53            mutated_objects: vec![],
54            deleted_objects: vec![],
55            wrapped_objects: vec![],
56            unwrapped_objects: vec![],
57            frozen_objects: BTreeSet::new(),
58            accumulator_events: vec![],
59            package_writes: vec![],
60        }
61    }
62
63    pub fn with_status(mut self, status: ExecutionStatus) -> Self {
64        self.status = Some(status);
65        self
66    }
67
68    pub fn with_shared_input_versions(
69        mut self,
70        versions: BTreeMap<ObjectID, SequenceNumber>,
71    ) -> Self {
72        assert!(self.shared_input_versions.is_empty());
73        self.shared_input_versions = versions;
74        self
75    }
76
77    pub fn with_events_digest(mut self, digest: TransactionEventsDigest) -> Self {
78        self.events_digest = Some(digest);
79        self
80    }
81
82    pub fn with_created_objects(
83        mut self,
84        objects: impl IntoIterator<Item = (ObjectID, Owner)>,
85    ) -> Self {
86        self.created_objects.extend(objects);
87        self
88    }
89
90    pub fn with_mutated_objects(
91        mut self,
92        // Object ID, old version, and new owner.
93        objects: impl IntoIterator<Item = (ObjectID, SequenceNumber, Owner)>,
94    ) -> Self {
95        self.mutated_objects.extend(objects);
96        self
97    }
98
99    pub fn with_wrapped_objects(
100        mut self,
101        objects: impl IntoIterator<Item = (ObjectID, SequenceNumber)>,
102    ) -> Self {
103        self.wrapped_objects.extend(objects);
104        self
105    }
106
107    pub fn with_unwrapped_objects(
108        mut self,
109        objects: impl IntoIterator<Item = (ObjectID, Owner)>,
110    ) -> Self {
111        self.unwrapped_objects.extend(objects);
112        self
113    }
114
115    pub fn with_deleted_objects(
116        mut self,
117        objects: impl IntoIterator<Item = (ObjectID, SequenceNumber)>,
118    ) -> Self {
119        self.deleted_objects.extend(objects);
120        self
121    }
122
123    pub fn with_frozen_objects(mut self, objects: impl IntoIterator<Item = ObjectID>) -> Self {
124        self.frozen_objects.extend(objects);
125        self
126    }
127
128    pub fn with_accumulator_events(
129        mut self,
130        events: impl IntoIterator<Item = AccumulatorEvent>,
131    ) -> Self {
132        self.accumulator_events.extend(events);
133        self
134    }
135
136    /// Add Move package writes that create a new on-chain id (a publish, or a
137    /// user upgrade that mints a new id). Each entry is `(id, version)`.
138    pub fn with_package_writes(
139        mut self,
140        packages: impl IntoIterator<Item = (ObjectID, SequenceNumber)>,
141    ) -> Self {
142        self.package_writes.extend(packages);
143        self
144    }
145
146    pub fn build(self) -> TransactionEffects {
147        let lamport_version = self.get_lamport_version();
148        let status = self.status.unwrap_or(ExecutionStatus::Success);
149        // TODO: This does not yet support deleted shared objects.
150        let shared_objects = self
151            .shared_input_versions
152            .iter()
153            .map(|(id, version)| SharedInput::Existing((*id, *version, ObjectDigest::MIN)))
154            .collect();
155        let executed_epoch = 0;
156        let sender = self.transaction.transaction_data().sender();
157        // TODO: Include receiving objects in the object changes as well.
158        let changed_objects = self
159            .transaction
160            .transaction_data()
161            .input_objects()
162            .unwrap()
163            .iter()
164            .filter_map(|kind| match kind {
165                InputObjectKind::ImmOrOwnedMoveObject((id, _, _))
166                    if self.frozen_objects.contains(id) =>
167                {
168                    None
169                }
170                InputObjectKind::ImmOrOwnedMoveObject(oref) => Some((
171                    oref.0,
172                    EffectsObjectChange {
173                        input_state: ObjectIn::Exist((
174                            (oref.1, oref.2),
175                            Owner::AddressOwner(sender),
176                        )),
177                        output_state: ObjectOut::ObjectWrite((
178                            // Digest must change with a mutation.
179                            ObjectDigest::MAX,
180                            Owner::AddressOwner(sender),
181                        )),
182                        id_operation: IDOperation::None,
183                    },
184                )),
185                InputObjectKind::MovePackage(_) => None,
186                InputObjectKind::SharedMoveObject {
187                    id,
188                    initial_shared_version,
189                    mutability,
190                } => {
191                    let mutable = match mutability {
192                        SharedObjectMutability::Mutable => true,
193                        SharedObjectMutability::Immutable => false,
194                        SharedObjectMutability::NonExclusiveWrite => todo!(),
195                    };
196                    mutable.then_some((
197                        *id,
198                        EffectsObjectChange {
199                            input_state: ObjectIn::Exist((
200                                (
201                                    *self
202                                        .shared_input_versions
203                                        .get(id)
204                                        .unwrap_or(initial_shared_version),
205                                    ObjectDigest::MIN,
206                                ),
207                                Owner::Shared {
208                                    initial_shared_version: *initial_shared_version,
209                                },
210                            )),
211                            output_state: ObjectOut::ObjectWrite((
212                                // Digest must change with a mutation.
213                                ObjectDigest::MAX,
214                                Owner::Shared {
215                                    initial_shared_version: *initial_shared_version,
216                                },
217                            )),
218                            id_operation: IDOperation::None,
219                        },
220                    ))
221                }
222            })
223            .chain(self.created_objects.into_iter().map(|(id, owner)| {
224                (
225                    id,
226                    EffectsObjectChange {
227                        input_state: ObjectIn::NotExist,
228                        output_state: ObjectOut::ObjectWrite((ObjectDigest::random(), owner)),
229                        id_operation: IDOperation::Created,
230                    },
231                )
232            }))
233            .chain(
234                self.mutated_objects
235                    .into_iter()
236                    .map(|(id, version, owner)| {
237                        (
238                            id,
239                            EffectsObjectChange {
240                                input_state: ObjectIn::Exist((
241                                    (version, ObjectDigest::random()),
242                                    Owner::AddressOwner(sender),
243                                )),
244                                output_state: ObjectOut::ObjectWrite((
245                                    ObjectDigest::random(),
246                                    owner,
247                                )),
248                                id_operation: IDOperation::None,
249                            },
250                        )
251                    }),
252            )
253            .chain(self.deleted_objects.into_iter().map(|(id, version)| {
254                (
255                    id,
256                    EffectsObjectChange {
257                        input_state: ObjectIn::Exist((
258                            (version, ObjectDigest::random()),
259                            Owner::AddressOwner(sender),
260                        )),
261                        output_state: ObjectOut::NotExist,
262                        id_operation: IDOperation::Deleted,
263                    },
264                )
265            }))
266            .chain(self.wrapped_objects.into_iter().map(|(id, version)| {
267                (
268                    id,
269                    EffectsObjectChange {
270                        input_state: ObjectIn::Exist((
271                            (version, ObjectDigest::random()),
272                            Owner::AddressOwner(sender),
273                        )),
274                        output_state: ObjectOut::NotExist,
275                        id_operation: IDOperation::None,
276                    },
277                )
278            }))
279            .chain(self.unwrapped_objects.into_iter().map(|(id, owner)| {
280                (
281                    id,
282                    EffectsObjectChange {
283                        input_state: ObjectIn::NotExist,
284                        output_state: ObjectOut::ObjectWrite((ObjectDigest::random(), owner)),
285                        id_operation: IDOperation::None,
286                    },
287                )
288            }))
289            .chain(self.accumulator_events.into_iter().map(|event| {
290                (
291                    *event.accumulator_obj.inner(),
292                    EffectsObjectChange {
293                        input_state: ObjectIn::NotExist,
294                        output_state: ObjectOut::AccumulatorWriteV1(event.write),
295                        id_operation: IDOperation::None,
296                    },
297                )
298            }))
299            .chain(self.package_writes.into_iter().map(|(id, version)| {
300                (
301                    id,
302                    EffectsObjectChange {
303                        input_state: ObjectIn::NotExist,
304                        output_state: ObjectOut::PackageWrite((version, ObjectDigest::random())),
305                        id_operation: IDOperation::Created,
306                    },
307                )
308            }))
309            .collect();
310        let gas_object_id = self.transaction.transaction_data().gas()[0].0;
311        let event_digest = self.events_digest;
312        let dependencies = vec![];
313        let unchanged_consensus_objects = TransactionEffectsV2::compute_unchanged_consensus_objects(
314            shared_objects,
315            BTreeSet::new(),
316            &changed_objects,
317        );
318        TransactionEffects::new_from_execution_v2(
319            status,
320            executed_epoch,
321            GasCostSummary::default(),
322            unchanged_consensus_objects,
323            self.transaction.digest(),
324            lamport_version,
325            changed_objects,
326            Some(gas_object_id),
327            event_digest,
328            dependencies,
329        )
330    }
331
332    fn get_lamport_version(&self) -> SequenceNumber {
333        SequenceNumber::lamport_increment(
334            self.transaction
335                .transaction_data()
336                .input_objects()
337                .unwrap()
338                .iter()
339                .filter_map(|kind| kind.version())
340                .chain(
341                    self.transaction
342                        .transaction_data()
343                        .receiving_objects()
344                        .iter()
345                        .map(|oref| oref.1),
346                )
347                .chain(self.shared_input_versions.values().copied())
348                .chain(self.mutated_objects.iter().map(|(_, v, _)| *v))
349                .chain(self.deleted_objects.iter().map(|(_, v)| *v))
350                .chain(self.wrapped_objects.iter().map(|(_, v)| *v)),
351        )
352    }
353}