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).
31    deleted_objects: Vec<(ObjectId, Version)>,
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    pub fn with_deleted_objects(
106        mut self,
107        objects: impl IntoIterator<Item = (ObjectId, Version)>,
108    ) -> Self {
109        self.deleted_objects.extend(objects);
110        self
111    }
112
113    pub fn with_frozen_objects(mut self, objects: impl IntoIterator<Item = ObjectId>) -> Self {
114        self.frozen_objects.extend(objects);
115        self
116    }
117
118    pub fn build(self) -> TransactionEffects {
119        let lamport_version = self.get_lamport_version();
120        let status = self.status.unwrap_or(ExecutionStatus::Success);
121        // TODO: This does not yet support deleted shared objects.
122        let shared_objects = self
123            .shared_input_versions
124            .iter()
125            .map(|(id, version)| {
126                SharedInput::Existing(ObjectReference::new(*id, *version, ObjectDigest::MIN))
127            })
128            .collect();
129        let epoch = 0;
130        let sender = self.transaction.transaction().sender();
131        // TODO: Include receiving objects in the object changes as well.
132        let changed_objects = self
133            .transaction
134            .transaction()
135            .input_objects()
136            .unwrap()
137            .iter()
138            .filter_map(|kind| match kind {
139                InputObjectKind::ImmOrOwnedMoveObject(object_ref)
140                    if self.frozen_objects.contains(&object_ref.object_id) =>
141                {
142                    None
143                }
144                InputObjectKind::ImmOrOwnedMoveObject(oref) => {
145                    Some((
146                        oref.object_id,
147                        ChangedObject {
148                            object_id: oref.object_id,
149                            input_state: ObjectIn::Data {
150                                version: oref.version,
151                                digest: oref.digest,
152                                owner: Owner::Address(sender),
153                            },
154                            output_state: ObjectOut::ObjectWrite {
155                                // Digest must change with a mutation.
156                                digest: ObjectDigest::MAX,
157                                owner: 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                    ChangedObject {
171                        object_id: *id,
172                        input_state: ObjectIn::Data {
173                            version: *self
174                                .shared_input_versions
175                                .get(id)
176                                .unwrap_or(initial_shared_version),
177                            digest: ObjectDigest::MIN,
178                            owner: Owner::Shared(*initial_shared_version),
179                        },
180                        output_state: ObjectOut::ObjectWrite {
181                            // Digest must change with a mutation.
182                            digest: ObjectDigest::MAX,
183                            owner: Owner::Shared(*initial_shared_version),
184                        },
185                        id_operation: IdOperation::None,
186                    },
187                )),
188            })
189            .chain(self.created_objects.into_iter().map(|(id, owner)| {
190                (
191                    id,
192                    ChangedObject {
193                        object_id: id,
194                        input_state: ObjectIn::Missing,
195                        output_state: ObjectOut::ObjectWrite {
196                            digest: ObjectDigest::random(),
197                            owner,
198                        },
199                        id_operation: IdOperation::Created,
200                    },
201                )
202            }))
203            .chain(
204                self.mutated_objects
205                    .into_iter()
206                    .map(|(id, version, owner)| {
207                        (
208                            id,
209                            ChangedObject {
210                                object_id: id,
211                                input_state: ObjectIn::Data {
212                                    version,
213                                    digest: ObjectDigest::random(),
214                                    owner: Owner::Address(sender),
215                                },
216                                output_state: ObjectOut::ObjectWrite {
217                                    digest: ObjectDigest::random(),
218                                    owner,
219                                },
220                                id_operation: IdOperation::None,
221                            },
222                        )
223                    }),
224            )
225            .chain(self.deleted_objects.into_iter().map(|(id, version)| {
226                (
227                    id,
228                    ChangedObject {
229                        object_id: id,
230                        input_state: ObjectIn::Data {
231                            version,
232                            digest: ObjectDigest::random(),
233                            owner: Owner::Address(sender),
234                        },
235                        output_state: ObjectOut::Missing,
236                        id_operation: IdOperation::Deleted,
237                    },
238                )
239            }))
240            .chain(self.wrapped_objects.into_iter().map(|(id, version)| {
241                (
242                    id,
243                    ChangedObject {
244                        object_id: id,
245                        input_state: ObjectIn::Data {
246                            version,
247                            digest: ObjectDigest::random(),
248                            owner: Owner::Address(sender),
249                        },
250                        output_state: ObjectOut::Missing,
251                        id_operation: IdOperation::None,
252                    },
253                )
254            }))
255            .chain(self.unwrapped_objects.into_iter().map(|(id, owner)| {
256                (
257                    id,
258                    ChangedObject {
259                        object_id: id,
260                        input_state: ObjectIn::Missing,
261                        output_state: ObjectOut::ObjectWrite {
262                            digest: ObjectDigest::random(),
263                            owner,
264                        },
265                        id_operation: IdOperation::None,
266                    },
267                )
268            }))
269            .collect();
270        // A system transaction carries only a placeholder gas payment that
271        // never appears in the changed objects.
272        let gas_object_id = (!self.transaction.transaction().is_system_tx())
273            .then(|| self.transaction.transaction().gas()[0].object_id);
274        let event_digest = self.events_digest;
275        let dependencies = vec![];
276
277        TransactionEffects::new_from_execution_v1(
278            status,
279            epoch,
280            GasCostSummary::default(),
281            shared_objects,
282            BTreeSet::new(),
283            self.transaction.digest(),
284            lamport_version,
285            changed_objects,
286            gas_object_id,
287            event_digest,
288            dependencies,
289        )
290    }
291
292    fn get_lamport_version(&self) -> Version {
293        Version::lamport_increment(
294            self.transaction
295                .transaction()
296                .input_objects()
297                .unwrap()
298                .iter()
299                .filter_map(|kind| kind.version())
300                .chain(
301                    self.transaction
302                        .transaction()
303                        .receiving_objects()
304                        .iter()
305                        .map(|oref| oref.version),
306                )
307                .chain(self.shared_input_versions.values().copied())
308                .chain(self.mutated_objects.iter().map(|(_, v, _)| *v))
309                .chain(self.deleted_objects.iter().map(|(_, v)| *v))
310                .chain(self.wrapped_objects.iter().map(|(_, v)| *v)),
311        )
312        .unwrap()
313    }
314}