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