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