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