iota_types/effects/
test_effects_builder.rs

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