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