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, ObjectDigest, ObjectId, ObjectReference, Owner, TransactionEventsDigest,
9    Version, gas::GasCostSummary,
10};
11
12use crate::{
13    effects::{
14        EffectsObjectChange, IDOperation, ObjectIn, ObjectOut, TransactionEffects,
15        TransactionEffectsExt,
16    },
17    execution::SharedInput,
18    message_envelope::Message,
19    transaction::{InputObjectKind, SenderSignedData, TransactionDataAPI},
20};
21
22pub struct TestEffectsBuilder {
23    transaction: SenderSignedData,
24    /// Override the execution status if provided.
25    status: Option<ExecutionStatus>,
26    /// Provide the assigned versions for all shared objects.
27    shared_input_versions: BTreeMap<ObjectId, Version>,
28    events_digest: Option<TransactionEventsDigest>,
29    created_objects: Vec<(ObjectId, Owner)>,
30    /// Objects that are mutated: (ID, old version, new owner).
31    mutated_objects: Vec<(ObjectId, Version, Owner)>,
32    /// Objects that are deleted: (ID, old version).
33    deleted_objects: Vec<(ObjectId, Version)>,
34    /// Objects that are wrapped: (ID, old version).
35    wrapped_objects: Vec<(ObjectId, Version)>,
36    /// Objects that are unwrapped: (ID, new owner).
37    unwrapped_objects: Vec<(ObjectId, Owner)>,
38    /// Immutable objects that are read.
39    frozen_objects: BTreeSet<ObjectId>,
40}
41
42impl TestEffectsBuilder {
43    pub fn new(transaction: &SenderSignedData) -> Self {
44        Self {
45            transaction: transaction.clone(),
46            status: None,
47            shared_input_versions: BTreeMap::new(),
48            events_digest: None,
49            created_objects: vec![],
50            mutated_objects: vec![],
51            deleted_objects: vec![],
52            wrapped_objects: vec![],
53            unwrapped_objects: vec![],
54            frozen_objects: BTreeSet::new(),
55        }
56    }
57
58    pub fn with_status(mut self, status: ExecutionStatus) -> Self {
59        self.status = Some(status);
60        self
61    }
62
63    pub fn with_shared_input_versions(mut self, versions: BTreeMap<ObjectId, Version>) -> 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, Version, 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, Version)>,
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, Version)>,
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(ObjectReference::new(*id, *version, ObjectDigest::MIN))
129            })
130            .collect();
131        let epoch = 0;
132        let sender = self.transaction.transaction().sender();
133        // TODO: Include receiving objects in the object changes as well.
134        let changed_objects = self
135            .transaction
136            .transaction()
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                            object_id: oref.object_id,
151                            input_state: ObjectIn::Data {
152                                version: oref.version,
153                                digest: oref.digest,
154                                owner: Owner::Address(sender),
155                            },
156                            output_state: ObjectOut::ObjectWrite {
157                                // Digest must change with a mutation.
158                                digest: ObjectDigest::MAX,
159                                owner: Owner::Address(sender),
160                            },
161                            id_operation: IDOperation::None,
162                        },
163                    ))
164                }
165                InputObjectKind::MovePackage(_) => None,
166                InputObjectKind::SharedMoveObject {
167                    id,
168                    initial_shared_version,
169                    mutable,
170                } => mutable.then_some((
171                    *id,
172                    EffectsObjectChange {
173                        object_id: *id,
174                        input_state: ObjectIn::Data {
175                            version: *self
176                                .shared_input_versions
177                                .get(id)
178                                .unwrap_or(initial_shared_version),
179                            digest: ObjectDigest::MIN,
180                            owner: Owner::Shared(*initial_shared_version),
181                        },
182                        output_state: ObjectOut::ObjectWrite {
183                            // Digest must change with a mutation.
184                            digest: ObjectDigest::MAX,
185                            owner: Owner::Shared(*initial_shared_version),
186                        },
187                        id_operation: IDOperation::None,
188                    },
189                )),
190            })
191            .chain(self.created_objects.into_iter().map(|(id, owner)| {
192                (
193                    id,
194                    EffectsObjectChange {
195                        object_id: id,
196                        input_state: ObjectIn::Missing,
197                        output_state: ObjectOut::ObjectWrite {
198                            digest: ObjectDigest::random(),
199                            owner,
200                        },
201                        id_operation: IDOperation::Created,
202                    },
203                )
204            }))
205            .chain(
206                self.mutated_objects
207                    .into_iter()
208                    .map(|(id, version, owner)| {
209                        (
210                            id,
211                            EffectsObjectChange {
212                                object_id: id,
213                                input_state: ObjectIn::Data {
214                                    version,
215                                    digest: ObjectDigest::random(),
216                                    owner: Owner::Address(sender),
217                                },
218                                output_state: ObjectOut::ObjectWrite {
219                                    digest: ObjectDigest::random(),
220                                    owner,
221                                },
222                                id_operation: IDOperation::None,
223                            },
224                        )
225                    }),
226            )
227            .chain(self.deleted_objects.into_iter().map(|(id, version)| {
228                (
229                    id,
230                    EffectsObjectChange {
231                        object_id: id,
232                        input_state: ObjectIn::Data {
233                            version,
234                            digest: ObjectDigest::random(),
235                            owner: Owner::Address(sender),
236                        },
237                        output_state: ObjectOut::Missing,
238                        id_operation: IDOperation::Deleted,
239                    },
240                )
241            }))
242            .chain(self.wrapped_objects.into_iter().map(|(id, version)| {
243                (
244                    id,
245                    EffectsObjectChange {
246                        object_id: id,
247                        input_state: ObjectIn::Data {
248                            version,
249                            digest: ObjectDigest::random(),
250                            owner: Owner::Address(sender),
251                        },
252                        output_state: ObjectOut::Missing,
253                        id_operation: IDOperation::None,
254                    },
255                )
256            }))
257            .chain(self.unwrapped_objects.into_iter().map(|(id, owner)| {
258                (
259                    id,
260                    EffectsObjectChange {
261                        object_id: id,
262                        input_state: ObjectIn::Missing,
263                        output_state: ObjectOut::ObjectWrite {
264                            digest: ObjectDigest::random(),
265                            owner,
266                        },
267                        id_operation: IDOperation::None,
268                    },
269                )
270            }))
271            .collect();
272        let gas_object_id = self.transaction.transaction().gas()[0].object_id;
273        let event_digest = self.events_digest;
274        let dependencies = vec![];
275
276        TransactionEffects::new_from_execution_v1(
277            status,
278            epoch,
279            GasCostSummary::default(),
280            shared_objects,
281            BTreeSet::new(),
282            self.transaction.digest(),
283            lamport_version,
284            changed_objects,
285            Some(gas_object_id),
286            event_digest,
287            dependencies,
288        )
289    }
290
291    fn get_lamport_version(&self) -> Version {
292        Version::lamport_increment(
293            self.transaction
294                .transaction()
295                .input_objects()
296                .unwrap()
297                .iter()
298                .filter_map(|kind| kind.version())
299                .chain(
300                    self.transaction
301                        .transaction()
302                        .receiving_objects()
303                        .iter()
304                        .map(|oref| oref.version),
305                )
306                .chain(self.shared_input_versions.values().copied())
307                .chain(self.mutated_objects.iter().map(|(_, v, _)| *v))
308                .chain(self.deleted_objects.iter().map(|(_, v)| *v))
309                .chain(self.wrapped_objects.iter().map(|(_, v)| *v)),
310        )
311        .unwrap()
312    }
313}