1mod epoch_state;
16pub mod state_reader;
17pub mod store;
18pub mod transaction_executor;
19
20use std::{
21 num::NonZeroUsize,
22 path::PathBuf,
23 sync::{Arc, RwLock},
24};
25
26use anyhow::{Result, anyhow};
27use fastcrypto::traits::Signer;
28use iota_config::{
29 genesis, transaction_deny_config::TransactionDenyConfig,
30 verifier_signing_config::VerifierSigningConfig,
31};
32use iota_protocol_config::ProtocolVersion;
33use iota_storage::blob::{Blob, BlobEncoding};
34use iota_swarm_config::{
35 genesis_config::AccountConfig, network_config::NetworkConfig,
36 network_config_builder::ConfigBuilder,
37};
38use iota_types::{
39 base_types::{AuthorityName, IotaAddress, ObjectID, VersionNumber},
40 committee::Committee,
41 crypto::{AuthoritySignature, KeypairTraits},
42 digests::ConsensusCommitDigest,
43 effects::TransactionEffects,
44 error::ExecutionError,
45 gas_coin::{GasCoin, NANOS_PER_IOTA},
46 inner_temporary_store::InnerTemporaryStore,
47 iota_system_state::epoch_start_iota_system_state::EpochStartSystemState,
48 messages_checkpoint::{
49 CheckpointContents, CheckpointSequenceNumber, EndOfEpochData, VerifiedCheckpoint,
50 },
51 mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
52 object::Object,
53 programmable_transaction_builder::ProgrammableTransactionBuilder,
54 signature::VerifyParams,
55 storage::{ObjectStore, ReadStore, RestStateReader},
56 transaction::{
57 EndOfEpochTransactionKind, GasData, Transaction, TransactionData, TransactionKind,
58 VerifiedTransaction,
59 },
60};
61use rand::rngs::OsRng;
62
63pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
64use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
65
66pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
76 inner: RwLock<SimulacrumInner<R, Store>>,
78 deny_config: TransactionDenyConfig,
80 verifier_signing_config: VerifierSigningConfig,
81}
82
83struct SimulacrumInner<R, Store: SimulatorStore> {
84 rng: R,
85 keystore: KeyStore,
86 #[expect(unused)]
87 genesis: genesis::Genesis,
88 store: Store,
89 checkpoint_builder: MockCheckpointBuilder,
90
91 epoch_state: EpochState,
93 data_ingestion_path: Option<PathBuf>,
94}
95
96impl Simulacrum {
97 #[expect(clippy::new_without_default)]
100 pub fn new() -> Self {
101 Self::new_with_rng(OsRng)
102 }
103}
104
105impl<R> Simulacrum<R>
106where
107 R: rand::RngCore + rand::CryptoRng,
108{
109 pub fn new_with_rng(mut rng: R) -> Self {
124 let config = ConfigBuilder::new_with_temp_dir()
125 .rng(&mut rng)
126 .with_chain_start_timestamp_ms(1)
127 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
128 .build();
129 Self::new_with_network_config_in_mem(&config, rng)
130 }
131
132 pub fn new_with_protocol_version_and_accounts(
133 mut rng: R,
134 chain_start_timestamp_ms: u64,
135 protocol_version: ProtocolVersion,
136 account_configs: Vec<AccountConfig>,
137 ) -> Self {
138 let config = ConfigBuilder::new_with_temp_dir()
139 .rng(&mut rng)
140 .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
141 .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
142 .with_protocol_version(protocol_version)
143 .with_accounts(account_configs)
144 .build();
145 Self::new_with_network_config_in_mem(&config, rng)
146 }
147
148 fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
149 let store = InMemoryStore::new(&config.genesis);
150 Self::new_with_network_config_store(config, rng, store)
151 }
152}
153
154impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
155 pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
156 let keystore = KeyStore::from_network_config(config);
157 let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
158
159 let genesis = &config.genesis;
160 let epoch_state = EpochState::new(genesis.iota_system_object());
161
162 Self {
163 deny_config: TransactionDenyConfig::default(),
164 verifier_signing_config: VerifierSigningConfig::default(),
165 inner: RwLock::new(SimulacrumInner {
166 rng,
167 keystore,
168 genesis: genesis.clone(),
169 store,
170 checkpoint_builder,
171 epoch_state,
172 data_ingestion_path: None,
173 }),
174 }
175 }
176
177 pub fn execute_transaction(
191 &self,
192 transaction: Transaction,
193 ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
194 let mut inner = self.inner.write().unwrap();
195 let transaction = transaction
196 .try_into_verified_for_testing(inner.epoch_state.epoch(), &VerifyParams::default())?;
197
198 let (inner_temporary_store, _, effects, execution_error_opt) =
199 inner.epoch_state.execute_transaction(
200 &inner.store,
201 &self.deny_config,
202 &self.verifier_signing_config,
203 &transaction,
204 )?;
205
206 let InnerTemporaryStore {
207 written, events, ..
208 } = inner_temporary_store;
209
210 inner.store.insert_executed_transaction(
211 transaction.clone(),
212 effects.clone(),
213 events,
214 written,
215 );
216
217 inner
219 .checkpoint_builder
220 .push_transaction(transaction, effects.clone());
221 Ok((effects, execution_error_opt.err()))
222 }
223
224 pub fn simulate_transaction(
227 &self,
228 transaction: TransactionData,
229 checks: iota_types::transaction_executor::VmChecks,
230 ) -> iota_types::error::IotaResult<iota_types::transaction_executor::SimulateTransactionResult>
231 {
232 let inner = self.inner.read().unwrap();
233 inner.epoch_state.simulate_transaction(
234 &inner.store,
235 &self.deny_config,
236 &self.verifier_signing_config,
237 transaction,
238 checks,
239 )
240 }
241
242 pub fn create_checkpoint(&self) -> VerifiedCheckpoint {
245 let (checkpoint, contents) = {
246 let mut inner = self.inner.write().unwrap();
247 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
248 let timestamp_ms = inner.store.get_clock().timestamp_ms();
249 let (checkpoint, contents, _) =
250 inner.checkpoint_builder.build(&committee, timestamp_ms);
251 inner.store.insert_checkpoint(checkpoint.clone());
252 inner.store.insert_checkpoint_contents(contents.clone());
253 (checkpoint, contents)
254 };
255 self.process_data_ingestion(checkpoint.clone(), contents)
257 .unwrap();
258 checkpoint
259 }
260
261 pub fn advance_clock(&self, duration: std::time::Duration) -> TransactionEffects {
266 let mut inner = self.inner.write().unwrap();
267 let epoch = inner.epoch_state.epoch();
268 let round = inner.epoch_state.next_consensus_round();
269 let timestamp_ms = inner.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
270 drop(inner);
271
272 let consensus_commit_prologue_transaction =
273 VerifiedTransaction::new_consensus_commit_prologue_v1(
274 epoch,
275 round,
276 timestamp_ms,
277 ConsensusCommitDigest::default(),
278 Vec::new(),
279 );
280
281 self.execute_transaction(consensus_commit_prologue_transaction.into())
282 .expect("advancing the clock cannot fail")
283 .0
284 }
285
286 pub fn advance_epoch(&self) {
296 let inner = self.inner.read().unwrap();
297 let current_epoch = inner.epoch_state.epoch();
298 let next_epoch = current_epoch + 1;
299 let next_epoch_protocol_version = inner.epoch_state.protocol_version();
300 let gas_cost_summary = inner
301 .checkpoint_builder
302 .epoch_rolling_gas_cost_summary()
303 .clone();
304 let epoch_start_timestamp_ms = inner.store.get_clock().timestamp_ms();
305 drop(inner);
306
307 let next_epoch_system_package_bytes = vec![];
308 let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v3(
309 next_epoch,
310 next_epoch_protocol_version,
311 gas_cost_summary.storage_cost,
312 gas_cost_summary.computation_cost,
313 gas_cost_summary.computation_cost_burned,
314 gas_cost_summary.storage_rebate,
315 gas_cost_summary.non_refundable_storage_fee,
316 epoch_start_timestamp_ms,
317 next_epoch_system_package_bytes,
318 vec![],
319 )];
320
321 let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
322 self.execute_transaction(tx.into())
323 .expect("advancing the epoch cannot fail");
324
325 let (checkpoint, contents, new_epoch_state) = {
326 let mut inner = self.inner.write().unwrap();
327 let new_epoch_state = EpochState::new(inner.store.get_system_state());
328 let end_of_epoch_data = EndOfEpochData {
329 next_epoch_committee: new_epoch_state.committee().voting_rights.clone(),
330 next_epoch_protocol_version,
331 epoch_commitments: vec![],
332 epoch_supply_change: 0,
334 };
335 let (checkpoint, contents, _) = {
336 let committee =
337 CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
338 let timestamp_ms = inner.store.get_clock().timestamp_ms();
339 inner.checkpoint_builder.build_end_of_epoch(
340 &committee,
341 timestamp_ms,
342 next_epoch,
343 end_of_epoch_data,
344 )
345 };
346
347 inner.store.insert_checkpoint(checkpoint.clone());
348 inner.store.insert_checkpoint_contents(contents.clone());
349 inner
350 .store
351 .update_last_checkpoint_of_epoch(current_epoch, *checkpoint.sequence_number());
352 (checkpoint, contents, new_epoch_state)
353 };
354
355 self.process_data_ingestion(checkpoint, contents).unwrap();
357
358 let mut inner = self.inner.write().unwrap();
360 inner.epoch_state = new_epoch_state;
361 }
362
363 pub fn with_store<F, T>(&self, f: F) -> T
368 where
369 F: FnOnce(&S) -> T,
370 {
371 let inner = self.inner.read().unwrap();
372 f(&inner.store)
373 }
374
375 pub fn with_keystore<F, T>(&self, f: F) -> T
380 where
381 F: FnOnce(&KeyStore) -> T,
382 {
383 let inner = self.inner.read().unwrap();
384 f(&inner.keystore)
385 }
386
387 pub fn epoch_start_state(&self) -> EpochStartSystemState {
388 let inner = self.inner.read().unwrap();
389 inner.epoch_state.epoch_start_state()
390 }
391
392 pub fn with_rng<F, T>(&self, f: F) -> T
399 where
400 F: FnOnce(&mut R) -> T,
401 {
402 let mut inner = self.inner.write().unwrap();
403 f(&mut inner.rng)
404 }
405
406 pub fn reference_gas_price(&self) -> u64 {
408 self.inner.read().unwrap().epoch_state.reference_gas_price()
409 }
410
411 pub fn request_gas(&self, address: IotaAddress, amount: u64) -> Result<TransactionEffects> {
427 let (sender, key) = self.with_keystore(|keystore| -> Result<(IotaAddress, _)> {
431 let (s, k) = keystore
432 .accounts()
433 .next()
434 .ok_or_else(|| anyhow!("no accounts available in keystore"))?;
435 Ok((*s, k.copy()))
436 })?;
437
438 let object = self
439 .with_store(|store| {
440 store.owned_objects(sender).find(|object| {
441 object.is_gas_coin() && object.get_coin_value_unsafe() > amount + NANOS_PER_IOTA
442 })
443 })
444 .ok_or_else(|| {
445 anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
446 })?;
447
448 let gas_data = iota_types::transaction::GasData {
449 payment: vec![object.compute_object_reference()],
450 owner: sender,
451 price: self.reference_gas_price(),
452 budget: NANOS_PER_IOTA,
453 };
454
455 let pt = {
456 let mut builder =
457 iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
458 builder.transfer_iota(address, Some(amount));
459 builder.finish()
460 };
461
462 let kind = iota_types::transaction::TransactionKind::ProgrammableTransaction(pt);
463 let tx_data =
464 iota_types::transaction::TransactionData::new_with_gas_data(kind, sender, gas_data);
465 let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
466
467 self.execute_transaction(tx).map(|x| x.0)
468 }
469
470 pub fn set_data_ingestion_path(&self, data_ingestion_path: PathBuf) {
471 let checkpoint = {
472 let mut inner = self.inner.write().unwrap();
473 inner.data_ingestion_path = Some(data_ingestion_path);
474 let checkpoint = inner.store.get_checkpoint_by_sequence_number(0).unwrap();
475 let contents = inner
476 .store
477 .get_checkpoint_contents_by_digest(&checkpoint.content_digest);
478 (checkpoint, contents)
479 };
480 if let (checkpoint, Some(contents)) = checkpoint {
482 self.process_data_ingestion(checkpoint, contents).unwrap();
483 }
484 }
485
486 pub fn override_next_checkpoint_number(&self, number: CheckpointSequenceNumber) {
492 let mut inner = self.inner.write().unwrap();
493 let committee = CommitteeWithKeys::new(&inner.keystore, inner.epoch_state.committee());
494 inner
495 .checkpoint_builder
496 .override_next_checkpoint_number(number, &committee);
497 }
498
499 fn process_data_ingestion(
502 &self,
503 checkpoint: VerifiedCheckpoint,
504 checkpoint_contents: CheckpointContents,
505 ) -> anyhow::Result<()> {
506 let path = self.inner.read().unwrap().data_ingestion_path.clone();
507 if let Some(data_path) = path {
508 let file_name = format!("{}.chk", checkpoint.sequence_number);
509 let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
510 std::fs::create_dir_all(&data_path)?;
511 let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
512 std::fs::write(data_path.join(file_name), blob.to_bytes())?;
513 }
514 Ok(())
515 }
516}
517
518pub struct CommitteeWithKeys {
519 keystore: KeyStore,
520 committee: Committee,
521}
522
523impl CommitteeWithKeys {
524 fn new(keystore: &KeyStore, committee: &Committee) -> Self {
525 Self {
526 keystore: keystore.clone(),
527 committee: committee.clone(),
528 }
529 }
530
531 pub fn keystore(&self) -> &KeyStore {
532 &self.keystore
533 }
534}
535
536impl ValidatorKeypairProvider for CommitteeWithKeys {
537 fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
538 self.keystore.validator(name).unwrap()
539 }
540
541 fn get_committee(&self) -> &Committee {
542 &self.committee
543 }
544}
545
546impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
547 fn try_get_object(
548 &self,
549 object_id: &ObjectID,
550 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
551 self.with_store(|store| store.try_get_object(object_id))
552 }
553
554 fn try_get_object_by_key(
555 &self,
556 object_id: &ObjectID,
557 version: VersionNumber,
558 ) -> Result<Option<Object>, iota_types::storage::error::Error> {
559 self.with_store(|store| store.try_get_object_by_key(object_id, version))
560 }
561}
562
563impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
564 fn try_get_committee(
565 &self,
566 _epoch: iota_types::committee::EpochId,
567 ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
568 todo!()
569 }
570
571 fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
572 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
573 }
574
575 fn try_get_highest_verified_checkpoint(
576 &self,
577 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
578 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
579 }
580
581 fn try_get_highest_synced_checkpoint(
582 &self,
583 ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
584 Ok(self.with_store(|store| store.get_highest_checkpoint().unwrap()))
585 }
586
587 fn try_get_lowest_available_checkpoint(
588 &self,
589 ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
590 {
591 Ok(0)
594 }
595
596 fn try_get_checkpoint_by_digest(
597 &self,
598 digest: &iota_types::messages_checkpoint::CheckpointDigest,
599 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
600 Ok(self.with_store(|store| store.get_checkpoint_by_digest(digest)))
601 }
602
603 fn try_get_checkpoint_by_sequence_number(
604 &self,
605 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
606 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
607 Ok(self.with_store(|store| store.get_checkpoint_by_sequence_number(sequence_number)))
608 }
609
610 fn try_get_checkpoint_contents_by_digest(
611 &self,
612 digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
613 ) -> iota_types::storage::error::Result<
614 Option<iota_types::messages_checkpoint::CheckpointContents>,
615 > {
616 Ok(self.with_store(|store| store.get_checkpoint_contents_by_digest(digest)))
617 }
618
619 fn try_get_checkpoint_contents_by_sequence_number(
620 &self,
621 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
622 ) -> iota_types::storage::error::Result<
623 Option<iota_types::messages_checkpoint::CheckpointContents>,
624 > {
625 Ok(self.with_store(|store| {
626 store
627 .get_checkpoint_by_sequence_number(sequence_number)
628 .and_then(|checkpoint| {
629 store.get_checkpoint_contents_by_digest(&checkpoint.content_digest)
630 })
631 }))
632 }
633
634 fn try_get_transaction(
635 &self,
636 tx_digest: &iota_types::digests::TransactionDigest,
637 ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
638 Ok(self.with_store(|store| store.get_transaction(tx_digest)))
639 }
640
641 fn try_get_transaction_effects(
642 &self,
643 tx_digest: &iota_types::digests::TransactionDigest,
644 ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
645 Ok(self.with_store(|store| store.get_transaction_effects(tx_digest)))
646 }
647
648 fn try_get_events(
649 &self,
650 event_digest: &iota_types::digests::TransactionEventsDigest,
651 ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
652 Ok(self.with_store(|store| store.get_events(event_digest)))
653 }
654
655 fn try_get_full_checkpoint_contents_by_sequence_number(
656 &self,
657 sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
658 ) -> iota_types::storage::error::Result<
659 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
660 > {
661 self.with_store(|store| {
662 store
663 .try_get_checkpoint_by_sequence_number(sequence_number)?
664 .and_then(|chk| store.get_checkpoint_contents_by_digest(&chk.content_digest))
665 .map_or(Ok(None), |contents| {
666 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
667 store,
668 contents.clone(),
669 )
670 })
671 })
672 }
673
674 fn try_get_full_checkpoint_contents(
675 &self,
676 digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
677 ) -> iota_types::storage::error::Result<
678 Option<iota_types::messages_checkpoint::FullCheckpointContents>,
679 > {
680 self.with_store(|store| {
681 store.get_checkpoint_contents_by_digest(digest)
682 .map_or(Ok(None), |contents| {
683 iota_types::messages_checkpoint::FullCheckpointContents::try_from_checkpoint_contents(
684 store,
685 contents.clone(),
686 )
687 })
688 })
689 }
690}
691
692impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> RestStateReader for Simulacrum<T, V> {
693 fn get_lowest_available_checkpoint_objects(
694 &self,
695 ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
696 Ok(0)
697 }
698
699 fn get_chain_identifier(
700 &self,
701 ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
702 Ok(self
703 .with_store(|store| store.get_checkpoint_by_sequence_number(0))
704 .expect("lowest available checkpoint should exist")
705 .digest()
706 .to_owned()
707 .into())
708 }
709
710 fn get_epoch_last_checkpoint(
711 &self,
712 epoch_id: iota_types::committee::EpochId,
713 ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
714 Ok(self.with_store(|store| {
715 store
716 .get_last_checkpoint_of_epoch(epoch_id)
717 .and_then(|seq| store.get_checkpoint_by_sequence_number(seq))
718 }))
719 }
720
721 fn indexes(&self) -> Option<&dyn iota_types::storage::RestIndexes> {
722 None
723 }
724
725 fn get_struct_layout(
726 &self,
727 _: &move_core_types::language_storage::StructTag,
728 ) -> iota_types::storage::error::Result<Option<move_core_types::annotated_value::MoveTypeLayout>>
729 {
730 Ok(None)
731 }
732}
733
734impl Simulacrum {
735 pub fn transfer_txn(&self, recipient: IotaAddress) -> (Transaction, u64) {
742 let (sender, key) = self.with_keystore(|keystore| {
743 let (s, k) = keystore.accounts().next().unwrap();
744 (*s, k.copy())
745 });
746
747 let (object, gas_coin_value) = self.with_store(|store| {
748 let object = store
749 .owned_objects(sender)
750 .find(|object| object.is_gas_coin())
751 .unwrap();
752 let gas_coin = GasCoin::try_from(object).unwrap();
753 (object.clone(), gas_coin.value())
754 });
755 let transfer_amount = gas_coin_value / 2;
756
757 let pt = {
758 let mut builder = ProgrammableTransactionBuilder::new();
759 builder.transfer_iota(recipient, Some(transfer_amount));
760 builder.finish()
761 };
762
763 let kind = TransactionKind::ProgrammableTransaction(pt);
764 let gas_data = GasData {
765 payment: vec![object.compute_object_reference()],
766 owner: sender,
767 price: self.reference_gas_price(),
768 budget: 1_000_000_000,
769 };
770 let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
771 let tx = Transaction::from_data_and_signer(tx_data, vec![&key]);
772 (tx, transfer_amount)
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use std::time::Duration;
779
780 use iota_types::{
781 base_types::IotaAddress, effects::TransactionEffectsAPI, gas_coin::GasCoin,
782 transaction::TransactionDataAPI,
783 };
784 use rand::{SeedableRng, rngs::StdRng};
785
786 use super::*;
787
788 #[test]
789 fn deterministic_genesis() {
790 let rng = StdRng::from_seed([9; 32]);
791 let chain1 = Simulacrum::new_with_rng(rng);
792 let genesis_checkpoint_digest1 = chain1
793 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
794
795 let rng = StdRng::from_seed([9; 32]);
796 let chain2 = Simulacrum::new_with_rng(rng);
797 let genesis_checkpoint_digest2 = chain2
798 .with_store(|store| *store.get_checkpoint_by_sequence_number(0).unwrap().digest());
799
800 assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
801
802 let rng = StdRng::from_seed([0; 32]);
804 let chain3 = Simulacrum::new_with_rng(rng);
805
806 let committee1 = chain1.with_store(|store| store.get_committee_by_epoch(0).cloned());
807 let committee3 = chain3.with_store(|store| store.get_committee_by_epoch(0).cloned());
808 assert_ne!(committee1, committee3);
809 }
810
811 #[test]
812 fn simple() {
813 let steps = 10;
814 let sim = Simulacrum::new();
815
816 let start_time_ms = sim.with_store(|store| {
817 let clock = store.get_clock();
818 println!("clock: {clock:#?}");
819 clock.timestamp_ms()
820 });
821
822 for _ in 0..steps {
823 sim.advance_clock(Duration::from_millis(1));
824 sim.create_checkpoint();
825 sim.with_store(|store| {
826 let clock = store.get_clock();
827 println!("clock: {clock:#?}");
828 });
829 }
830 let end_time_ms = sim.with_store(|store| store.get_clock().timestamp_ms());
831 assert_eq!(end_time_ms - start_time_ms, steps);
832 sim.with_store(|store| {
833 dbg!(store.get_highest_checkpoint());
834 });
835 }
836
837 #[test]
838 fn simple_epoch() {
839 let steps = 10;
840 let sim = Simulacrum::new();
841
842 let start_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
843 for i in 0..steps {
844 sim.advance_epoch();
845 sim.advance_clock(Duration::from_millis(1));
846 sim.create_checkpoint();
847 println!("{i}");
848 }
849 let end_epoch = sim.with_store(|store| store.get_highest_checkpoint().unwrap().epoch);
850 assert_eq!(end_epoch - start_epoch, steps);
851 sim.with_store(|store| {
852 dbg!(store.get_highest_checkpoint());
853 });
854 }
855
856 #[test]
857 fn transfer() {
858 let sim = Simulacrum::new();
859 let recipient = IotaAddress::random_for_testing_only();
860 let (tx, transfer_amount) = sim.transfer_txn(recipient);
861
862 let gas_id = tx.data().transaction_data().gas_data().payment[0].0;
863 let effects = sim.execute_transaction(tx).unwrap().0;
864 let gas_summary = effects.gas_cost_summary();
865 let gas_paid = gas_summary.net_gas_usage();
866
867 sim.with_store(|store| {
868 assert_eq!(
869 (transfer_amount as i64 - gas_paid) as u64,
870 store::SimulatorStore::get_object(store, &gas_id)
871 .and_then(|object| GasCoin::try_from(&object).ok())
872 .unwrap()
873 .value()
874 );
875
876 assert_eq!(
877 transfer_amount,
878 store
879 .owned_objects(recipient)
880 .next()
881 .and_then(|object| GasCoin::try_from(object).ok())
882 .unwrap()
883 .value()
884 );
885 });
886
887 let checkpoint = sim.create_checkpoint();
888
889 assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
890 assert_eq!(checkpoint.network_total_transactions, 2); }
892}