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)>,
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(
106 mut self,
107 objects: impl IntoIterator<Item = (ObjectId, Version)>,
108 ) -> Self {
109 self.deleted_objects.extend(objects);
110 self
111 }
112
113 pub fn with_frozen_objects(mut self, objects: impl IntoIterator<Item = ObjectId>) -> Self {
114 self.frozen_objects.extend(objects);
115 self
116 }
117
118 pub fn build(self) -> TransactionEffects {
119 let lamport_version = self.get_lamport_version();
120 let status = self.status.unwrap_or(ExecutionStatus::Success);
121 let shared_objects = self
123 .shared_input_versions
124 .iter()
125 .map(|(id, version)| {
126 SharedInput::Existing(ObjectReference::new(*id, *version, ObjectDigest::MIN))
127 })
128 .collect();
129 let epoch = 0;
130 let sender = self.transaction.transaction().sender();
131 let changed_objects = self
133 .transaction
134 .transaction()
135 .input_objects()
136 .unwrap()
137 .iter()
138 .filter_map(|kind| match kind {
139 InputObjectKind::ImmOrOwnedMoveObject(object_ref)
140 if self.frozen_objects.contains(&object_ref.object_id) =>
141 {
142 None
143 }
144 InputObjectKind::ImmOrOwnedMoveObject(oref) => {
145 Some((
146 oref.object_id,
147 ChangedObject {
148 object_id: oref.object_id,
149 input_state: ObjectIn::Data {
150 version: oref.version,
151 digest: oref.digest,
152 owner: Owner::Address(sender),
153 },
154 output_state: ObjectOut::ObjectWrite {
155 digest: ObjectDigest::MAX,
157 owner: Owner::Address(sender),
158 },
159 id_operation: IdOperation::None,
160 },
161 ))
162 }
163 InputObjectKind::MovePackage(_) => None,
164 InputObjectKind::SharedMoveObject {
165 id,
166 initial_shared_version,
167 mutable,
168 } => mutable.then_some((
169 *id,
170 ChangedObject {
171 object_id: *id,
172 input_state: ObjectIn::Data {
173 version: *self
174 .shared_input_versions
175 .get(id)
176 .unwrap_or(initial_shared_version),
177 digest: ObjectDigest::MIN,
178 owner: Owner::Shared(*initial_shared_version),
179 },
180 output_state: ObjectOut::ObjectWrite {
181 digest: ObjectDigest::MAX,
183 owner: Owner::Shared(*initial_shared_version),
184 },
185 id_operation: IdOperation::None,
186 },
187 )),
188 })
189 .chain(self.created_objects.into_iter().map(|(id, owner)| {
190 (
191 id,
192 ChangedObject {
193 object_id: id,
194 input_state: ObjectIn::Missing,
195 output_state: ObjectOut::ObjectWrite {
196 digest: ObjectDigest::random(),
197 owner,
198 },
199 id_operation: IdOperation::Created,
200 },
201 )
202 }))
203 .chain(
204 self.mutated_objects
205 .into_iter()
206 .map(|(id, version, owner)| {
207 (
208 id,
209 ChangedObject {
210 object_id: id,
211 input_state: ObjectIn::Data {
212 version,
213 digest: ObjectDigest::random(),
214 owner: Owner::Address(sender),
215 },
216 output_state: ObjectOut::ObjectWrite {
217 digest: ObjectDigest::random(),
218 owner,
219 },
220 id_operation: IdOperation::None,
221 },
222 )
223 }),
224 )
225 .chain(self.deleted_objects.into_iter().map(|(id, version)| {
226 (
227 id,
228 ChangedObject {
229 object_id: id,
230 input_state: ObjectIn::Data {
231 version,
232 digest: ObjectDigest::random(),
233 owner: Owner::Address(sender),
234 },
235 output_state: ObjectOut::Missing,
236 id_operation: IdOperation::Deleted,
237 },
238 )
239 }))
240 .chain(self.wrapped_objects.into_iter().map(|(id, version)| {
241 (
242 id,
243 ChangedObject {
244 object_id: id,
245 input_state: ObjectIn::Data {
246 version,
247 digest: ObjectDigest::random(),
248 owner: Owner::Address(sender),
249 },
250 output_state: ObjectOut::Missing,
251 id_operation: IdOperation::None,
252 },
253 )
254 }))
255 .chain(self.unwrapped_objects.into_iter().map(|(id, owner)| {
256 (
257 id,
258 ChangedObject {
259 object_id: id,
260 input_state: ObjectIn::Missing,
261 output_state: ObjectOut::ObjectWrite {
262 digest: ObjectDigest::random(),
263 owner,
264 },
265 id_operation: IdOperation::None,
266 },
267 )
268 }))
269 .collect();
270 let gas_object_id = (!self.transaction.transaction().is_system_tx())
273 .then(|| self.transaction.transaction().gas()[0].object_id);
274 let event_digest = self.events_digest;
275 let dependencies = vec![];
276
277 TransactionEffects::new_from_execution_v1(
278 status,
279 epoch,
280 GasCostSummary::default(),
281 shared_objects,
282 BTreeSet::new(),
283 self.transaction.digest(),
284 lamport_version,
285 changed_objects,
286 gas_object_id,
287 event_digest,
288 dependencies,
289 )
290 }
291
292 fn get_lamport_version(&self) -> Version {
293 Version::lamport_increment(
294 self.transaction
295 .transaction()
296 .input_objects()
297 .unwrap()
298 .iter()
299 .filter_map(|kind| kind.version())
300 .chain(
301 self.transaction
302 .transaction()
303 .receiving_objects()
304 .iter()
305 .map(|oref| oref.version),
306 )
307 .chain(self.shared_input_versions.values().copied())
308 .chain(self.mutated_objects.iter().map(|(_, v, _)| *v))
309 .chain(self.deleted_objects.iter().map(|(_, v)| *v))
310 .chain(self.wrapped_objects.iter().map(|(_, v)| *v)),
311 )
312 .unwrap()
313 }
314}