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