1use std::collections::{BTreeMap, BTreeSet};
6
7use iota_sdk_types::{Address, ObjectDigest, TransactionDigest, TransactionEventsDigest};
8
9use super::{
10 EffectsObjectChange, EpochId, ExecutionStatus, GasCostSummary, IDOperation, InputSharedObject,
11 ObjectChange, ObjectId, ObjectIn, ObjectOut, ObjectReference, Owner, TransactionEffectsV1,
12 UnchangedSharedKind, UnchangedSharedObject, Version,
13};
14use crate::{
15 effects::{TransactionEffectsAPI, TransactionEffectsAPIForTesting},
16 execution::SharedInput,
17 object::OBJECT_START_VERSION,
18};
19
20impl TransactionEffectsAPI for TransactionEffectsV1 {
21 fn status(&self) -> &ExecutionStatus {
22 &self.status
23 }
24
25 fn into_status(self) -> ExecutionStatus {
26 self.status
27 }
28
29 fn epoch(&self) -> EpochId {
30 self.epoch
31 }
32
33 fn modified_at_versions(&self) -> Vec<(ObjectId, Version)> {
34 self.changed_objects
35 .iter()
36 .filter_map(|change| {
37 if let ObjectIn::Data { version, .. } = &change.input_state {
38 Some((change.object_id, *version))
39 } else {
40 None
41 }
42 })
43 .collect()
44 }
45
46 fn lamport_version(&self) -> Version {
47 self.lamport_version
48 }
49
50 fn old_object_metadata(&self) -> Vec<(ObjectReference, Owner)> {
51 self.changed_objects
52 .iter()
53 .filter_map(|change| {
54 if let ObjectIn::Data {
55 version,
56 digest,
57 owner,
58 } = change.input_state
59 {
60 Some((
61 ObjectReference::new(change.object_id, version, digest),
62 owner,
63 ))
64 } else {
65 None
66 }
67 })
68 .collect()
69 }
70
71 fn input_shared_objects(&self) -> Vec<InputSharedObject> {
72 self.changed_objects
73 .iter()
74 .filter_map(|changed| {
75 if let ObjectIn::Data {
76 version,
77 digest,
78 owner: Owner::Shared { .. },
79 } = changed.input_state
80 {
81 Some(InputSharedObject::Mutate(ObjectReference::new(
82 changed.object_id,
83 version,
84 digest,
85 )))
86 } else {
87 None
88 }
89 })
90 .chain(self.unchanged_shared_objects.iter().filter_map(
91 |unchanged| match unchanged.kind {
92 UnchangedSharedKind::ReadOnlyRoot { version, digest } => {
93 Some(InputSharedObject::ReadOnly(ObjectReference::new(
94 unchanged.object_id,
95 version,
96 digest,
97 )))
98 }
99 UnchangedSharedKind::MutateDeleted { version } => Some(
100 InputSharedObject::MutateDeleted(unchanged.object_id, version),
101 ),
102 UnchangedSharedKind::ReadDeleted { version } => {
103 Some(InputSharedObject::ReadDeleted(unchanged.object_id, version))
104 }
105 UnchangedSharedKind::Cancelled { version } => {
106 Some(InputSharedObject::Cancelled(unchanged.object_id, version))
107 }
108 UnchangedSharedKind::PerEpochConfig => None,
112 _ => unimplemented!(
113 "a new UnchangedSharedKind enum variant was added and needs to be handled"
114 ),
115 },
116 ))
117 .collect()
118 }
119
120 fn created(&self) -> Vec<(ObjectReference, Owner)> {
121 self.changed_objects
122 .iter()
123 .filter_map(|changed| {
124 match (
125 &changed.input_state,
126 &changed.output_state,
127 &changed.id_operation,
128 ) {
129 (
130 ObjectIn::Missing,
131 ObjectOut::ObjectWrite { digest, owner },
132 IDOperation::Created,
133 ) => Some((
134 ObjectReference::new(changed.object_id, self.lamport_version, *digest),
135 *owner,
136 )),
137 (
138 ObjectIn::Missing,
139 ObjectOut::PackageWrite { version, digest },
140 IDOperation::Created,
141 ) => Some((
142 ObjectReference::new(changed.object_id, *version, *digest),
143 Owner::Immutable,
144 )),
145 _ => None,
146 }
147 })
148 .collect()
149 }
150
151 fn mutated(&self) -> Vec<(ObjectReference, Owner)> {
152 self.changed_objects
153 .iter()
154 .filter_map(
155 |changed| match (&changed.input_state, &changed.output_state) {
156 (ObjectIn::Data { .. }, ObjectOut::ObjectWrite { digest, owner }) => Some((
157 ObjectReference::new(changed.object_id, self.lamport_version, *digest),
158 *owner,
159 )),
160 (ObjectIn::Data { .. }, ObjectOut::PackageWrite { version, digest }) => Some((
161 ObjectReference::new(changed.object_id, *version, *digest),
162 Owner::Immutable,
163 )),
164 _ => None,
165 },
166 )
167 .collect()
168 }
169
170 fn unwrapped(&self) -> Vec<(ObjectReference, Owner)> {
171 self.changed_objects
172 .iter()
173 .filter_map(|changed| {
174 match (
175 &changed.input_state,
176 &changed.output_state,
177 &changed.id_operation,
178 ) {
179 (
180 ObjectIn::Missing,
181 ObjectOut::ObjectWrite { digest, owner },
182 IDOperation::None,
183 ) => Some((
184 ObjectReference::new(changed.object_id, self.lamport_version, *digest),
185 *owner,
186 )),
187 _ => None,
188 }
189 })
190 .collect()
191 }
192
193 fn deleted(&self) -> Vec<ObjectReference> {
194 self.changed_objects
195 .iter()
196 .filter_map(|changed| {
197 match (
198 &changed.input_state,
199 &changed.output_state,
200 &changed.id_operation,
201 ) {
202 (ObjectIn::Data { .. }, ObjectOut::Missing, IDOperation::Deleted) => {
203 Some(ObjectReference::new(
204 changed.object_id,
205 self.lamport_version,
206 ObjectDigest::OBJECT_DELETED,
207 ))
208 }
209 _ => None,
210 }
211 })
212 .collect()
213 }
214
215 fn unwrapped_then_deleted(&self) -> Vec<ObjectReference> {
216 self.changed_objects
217 .iter()
218 .filter_map(|changed| {
219 match (
220 &changed.input_state,
221 &changed.output_state,
222 &changed.id_operation,
223 ) {
224 (ObjectIn::Missing, ObjectOut::Missing, IDOperation::Deleted) => {
225 Some(ObjectReference::new(
226 changed.object_id,
227 self.lamport_version,
228 ObjectDigest::OBJECT_DELETED,
229 ))
230 }
231 _ => None,
232 }
233 })
234 .collect()
235 }
236
237 fn wrapped(&self) -> Vec<ObjectReference> {
238 self.changed_objects
239 .iter()
240 .filter_map(|changed| {
241 match (
242 &changed.input_state,
243 &changed.output_state,
244 &changed.id_operation,
245 ) {
246 (ObjectIn::Data { .. }, ObjectOut::Missing, IDOperation::None) => {
247 Some(ObjectReference::new(
248 changed.object_id,
249 self.lamport_version,
250 ObjectDigest::OBJECT_WRAPPED,
251 ))
252 }
253 _ => None,
254 }
255 })
256 .collect()
257 }
258
259 fn object_changes(&self) -> Vec<ObjectChange> {
260 self.changed_objects
261 .iter()
262 .map(|changed| {
263 let input_version_digest = match &changed.input_state {
264 ObjectIn::Missing => None,
265 ObjectIn::Data {
266 version, digest, ..
267 } => Some((version, digest)),
268 _ => unimplemented!(
269 "a new ObjectIn enum variant was added and needs to be handled"
270 ),
271 };
272
273 let output_version_digest = match &changed.output_state {
274 ObjectOut::Missing => None,
275 ObjectOut::ObjectWrite { digest, .. } => Some((&self.lamport_version, digest)),
276 ObjectOut::PackageWrite { version, digest } => Some((version, digest)),
277 _ => unimplemented!(
278 "a new ObjectOut enum variant was added and needs to be handled"
279 ),
280 };
281
282 ObjectChange {
283 id: changed.object_id,
284 input_version: input_version_digest.map(|k| *k.0),
285 input_digest: input_version_digest.map(|k| *k.1),
286 output_version: output_version_digest.map(|k| *k.0),
287 output_digest: output_version_digest.map(|k| *k.1),
288 id_operation: changed.id_operation,
289 }
290 })
291 .collect()
292 }
293
294 fn gas_object(&self) -> (ObjectReference, Owner) {
295 if let Some(gas_object_index) = self.gas_object_index {
296 let changed = &self.changed_objects[gas_object_index as usize];
297 match changed.output_state {
298 ObjectOut::ObjectWrite { digest, owner } => (
299 ObjectReference::new(changed.object_id, self.lamport_version, digest),
300 owner,
301 ),
302 _ => panic!("Gas object must be an ObjectWrite in changed_objects"),
303 }
304 } else {
305 (
306 ObjectReference::new(ObjectId::ZERO, Version::default(), ObjectDigest::MIN),
307 Owner::Address(Address::ZERO),
308 )
309 }
310 }
311
312 fn events_digest(&self) -> Option<&TransactionEventsDigest> {
313 self.events_digest.as_ref()
314 }
315
316 fn dependencies(&self) -> &[TransactionDigest] {
317 &self.dependencies
318 }
319
320 fn transaction_digest(&self) -> &TransactionDigest {
321 &self.transaction_digest
322 }
323
324 fn gas_cost_summary(&self) -> &GasCostSummary {
325 &self.gas_cost_summary
326 }
327
328 fn unchanged_shared_objects(&self) -> Vec<(ObjectId, UnchangedSharedKind)> {
329 self.unchanged_shared_objects
330 .iter()
331 .map(|unchanged| (unchanged.object_id, unchanged.kind.clone()))
332 .collect()
333 }
334}
335
336impl TransactionEffectsAPIForTesting for TransactionEffectsV1 {
337 fn status_mut_for_testing(&mut self) -> &mut ExecutionStatus {
338 &mut self.status
339 }
340
341 fn gas_cost_summary_mut_for_testing(&mut self) -> &mut GasCostSummary {
342 &mut self.gas_cost_summary
343 }
344
345 fn transaction_digest_mut_for_testing(&mut self) -> &mut TransactionDigest {
346 &mut self.transaction_digest
347 }
348
349 fn dependencies_mut_for_testing(&mut self) -> &mut Vec<TransactionDigest> {
350 &mut self.dependencies
351 }
352
353 fn unsafe_add_input_shared_object_for_testing(&mut self, kind: InputSharedObject) {
354 match kind {
355 InputSharedObject::Mutate(object_ref) => {
356 let (object_id, version, digest) = object_ref.into_parts();
357 self.changed_objects.push(EffectsObjectChange {
358 object_id,
359 input_state: ObjectIn::Data {
360 version,
361 digest,
362 owner: Owner::Shared(OBJECT_START_VERSION),
363 },
364 output_state: ObjectOut::ObjectWrite {
365 digest,
366 owner: Owner::Shared(version),
367 },
368 id_operation: IDOperation::None,
369 })
370 }
371 InputSharedObject::ReadOnly(object_ref) => {
372 let (object_id, version, digest) = object_ref.into_parts();
373 self.unchanged_shared_objects.push(UnchangedSharedObject {
374 object_id,
375 kind: UnchangedSharedKind::ReadOnlyRoot { version, digest },
376 })
377 }
378 InputSharedObject::ReadDeleted(object_id, version) => {
379 self.unchanged_shared_objects.push(UnchangedSharedObject {
380 object_id,
381 kind: UnchangedSharedKind::ReadDeleted { version },
382 })
383 }
384 InputSharedObject::MutateDeleted(object_id, version) => {
385 self.unchanged_shared_objects.push(UnchangedSharedObject {
386 object_id,
387 kind: UnchangedSharedKind::MutateDeleted { version },
388 })
389 }
390 InputSharedObject::Cancelled(object_id, version) => {
391 self.unchanged_shared_objects.push(UnchangedSharedObject {
392 object_id,
393 kind: UnchangedSharedKind::Cancelled { version },
394 })
395 }
396 }
397 }
398
399 fn unsafe_add_deleted_live_object_for_testing(&mut self, object_ref: ObjectReference) {
400 let (object_id, version, digest) = object_ref.into_parts();
401 self.changed_objects.push(EffectsObjectChange {
402 object_id,
403 input_state: ObjectIn::Data {
404 version,
405 digest,
406 owner: Owner::Address(Address::ZERO),
407 },
408 output_state: ObjectOut::ObjectWrite {
409 digest,
410 owner: Owner::Address(Address::ZERO),
411 },
412 id_operation: IDOperation::None,
413 })
414 }
415
416 fn unsafe_add_object_tombstone_for_testing(&mut self, object_ref: ObjectReference) {
417 let (object_id, version, digest) = object_ref.into_parts();
418 self.changed_objects.push(EffectsObjectChange {
419 object_id,
420 input_state: ObjectIn::Data {
421 version,
422 digest,
423 owner: Owner::Address(Address::ZERO),
424 },
425 output_state: ObjectOut::Missing,
426 id_operation: IDOperation::Deleted,
427 })
428 }
429}
430
431pub(crate) fn new_from_execution(
432 status: ExecutionStatus,
433 epoch: EpochId,
434 gas_cost_summary: GasCostSummary,
435 shared_objects: Vec<SharedInput>,
436 loaded_per_epoch_config_objects: BTreeSet<ObjectId>,
437 transaction_digest: TransactionDigest,
438 lamport_version: Version,
439 changed_objects: BTreeMap<ObjectId, EffectsObjectChange>,
440 gas_object: Option<ObjectId>,
441 events_digest: Option<TransactionEventsDigest>,
442 dependencies: Vec<TransactionDigest>,
443) -> TransactionEffectsV1 {
444 let unchanged_shared_objects = shared_objects
445 .into_iter()
446 .filter_map(|shared_input| match shared_input {
447 SharedInput::Existing(ObjectReference {
448 object_id: id,
449 version,
450 digest,
451 }) => {
452 if changed_objects.contains_key(&id) {
453 None
454 } else {
455 Some((id, UnchangedSharedKind::ReadOnlyRoot { version, digest }))
456 }
457 }
458 SharedInput::Deleted((id, version, mutable, _)) => {
459 debug_assert!(!changed_objects.contains_key(&id));
460 if mutable {
461 Some((id, UnchangedSharedKind::MutateDeleted { version }))
462 } else {
463 Some((id, UnchangedSharedKind::ReadDeleted { version }))
464 }
465 }
466 SharedInput::Cancelled((id, version)) => {
467 debug_assert!(!changed_objects.contains_key(&id));
468 Some((id, UnchangedSharedKind::Cancelled { version }))
469 }
470 })
471 .chain(
472 loaded_per_epoch_config_objects
473 .into_iter()
474 .map(|id| (id, UnchangedSharedKind::PerEpochConfig)),
475 )
476 .map(|(object_id, kind)| UnchangedSharedObject { object_id, kind })
477 .collect();
478
479 let changed_objects: Vec<_> = changed_objects.into_values().collect();
480
481 let gas_object_index = gas_object.map(|gas_id| {
482 changed_objects
483 .iter()
484 .position(|changed| changed.object_id == gas_id)
485 .unwrap() as u32
486 });
487
488 let v1 = TransactionEffectsV1 {
489 status,
490 epoch,
491 gas_cost_summary,
492 transaction_digest,
493 lamport_version,
494 changed_objects,
495 unchanged_shared_objects,
496 gas_object_index,
497 events_digest,
498 dependencies,
499 auxiliary_data_digest: None,
500 };
501
502 #[cfg(debug_assertions)]
503 check_invariant(&v1);
504
505 v1
506}
507
508#[cfg(debug_assertions)]
512fn check_invariant(v1: &TransactionEffectsV1) {
513 use std::collections::HashSet;
514
515 let mut unique_ids = HashSet::new();
516 for changed in &v1.changed_objects {
517 let id = &changed.object_id;
518 assert!(unique_ids.insert(*id));
519 match (
520 &changed.input_state,
521 &changed.output_state,
522 &changed.id_operation,
523 ) {
524 (ObjectIn::Missing, ObjectOut::Missing, IDOperation::Created) => {
525 }
527 (ObjectIn::Missing, ObjectOut::Missing, IDOperation::Deleted) => {
528 }
530 (ObjectIn::Missing, ObjectOut::ObjectWrite { owner, .. }, IDOperation::None) => {
531 assert!(!owner.is_shared());
534 }
535 (ObjectIn::Missing, ObjectOut::ObjectWrite { .. }, IDOperation::Created) => {
536 }
538 (ObjectIn::Missing, ObjectOut::PackageWrite { .. }, IDOperation::Created) => {
539 }
541 (
542 ObjectIn::Data {
543 version: old_version,
544 owner: old_owner,
545 ..
546 },
547 ObjectOut::Missing,
548 IDOperation::None,
549 ) => {
550 assert!(*old_version < v1.lamport_version);
552 assert!(
553 !old_owner.is_shared() && !old_owner.is_immutable(),
554 "Cannot wrap shared or immutable object"
555 );
556 }
557 (
558 ObjectIn::Data {
559 version: old_version,
560 owner: old_owner,
561 ..
562 },
563 ObjectOut::Missing,
564 IDOperation::Deleted,
565 ) => {
566 assert!(*old_version < v1.lamport_version);
568 assert!(!old_owner.is_immutable(), "Cannot delete immutable object");
569 }
570 (
571 ObjectIn::Data {
572 version: old_version,
573 digest: old_digest,
574 owner: old_owner,
575 },
576 ObjectOut::ObjectWrite {
577 digest: new_digest,
578 owner: new_owner,
579 ..
580 },
581 IDOperation::None,
582 ) => {
583 assert!(*old_version < v1.lamport_version);
585 assert_ne!(old_digest, new_digest);
586 assert!(!old_owner.is_immutable(), "Cannot mutate immutable object");
587 if old_owner.is_shared() {
588 assert!(new_owner.is_shared(), "Cannot un-share an object");
589 } else {
590 assert!(!new_owner.is_shared(), "Cannot share an existing object");
591 }
592 }
593 (
594 ObjectIn::Data {
595 version: old_version,
596 digest: old_digest,
597 owner: old_owner,
598 },
599 ObjectOut::PackageWrite {
600 version: new_version,
601 digest: new_digest,
602 ..
603 },
604 IDOperation::None,
605 ) => {
606 assert!(
608 old_owner.is_immutable() && id.is_system_package(),
609 "Must be a system package"
610 );
611 assert_eq!(*old_version + 1, *new_version);
612 assert_ne!(old_digest, new_digest);
613 }
614 _ => {
615 panic!("Impossible object change: {id:?}, {changed:?}");
616 }
617 }
618 }
619
620 let (_, owner) = v1.gas_object();
622 assert!(matches!(owner, Owner::Address(_)));
623
624 for unchanged in &v1.unchanged_shared_objects {
625 let id = &unchanged.object_id;
626 assert!(
627 unique_ids.insert(*id),
628 "Duplicate object id: {id:?}\n{v1:#?}"
629 );
630 }
631}