1use move_core_types::{
5 ident_str,
6 language_storage::{StructTag, TypeTag},
7};
8use std::collections::{BTreeMap, BTreeSet, HashMap};
9use sui_protocol_config::{ProtocolConfig, ProtocolVersion};
10use sui_sdk_types::CheckpointTimestamp;
11
12use crate::messages_checkpoint::CheckpointCommitment;
13use crate::{
14 SUI_SYSTEM_ADDRESS,
15 base_types::{
16 ExecutionDigests, ObjectID, ObjectRef, SequenceNumber, SuiAddress, dbg_addr,
17 random_object_ref,
18 },
19 committee::Committee,
20 digests::TransactionDigest,
21 effects::{
22 self, TestEffectsBuilder, TransactionEffects, TransactionEffectsAPI, TransactionEvents,
23 },
24 event::{Event, SystemEpochInfoEvent},
25 execution_status::ExecutionStatus,
26 full_checkpoint_content::{Checkpoint, CheckpointTransaction, ExecutedTransaction, ObjectSet},
27 gas::GasCostSummary,
28 gas_coin::GAS,
29 message_envelope::Message,
30 messages_checkpoint::{
31 CertifiedCheckpointSummary, CheckpointContents, CheckpointSummary, EndOfEpochData,
32 },
33 object::{GAS_VALUE_FOR_TESTING, MoveObject, Object, Owner},
34 programmable_transaction_builder::ProgrammableTransactionBuilder,
35 transaction::{
36 EndOfEpochTransactionKind, ObjectArg, SenderSignedData, SharedObjectMutability,
37 Transaction, TransactionData, TransactionKind,
38 },
39};
40
41pub struct TestCheckpointBuilder {
53 live_objects: HashMap<ObjectID, Object>,
55 wrapped_objects: HashMap<ObjectID, Object>,
57 gas_map: HashMap<SuiAddress, ObjectID>,
61
62 checkpoint_builder: CheckpointBuilder,
65}
66
67struct CheckpointBuilder {
68 checkpoint: u64,
70 epoch: u64,
72 network_total_transactions: u64,
74 timestamp_ms: CheckpointTimestamp,
76 transactions: Vec<CheckpointTransaction>,
78 next_transaction: Option<TransactionBuilder>,
80}
81
82struct TransactionBuilder {
83 sender_idx: u8,
84 gas: ObjectRef,
85 move_calls: Vec<(ObjectID, &'static str, &'static str)>,
86 created_objects: BTreeMap<ObjectID, Object>,
87 mutated_objects: BTreeMap<ObjectID, Object>,
88 unwrapped_objects: BTreeSet<ObjectID>,
89 wrapped_objects: BTreeSet<ObjectID>,
90 deleted_objects: BTreeSet<ObjectID>,
91 frozen_objects: BTreeSet<ObjectRef>,
92 shared_inputs: BTreeMap<ObjectID, Shared>,
93 events: Option<Vec<Event>>,
94}
95
96struct Shared {
97 mutable: bool,
98 object: Object,
99}
100
101impl TransactionBuilder {
102 pub fn new(sender_idx: u8, gas: ObjectRef) -> Self {
103 Self {
104 sender_idx,
105 gas,
106 move_calls: vec![],
107 created_objects: BTreeMap::new(),
108 mutated_objects: BTreeMap::new(),
109 unwrapped_objects: BTreeSet::new(),
110 wrapped_objects: BTreeSet::new(),
111 deleted_objects: BTreeSet::new(),
112 frozen_objects: BTreeSet::new(),
113 shared_inputs: BTreeMap::new(),
114 events: None,
115 }
116 }
117}
118
119pub struct AdvanceEpochConfig {
120 pub safe_mode: bool,
121 pub protocol_version: ProtocolVersion,
122 pub output_objects: Vec<Object>,
123 pub epoch_commitments: Vec<CheckpointCommitment>,
124}
125
126impl Default for AdvanceEpochConfig {
127 fn default() -> Self {
128 Self {
129 safe_mode: false,
130 protocol_version: ProtocolVersion::MAX,
131 output_objects: vec![],
132 epoch_commitments: vec![],
133 }
134 }
135}
136
137impl TestCheckpointBuilder {
138 pub fn new(checkpoint: u64) -> Self {
139 Self {
140 live_objects: HashMap::new(),
141 wrapped_objects: HashMap::new(),
142 gas_map: HashMap::new(),
143 checkpoint_builder: CheckpointBuilder {
144 checkpoint,
145 epoch: 0,
146 network_total_transactions: 0,
147 timestamp_ms: 0,
148 transactions: vec![],
149 next_transaction: None,
150 },
151 }
152 }
153
154 pub fn with_epoch(mut self, epoch: u64) -> Self {
156 self.checkpoint_builder.epoch = epoch;
157 self
158 }
159
160 pub fn with_network_total_transactions(mut self, network_total_transactions: u64) -> Self {
162 self.checkpoint_builder.network_total_transactions = network_total_transactions;
163 self
164 }
165
166 pub fn with_timestamp_ms(mut self, timestamp_ms: CheckpointTimestamp) -> Self {
168 self.checkpoint_builder.timestamp_ms = timestamp_ms;
169 self
170 }
171
172 pub fn start_transaction(mut self, sender_idx: u8) -> Self {
178 assert!(self.checkpoint_builder.next_transaction.is_none());
179 let sender = Self::derive_address(sender_idx);
180 let gas_id = self.gas_map.entry(sender).or_insert_with(|| {
181 let gas = Object::with_owner_for_testing(sender);
182 let id = gas.id();
183 self.live_objects.insert(id, gas);
184 id
185 });
186 let gas_ref = self
187 .live_objects
188 .get(gas_id)
189 .cloned()
190 .unwrap()
191 .compute_object_reference();
192 self.checkpoint_builder.next_transaction =
193 Some(TransactionBuilder::new(sender_idx, gas_ref));
194 self
195 }
196
197 pub fn create_owned_object(self, object_idx: u64) -> Self {
202 self.create_sui_object(object_idx, GAS_VALUE_FOR_TESTING)
203 }
204
205 pub fn create_shared_object(self, object_idx: u64) -> Self {
210 self.create_coin_object_with_owner(
211 object_idx,
212 Owner::Shared {
213 initial_shared_version: SequenceNumber::MIN,
214 },
215 GAS_VALUE_FOR_TESTING,
216 GAS::type_tag(),
217 )
218 }
219
220 pub fn create_sui_object(self, object_idx: u64, balance: u64) -> Self {
224 let sender_idx = self
225 .checkpoint_builder
226 .next_transaction
227 .as_ref()
228 .unwrap()
229 .sender_idx;
230 self.create_coin_object(object_idx, sender_idx, balance, GAS::type_tag())
231 }
232
233 pub fn create_coin_object(
239 self,
240 object_idx: u64,
241 owner_idx: u8,
242 balance: u64,
243 coin_type: TypeTag,
244 ) -> Self {
245 self.create_coin_object_with_owner(
246 object_idx,
247 Owner::AddressOwner(Self::derive_address(owner_idx)),
248 balance,
249 coin_type,
250 )
251 }
252
253 fn create_coin_object_with_owner(
254 mut self,
255 object_idx: u64,
256 owner: Owner,
257 balance: u64,
258 coin_type: TypeTag,
259 ) -> Self {
260 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
261 let object_id = Self::derive_object_id(object_idx);
262 assert!(
263 !self.live_objects.contains_key(&object_id),
264 "Object already exists: {}. Please use a different object index.",
265 object_id
266 );
267 let move_object = MoveObject::new_coin(
268 coin_type,
269 SequenceNumber::MIN,
271 object_id,
272 balance,
273 );
274 let object = Object::new_move(move_object, owner, TransactionDigest::ZERO);
275 tx_builder.created_objects.insert(object_id, object);
276 self
277 }
278
279 pub fn mutate_owned_object(mut self, object_idx: u64) -> Self {
282 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
283 let object_id = Self::derive_object_id(object_idx);
284 let object = self
285 .live_objects
286 .get(&object_id)
287 .cloned()
288 .expect("Mutating an object that doesn't exist");
289 tx_builder.mutated_objects.insert(object_id, object);
290 self
291 }
292
293 pub fn mutate_shared_object(self, object_idx: u64) -> Self {
295 self.access_shared_object(object_idx, true)
296 }
297
298 pub fn transfer_object(self, object_idx: u64, recipient_idx: u8) -> Self {
302 self.change_object_owner(
303 object_idx,
304 Owner::AddressOwner(Self::derive_address(recipient_idx)),
305 )
306 }
307
308 pub fn change_object_owner(mut self, object_idx: u64, owner: Owner) -> Self {
312 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
313 let object_id = Self::derive_object_id(object_idx);
314 let mut object = self.live_objects.get(&object_id).unwrap().clone();
315 object.owner = owner;
316 tx_builder.mutated_objects.insert(object_id, object);
317 self
318 }
319
320 pub fn transfer_coin_balance(
326 mut self,
327 object_idx: u64,
328 new_object_idx: u64,
329 recipient_idx: u8,
330 amount: u64,
331 ) -> Self {
332 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
333 let object_id = Self::derive_object_id(object_idx);
334 let mut object = self
335 .live_objects
336 .get(&object_id)
337 .cloned()
338 .expect("Mutating an object that does not exist");
339 let coin_type = object.coin_type_maybe().unwrap();
340 let move_object = object.data.try_as_move_mut().unwrap();
342 let old_balance = move_object.get_coin_value_unsafe();
343 let new_balance = old_balance - amount;
344 move_object.set_coin_value_unsafe(new_balance);
345 tx_builder.mutated_objects.insert(object_id, object);
346
347 self.create_coin_object(new_object_idx, recipient_idx, amount, coin_type)
349 }
350
351 pub fn wrap_object(mut self, object_idx: u64) -> Self {
354 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
355 let object_id = Self::derive_object_id(object_idx);
356 assert!(self.live_objects.contains_key(&object_id));
357 tx_builder.wrapped_objects.insert(object_id);
358 self
359 }
360
361 pub fn unwrap_object(mut self, object_idx: u64) -> Self {
364 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
365 let object_id = Self::derive_object_id(object_idx);
366 assert!(self.wrapped_objects.contains_key(&object_id));
367 tx_builder.unwrapped_objects.insert(object_id);
368 self
369 }
370
371 pub fn delete_object(mut self, object_idx: u64) -> Self {
374 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
375 let object_id = Self::derive_object_id(object_idx);
376 assert!(self.live_objects.contains_key(&object_id));
377 tx_builder.deleted_objects.insert(object_id);
378 self
379 }
380
381 pub fn read_frozen_object(mut self, object_id: u64) -> Self {
385 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
386 let object_id = Self::derive_object_id(object_id);
387
388 let Some(obj) = self.live_objects.get(&object_id) else {
389 panic!("Frozen object not found");
390 };
391
392 assert!(obj.owner().is_immutable());
393 tx_builder
394 .frozen_objects
395 .insert(obj.compute_object_reference());
396 self
397 }
398
399 pub fn read_shared_object(self, object_idx: u64) -> Self {
401 self.access_shared_object(object_idx, false)
402 }
403
404 pub fn with_events(mut self, events: Vec<Event>) -> Self {
407 self.checkpoint_builder
408 .next_transaction
409 .as_mut()
410 .unwrap()
411 .events = Some(events);
412 self
413 }
414
415 pub fn add_move_call(
420 mut self,
421 package: ObjectID,
422 module: &'static str,
423 function: &'static str,
424 ) -> Self {
425 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
426 tx_builder.move_calls.push((package, module, function));
427 self
428 }
429
430 pub fn finish_transaction(mut self) -> Self {
433 let TransactionBuilder {
434 sender_idx,
435 gas,
436 move_calls,
437 created_objects,
438 mutated_objects,
439 unwrapped_objects,
440 wrapped_objects,
441 deleted_objects,
442 frozen_objects,
443 shared_inputs,
444 events,
445 } = self.checkpoint_builder.next_transaction.take().unwrap();
446
447 let sender = Self::derive_address(sender_idx);
448 let events = events.map(|events| TransactionEvents { data: events });
449 let events_digest = events.as_ref().map(|events| events.digest());
450
451 let mut pt_builder = ProgrammableTransactionBuilder::new();
452 for (package, module, function) in move_calls {
453 pt_builder
454 .move_call(
455 package,
456 ident_str!(module).to_owned(),
457 ident_str!(function).to_owned(),
458 vec![],
459 vec![],
460 )
461 .unwrap();
462 }
463
464 for &object_ref in &frozen_objects {
465 pt_builder
466 .obj(ObjectArg::ImmOrOwnedObject(object_ref))
467 .expect("Failed to add frozen object input");
468 }
469
470 for (id, input) in &shared_inputs {
471 let &Owner::Shared {
472 initial_shared_version,
473 } = input.object.owner()
474 else {
475 panic!("Accessing a non-shared object as shared");
476 };
477
478 pt_builder
479 .obj(ObjectArg::SharedObject {
480 id: *id,
481 initial_shared_version,
482 mutability: if input.mutable {
483 SharedObjectMutability::Mutable
484 } else {
485 SharedObjectMutability::Immutable
486 },
487 })
488 .expect("Failed to add shared object input");
489 }
490
491 let pt = pt_builder.finish();
492 let tx_data = TransactionData::new(
493 TransactionKind::ProgrammableTransaction(pt),
494 sender,
495 gas,
496 1,
497 1,
498 );
499
500 let tx = Transaction::new(SenderSignedData::new(tx_data, vec![]));
501
502 let wrapped_objects: Vec<_> = wrapped_objects
503 .into_iter()
504 .map(|id| self.live_objects.remove(&id).unwrap())
505 .collect();
506 let deleted_objects: Vec<_> = deleted_objects
507 .into_iter()
508 .map(|id| self.live_objects.remove(&id).unwrap())
509 .collect();
510 let unwrapped_objects: Vec<_> = unwrapped_objects
511 .into_iter()
512 .map(|id| self.wrapped_objects.remove(&id).unwrap())
513 .collect();
514
515 let mut effects_builder = TestEffectsBuilder::new(tx.data())
516 .with_created_objects(
517 created_objects
518 .iter()
519 .map(|(id, o)| (*id, o.owner().clone())),
520 )
521 .with_mutated_objects(
522 mutated_objects
523 .iter()
524 .map(|(id, o)| (*id, o.version(), o.owner().clone())),
525 )
526 .with_wrapped_objects(wrapped_objects.iter().map(|o| (o.id(), o.version())))
527 .with_unwrapped_objects(
528 unwrapped_objects
529 .iter()
530 .map(|o| (o.id(), o.owner().clone())),
531 )
532 .with_deleted_objects(deleted_objects.iter().map(|o| (o.id(), o.version())))
533 .with_frozen_objects(frozen_objects.into_iter().map(|(id, _, _)| id))
534 .with_shared_input_versions(
535 shared_inputs
536 .iter()
537 .map(|(id, input)| (*id, input.object.version()))
538 .collect(),
539 );
540
541 if let Some(events_digest) = &events_digest {
542 effects_builder = effects_builder.with_events_digest(*events_digest);
543 }
544
545 let effects = effects_builder.build();
546 let lamport_version = effects.lamport_version();
547 let input_objects: Vec<_> = mutated_objects
548 .keys()
549 .chain(
550 shared_inputs
551 .iter()
552 .filter(|(_, i)| i.mutable)
553 .map(|(id, _)| id),
554 )
555 .map(|id| self.live_objects.get(id).unwrap().clone())
556 .chain(deleted_objects.clone())
557 .chain(wrapped_objects.clone())
558 .chain(std::iter::once(
559 self.live_objects.get(&gas.0).unwrap().clone(),
560 ))
561 .collect();
562 let output_objects: Vec<_> = created_objects
563 .values()
564 .cloned()
565 .chain(mutated_objects.values().cloned())
566 .chain(
567 shared_inputs
568 .values()
569 .filter(|i| i.mutable)
570 .map(|i| i.object.clone()),
571 )
572 .chain(unwrapped_objects.clone())
573 .chain(std::iter::once(
574 self.live_objects.get(&gas.0).cloned().unwrap(),
575 ))
576 .map(|mut o| {
577 o.data
578 .try_as_move_mut()
579 .unwrap()
580 .increment_version_to(lamport_version);
581 o
582 })
583 .collect();
584 self.live_objects
585 .extend(output_objects.iter().map(|o| (o.id(), o.clone())));
586 self.wrapped_objects
587 .extend(wrapped_objects.iter().map(|o| (o.id(), o.clone())));
588
589 self.checkpoint_builder
590 .transactions
591 .push(CheckpointTransaction {
592 transaction: tx,
593 effects,
594 events,
595 input_objects,
596 output_objects,
597 });
598 self
599 }
600
601 pub fn build_checkpoint(&mut self) -> Checkpoint {
604 assert!(self.checkpoint_builder.next_transaction.is_none());
605 let transactions = std::mem::take(&mut self.checkpoint_builder.transactions);
606 let contents = CheckpointContents::new_with_digests_only_for_tests(
607 transactions
608 .iter()
609 .map(|tx| ExecutionDigests::new(*tx.transaction.digest(), tx.effects.digest())),
610 );
611 self.checkpoint_builder.network_total_transactions += transactions.len() as u64;
612 let checkpoint_summary = CheckpointSummary::new(
613 &ProtocolConfig::get_for_max_version_UNSAFE(),
614 self.checkpoint_builder.epoch,
615 self.checkpoint_builder.checkpoint,
616 self.checkpoint_builder.network_total_transactions,
617 &contents,
618 None,
619 Default::default(),
620 None,
621 self.checkpoint_builder.timestamp_ms,
622 vec![],
623 Vec::new(),
624 );
625 let (committee, keys) = Committee::new_simple_test_committee();
626 let checkpoint_cert = CertifiedCheckpointSummary::new_from_keypairs_for_testing(
627 checkpoint_summary,
628 &keys,
629 &committee,
630 );
631 self.checkpoint_builder.checkpoint += 1;
632
633 let mut object_set = ObjectSet::default();
635 let executed_transactions = transactions
636 .into_iter()
637 .map(|tx| {
638 for o in tx.input_objects.into_iter().chain(tx.output_objects) {
640 object_set.insert(o);
641 }
642
643 let sender_signed = tx.transaction.into_data().into_inner();
645
646 ExecutedTransaction {
647 transaction: sender_signed.intent_message.value,
648 signatures: sender_signed.tx_signatures,
649 effects: tx.effects,
650 events: tx.events,
651 unchanged_loaded_runtime_objects: Vec::new(),
652 }
653 })
654 .collect();
655
656 Checkpoint {
657 summary: checkpoint_cert,
658 contents,
659 transactions: executed_transactions,
660 object_set,
661 }
662 }
663
664 pub fn advance_epoch(
670 &mut self,
671 AdvanceEpochConfig {
672 safe_mode,
673 protocol_version,
674 output_objects,
675 epoch_commitments,
676 }: AdvanceEpochConfig,
677 ) -> Checkpoint {
678 let (committee, _) = Committee::new_simple_test_committee();
679 let tx_kind = EndOfEpochTransactionKind::new_change_epoch(
680 self.checkpoint_builder.epoch + 1,
681 protocol_version,
682 Default::default(),
683 Default::default(),
684 Default::default(),
685 Default::default(),
686 Default::default(),
687 Default::default(),
688 );
689 let end_of_epoch_tx_data = TransactionData::new(
690 TransactionKind::EndOfEpochTransaction(vec![tx_kind]),
691 SuiAddress::default(),
692 random_object_ref(),
693 1,
694 1,
695 );
696 let end_of_epoch_tx_signed = SenderSignedData::new(end_of_epoch_tx_data, vec![]);
697 let end_of_epoch_tx = Transaction::new(end_of_epoch_tx_signed.clone());
698
699 let events = if !safe_mode {
700 let system_epoch_info_event = SystemEpochInfoEvent {
701 epoch: self.checkpoint_builder.epoch,
702 protocol_version: protocol_version.as_u64(),
703 ..Default::default()
704 };
705 let struct_tag = StructTag {
706 address: SUI_SYSTEM_ADDRESS,
707 module: ident_str!("sui_system_state_inner").to_owned(),
708 name: ident_str!("SystemEpochInfoEvent").to_owned(),
709 type_params: vec![],
710 };
711 Some(vec![Event::new(
712 &SUI_SYSTEM_ADDRESS,
713 ident_str!("sui_system_state_inner"),
714 TestCheckpointBuilder::derive_address(0),
715 struct_tag,
716 bcs::to_bytes(&system_epoch_info_event).unwrap(),
717 )])
718 } else {
719 None
720 };
721
722 let transaction_events = events.map(|events| TransactionEvents { data: events });
723 let events_digest = transaction_events.as_ref().map(|events| events.digest());
724
725 let changed_objects = output_objects
726 .iter()
727 .map(|obj| {
728 (
729 obj.id(),
730 effects::EffectsObjectChange {
731 input_state: effects::ObjectIn::NotExist,
732 output_state: effects::ObjectOut::ObjectWrite((
733 obj.digest(),
734 obj.owner().clone(),
735 )),
736 id_operation: effects::IDOperation::Created,
737 },
738 )
739 })
740 .collect();
741
742 let lamport_version = SequenceNumber::from_u64(1);
743
744 let output_objects: Vec<Object> = output_objects
745 .into_iter()
746 .map(|mut obj| {
747 if let Some(move_obj) = obj.data.try_as_move_mut() {
748 move_obj.increment_version_to(lamport_version);
749 }
750 obj
751 })
752 .collect();
753
754 let effects = TransactionEffects::new_from_execution_v2(
755 ExecutionStatus::Success,
756 self.checkpoint_builder.epoch,
757 GasCostSummary::default(),
758 vec![],
759 BTreeSet::new(),
760 end_of_epoch_tx_signed.digest(),
761 lamport_version,
762 changed_objects,
763 None,
764 events_digest,
765 vec![],
766 );
767 self.checkpoint_builder
768 .transactions
769 .push(CheckpointTransaction {
770 transaction: end_of_epoch_tx,
771 effects,
772 events: transaction_events,
773 input_objects: vec![],
774 output_objects,
775 });
776 let mut checkpoint = self.build_checkpoint();
777 let end_of_epoch_data = EndOfEpochData {
778 next_epoch_committee: committee.voting_rights.clone(),
779 next_epoch_protocol_version: protocol_version,
780 epoch_commitments,
781 };
782 checkpoint.summary.end_of_epoch_data = Some(end_of_epoch_data);
783 self.checkpoint_builder.epoch += 1;
784 checkpoint
785 }
786
787 pub fn derive_object_id(object_idx: u64) -> ObjectID {
790 let mut bytes = [0; ObjectID::LENGTH];
792 bytes[0..8].copy_from_slice(&object_idx.to_le_bytes());
793 ObjectID::from_bytes(bytes).unwrap()
794 }
795
796 pub fn derive_address(address_idx: u8) -> SuiAddress {
798 dbg_addr(address_idx)
799 }
800
801 fn access_shared_object(mut self, object_idx: u64, mutability: bool) -> Self {
804 let tx_builder = self.checkpoint_builder.next_transaction.as_mut().unwrap();
805 let object_id = Self::derive_object_id(object_idx);
806 let object = self
807 .live_objects
808 .get(&object_id)
809 .cloned()
810 .expect("Accessing a shared object that doesn't exist");
811 tx_builder.shared_inputs.insert(
812 object_id,
813 Shared {
814 mutable: mutability,
815 object,
816 },
817 );
818 self
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use std::str::FromStr;
825
826 use move_core_types::ident_str;
827
828 use crate::transaction::{Command, ProgrammableMoveCall, TransactionDataAPI};
829
830 use super::*;
831 #[test]
832 fn test_basic_checkpoint_builder() {
833 let checkpoint = TestCheckpointBuilder::new(1)
835 .with_epoch(5)
836 .start_transaction(0)
837 .finish_transaction()
838 .build_checkpoint();
839
840 assert_eq!(*checkpoint.summary.sequence_number(), 1);
841 assert_eq!(checkpoint.summary.epoch, 5);
842 assert_eq!(checkpoint.transactions.len(), 1);
843 let tx = &checkpoint.transactions[0];
844 assert_eq!(
845 tx.transaction.sender(),
846 TestCheckpointBuilder::derive_address(0)
847 );
848 assert_eq!(tx.effects.mutated().len(), 1); assert_eq!(tx.effects.deleted().len(), 0);
850 assert_eq!(tx.effects.created().len(), 0);
851 assert_eq!(checkpoint.object_set.iter().count(), 2);
853 }
854
855 #[test]
856 fn test_multiple_transactions() {
857 let checkpoint = TestCheckpointBuilder::new(1)
858 .start_transaction(0)
859 .finish_transaction()
860 .start_transaction(1)
861 .finish_transaction()
862 .start_transaction(2)
863 .finish_transaction()
864 .build_checkpoint();
865
866 assert_eq!(checkpoint.transactions.len(), 3);
867
868 let senders: Vec<_> = checkpoint
870 .transactions
871 .iter()
872 .map(|tx| tx.transaction.sender())
873 .collect();
874 assert_eq!(
875 senders,
876 vec![
877 TestCheckpointBuilder::derive_address(0),
878 TestCheckpointBuilder::derive_address(1),
879 TestCheckpointBuilder::derive_address(2)
880 ]
881 );
882 }
883
884 #[test]
885 fn test_object_creation() {
886 let checkpoint = TestCheckpointBuilder::new(1)
887 .start_transaction(0)
888 .create_owned_object(0)
889 .finish_transaction()
890 .build_checkpoint();
891
892 let tx = &checkpoint.transactions[0];
893 let created_obj_id = TestCheckpointBuilder::derive_object_id(0);
894
895 assert!(
897 checkpoint
898 .object_set
899 .iter()
900 .any(|obj| obj.id() == created_obj_id)
901 );
902
903 assert!(
905 tx.effects
906 .created()
907 .iter()
908 .any(|((id, ..), owner)| *id == created_obj_id
909 && owner.get_owner_address().unwrap()
910 == TestCheckpointBuilder::derive_address(0))
911 );
912 }
913
914 #[test]
915 fn test_object_mutation() {
916 let checkpoint = TestCheckpointBuilder::new(1)
917 .start_transaction(0)
918 .create_owned_object(0)
919 .finish_transaction()
920 .start_transaction(0)
921 .mutate_owned_object(0)
922 .finish_transaction()
923 .build_checkpoint();
924
925 let obj_id = TestCheckpointBuilder::derive_object_id(0);
926
927 assert!(checkpoint.object_set.iter().any(|obj| obj.id() == obj_id));
929
930 let tx = &checkpoint.transactions[1];
932 assert!(
933 tx.effects
934 .mutated()
935 .iter()
936 .any(|((id, ..), _)| *id == obj_id)
937 );
938 }
939
940 #[test]
941 fn test_object_deletion() {
942 let checkpoint = TestCheckpointBuilder::new(1)
943 .start_transaction(0)
944 .create_owned_object(0)
945 .finish_transaction()
946 .start_transaction(0)
947 .delete_object(0)
948 .finish_transaction()
949 .build_checkpoint();
950
951 let obj_id = TestCheckpointBuilder::derive_object_id(0);
952
953 assert!(checkpoint.object_set.iter().any(|obj| obj.id() == obj_id));
956
957 let tx = &checkpoint.transactions[1];
959 assert!(tx.effects.deleted().iter().any(|(id, ..)| *id == obj_id));
960 }
961
962 #[test]
963 fn test_object_wrapping() {
964 let checkpoint = TestCheckpointBuilder::new(1)
965 .start_transaction(0)
966 .create_owned_object(0)
967 .finish_transaction()
968 .start_transaction(0)
969 .wrap_object(0)
970 .finish_transaction()
971 .start_transaction(0)
972 .unwrap_object(0)
973 .finish_transaction()
974 .build_checkpoint();
975
976 let obj_id = TestCheckpointBuilder::derive_object_id(0);
977
978 assert!(checkpoint.object_set.iter().any(|obj| obj.id() == obj_id));
980
981 let tx = &checkpoint.transactions[1];
983 assert!(tx.effects.wrapped().iter().any(|(id, ..)| *id == obj_id));
984
985 let tx = &checkpoint.transactions[2];
987 assert!(
988 tx.effects
989 .unwrapped()
990 .iter()
991 .any(|((id, ..), _)| *id == obj_id)
992 );
993 }
994
995 #[test]
996 fn test_object_transfer() {
997 let checkpoint = TestCheckpointBuilder::new(1)
998 .start_transaction(0)
999 .create_owned_object(0)
1000 .finish_transaction()
1001 .start_transaction(1)
1002 .transfer_object(0, 1)
1003 .finish_transaction()
1004 .build_checkpoint();
1005
1006 let obj_id = TestCheckpointBuilder::derive_object_id(0);
1007
1008 assert!(checkpoint.object_set.iter().any(|obj| obj.id() == obj_id));
1010
1011 let tx = &checkpoint.transactions[1];
1013 assert!(
1014 tx.effects
1015 .mutated()
1016 .iter()
1017 .any(|((id, ..), owner)| *id == obj_id
1018 && owner.get_owner_address().unwrap()
1019 == TestCheckpointBuilder::derive_address(1))
1020 );
1021 }
1022
1023 #[test]
1024 fn test_shared_object() {
1025 let checkpoint = TestCheckpointBuilder::new(1)
1026 .start_transaction(0)
1027 .create_shared_object(0)
1028 .finish_transaction()
1029 .build_checkpoint();
1030
1031 let obj_id = TestCheckpointBuilder::derive_object_id(0);
1032
1033 assert!(
1035 checkpoint
1036 .object_set
1037 .iter()
1038 .any(|obj| obj.id() == obj_id && obj.owner().is_shared())
1039 );
1040 }
1041
1042 #[test]
1043 fn test_freeze_object() {
1044 let checkpoint = TestCheckpointBuilder::new(1)
1045 .start_transaction(0)
1046 .create_owned_object(0)
1047 .finish_transaction()
1048 .start_transaction(0)
1049 .change_object_owner(0, Owner::Immutable)
1050 .finish_transaction()
1051 .build_checkpoint();
1052
1053 let obj_id = TestCheckpointBuilder::derive_object_id(0);
1054
1055 assert!(
1057 checkpoint
1058 .object_set
1059 .iter()
1060 .any(|obj| obj.id() == obj_id && obj.owner().is_immutable())
1061 );
1062 }
1063
1064 #[test]
1065 fn test_sui_balance_transfer() {
1066 let checkpoint = TestCheckpointBuilder::new(1)
1067 .start_transaction(0)
1068 .create_sui_object(0, 100)
1069 .finish_transaction()
1070 .start_transaction(1)
1071 .transfer_coin_balance(0, 1, 1, 10)
1072 .finish_transaction()
1073 .build_checkpoint();
1074
1075 let obj_id0 = TestCheckpointBuilder::derive_object_id(0);
1076 let obj_id1 = TestCheckpointBuilder::derive_object_id(1);
1077
1078 assert!(checkpoint.object_set.iter().any(|obj| obj.id() == obj_id0
1080 && obj.is_gas_coin()
1081 && obj.data.try_as_move().unwrap().get_coin_value_unsafe() == 90));
1082
1083 assert!(checkpoint.object_set.iter().any(|obj| obj.id() == obj_id1
1084 && obj.is_gas_coin()
1085 && obj.data.try_as_move().unwrap().get_coin_value_unsafe() == 10));
1086 }
1087
1088 #[test]
1089 fn test_coin_balance_transfer() {
1090 let type_tag = TypeTag::from_str("0x100::a::b").unwrap();
1091 let checkpoint = TestCheckpointBuilder::new(1)
1092 .start_transaction(0)
1093 .create_coin_object(0, 0, 100, type_tag.clone())
1094 .finish_transaction()
1095 .start_transaction(1)
1096 .transfer_coin_balance(0, 1, 1, 10)
1097 .finish_transaction()
1098 .build_checkpoint();
1099
1100 let obj_id0 = TestCheckpointBuilder::derive_object_id(0);
1101 let obj_id1 = TestCheckpointBuilder::derive_object_id(1);
1102
1103 assert!(checkpoint.object_set.iter().any(|obj| obj.id() == obj_id0
1105 && obj.coin_type_maybe().unwrap() == type_tag
1106 && obj.data.try_as_move().unwrap().get_coin_value_unsafe() == 90));
1107
1108 assert!(checkpoint.object_set.iter().any(|obj| obj.id() == obj_id1
1109 && obj.coin_type_maybe().unwrap() == type_tag
1110 && obj.data.try_as_move().unwrap().get_coin_value_unsafe() == 10));
1111 }
1112
1113 #[test]
1114 fn test_events() {
1115 let checkpoint = TestCheckpointBuilder::new(1)
1116 .start_transaction(0)
1117 .with_events(vec![Event::new(
1118 &ObjectID::ZERO,
1119 ident_str!("test"),
1120 TestCheckpointBuilder::derive_address(0),
1121 GAS::type_(),
1122 vec![],
1123 )])
1124 .finish_transaction()
1125 .build_checkpoint();
1126 let tx = &checkpoint.transactions[0];
1127
1128 assert!(tx.effects.events_digest().is_some());
1130
1131 assert_eq!(tx.events.as_ref().unwrap().data.len(), 1);
1133 }
1134
1135 #[test]
1136 fn test_move_call() {
1137 let checkpoint = TestCheckpointBuilder::new(1)
1138 .start_transaction(0)
1139 .add_move_call(ObjectID::ZERO, "test", "test")
1140 .finish_transaction()
1141 .build_checkpoint();
1142 let tx = &checkpoint.transactions[0];
1143
1144 assert!(tx.transaction.kind().iter_commands().any(|cmd| {
1146 cmd == &Command::MoveCall(Box::new(ProgrammableMoveCall {
1147 package: ObjectID::ZERO,
1148 module: "test".to_string(),
1149 function: "test".to_string(),
1150 type_arguments: vec![],
1151 arguments: vec![],
1152 }))
1153 }));
1154 }
1155
1156 #[test]
1157 fn test_multiple_checkpoints() {
1158 let mut builder = TestCheckpointBuilder::new(1)
1159 .start_transaction(0)
1160 .create_owned_object(0)
1161 .finish_transaction();
1162 let checkpoint1 = builder.build_checkpoint();
1163 builder = builder
1164 .start_transaction(0)
1165 .mutate_owned_object(0)
1166 .finish_transaction();
1167 let checkpoint2 = builder.build_checkpoint();
1168 builder = builder
1169 .start_transaction(0)
1170 .delete_object(0)
1171 .finish_transaction();
1172 let checkpoint3 = builder.build_checkpoint();
1173
1174 assert_eq!(checkpoint1.summary.sequence_number, 1);
1176 assert_eq!(checkpoint2.summary.sequence_number, 2);
1177 assert_eq!(checkpoint3.summary.sequence_number, 3);
1178 }
1179}