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