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}
36
37impl TestEffectsBuilder {
38    pub fn new(transaction: &SenderSignedData) -> Self {
39        Self {
40            transaction: transaction.clone(),
41            status: None,
42            shared_input_versions: BTreeMap::new(),
43            events_digest: None,
44            created_objects: vec![],
45            mutated_objects: vec![],
46            deleted_objects: vec![],
47            wrapped_objects: vec![],
48            unwrapped_objects: vec![],
49        }
50    }
51
52    pub fn with_status(mut self, status: ExecutionStatus) -> Self {
53        self.status = Some(status);
54        self
55    }
56
57    pub fn with_shared_input_versions(
58        mut self,
59        versions: BTreeMap<ObjectID, SequenceNumber>,
60    ) -> Self {
61        assert!(self.shared_input_versions.is_empty());
62        self.shared_input_versions = versions;
63        self
64    }
65
66    pub fn with_events_digest(mut self, digest: TransactionEventsDigest) -> Self {
67        self.events_digest = Some(digest);
68        self
69    }
70
71    pub fn with_created_objects(
72        mut self,
73        objects: impl IntoIterator<Item = (ObjectID, Owner)>,
74    ) -> Self {
75        self.created_objects.extend(objects);
76        self
77    }
78
79    pub fn with_mutated_objects(
80        mut self,
81        // Object ID, old version, and new owner.
82        objects: impl IntoIterator<Item = (ObjectID, SequenceNumber, Owner)>,
83    ) -> Self {
84        self.mutated_objects.extend(objects);
85        self
86    }
87
88    pub fn with_wrapped_objects(
89        mut self,
90        objects: impl IntoIterator<Item = (ObjectID, SequenceNumber)>,
91    ) -> Self {
92        self.wrapped_objects.extend(objects);
93        self
94    }
95
96    pub fn with_unwrapped_objects(
97        mut self,
98        objects: impl IntoIterator<Item = (ObjectID, Owner)>,
99    ) -> Self {
100        self.unwrapped_objects.extend(objects);
101        self
102    }
103
104    pub fn with_deleted_objects(
105        mut self,
106        objects: impl IntoIterator<Item = (ObjectID, SequenceNumber)>,
107    ) -> Self {
108        self.deleted_objects.extend(objects);
109        self
110    }
111
112    pub fn build(self) -> TransactionEffects {
113        let lamport_version = self.get_lamport_version();
114        let status = self.status.unwrap_or(ExecutionStatus::Success);
115        // TODO: This does not yet support deleted shared objects.
116        let shared_objects = self
117            .shared_input_versions
118            .iter()
119            .map(|(id, version)| SharedInput::Existing((*id, *version, ObjectDigest::MIN)))
120            .collect();
121        let executed_epoch = 0;
122        let sender = self.transaction.transaction_data().sender();
123        // TODO: Include receiving objects in the object changes as well.
124        let changed_objects = self
125            .transaction
126            .transaction_data()
127            .input_objects()
128            .unwrap()
129            .iter()
130            .filter_map(|kind| match kind {
131                InputObjectKind::ImmOrOwnedMoveObject(oref) => {
132                    Some((
133                        oref.0,
134                        EffectsObjectChange {
135                            input_state: ObjectIn::Exist((
136                                (oref.1, oref.2),
137                                Owner::AddressOwner(sender),
138                            )),
139                            output_state: ObjectOut::ObjectWrite((
140                                // Digest must change with a mutation.
141                                ObjectDigest::MAX,
142                                Owner::AddressOwner(sender),
143                            )),
144                            id_operation: IDOperation::None,
145                        },
146                    ))
147                }
148                InputObjectKind::MovePackage(_) => None,
149                InputObjectKind::SharedMoveObject {
150                    id,
151                    initial_shared_version,
152                    mutable,
153                } => mutable.then_some((
154                    *id,
155                    EffectsObjectChange {
156                        input_state: ObjectIn::Exist((
157                            (
158                                *self
159                                    .shared_input_versions
160                                    .get(id)
161                                    .unwrap_or(initial_shared_version),
162                                ObjectDigest::MIN,
163                            ),
164                            Owner::Shared {
165                                initial_shared_version: *initial_shared_version,
166                            },
167                        )),
168                        output_state: ObjectOut::ObjectWrite((
169                            // Digest must change with a mutation.
170                            ObjectDigest::MAX,
171                            Owner::Shared {
172                                initial_shared_version: *initial_shared_version,
173                            },
174                        )),
175                        id_operation: IDOperation::None,
176                    },
177                )),
178            })
179            .chain(self.created_objects.into_iter().map(|(id, owner)| {
180                (
181                    id,
182                    EffectsObjectChange {
183                        input_state: ObjectIn::NotExist,
184                        output_state: ObjectOut::ObjectWrite((ObjectDigest::random(), owner)),
185                        id_operation: IDOperation::Created,
186                    },
187                )
188            }))
189            .chain(
190                self.mutated_objects
191                    .into_iter()
192                    .map(|(id, version, owner)| {
193                        (
194                            id,
195                            EffectsObjectChange {
196                                input_state: ObjectIn::Exist((
197                                    (version, ObjectDigest::random()),
198                                    Owner::AddressOwner(sender),
199                                )),
200                                output_state: ObjectOut::ObjectWrite((
201                                    ObjectDigest::random(),
202                                    owner,
203                                )),
204                                id_operation: IDOperation::None,
205                            },
206                        )
207                    }),
208            )
209            .chain(self.deleted_objects.into_iter().map(|(id, version)| {
210                (
211                    id,
212                    EffectsObjectChange {
213                        input_state: ObjectIn::Exist((
214                            (version, ObjectDigest::random()),
215                            Owner::AddressOwner(sender),
216                        )),
217                        output_state: ObjectOut::NotExist,
218                        id_operation: IDOperation::Deleted,
219                    },
220                )
221            }))
222            .chain(self.wrapped_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::None,
232                    },
233                )
234            }))
235            .chain(self.unwrapped_objects.into_iter().map(|(id, owner)| {
236                (
237                    id,
238                    EffectsObjectChange {
239                        input_state: ObjectIn::NotExist,
240                        output_state: ObjectOut::ObjectWrite((ObjectDigest::random(), owner)),
241                        id_operation: IDOperation::None,
242                    },
243                )
244            }))
245            .collect();
246        let gas_object_id = self.transaction.transaction_data().gas()[0].0;
247        let event_digest = self.events_digest;
248        let dependencies = vec![];
249        TransactionEffects::new_from_execution_v1(
250            status,
251            executed_epoch,
252            GasCostSummary::default(),
253            shared_objects,
254            BTreeSet::new(),
255            self.transaction.digest(),
256            lamport_version,
257            changed_objects,
258            Some(gas_object_id),
259            event_digest,
260            dependencies,
261        )
262    }
263
264    fn get_lamport_version(&self) -> SequenceNumber {
265        SequenceNumber::lamport_increment(
266            self.transaction
267                .transaction_data()
268                .input_objects()
269                .unwrap()
270                .iter()
271                .filter_map(|kind| kind.version())
272                .chain(
273                    self.transaction
274                        .transaction_data()
275                        .receiving_objects()
276                        .iter()
277                        .map(|oref| oref.1),
278                )
279                .chain(self.shared_input_versions.values().copied())
280                .chain(self.mutated_objects.iter().map(|(_, v, _)| *v))
281                .chain(self.deleted_objects.iter().map(|(_, v)| *v))
282                .chain(self.wrapped_objects.iter().map(|(_, v)| *v)),
283        )
284    }
285}