1use std::collections::{BTreeMap, BTreeSet, HashMap};
6
7use iota_protocol_config::ProtocolConfig;
8use iota_sdk_types::{
9 Address, EndOfEpochTransactionKind, Event, Identifier, MoveStruct, ObjectId, ObjectReference,
10 Owner, SharedObjectReference, StructTag, TransactionDigest, TransactionKind, TypeTag, Version,
11 checkpoint::{CheckpointContents, CheckpointSummary, EndOfEpochData},
12};
13use tap::Pipe;
14
15use crate::{
16 base_types::{ExecutionDigests, dbg_addr, random_object_ref},
17 committee::Committee,
18 effects::{
19 TestEffectsBuilder, TransactionEffects, TransactionEffectsAPI,
20 TransactionEffectsExtForTesting, TransactionEvents,
21 },
22 event::SystemEpochInfoEventV2,
23 full_checkpoint_content::{CheckpointData, CheckpointTransaction},
24 gas_coin::GAS,
25 messages_checkpoint::{
26 CertifiedCheckpointSummary, CheckpointContentsExt, CheckpointSummaryExt,
27 },
28 object::{GAS_VALUE_FOR_TESTING, MoveStructExt, Object},
29 programmable_transaction_builder::ProgrammableTransactionBuilder,
30 transaction::{CallArg, SenderSignedData, Transaction, TransactionData, TransactionDataAPI},
31};
32
33pub struct TestCheckpointDataBuilder {
47 live_objects: HashMap<ObjectId, Object>,
49 wrapped_objects: HashMap<ObjectId, Object>,
51 gas_map: HashMap<Address, ObjectId>,
55
56 checkpoint_builder: CheckpointBuilder,
60}
61
62struct CheckpointBuilder {
63 checkpoint: u64,
65 epoch: u64,
67 network_total_transactions: u64,
69 transactions: Vec<CheckpointTransaction>,
71 next_transaction: Option<TransactionBuilder>,
73}
74
75struct TransactionBuilder {
76 sender_idx: u8,
77 gas: ObjectReference,
78 move_calls: Vec<(ObjectId, &'static str, &'static str)>,
79 created_objects: BTreeMap<ObjectId, Object>,
80 mutated_objects: BTreeMap<ObjectId, Object>,
81 unwrapped_objects: BTreeSet<ObjectId>,
82 wrapped_objects: BTreeSet<ObjectId>,
83 deleted_objects: BTreeSet<ObjectId>,
84 frozen_objects: BTreeSet<ObjectReference>,
85 shared_inputs: BTreeMap<ObjectId, Shared>,
86 events: Option<Vec<Event>>,
87}
88
89struct Shared {
90 mutable: bool,
91 object: Object,
92}
93
94impl TransactionBuilder {
95 pub fn new(sender_idx: u8, gas: ObjectReference) -> Self {
96 Self {
97 sender_idx,
98 gas,
99 move_calls: vec![],
100 created_objects: BTreeMap::new(),
101 mutated_objects: BTreeMap::new(),
102 unwrapped_objects: BTreeSet::new(),
103 wrapped_objects: BTreeSet::new(),
104 deleted_objects: BTreeSet::new(),
105 frozen_objects: BTreeSet::new(),
106 shared_inputs: BTreeMap::new(),
107 events: None,
108 }
109 }
110}
111
112impl TestCheckpointDataBuilder {
113 pub fn new(checkpoint: u64) -> Self {
114 Self {
115 live_objects: HashMap::new(),
116 wrapped_objects: HashMap::new(),
117 gas_map: HashMap::new(),
118 checkpoint_builder: CheckpointBuilder {
119 checkpoint,
120 epoch: 0,
121 network_total_transactions: 0,
122 transactions: vec![],
123 next_transaction: None,
124 },
125 }
126 }
127
128 pub fn with_epoch(mut self, epoch: u64) -> Self {
130 self.checkpoint_builder.epoch = epoch;
131 self
132 }
133
134 pub fn start_transaction(mut self, sender_idx: u8) -> Self {
141 assert!(self.checkpoint_builder.next_transaction.is_none());
142 let sender = Self::derive_address(sender_idx);
143 let gas_id = self.gas_map.entry(sender).or_insert_with(|| {
144 let gas = Object::with_owner_for_testing(sender);
145 let id = gas.id();
146 self.live_objects.insert(id, gas);
147 id
148 });
149 let gas_ref = self.live_objects.get(gas_id).cloned().unwrap().object_ref();
150 self.checkpoint_builder.next_transaction =
151 Some(TransactionBuilder::new(sender_idx, gas_ref));
152 self
153 }
154
155 pub fn create_owned_object(self, object_idx: u64) -> Self {
160 self.create_iota_object(object_idx, GAS_VALUE_FOR_TESTING)
161 }
162
163 pub fn create_shared_object(self, object_idx: u64) -> Self {
168 self.create_coin_object_with_owner(
169 object_idx,
170 Owner::Shared(Version::MIN_VALID_INCL),
171 GAS_VALUE_FOR_TESTING,
172 GAS::type_tag(),
173 )
174 }
175
176 pub fn create_iota_object(self, object_idx: u64, balance: u64) -> Self {
180 let sender_idx = self
181 .checkpoint_builder
182 .next_transaction
183 .as_ref()
184 .unwrap()
185 .sender_idx;
186 self.create_coin_object(object_idx, sender_idx, balance, GAS::type_tag())
187 }
188
189 pub fn create_coin_object(
195 self,
196 object_idx: u64,
197 owner_idx: u8,
198 balance: u64,
199 coin_type: TypeTag,
200 ) -> Self {
201 self.create_coin_object_with_owner(
202 object_idx,
203 Owner::Address(Self::derive_address(owner_idx)),
204 balance,
205 coin_type,
206 )
207 }
208
209 fn create_coin_object_with_owner(
210 mut self,
211 object_idx: u64,
212 owner: Owner,
213 balance: u64,
214 coin_type: TypeTag,
215 ) -> Self {
216 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
217 let object_id = Self::derive_object_id(object_idx);
218 assert!(
219 !self.live_objects.contains_key(&object_id),
220 "Object already exists: {object_id}. Please use a different object index.",
221 );
222 let move_object = MoveStruct::new_coin(
223 coin_type,
224 Version::MIN_VALID_INCL,
227 object_id,
228 balance,
229 );
230 let object = Object::new_move(move_object, owner, TransactionDigest::ZERO);
231 tx_builder.created_objects.insert(object_id, object);
232 self
233 }
234
235 pub fn mutate_owned_object(mut self, object_idx: u64) -> Self {
238 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
239 let object_id = Self::derive_object_id(object_idx);
240 let object = self
241 .live_objects
242 .get(&object_id)
243 .cloned()
244 .expect("Mutating an object that doesn't exist");
245 tx_builder.mutated_objects.insert(object_id, object);
246 self
247 }
248
249 pub fn mutate_shared_object(self, object_idx: u64) -> Self {
251 self.access_shared_object(object_idx, true)
252 }
253
254 pub fn transfer_object(self, object_idx: u64, recipient_idx: u8) -> Self {
259 self.change_object_owner(
260 object_idx,
261 Owner::Address(Self::derive_address(recipient_idx)),
262 )
263 }
264
265 pub fn change_object_owner(mut self, object_idx: u64, owner: Owner) -> Self {
269 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
270 let object_id = Self::derive_object_id(object_idx);
271 let mut object = self.live_objects.get(&object_id).unwrap().clone();
272 object.owner = owner;
273 tx_builder.mutated_objects.insert(object_id, object);
274 self
275 }
276
277 pub fn transfer_coin_balance(
283 mut self,
284 object_idx: u64,
285 new_object_idx: u64,
286 recipient_idx: u8,
287 amount: u64,
288 ) -> Self {
289 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
290 let object_id = Self::derive_object_id(object_idx);
291 let mut object = self
292 .live_objects
293 .get(&object_id)
294 .cloned()
295 .expect("Mutating an object that does not exist");
296 let coin_type = object.coin_type_opt().cloned().unwrap();
297 let move_object = object.data.as_opt_mut_struct().unwrap();
299 let old_balance = move_object.get_coin_value_unchecked();
300 let new_balance = old_balance - amount;
301 move_object.set_coin_value_unchecked(new_balance);
302 tx_builder.mutated_objects.insert(object_id, object);
303
304 self.create_coin_object(new_object_idx, recipient_idx, amount, coin_type)
306 }
307
308 pub fn wrap_object(mut self, object_idx: u64) -> Self {
311 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
312 let object_id = Self::derive_object_id(object_idx);
313 assert!(self.live_objects.contains_key(&object_id));
314 tx_builder.wrapped_objects.insert(object_id);
315 self
316 }
317
318 pub fn unwrap_object(mut self, object_idx: u64) -> Self {
321 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
322 let object_id = Self::derive_object_id(object_idx);
323 assert!(self.wrapped_objects.contains_key(&object_id));
324 tx_builder.unwrapped_objects.insert(object_id);
325 self
326 }
327
328 pub fn delete_object(mut self, object_idx: u64) -> Self {
331 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
332 let object_id = Self::derive_object_id(object_idx);
333 assert!(self.live_objects.contains_key(&object_id));
334 tx_builder.deleted_objects.insert(object_id);
335 self
336 }
337
338 pub fn read_frozen_object(mut self, object_id: u64) -> Self {
343 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
344 let object_id = Self::derive_object_id(object_id);
345
346 let obj = self
347 .live_objects
348 .get(&object_id)
349 .expect("Frozen object not found");
350
351 assert!(obj.owner().is_immutable());
352 tx_builder.frozen_objects.insert(obj.object_ref());
353 self
354 }
355
356 pub fn read_shared_object(self, object_idx: u64) -> Self {
358 self.access_shared_object(object_idx, false)
359 }
360
361 pub fn with_events(mut self, events: Vec<Event>) -> Self {
364 self.checkpoint_builder
365 .next_transaction
366 .as_mut()
367 .unwrap()
368 .events = Some(events);
369 self
370 }
371
372 pub fn add_move_call(
377 mut self,
378 package: ObjectId,
379 module: &'static str,
380 function: &'static str,
381 ) -> Self {
382 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
383 tx_builder.move_calls.push((package, module, function));
384 self
385 }
386
387 pub fn finish_transaction(mut self) -> Self {
391 let TransactionBuilder {
392 sender_idx,
393 gas,
394 move_calls,
395 created_objects,
396 mutated_objects,
397 unwrapped_objects,
398 wrapped_objects,
399 deleted_objects,
400 frozen_objects,
401 shared_inputs,
402 events,
403 } = self.checkpoint_builder.next_transaction.take().unwrap();
404
405 let sender = Self::derive_address(sender_idx);
406 let events = events.map(TransactionEvents);
407 let events_digest = events.as_ref().map(|events| events.digest());
408
409 let mut pt_builder = ProgrammableTransactionBuilder::new();
410 for (package, module, function) in move_calls {
411 pt_builder
412 .move_call(
413 package,
414 Identifier::from_static(module),
415 Identifier::from_static(function),
416 vec![],
417 vec![],
418 )
419 .unwrap();
420 }
421
422 for &object_ref in &frozen_objects {
423 pt_builder
424 .obj(CallArg::ImmutableOrOwned(object_ref))
425 .expect("Failed to add frozen object input");
426 }
427
428 for (id, input) in &shared_inputs {
429 let &Owner::Shared(initial_shared_version) = input.object.owner() else {
430 panic!("Accessing a non-shared object as shared");
431 };
432
433 pt_builder
434 .obj(CallArg::Shared(SharedObjectReference::new(
435 *id,
436 initial_shared_version,
437 input.mutable,
438 )))
439 .expect("Failed to add shared object input");
440 }
441
442 let pt = pt_builder.finish();
443 let tx_data = TransactionData::new(TransactionKind::Programmable(pt), sender, gas, 1, 1);
444 let tx = Transaction::new(SenderSignedData::new(tx_data, vec![]));
445
446 let wrapped_objects: Vec<_> = wrapped_objects
447 .into_iter()
448 .map(|id| self.live_objects.remove(&id).unwrap())
449 .collect();
450 let deleted_objects: Vec<_> = deleted_objects
451 .into_iter()
452 .map(|id| self.live_objects.remove(&id).unwrap())
453 .collect();
454 let unwrapped_objects: Vec<_> = unwrapped_objects
455 .into_iter()
456 .map(|id| self.wrapped_objects.remove(&id).unwrap())
457 .collect();
458
459 let mut effects_builder = TestEffectsBuilder::new(tx.data())
460 .with_created_objects(created_objects.iter().map(|(id, o)| (*id, *o.owner())))
461 .with_mutated_objects(
462 mutated_objects
463 .iter()
464 .map(|(id, o)| (*id, o.version(), *o.owner())),
465 )
466 .with_wrapped_objects(wrapped_objects.iter().map(|o| (o.id(), o.version())))
467 .with_unwrapped_objects(unwrapped_objects.iter().map(|o| (o.id(), *o.owner())))
468 .with_deleted_objects(deleted_objects.iter().map(|o| (o.id(), o.version())))
469 .with_frozen_objects(
470 frozen_objects
471 .into_iter()
472 .map(|object_ref| object_ref.object_id),
473 )
474 .with_shared_input_versions(
475 shared_inputs
476 .iter()
477 .map(|(id, input)| (*id, input.object.version()))
478 .collect(),
479 );
480
481 if let Some(events_digest) = &events_digest {
482 effects_builder = effects_builder.with_events_digest(*events_digest);
483 }
484
485 let effects = effects_builder.build();
486 let lamport_version = effects.lamport_version();
487 let input_objects: Vec<_> = mutated_objects
488 .keys()
489 .chain(
490 shared_inputs
491 .iter()
492 .filter(|(_, i)| i.mutable)
493 .map(|(id, _)| id),
494 )
495 .map(|id| self.live_objects.get(id).unwrap().clone())
496 .chain(deleted_objects)
497 .chain(wrapped_objects.clone())
498 .chain(std::iter::once(
499 self.live_objects.get(&gas.object_id).unwrap().clone(),
500 ))
501 .collect();
502 let output_objects: Vec<_> = created_objects
503 .values()
504 .cloned()
505 .chain(mutated_objects.values().cloned())
506 .chain(
507 shared_inputs
508 .values()
509 .filter(|i| i.mutable)
510 .map(|i| i.object.clone()),
511 )
512 .chain(unwrapped_objects)
513 .chain(std::iter::once(
514 self.live_objects.get(&gas.object_id).cloned().unwrap(),
515 ))
516 .map(|mut o| {
517 o.data
518 .as_opt_mut_struct()
519 .unwrap()
520 .increment_version_to(lamport_version);
521 o
522 })
523 .collect();
524 self.live_objects
525 .extend(output_objects.iter().map(|o| (o.id(), o.clone())));
526 self.wrapped_objects
527 .extend(wrapped_objects.iter().map(|o| (o.id(), o.clone())));
528
529 self.checkpoint_builder
530 .transactions
531 .push(CheckpointTransaction {
532 transaction: tx,
533 effects,
534 events,
535 input_objects,
536 output_objects,
537 });
538 self
539 }
540
541 pub fn advance_epoch(&mut self, safe_mode: bool) -> CheckpointData {
546 let (committee, _) = Committee::new_simple_test_committee();
547 let protocol_config = ProtocolConfig::get_for_max_version_UNSAFE();
548 let tx_kind = EndOfEpochTransactionKind::new_change_epoch(
549 self.checkpoint_builder.epoch + 1,
550 protocol_config.version.as_u64(),
551 Default::default(),
552 Default::default(),
553 Default::default(),
554 Default::default(),
555 Default::default(),
556 Default::default(),
557 );
558
559 let end_of_epoch_tx = TransactionData::new(
563 TransactionKind::EndOfEpoch(vec![tx_kind]),
564 Address::ZERO,
565 random_object_ref(),
566 1,
567 1,
568 )
569 .pipe(|data| SenderSignedData::new(data, vec![]))
570 .pipe(Transaction::new);
571
572 let events = if !safe_mode {
573 let system_epoch_info_event = SystemEpochInfoEventV2 {
574 epoch: self.checkpoint_builder.epoch,
575 protocol_version: protocol_config.version.as_u64(),
576 ..Default::default()
577 };
578 Some(vec![Event {
579 package_id: ObjectId::SYSTEM,
580 module: Identifier::from_static("iota_system_state_inner"),
581 sender: TestCheckpointDataBuilder::derive_address(0),
582 type_: StructTag::new_system_epoch_info_event(),
583 contents: bcs::to_bytes(&system_epoch_info_event).unwrap(),
584 }])
585 } else {
586 None
587 };
588
589 let transaction_events = events.map(TransactionEvents);
590
591 let effects = TransactionEffects::new_empty_v1_for_testing(*end_of_epoch_tx.digest());
592
593 self.checkpoint_builder
595 .transactions
596 .push(CheckpointTransaction {
597 transaction: end_of_epoch_tx,
598 effects,
599 events: transaction_events,
600 input_objects: vec![],
601 output_objects: vec![],
602 });
603
604 let mut checkpoint = self.build_checkpoint();
607 let end_of_epoch_data = EndOfEpochData {
608 next_epoch_committee: committee.committee_members(),
609 next_epoch_protocol_version: protocol_config.version.as_u64(),
610 epoch_commitments: vec![],
611 epoch_supply_change: 0,
613 };
614 checkpoint.checkpoint_summary.end_of_epoch_data = Some(end_of_epoch_data);
615 self.checkpoint_builder.epoch += 1;
616 checkpoint
617 }
618
619 pub fn build_checkpoint(&mut self) -> CheckpointData {
623 assert!(self.checkpoint_builder.next_transaction.is_none());
624 let transactions = std::mem::take(&mut self.checkpoint_builder.transactions);
625 let contents = CheckpointContents::new_with_digests_only_for_tests(
626 transactions
627 .iter()
628 .map(|tx| ExecutionDigests::new(*tx.transaction.digest(), tx.effects.digest())),
629 );
630
631 self.checkpoint_builder.network_total_transactions += transactions.len() as u64;
632
633 let checkpoint_summary = CheckpointSummary::new_with_protocol_config(
634 &ProtocolConfig::get_for_max_version_UNSAFE(),
635 self.checkpoint_builder.epoch,
636 self.checkpoint_builder.checkpoint,
637 self.checkpoint_builder.network_total_transactions,
638 &contents,
639 None,
640 Default::default(),
641 None,
642 0,
643 vec![],
644 );
645
646 let (committee, keys) = Committee::new_simple_test_committee();
647
648 let checkpoint_cert = CertifiedCheckpointSummary::new_from_keypairs_for_testing(
649 checkpoint_summary,
650 &keys,
651 &committee,
652 );
653
654 self.checkpoint_builder.checkpoint += 1;
655 CheckpointData {
656 checkpoint_summary: checkpoint_cert,
657 checkpoint_contents: contents,
658 transactions,
659 }
660 }
661
662 pub fn derive_object_id(object_idx: u64) -> ObjectId {
666 let mut bytes = [0; ObjectId::LENGTH];
669 bytes[0..8].copy_from_slice(&object_idx.to_le_bytes());
670 ObjectId::from_bytes(bytes).unwrap()
671 }
672
673 pub fn derive_address(address_idx: u8) -> Address {
675 dbg_addr(address_idx)
676 }
677
678 fn access_shared_object(mut self, object_idx: u64, mutable: bool) -> Self {
681 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
682 let object_id = Self::derive_object_id(object_idx);
683 let object = self
684 .live_objects
685 .get(&object_id)
686 .cloned()
687 .expect("Accessing a shared object that doesn't exist");
688 tx_builder
689 .shared_inputs
690 .insert(object_id, Shared { mutable, object });
691 self
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use std::str::FromStr;
698
699 use iota_sdk_types::Command;
700
701 use super::*;
702 use crate::{
703 ObjectId,
704 transaction::{TransactionDataAPI, TransactionKindExt},
705 };
706 #[test]
707 fn test_basic_checkpoint_builder() {
708 let checkpoint = TestCheckpointDataBuilder::new(1)
710 .with_epoch(5)
711 .start_transaction(0)
712 .finish_transaction()
713 .build_checkpoint();
714
715 assert_eq!(checkpoint.checkpoint_summary.sequence_number(), 1);
716 assert_eq!(checkpoint.checkpoint_summary.epoch, 5);
717 assert_eq!(checkpoint.transactions.len(), 1);
718 let tx = &checkpoint.transactions[0];
719 assert_eq!(
720 tx.transaction.sender_address(),
721 TestCheckpointDataBuilder::derive_address(0)
722 );
723 assert_eq!(tx.effects.mutated().len(), 1); assert_eq!(tx.effects.deleted().len(), 0);
725 assert_eq!(tx.effects.created().len(), 0);
726 assert_eq!(tx.input_objects.len(), 1);
727 assert_eq!(tx.output_objects.len(), 1);
728 }
729
730 #[test]
731 fn test_multiple_transactions() {
732 let checkpoint = TestCheckpointDataBuilder::new(1)
733 .start_transaction(0)
734 .finish_transaction()
735 .start_transaction(1)
736 .finish_transaction()
737 .start_transaction(2)
738 .finish_transaction()
739 .build_checkpoint();
740
741 assert_eq!(checkpoint.transactions.len(), 3);
742
743 let senders: Vec<_> = checkpoint
746 .transactions
747 .iter()
748 .map(|tx| tx.transaction.transaction().sender())
749 .collect();
750 assert_eq!(
751 senders,
752 vec![
753 TestCheckpointDataBuilder::derive_address(0),
754 TestCheckpointDataBuilder::derive_address(1),
755 TestCheckpointDataBuilder::derive_address(2)
756 ]
757 );
758 }
759
760 #[test]
761 fn test_object_creation() {
762 let checkpoint = TestCheckpointDataBuilder::new(1)
763 .start_transaction(0)
764 .create_owned_object(0)
765 .finish_transaction()
766 .build_checkpoint();
767
768 let tx = &checkpoint.transactions[0];
769 let created_obj_id = TestCheckpointDataBuilder::derive_object_id(0);
770
771 assert!(
773 tx.output_objects
774 .iter()
775 .any(|obj| obj.id() == created_obj_id)
776 );
777
778 assert!(
780 tx.effects
781 .created()
782 .iter()
783 .any(|(object_ref, owner)| object_ref.object_id == created_obj_id
784 && owner.address_or_object().unwrap()
785 == &TestCheckpointDataBuilder::derive_address(0))
786 );
787 }
788
789 #[test]
790 fn test_object_mutation() {
791 let checkpoint = TestCheckpointDataBuilder::new(1)
792 .start_transaction(0)
793 .create_owned_object(0)
794 .finish_transaction()
795 .start_transaction(0)
796 .mutate_owned_object(0)
797 .finish_transaction()
798 .build_checkpoint();
799
800 let tx = &checkpoint.transactions[1];
801 let obj_id = TestCheckpointDataBuilder::derive_object_id(0);
802
803 assert!(tx.input_objects.iter().any(|obj| obj.id() == obj_id));
805 assert!(tx.output_objects.iter().any(|obj| obj.id() == obj_id));
806
807 assert!(
809 tx.effects
810 .mutated()
811 .iter()
812 .any(|(object_ref, _)| object_ref.object_id == obj_id)
813 );
814 }
815
816 #[test]
817 fn test_object_deletion() {
818 let checkpoint = TestCheckpointDataBuilder::new(1)
819 .start_transaction(0)
820 .create_owned_object(0)
821 .finish_transaction()
822 .start_transaction(0)
823 .delete_object(0)
824 .finish_transaction()
825 .build_checkpoint();
826
827 let tx = &checkpoint.transactions[1];
828 let obj_id = TestCheckpointDataBuilder::derive_object_id(0);
829
830 assert!(tx.input_objects.iter().any(|obj| obj.id() == obj_id));
832 assert!(!tx.output_objects.iter().any(|obj| obj.id() == obj_id));
833
834 assert!(
836 tx.effects
837 .deleted()
838 .iter()
839 .any(|object_ref| object_ref.object_id == obj_id)
840 );
841 }
842
843 #[test]
844 fn test_object_wrapping() {
845 let checkpoint = TestCheckpointDataBuilder::new(1)
846 .start_transaction(0)
847 .create_owned_object(0)
848 .finish_transaction()
849 .start_transaction(0)
850 .wrap_object(0)
851 .finish_transaction()
852 .start_transaction(0)
853 .unwrap_object(0)
854 .finish_transaction()
855 .build_checkpoint();
856
857 let tx = &checkpoint.transactions[1];
858 let obj_id = TestCheckpointDataBuilder::derive_object_id(0);
859
860 assert!(tx.input_objects.iter().any(|obj| obj.id() == obj_id));
862 assert!(!tx.output_objects.iter().any(|obj| obj.id() == obj_id));
863
864 assert!(
866 tx.effects
867 .wrapped()
868 .iter()
869 .any(|object_ref| object_ref.object_id == obj_id)
870 );
871
872 let tx = &checkpoint.transactions[2];
873
874 assert!(!tx.input_objects.iter().any(|obj| obj.id() == obj_id));
876 assert!(tx.output_objects.iter().any(|obj| obj.id() == obj_id));
877
878 assert!(
880 tx.effects
881 .unwrapped()
882 .iter()
883 .any(|(object_ref, _owner)| object_ref.object_id == obj_id)
884 );
885 }
886
887 #[test]
888 fn test_object_transfer() {
889 let checkpoint = TestCheckpointDataBuilder::new(1)
890 .start_transaction(0)
891 .create_owned_object(0)
892 .finish_transaction()
893 .start_transaction(1)
894 .transfer_object(0, 1)
895 .finish_transaction()
896 .build_checkpoint();
897
898 let tx = &checkpoint.transactions[1];
899 let obj_id = TestCheckpointDataBuilder::derive_object_id(0);
900
901 assert!(tx.input_objects.iter().any(|obj| obj.id() == obj_id));
903 assert!(tx.output_objects.iter().any(|obj| obj.id() == obj_id));
904
905 assert!(
907 tx.effects
908 .mutated()
909 .iter()
910 .any(|(object_ref, owner)| object_ref.object_id == obj_id
911 && owner.address_or_object().unwrap()
912 == &TestCheckpointDataBuilder::derive_address(1))
913 );
914 }
915
916 #[test]
917 fn test_shared_object() {
918 let checkpoint = TestCheckpointDataBuilder::new(1)
919 .start_transaction(0)
920 .create_shared_object(0)
921 .finish_transaction()
922 .build_checkpoint();
923
924 let tx = &checkpoint.transactions[0];
925 let obj_id = TestCheckpointDataBuilder::derive_object_id(0);
926
927 assert!(
929 tx.output_objects
930 .iter()
931 .any(|obj| obj.id() == obj_id && obj.owner().is_shared())
932 );
933 }
934
935 #[test]
936 fn test_freeze_object() {
937 let checkpoint = TestCheckpointDataBuilder::new(1)
938 .start_transaction(0)
939 .create_owned_object(0)
940 .finish_transaction()
941 .start_transaction(0)
942 .change_object_owner(0, Owner::Immutable)
943 .finish_transaction()
944 .build_checkpoint();
945
946 let tx = &checkpoint.transactions[1];
947 let obj_id = TestCheckpointDataBuilder::derive_object_id(0);
948
949 assert!(
951 tx.output_objects
952 .iter()
953 .any(|obj| obj.id() == obj_id && obj.owner().is_immutable())
954 );
955 }
956
957 #[test]
958 fn test_iota_balance_transfer() {
959 let checkpoint = TestCheckpointDataBuilder::new(1)
960 .start_transaction(0)
961 .create_iota_object(0, 100)
962 .finish_transaction()
963 .start_transaction(1)
964 .transfer_coin_balance(0, 1, 1, 10)
965 .finish_transaction()
966 .build_checkpoint();
967
968 let tx = &checkpoint.transactions[0];
969 let obj_id0 = TestCheckpointDataBuilder::derive_object_id(0);
970
971 assert!(tx.output_objects.iter().any(|obj| obj.id() == obj_id0
974 && obj.is_gas_coin()
975 && obj.data.as_opt_struct().unwrap().get_coin_value_unchecked() == 100));
976
977 let tx = &checkpoint.transactions[1];
978 let obj_id1 = TestCheckpointDataBuilder::derive_object_id(1);
979
980 assert!(tx.output_objects.iter().any(|obj| obj.id() == obj_id0
982 && obj.is_gas_coin()
983 && obj.data.as_opt_struct().unwrap().get_coin_value_unchecked() == 90));
984
985 assert!(tx.output_objects.iter().any(|obj| obj.id() == obj_id1
987 && obj.is_gas_coin()
988 && obj.data.as_opt_struct().unwrap().get_coin_value_unchecked() == 10));
989 }
990
991 #[test]
992 fn test_coin_balance_transfer() {
993 let type_tag = TypeTag::from_str("0x100::a::b").unwrap();
994 let checkpoint = TestCheckpointDataBuilder::new(1)
995 .start_transaction(0)
996 .create_coin_object(0, 0, 100, type_tag.clone())
997 .finish_transaction()
998 .start_transaction(1)
999 .transfer_coin_balance(0, 1, 1, 10)
1000 .finish_transaction()
1001 .build_checkpoint();
1002
1003 let tx = &checkpoint.transactions[1];
1004 let obj_id0 = TestCheckpointDataBuilder::derive_object_id(0);
1005 let obj_id1 = TestCheckpointDataBuilder::derive_object_id(1);
1006
1007 assert!(tx.output_objects.iter().any(|obj| obj.id() == obj_id0
1009 && obj.coin_type_opt() == Some(&type_tag)
1010 && obj.data.as_opt_struct().unwrap().get_coin_value_unchecked() == 90));
1011
1012 assert!(tx.output_objects.iter().any(|obj| obj.id() == obj_id1
1014 && obj.coin_type_opt() == Some(&type_tag)
1015 && obj.data.as_opt_struct().unwrap().get_coin_value_unchecked() == 10));
1016 }
1017
1018 #[test]
1019 fn test_events() {
1020 let checkpoint = TestCheckpointDataBuilder::new(1)
1021 .start_transaction(0)
1022 .with_events(vec![Event {
1023 package_id: ObjectId::ZERO,
1024 module: Identifier::from_static("test"),
1025 sender: TestCheckpointDataBuilder::derive_address(0),
1026 type_: StructTag::new_gas(),
1027 contents: vec![],
1028 }])
1029 .finish_transaction()
1030 .build_checkpoint();
1031 let tx = &checkpoint.transactions[0];
1032
1033 assert!(tx.effects.events_digest().is_some());
1035
1036 assert_eq!(tx.events.as_ref().unwrap().len(), 1);
1038 }
1039
1040 #[test]
1041 fn test_move_call() {
1042 let checkpoint = TestCheckpointDataBuilder::new(1)
1043 .start_transaction(0)
1044 .add_move_call(ObjectId::ZERO, "test", "test")
1045 .finish_transaction()
1046 .build_checkpoint();
1047 let tx = &checkpoint.transactions[0];
1048
1049 assert!(
1051 tx.transaction
1052 .transaction()
1053 .kind()
1054 .iter_commands()
1055 .any(|cmd| {
1056 cmd == &Command::new_move_call(
1057 ObjectId::ZERO,
1058 Identifier::new_unchecked("test"),
1059 Identifier::new_unchecked("test"),
1060 vec![],
1061 vec![],
1062 )
1063 })
1064 )
1065 }
1066
1067 #[test]
1068 fn test_multiple_checkpoints() {
1069 let mut builder = TestCheckpointDataBuilder::new(1)
1070 .start_transaction(0)
1071 .create_owned_object(0)
1072 .finish_transaction();
1073 let checkpoint1 = builder.build_checkpoint();
1074 builder = builder
1075 .start_transaction(0)
1076 .mutate_owned_object(0)
1077 .finish_transaction();
1078 let checkpoint2 = builder.build_checkpoint();
1079 builder = builder
1080 .start_transaction(0)
1081 .delete_object(0)
1082 .finish_transaction();
1083 let checkpoint3 = builder.build_checkpoint();
1084
1085 assert_eq!(checkpoint1.checkpoint_summary.sequence_number, 1);
1087 assert_eq!(checkpoint2.checkpoint_summary.sequence_number, 2);
1088 assert_eq!(checkpoint3.checkpoint_summary.sequence_number, 3);
1089 }
1090}