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