Skip to main content

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