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