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_v2(
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        )];
274
275        let tx = VerifiedTransaction::new_end_of_epoch_transaction(kinds);
276        self.execute_transaction(tx.into())
277            .expect("advancing the epoch cannot fail");
278
279        let new_epoch_state = EpochState::new(self.store.get_system_state());
280        let end_of_epoch_data = EndOfEpochData {
281            next_epoch_committee: new_epoch_state.committee().voting_rights.clone(),
282            next_epoch_protocol_version,
283            epoch_commitments: vec![],
284            // Do not simulate supply changes for now.
285            epoch_supply_change: 0,
286        };
287        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
288        let (checkpoint, contents, _) = self.checkpoint_builder.build_end_of_epoch(
289            &committee,
290            self.store.get_clock().timestamp_ms(),
291            next_epoch,
292            end_of_epoch_data,
293        );
294
295        self.store.insert_checkpoint(checkpoint.clone());
296        self.store.insert_checkpoint_contents(contents.clone());
297        self.process_data_ingestion(checkpoint, contents).unwrap();
298        self.epoch_state = new_epoch_state;
299    }
300
301    pub fn store(&self) -> &dyn SimulatorStore {
302        &self.store
303    }
304
305    pub fn keystore(&self) -> &KeyStore {
306        &self.keystore
307    }
308
309    pub fn epoch_start_state(&self) -> &EpochStartSystemState {
310        self.epoch_state.epoch_start_state()
311    }
312
313    /// Return a handle to the internally held RNG.
314    ///
315    /// Returns a handle to the RNG used to create this Simulacrum for use as a
316    /// source of randomness. Using a seeded RNG to build a Simulacrum and
317    /// then utilizing the stored RNG as a source of randomness can lead to
318    /// a fully deterministic chain evolution.
319    pub fn rng(&mut self) -> &mut R {
320        &mut self.rng
321    }
322
323    /// Return the reference gas price for the current epoch
324    pub fn reference_gas_price(&self) -> u64 {
325        self.epoch_state.reference_gas_price()
326    }
327
328    /// Request that `amount` Nanos be sent to `address` from a faucet account.
329    ///
330    /// ```
331    /// use iota_types::{base_types::IotaAddress, gas_coin::NANOS_PER_IOTA};
332    /// use simulacrum::Simulacrum;
333    ///
334    /// # fn main() {
335    /// let mut simulacrum = Simulacrum::new();
336    /// let address = IotaAddress::generate(simulacrum.rng());
337    /// simulacrum.request_gas(address, NANOS_PER_IOTA).unwrap();
338    ///
339    /// // `account` now has a Coin<IOTA> object with single IOTA in it.
340    /// // ...
341    /// # }
342    /// ```
343    pub fn request_gas(&mut self, address: IotaAddress, amount: u64) -> Result<TransactionEffects> {
344        // For right now we'll just use the first account as the `faucet` account. We
345        // may want to explicitly cordon off the faucet account from the rest of
346        // the accounts though.
347        let (sender, key) = self.keystore().accounts().next().unwrap();
348        let object = self
349            .store()
350            .owned_objects(*sender)
351            .find(|object| {
352                object.is_gas_coin() && object.get_coin_value_unsafe() > amount + NANOS_PER_IOTA
353            })
354            .ok_or_else(|| {
355                anyhow!("unable to find a coin with enough to satisfy request for {amount} Nanos")
356            })?;
357
358        let gas_data = iota_types::transaction::GasData {
359            payment: vec![object.compute_object_reference()],
360            owner: *sender,
361            price: self.reference_gas_price(),
362            budget: NANOS_PER_IOTA,
363        };
364
365        let pt = {
366            let mut builder =
367                iota_types::programmable_transaction_builder::ProgrammableTransactionBuilder::new();
368            builder.transfer_iota(address, Some(amount));
369            builder.finish()
370        };
371
372        let kind = iota_types::transaction::TransactionKind::ProgrammableTransaction(pt);
373        let tx_data =
374            iota_types::transaction::TransactionData::new_with_gas_data(kind, *sender, gas_data);
375        let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
376
377        self.execute_transaction(tx).map(|x| x.0)
378    }
379
380    pub fn set_data_ingestion_path(&mut self, data_ingestion_path: PathBuf) {
381        self.data_ingestion_path = Some(data_ingestion_path);
382        let checkpoint = self.store.get_checkpoint_by_sequence_number(0).unwrap();
383        let contents = self
384            .store
385            .get_checkpoint_contents(&checkpoint.content_digest);
386        self.process_data_ingestion(checkpoint, contents.unwrap())
387            .unwrap();
388    }
389
390    /// Overrides the next checkpoint number indirectly by setting the previous
391    /// checkpoint's number to checkpoint_number - 1. This ensures the next
392    /// generated checkpoint has the exact sequence number provided. This
393    /// can be useful to generate checkpoints with specific sequence
394    /// numbers. Monotonicity of checkpoint numbers is enforced strictly.
395    pub fn override_next_checkpoint_number(&mut self, number: CheckpointSequenceNumber) {
396        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
397        self.checkpoint_builder
398            .override_next_checkpoint_number(number, &committee);
399    }
400
401    fn process_data_ingestion(
402        &self,
403        checkpoint: VerifiedCheckpoint,
404        checkpoint_contents: CheckpointContents,
405    ) -> anyhow::Result<()> {
406        if let Some(path) = &self.data_ingestion_path {
407            let file_name = format!("{}.chk", checkpoint.sequence_number);
408            let checkpoint_data = self.try_get_checkpoint_data(checkpoint, checkpoint_contents)?;
409            std::fs::create_dir_all(path)?;
410            let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
411            std::fs::write(path.join(file_name), blob.to_bytes())?;
412        }
413        Ok(())
414    }
415}
416
417pub struct CommitteeWithKeys<'a> {
418    keystore: &'a KeyStore,
419    committee: &'a Committee,
420}
421
422impl<'a> CommitteeWithKeys<'a> {
423    fn new(keystore: &'a KeyStore, committee: &'a Committee) -> Self {
424        Self {
425            keystore,
426            committee,
427        }
428    }
429
430    pub fn keystore(&self) -> &KeyStore {
431        self.keystore
432    }
433}
434
435impl ValidatorKeypairProvider for CommitteeWithKeys<'_> {
436    fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
437        self.keystore.validator(name).unwrap()
438    }
439
440    fn get_committee(&self) -> &Committee {
441        self.committee
442    }
443}
444
445impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
446    fn try_get_object(
447        &self,
448        object_id: &ObjectID,
449    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
450        Ok(store::SimulatorStore::get_object(&self.store, object_id))
451    }
452
453    fn try_get_object_by_key(
454        &self,
455        object_id: &ObjectID,
456        version: VersionNumber,
457    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
458        self.store.try_get_object_by_key(object_id, version)
459    }
460}
461
462impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
463    fn try_get_committee(
464        &self,
465        _epoch: iota_types::committee::EpochId,
466    ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
467        todo!()
468    }
469
470    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
471        Ok(self.store().get_highest_checkpoint().unwrap())
472    }
473
474    fn try_get_highest_verified_checkpoint(
475        &self,
476    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
477        todo!()
478    }
479
480    fn try_get_highest_synced_checkpoint(
481        &self,
482    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
483        todo!()
484    }
485
486    fn try_get_lowest_available_checkpoint(
487        &self,
488    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
489    {
490        // TODO wire this up to the underlying sim store, for now this will work since
491        // we never prune the sim store
492        Ok(0)
493    }
494
495    fn try_get_checkpoint_by_digest(
496        &self,
497        digest: &iota_types::messages_checkpoint::CheckpointDigest,
498    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
499        Ok(self.store().get_checkpoint_by_digest(digest))
500    }
501
502    fn try_get_checkpoint_by_sequence_number(
503        &self,
504        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
505    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
506        Ok(self
507            .store()
508            .get_checkpoint_by_sequence_number(sequence_number))
509    }
510
511    fn try_get_checkpoint_contents_by_digest(
512        &self,
513        digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
514    ) -> iota_types::storage::error::Result<
515        Option<iota_types::messages_checkpoint::CheckpointContents>,
516    > {
517        Ok(self.store().get_checkpoint_contents(digest))
518    }
519
520    fn try_get_checkpoint_contents_by_sequence_number(
521        &self,
522        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
523    ) -> iota_types::storage::error::Result<
524        Option<iota_types::messages_checkpoint::CheckpointContents>,
525    > {
526        todo!()
527    }
528
529    fn try_get_transaction(
530        &self,
531        tx_digest: &iota_types::digests::TransactionDigest,
532    ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
533        Ok(self.store().get_transaction(tx_digest).map(Arc::new))
534    }
535
536    fn try_get_transaction_effects(
537        &self,
538        tx_digest: &iota_types::digests::TransactionDigest,
539    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
540        Ok(self.store().get_transaction_effects(tx_digest))
541    }
542
543    fn try_get_events(
544        &self,
545        event_digest: &iota_types::digests::TransactionEventsDigest,
546    ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
547        Ok(self.store().get_transaction_events(event_digest))
548    }
549
550    fn try_get_full_checkpoint_contents_by_sequence_number(
551        &self,
552        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
553    ) -> iota_types::storage::error::Result<
554        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
555    > {
556        todo!()
557    }
558
559    fn try_get_full_checkpoint_contents(
560        &self,
561        _digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
562    ) -> iota_types::storage::error::Result<
563        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
564    > {
565        todo!()
566    }
567}
568
569impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> RestStateReader for Simulacrum<T, V> {
570    fn get_lowest_available_checkpoint_objects(
571        &self,
572    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
573        Ok(0)
574    }
575
576    fn get_chain_identifier(
577        &self,
578    ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
579        Ok(self
580            .store()
581            .get_checkpoint_by_sequence_number(0)
582            .unwrap()
583            .digest()
584            .to_owned()
585            .into())
586    }
587
588    fn get_epoch_last_checkpoint(
589        &self,
590        _epoch_id: iota_types::committee::EpochId,
591    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
592        todo!()
593    }
594
595    fn indexes(&self) -> Option<&dyn iota_types::storage::RestIndexes> {
596        None
597    }
598}
599
600impl Simulacrum {
601    /// Generate a random transfer transaction.
602    /// TODO: This is here today to make it easier to write tests. But we should
603    /// utilize all the existing code for generating transactions in
604    /// iota-test-transaction-builder by defining a trait
605    /// that both WalletContext and Simulacrum implement. Then we can remove
606    /// this function.
607    pub fn transfer_txn(&mut self, recipient: IotaAddress) -> (Transaction, u64) {
608        let (sender, key) = self.keystore().accounts().next().unwrap();
609        let sender = *sender;
610
611        let object = self
612            .store()
613            .owned_objects(sender)
614            .find(|object| object.is_gas_coin())
615            .unwrap();
616        let gas_coin = GasCoin::try_from(&object).unwrap();
617        let transfer_amount = gas_coin.value() / 2;
618
619        let pt = {
620            let mut builder = ProgrammableTransactionBuilder::new();
621            builder.transfer_iota(recipient, Some(transfer_amount));
622            builder.finish()
623        };
624
625        let kind = TransactionKind::ProgrammableTransaction(pt);
626        let gas_data = GasData {
627            payment: vec![object.compute_object_reference()],
628            owner: sender,
629            price: self.reference_gas_price(),
630            budget: 1_000_000_000,
631        };
632        let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
633        let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
634        (tx, transfer_amount)
635    }
636}
637
638#[cfg(test)]
639mod tests {
640    use std::time::Duration;
641
642    use iota_types::{
643        base_types::IotaAddress, effects::TransactionEffectsAPI, gas_coin::GasCoin,
644        transaction::TransactionDataAPI,
645    };
646    use rand::{SeedableRng, rngs::StdRng};
647
648    use super::*;
649
650    #[test]
651    fn deterministic_genesis() {
652        let rng = StdRng::from_seed([9; 32]);
653        let chain1 = Simulacrum::new_with_rng(rng);
654        let genesis_checkpoint_digest1 = *chain1
655            .store()
656            .get_checkpoint_by_sequence_number(0)
657            .unwrap()
658            .digest();
659
660        let rng = StdRng::from_seed([9; 32]);
661        let chain2 = Simulacrum::new_with_rng(rng);
662        let genesis_checkpoint_digest2 = *chain2
663            .store()
664            .get_checkpoint_by_sequence_number(0)
665            .unwrap()
666            .digest();
667
668        assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
669
670        // Ensure the committees are different when using different seeds
671        let rng = StdRng::from_seed([0; 32]);
672        let chain3 = Simulacrum::new_with_rng(rng);
673
674        assert_ne!(
675            chain1.store().get_committee_by_epoch(0),
676            chain3.store().get_committee_by_epoch(0),
677        );
678    }
679
680    #[test]
681    fn simple() {
682        let steps = 10;
683        let mut chain = Simulacrum::new();
684
685        let clock = chain.store().get_clock();
686        let start_time_ms = clock.timestamp_ms();
687        println!("clock: {clock:#?}");
688        for _ in 0..steps {
689            chain.advance_clock(Duration::from_millis(1));
690            chain.create_checkpoint();
691            let clock = chain.store().get_clock();
692            println!("clock: {clock:#?}");
693        }
694        let end_time_ms = chain.store().get_clock().timestamp_ms();
695        assert_eq!(end_time_ms - start_time_ms, steps);
696        dbg!(chain.store().get_highest_checkpoint());
697    }
698
699    #[test]
700    fn simple_epoch() {
701        let steps = 10;
702        let mut chain = Simulacrum::new();
703
704        let start_epoch = chain.store.get_highest_checkpoint().unwrap().epoch;
705        for i in 0..steps {
706            chain.advance_epoch();
707            chain.advance_clock(Duration::from_millis(1));
708            chain.create_checkpoint();
709            println!("{i}");
710        }
711        let end_epoch = chain.store.get_highest_checkpoint().unwrap().epoch;
712        assert_eq!(end_epoch - start_epoch, steps);
713        dbg!(chain.store().get_highest_checkpoint());
714    }
715
716    #[test]
717    fn transfer() {
718        let mut sim = Simulacrum::new();
719        let recipient = IotaAddress::random_for_testing_only();
720        let (tx, transfer_amount) = sim.transfer_txn(recipient);
721
722        let gas_id = tx.data().transaction_data().gas_data().payment[0].0;
723        let effects = sim.execute_transaction(tx).unwrap().0;
724        let gas_summary = effects.gas_cost_summary();
725        let gas_paid = gas_summary.net_gas_usage();
726
727        assert_eq!(
728            (transfer_amount as i64 - gas_paid) as u64,
729            store::SimulatorStore::get_object(sim.store(), &gas_id)
730                .and_then(|object| GasCoin::try_from(&object).ok())
731                .unwrap()
732                .value()
733        );
734
735        assert_eq!(
736            transfer_amount,
737            sim.store()
738                .owned_objects(recipient)
739                .next()
740                .and_then(|object| GasCoin::try_from(&object).ok())
741                .unwrap()
742                .value()
743        );
744
745        let checkpoint = sim.create_checkpoint();
746
747        assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
748        assert_eq!(checkpoint.network_total_transactions, 2); // genesis + 1 txn
749    }
750}