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