simulacrum/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5//! A `Simulacrum` of IOTA.
6//!
7//! The word simulacrum is latin for "likeness, semblance", it is also a spell
8//! in D&D which creates a copy of a creature which then follows the player's
9//! commands and wishes. As such this crate provides the [`Simulacrum`] type
10//! which is a implementation or instantiation of a iota blockchain, one which
11//! doesn't do anything unless acted upon.
12//!
13//! [`Simulacrum`]: crate::Simulacrum
14
15mod epoch_state;
16pub mod store;
17
18use std::{num::NonZeroUsize, path::PathBuf, sync::Arc};
19
20use anyhow::{Result, anyhow};
21use fastcrypto::traits::Signer;
22use iota_config::{
23    genesis, transaction_deny_config::TransactionDenyConfig,
24    verifier_signing_config::VerifierSigningConfig,
25};
26use iota_protocol_config::ProtocolVersion;
27use iota_storage::blob::{Blob, BlobEncoding};
28use iota_swarm_config::{
29    genesis_config::AccountConfig, network_config::NetworkConfig,
30    network_config_builder::ConfigBuilder,
31};
32use iota_types::{
33    base_types::{AuthorityName, IotaAddress, ObjectID, VersionNumber},
34    committee::Committee,
35    crypto::AuthoritySignature,
36    digests::ConsensusCommitDigest,
37    effects::TransactionEffects,
38    error::ExecutionError,
39    gas_coin::{GasCoin, NANOS_PER_IOTA},
40    inner_temporary_store::InnerTemporaryStore,
41    iota_system_state::epoch_start_iota_system_state::EpochStartSystemState,
42    messages_checkpoint::{
43        CheckpointContents, CheckpointSequenceNumber, EndOfEpochData, VerifiedCheckpoint,
44    },
45    mock_checkpoint_builder::{MockCheckpointBuilder, ValidatorKeypairProvider},
46    object::Object,
47    programmable_transaction_builder::ProgrammableTransactionBuilder,
48    signature::VerifyParams,
49    storage::{ObjectStore, ReadStore, RestStateReader},
50    transaction::{
51        EndOfEpochTransactionKind, GasData, Transaction, TransactionData, TransactionKind,
52        VerifiedTransaction,
53    },
54};
55use rand::rngs::OsRng;
56
57pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
58use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
59
60/// A `Simulacrum` of IOTA.
61///
62/// This type represents a simulated instantiation of an IOTA blockchain that
63/// needs to be driven manually, that is time doesn't advance and checkpoints
64/// are not formed unless explicitly requested.
65///
66/// See [module level][mod] documentation for more details.
67///
68/// [mod]: index.html
69pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
70    rng: R,
71    keystore: KeyStore,
72    #[expect(unused)]
73    genesis: genesis::Genesis,
74    store: Store,
75    checkpoint_builder: MockCheckpointBuilder,
76
77    // Epoch specific data
78    epoch_state: EpochState,
79
80    // Other
81    deny_config: TransactionDenyConfig,
82    data_ingestion_path: Option<PathBuf>,
83    verifier_signing_config: VerifierSigningConfig,
84}
85
86impl Simulacrum {
87    /// Create a new, random Simulacrum instance using an `OsRng` as the source
88    /// of randomness.
89    #[expect(clippy::new_without_default)]
90    pub fn new() -> Self {
91        Self::new_with_rng(OsRng)
92    }
93}
94
95impl<R> Simulacrum<R>
96where
97    R: rand::RngCore + rand::CryptoRng,
98{
99    /// Create a new Simulacrum instance using the provided `rng`.
100    ///
101    /// This allows you to create a fully deterministic initial chainstate when
102    /// a seeded rng is used.
103    ///
104    /// ```
105    /// use rand::{SeedableRng, rngs::StdRng};
106    /// use simulacrum::Simulacrum;
107    ///
108    /// # fn main() {
109    /// let mut rng = StdRng::seed_from_u64(1);
110    /// let simulacrum = Simulacrum::new_with_rng(rng);
111    /// # }
112    /// ```
113    pub fn new_with_rng(mut rng: R) -> Self {
114        let config = ConfigBuilder::new_with_temp_dir()
115            .rng(&mut rng)
116            .with_chain_start_timestamp_ms(1)
117            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
118            .build();
119        Self::new_with_network_config_in_mem(&config, rng)
120    }
121
122    pub fn new_with_protocol_version_and_accounts(
123        mut rng: R,
124        chain_start_timestamp_ms: u64,
125        protocol_version: ProtocolVersion,
126        account_configs: Vec<AccountConfig>,
127    ) -> Self {
128        let config = ConfigBuilder::new_with_temp_dir()
129            .rng(&mut rng)
130            .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
131            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
132            .with_protocol_version(protocol_version)
133            .with_accounts(account_configs)
134            .build();
135        Self::new_with_network_config_in_mem(&config, rng)
136    }
137
138    fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
139        let store = InMemoryStore::new(&config.genesis);
140        Self::new_with_network_config_store(config, rng, store)
141    }
142}
143
144impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
145    pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
146        let keystore = KeyStore::from_network_config(config);
147        let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
148
149        let genesis = &config.genesis;
150        let epoch_state = EpochState::new(genesis.iota_system_object());
151
152        Self {
153            rng,
154            keystore,
155            genesis: genesis.clone(),
156            store,
157            checkpoint_builder,
158            epoch_state,
159            deny_config: TransactionDenyConfig::default(),
160            verifier_signing_config: VerifierSigningConfig::default(),
161            data_ingestion_path: None,
162        }
163    }
164
165    /// Attempts to execute the provided Transaction.
166    ///
167    /// The provided Transaction undergoes the same types of checks that a
168    /// Validator does prior to signing and executing in the production
169    /// system. Some of these checks are as follows:
170    /// - User signature is valid
171    /// - Sender owns all OwnedObject inputs
172    /// - etc
173    ///
174    /// If the above checks are successful then the transaction is immediately
175    /// executed, enqueued to be included in the next checkpoint (the next
176    /// time `create_checkpoint` is called) and the corresponding
177    /// TransactionEffects are returned.
178    pub fn execute_transaction(
179        &mut self,
180        transaction: Transaction,
181    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
182        let transaction = transaction
183            .try_into_verified_for_testing(self.epoch_state.epoch(), &VerifyParams::default())?;
184
185        let (inner_temporary_store, _, effects, execution_error_opt) =
186            self.epoch_state.execute_transaction(
187                &self.store,
188                &self.deny_config,
189                &self.verifier_signing_config,
190                &transaction,
191            )?;
192
193        let InnerTemporaryStore {
194            written, events, ..
195        } = inner_temporary_store;
196
197        self.store.insert_executed_transaction(
198            transaction.clone(),
199            effects.clone(),
200            events,
201            written,
202        );
203
204        // Insert into checkpoint builder
205        self.checkpoint_builder
206            .push_transaction(transaction, effects.clone());
207        Ok((effects, execution_error_opt.err()))
208    }
209
210    /// Creates the next Checkpoint using the Transactions enqueued since the
211    /// last checkpoint was created.
212    pub fn create_checkpoint(&mut self) -> VerifiedCheckpoint {
213        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
214        let (checkpoint, contents, _) = self
215            .checkpoint_builder
216            .build(&committee, self.store.get_clock().timestamp_ms());
217        self.store.insert_checkpoint(checkpoint.clone());
218        self.store.insert_checkpoint_contents(contents.clone());
219        self.process_data_ingestion(checkpoint.clone(), contents)
220            .unwrap();
221        checkpoint
222    }
223
224    /// Advances the clock by `duration`.
225    ///
226    /// This creates and executes a ConsensusCommitPrologue transaction which
227    /// advances the chain Clock by the provided duration.
228    pub fn advance_clock(&mut self, duration: std::time::Duration) -> TransactionEffects {
229        let epoch = self.epoch_state.epoch();
230        let round = self.epoch_state.next_consensus_round();
231        let timestamp_ms = self.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
232
233        let consensus_commit_prologue_transaction =
234            VerifiedTransaction::new_consensus_commit_prologue_v1(
235                epoch,
236                round,
237                timestamp_ms,
238                ConsensusCommitDigest::default(),
239                Vec::new(),
240            );
241
242        self.execute_transaction(consensus_commit_prologue_transaction.into())
243            .expect("advancing the clock cannot fail")
244            .0
245    }
246
247    /// Advances the epoch.
248    ///
249    /// This creates and executes an EndOfEpoch transaction which advances the
250    /// chain into the next epoch. Since it is required to be the final
251    /// transaction in an epoch, the final checkpoint in the epoch is also
252    /// created.
253    ///
254    /// NOTE: This function does not currently support updating the protocol
255    /// version or the system packages
256    pub fn advance_epoch(&mut self) {
257        let next_epoch = self.epoch_state.epoch() + 1;
258        let next_epoch_protocol_version = self.epoch_state.protocol_version();
259        let gas_cost_summary = self.checkpoint_builder.epoch_rolling_gas_cost_summary();
260        let epoch_start_timestamp_ms = self.store.get_clock().timestamp_ms();
261        let next_epoch_system_package_bytes = vec![];
262
263        let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v3(
264            next_epoch,
265            next_epoch_protocol_version,
266            gas_cost_summary.storage_cost,
267            gas_cost_summary.computation_cost,
268            gas_cost_summary.computation_cost_burned,
269            gas_cost_summary.storage_rebate,
270            gas_cost_summary.non_refundable_storage_fee,
271            epoch_start_timestamp_ms,
272            next_epoch_system_package_bytes,
273            vec![],
274        )];
275
276        let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
277        self.execute_transaction(tx.into())
278            .expect("advancing the epoch cannot fail");
279
280        let new_epoch_state = EpochState::new(self.store.get_system_state());
281        let end_of_epoch_data = EndOfEpochData {
282            next_epoch_committee: new_epoch_state.committee().voting_rights.clone(),
283            next_epoch_protocol_version,
284            epoch_commitments: vec![],
285            // Do not simulate supply changes for now.
286            epoch_supply_change: 0,
287        };
288        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
289        let (checkpoint, contents, _) = self.checkpoint_builder.build_end_of_epoch(
290            &committee,
291            self.store.get_clock().timestamp_ms(),
292            next_epoch,
293            end_of_epoch_data,
294        );
295
296        self.store.insert_checkpoint(checkpoint.clone());
297        self.store.insert_checkpoint_contents(contents.clone());
298        self.process_data_ingestion(checkpoint, contents).unwrap();
299        self.epoch_state = new_epoch_state;
300    }
301
302    pub fn store(&self) -> &dyn SimulatorStore {
303        &self.store
304    }
305
306    pub fn keystore(&self) -> &KeyStore {
307        &self.keystore
308    }
309
310    pub fn epoch_start_state(&self) -> &EpochStartSystemState {
311        self.epoch_state.epoch_start_state()
312    }
313
314    /// Return a handle to the internally held RNG.
315    ///
316    /// Returns a handle to the RNG used to create this Simulacrum for use as a
317    /// source of randomness. Using a seeded RNG to build a Simulacrum and
318    /// then utilizing the stored RNG as a source of randomness can lead to
319    /// a fully deterministic chain evolution.
320    pub fn rng(&mut self) -> &mut R {
321        &mut self.rng
322    }
323
324    /// Return the reference gas price for the current epoch
325    pub fn reference_gas_price(&self) -> u64 {
326        self.epoch_state.reference_gas_price()
327    }
328
329    /// Request that `amount` Nanos be sent to `address` from a faucet account.
330    ///
331    /// ```
332    /// use iota_types::{base_types::IotaAddress, gas_coin::NANOS_PER_IOTA};
333    /// use simulacrum::Simulacrum;
334    ///
335    /// # fn main() {
336    /// let mut simulacrum = Simulacrum::new();
337    /// let address = IotaAddress::generate(simulacrum.rng());
338    /// simulacrum.request_gas(address, NANOS_PER_IOTA).unwrap();
339    ///
340    /// // `account` now has a Coin<IOTA> object with single IOTA in it.
341    /// // ...
342    /// # }
343    /// ```
344    pub fn request_gas(&mut self, address: IotaAddress, amount: u64) -> Result<TransactionEffects> {
345        // For right now we'll just use the first account as the `faucet` account. We
346        // may want to explicitly cordon off the faucet account from the rest of
347        // the accounts though.
348        let (sender, key) = self.keystore().accounts().next().unwrap();
349        let object = self
350            .store()
351            .owned_objects(*sender)
352            .find(|object| {
353                object.is_gas_coin() && object.get_coin_value_unsafe() > amount + NANOS_PER_IOTA
354            })
355            .ok_or_else(|| {
356                anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
357            })?;
358
359        let gas_data = iota_types::transaction::GasData {
360            payment: vec![object.compute_object_reference()],
361            owner: *sender,
362            price: self.reference_gas_price(),
363            budget: NANOS_PER_IOTA,
364        };
365
366        let pt = {
367            let mut builder =
368                iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
369            builder.transfer_iota(address, Some(amount));
370            builder.finish()
371        };
372
373        let kind = iota_types::transaction::TransactionKind::ProgrammableTransaction(pt);
374        let tx_data =
375            iota_types::transaction::TransactionData::new_with_gas_data(kind, *sender, gas_data);
376        let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
377
378        self.execute_transaction(tx).map(|x| x.0)
379    }
380
381    pub fn set_data_ingestion_path(&mut self, data_ingestion_path: PathBuf) {
382        self.data_ingestion_path = Some(data_ingestion_path);
383        let checkpoint = self.store.get_checkpoint_by_sequence_number(0).unwrap();
384        let contents = self
385            .store
386            .get_checkpoint_contents(&checkpoint.content_digest);
387        self.process_data_ingestion(checkpoint, contents.unwrap())
388            .unwrap();
389    }
390
391    /// Overrides the next checkpoint number indirectly by setting the previous
392    /// checkpoint's number to checkpoint_number - 1. This ensures the next
393    /// generated checkpoint has the exact sequence number provided. This
394    /// can be useful to generate checkpoints with specific sequence
395    /// numbers. Monotonicity of checkpoint numbers is enforced strictly.
396    pub fn override_next_checkpoint_number(&mut self, number: CheckpointSequenceNumber) {
397        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
398        self.checkpoint_builder
399            .override_next_checkpoint_number(number, &committee);
400    }
401
402    fn process_data_ingestion(
403        &self,
404        checkpoint: VerifiedCheckpoint,
405        checkpoint_contents: CheckpointContents,
406    ) -> anyhow::Result<()> {
407        if let Some(path) = &self.data_ingestion_path {
408            let file_name = format!("{}.chk", checkpoint.sequence_number);
409            let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
410            std::fs::create_dir_all(path)?;
411            let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
412            std::fs::write(path.join(file_name), blob.to_bytes())?;
413        }
414        Ok(())
415    }
416}
417
418pub struct CommitteeWithKeys<'a> {
419    keystore: &'a KeyStore,
420    committee: &'a Committee,
421}
422
423impl<'a> CommitteeWithKeys<'a> {
424    fn new(keystore: &'a KeyStore, committee: &'a Committee) -> Self {
425        Self {
426            keystore,
427            committee,
428        }
429    }
430
431    pub fn keystore(&self) -> &KeyStore {
432        self.keystore
433    }
434}
435
436impl ValidatorKeypairProvider for CommitteeWithKeys<'_> {
437    fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
438        self.keystore.validator(name).unwrap()
439    }
440
441    fn get_committee(&self) -> &Committee {
442        self.committee
443    }
444}
445
446impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
447    fn try_get_object(
448        &self,
449        object_id: &ObjectID,
450    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
451        Ok(store::SimulatorStore::get_object(&self.store, object_id))
452    }
453
454    fn try_get_object_by_key(
455        &self,
456        object_id: &ObjectID,
457        version: VersionNumber,
458    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
459        self.store.try_get_object_by_key(object_id, version)
460    }
461}
462
463impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
464    fn try_get_committee(
465        &self,
466        _epoch: iota_types::committee::EpochId,
467    ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
468        todo!()
469    }
470
471    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
472        Ok(self.store().get_highest_checkpoint().unwrap())
473    }
474
475    fn try_get_highest_verified_checkpoint(
476        &self,
477    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
478        todo!()
479    }
480
481    fn try_get_highest_synced_checkpoint(
482        &self,
483    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
484        todo!()
485    }
486
487    fn try_get_lowest_available_checkpoint(
488        &self,
489    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
490    {
491        // TODO wire this up to the underlying sim store, for now this will work since
492        // we never prune the sim store
493        Ok(0)
494    }
495
496    fn try_get_checkpoint_by_digest(
497        &self,
498        digest: &iota_types::messages_checkpoint::CheckpointDigest,
499    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
500        Ok(self.store().get_checkpoint_by_digest(digest))
501    }
502
503    fn try_get_checkpoint_by_sequence_number(
504        &self,
505        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
506    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
507        Ok(self
508            .store()
509            .get_checkpoint_by_sequence_number(sequence_number))
510    }
511
512    fn try_get_checkpoint_contents_by_digest(
513        &self,
514        digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
515    ) -> iota_types::storage::error::Result<
516        Option<iota_types::messages_checkpoint::CheckpointContents>,
517    > {
518        Ok(self.store().get_checkpoint_contents(digest))
519    }
520
521    fn try_get_checkpoint_contents_by_sequence_number(
522        &self,
523        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
524    ) -> iota_types::storage::error::Result<
525        Option<iota_types::messages_checkpoint::CheckpointContents>,
526    > {
527        todo!()
528    }
529
530    fn try_get_transaction(
531        &self,
532        tx_digest: &iota_types::digests::TransactionDigest,
533    ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
534        Ok(self.store().get_transaction(tx_digest).map(Arc::new))
535    }
536
537    fn try_get_transaction_effects(
538        &self,
539        tx_digest: &iota_types::digests::TransactionDigest,
540    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
541        Ok(self.store().get_transaction_effects(tx_digest))
542    }
543
544    fn try_get_events(
545        &self,
546        event_digest: &iota_types::digests::TransactionEventsDigest,
547    ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
548        Ok(self.store().get_transaction_events(event_digest))
549    }
550
551    fn try_get_full_checkpoint_contents_by_sequence_number(
552        &self,
553        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
554    ) -> iota_types::storage::error::Result<
555        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
556    > {
557        todo!()
558    }
559
560    fn try_get_full_checkpoint_contents(
561        &self,
562        _digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
563    ) -> iota_types::storage::error::Result<
564        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
565    > {
566        todo!()
567    }
568}
569
570impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> RestStateReader for Simulacrum<T, V> {
571    fn get_lowest_available_checkpoint_objects(
572        &self,
573    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
574        Ok(0)
575    }
576
577    fn get_chain_identifier(
578        &self,
579    ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
580        Ok(self
581            .store()
582            .get_checkpoint_by_sequence_number(0)
583            .unwrap()
584            .digest()
585            .to_owned()
586            .into())
587    }
588
589    fn get_epoch_last_checkpoint(
590        &self,
591        _epoch_id: iota_types::committee::EpochId,
592    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
593        todo!()
594    }
595
596    fn indexes(&self) -> Option<&dyn iota_types::storage::RestIndexes> {
597        None
598    }
599}
600
601impl Simulacrum {
602    /// Generate a random transfer transaction.
603    /// TODO: This is here today to make it easier to write tests. But we should
604    /// utilize all the existing code for generating transactions in
605    /// iota-test-transaction-builder by defining a trait
606    /// that both WalletContext and Simulacrum implement. Then we can remove
607    /// this function.
608    pub fn transfer_txn(&mut self, recipient: IotaAddress) -> (Transaction, u64) {
609        let (sender, key) = self.keystore().accounts().next().unwrap();
610        let sender = *sender;
611
612        let object = self
613            .store()
614            .owned_objects(sender)
615            .find(|object| object.is_gas_coin())
616            .unwrap();
617        let gas_coin = GasCoin::try_from(&object).unwrap();
618        let transfer_amount = gas_coin.value() / 2;
619
620        let pt = {
621            let mut builder = ProgrammableTransactionBuilder::new();
622            builder.transfer_iota(recipient, Some(transfer_amount));
623            builder.finish()
624        };
625
626        let kind = TransactionKind::ProgrammableTransaction(pt);
627        let gas_data = GasData {
628            payment: vec![object.compute_object_reference()],
629            owner: sender,
630            price: self.reference_gas_price(),
631            budget: 1_000_000_000,
632        };
633        let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
634        let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
635        (tx, transfer_amount)
636    }
637}
638
639#[cfg(test)]
640mod tests {
641    use std::time::Duration;
642
643    use iota_types::{
644        base_types::IotaAddress, effects::TransactionEffectsAPI, gas_coin::GasCoin,
645        transaction::TransactionDataAPI,
646    };
647    use rand::{SeedableRng, rngs::StdRng};
648
649    use super::*;
650
651    #[test]
652    fn deterministic_genesis() {
653        let rng = StdRng::from_seed([9; 32]);
654        let chain1 = Simulacrum::new_with_rng(rng);
655        let genesis_checkpoint_digest1 = *chain1
656            .store()
657            .get_checkpoint_by_sequence_number(0)
658            .unwrap()
659            .digest();
660
661        let rng = StdRng::from_seed([9; 32]);
662        let chain2 = Simulacrum::new_with_rng(rng);
663        let genesis_checkpoint_digest2 = *chain2
664            .store()
665            .get_checkpoint_by_sequence_number(0)
666            .unwrap()
667            .digest();
668
669        assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
670
671        // Ensure the committees are different when using different seeds
672        let rng = StdRng::from_seed([0; 32]);
673        let chain3 = Simulacrum::new_with_rng(rng);
674
675        assert_ne!(
676            chain1.store().get_committee_by_epoch(0),
677            chain3.store().get_committee_by_epoch(0),
678        );
679    }
680
681    #[test]
682    fn simple() {
683        let steps = 10;
684        let mut chain = Simulacrum::new();
685
686        let clock = chain.store().get_clock();
687        let start_time_ms = clock.timestamp_ms();
688        println!("clock: {clock:#?}");
689        for _ in 0..steps {
690            chain.advance_clock(Duration::from_millis(1));
691            chain.create_checkpoint();
692            let clock = chain.store().get_clock();
693            println!("clock: {clock:#?}");
694        }
695        let end_time_ms = chain.store().get_clock().timestamp_ms();
696        assert_eq!(end_time_ms - start_time_ms, steps);
697        dbg!(chain.store().get_highest_checkpoint());
698    }
699
700    #[test]
701    fn simple_epoch() {
702        let steps = 10;
703        let mut chain = Simulacrum::new();
704
705        let start_epoch = chain.store.get_highest_checkpoint().unwrap().epoch;
706        for i in 0..steps {
707            chain.advance_epoch();
708            chain.advance_clock(Duration::from_millis(1));
709            chain.create_checkpoint();
710            println!("{i}");
711        }
712        let end_epoch = chain.store.get_highest_checkpoint().unwrap().epoch;
713        assert_eq!(end_epoch - start_epoch, steps);
714        dbg!(chain.store().get_highest_checkpoint());
715    }
716
717    #[test]
718    fn transfer() {
719        let mut sim = Simulacrum::new();
720        let recipient = IotaAddress::random_for_testing_only();
721        let (tx, transfer_amount) = sim.transfer_txn(recipient);
722
723        let gas_id = tx.data().transaction_data().gas_data().payment[0].0;
724        let effects = sim.execute_transaction(tx).unwrap().0;
725        let gas_summary = effects.gas_cost_summary();
726        let gas_paid = gas_summary.net_gas_usage();
727
728        assert_eq!(
729            (transfer_amount as i64 - gas_paid) as u64,
730            store::SimulatorStore::get_object(sim.store(), &gas_id)
731                .and_then(|object| GasCoin::try_from(&object).ok())
732                .unwrap()
733                .value()
734        );
735
736        assert_eq!(
737            transfer_amount,
738            sim.store()
739                .owned_objects(recipient)
740                .next()
741                .and_then(|object| GasCoin::try_from(&object).ok())
742                .unwrap()
743                .value()
744        );
745
746        let checkpoint = sim.create_checkpoint();
747
748        assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
749        assert_eq!(checkpoint.network_total_transactions, 2); // genesis + 1 txn
750    }
751}