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 move_core_types::language_storage::StructTag;
56use rand::rngs::OsRng;
57
58pub use self::store::{SimulatorStore, in_mem_store::InMemoryStore};
59use self::{epoch_state::EpochState, store::in_mem_store::KeyStore};
60
61/// A `Simulacrum` of IOTA.
62///
63/// This type represents a simulated instantiation of an IOTA blockchain that
64/// needs to be driven manually, that is time doesn't advance and checkpoints
65/// are not formed unless explicitly requested.
66///
67/// See [module level][mod] documentation for more details.
68///
69/// [mod]: index.html
70pub struct Simulacrum<R = OsRng, Store: SimulatorStore = InMemoryStore> {
71    rng: R,
72    keystore: KeyStore,
73    #[expect(unused)]
74    genesis: genesis::Genesis,
75    store: Store,
76    checkpoint_builder: MockCheckpointBuilder,
77
78    // Epoch specific data
79    epoch_state: EpochState,
80
81    // Other
82    deny_config: TransactionDenyConfig,
83    data_ingestion_path: Option<PathBuf>,
84    verifier_signing_config: VerifierSigningConfig,
85}
86
87impl Simulacrum {
88    /// Create a new, random Simulacrum instance using an `OsRng` as the source
89    /// of randomness.
90    #[expect(clippy::new_without_default)]
91    pub fn new() -> Self {
92        Self::new_with_rng(OsRng)
93    }
94}
95
96impl<R> Simulacrum<R>
97where
98    R: rand::RngCore + rand::CryptoRng,
99{
100    /// Create a new Simulacrum instance using the provided `rng`.
101    ///
102    /// This allows you to create a fully deterministic initial chainstate when
103    /// a seeded rng is used.
104    ///
105    /// ```
106    /// use rand::{SeedableRng, rngs::StdRng};
107    /// use simulacrum::Simulacrum;
108    ///
109    /// # fn main() {
110    /// let mut rng = StdRng::seed_from_u64(1);
111    /// let simulacrum = Simulacrum::new_with_rng(rng);
112    /// # }
113    /// ```
114    pub fn new_with_rng(mut rng: R) -> Self {
115        let config = ConfigBuilder::new_with_temp_dir()
116            .rng(&mut rng)
117            .with_chain_start_timestamp_ms(1)
118            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
119            .build();
120        Self::new_with_network_config_in_mem(&config, rng)
121    }
122
123    pub fn new_with_protocol_version_and_accounts(
124        mut rng: R,
125        chain_start_timestamp_ms: u64,
126        protocol_version: ProtocolVersion,
127        account_configs: Vec<AccountConfig>,
128    ) -> Self {
129        let config = ConfigBuilder::new_with_temp_dir()
130            .rng(&mut rng)
131            .with_chain_start_timestamp_ms(chain_start_timestamp_ms)
132            .deterministic_committee_size(NonZeroUsize::new(1).unwrap())
133            .with_protocol_version(protocol_version)
134            .with_accounts(account_configs)
135            .build();
136        Self::new_with_network_config_in_mem(&config, rng)
137    }
138
139    fn new_with_network_config_in_mem(config: &NetworkConfig, rng: R) -> Self {
140        let store = InMemoryStore::new(&config.genesis);
141        Self::new_with_network_config_store(config, rng, store)
142    }
143}
144
145impl<R, S: store::SimulatorStore> Simulacrum<R, S> {
146    pub fn new_with_network_config_store(config: &NetworkConfig, rng: R, store: S) -> Self {
147        let keystore = KeyStore::from_network_config(config);
148        let checkpoint_builder = MockCheckpointBuilder::new(config.genesis.checkpoint());
149
150        let genesis = &config.genesis;
151        let epoch_state = EpochState::new(genesis.iota_system_object());
152
153        Self {
154            rng,
155            keystore,
156            genesis: genesis.clone(),
157            store,
158            checkpoint_builder,
159            epoch_state,
160            deny_config: TransactionDenyConfig::default(),
161            verifier_signing_config: VerifierSigningConfig::default(),
162            data_ingestion_path: None,
163        }
164    }
165
166    /// Attempts to execute the provided Transaction.
167    ///
168    /// The provided Transaction undergoes the same types of checks that a
169    /// Validator does prior to signing and executing in the production
170    /// system. Some of these checks are as follows:
171    /// - User signature is valid
172    /// - Sender owns all OwnedObject inputs
173    /// - etc
174    ///
175    /// If the above checks are successful then the transaction is immediately
176    /// executed, enqueued to be included in the next checkpoint (the next
177    /// time `create_checkpoint` is called) and the corresponding
178    /// TransactionEffects are returned.
179    pub fn execute_transaction(
180        &mut self,
181        transaction: Transaction,
182    ) -> anyhow::Result<(TransactionEffects, Option<ExecutionError>)> {
183        let transaction = transaction
184            .try_into_verified_for_testing(self.epoch_state.epoch(), &VerifyParams::default())?;
185
186        let (inner_temporary_store, _, effects, execution_error_opt) =
187            self.epoch_state.execute_transaction(
188                &self.store,
189                &self.deny_config,
190                &self.verifier_signing_config,
191                &transaction,
192            )?;
193
194        let InnerTemporaryStore {
195            written, events, ..
196        } = inner_temporary_store;
197
198        self.store.insert_executed_transaction(
199            transaction.clone(),
200            effects.clone(),
201            events,
202            written,
203        );
204
205        // Insert into checkpoint builder
206        self.checkpoint_builder
207            .push_transaction(transaction, effects.clone());
208        Ok((effects, execution_error_opt.err()))
209    }
210
211    /// Creates the next Checkpoint using the Transactions enqueued since the
212    /// last checkpoint was created.
213    pub fn create_checkpoint(&mut self) -> VerifiedCheckpoint {
214        let committee = CommitteeWithKeys::new(&self.keystore, self.epoch_state.committee());
215        let (checkpoint, contents, _) = self
216            .checkpoint_builder
217            .build(&committee, self.store.get_clock().timestamp_ms());
218        self.store.insert_checkpoint(checkpoint.clone());
219        self.store.insert_checkpoint_contents(contents.clone());
220        self.process_data_ingestion(checkpoint.clone(), contents)
221            .unwrap();
222        checkpoint
223    }
224
225    /// Advances the clock by `duration`.
226    ///
227    /// This creates and executes a ConsensusCommitPrologue transaction which
228    /// advances the chain Clock by the provided duration.
229    pub fn advance_clock(&mut self, duration: std::time::Duration) -> TransactionEffects {
230        let epoch = self.epoch_state.epoch();
231        let round = self.epoch_state.next_consensus_round();
232        let timestamp_ms = self.store.get_clock().timestamp_ms() + duration.as_millis() as u64;
233
234        let consensus_commit_prologue_transaction =
235            VerifiedTransaction::new_consensus_commit_prologue_v1(
236                epoch,
237                round,
238                timestamp_ms,
239                ConsensusCommitDigest::default(),
240                Vec::new(),
241            );
242
243        self.execute_transaction(consensus_commit_prologue_transaction.into())
244            .expect("advancing the clock cannot fail")
245            .0
246    }
247
248    /// Advances the epoch.
249    ///
250    /// This creates and executes an EndOfEpoch transaction which advances the
251    /// chain into the next epoch. Since it is required to be the final
252    /// transaction in an epoch, the final checkpoint in the epoch is also
253    /// created.
254    ///
255    /// NOTE: This function does not currently support updating the protocol
256    /// version or the system packages
257    pub fn advance_epoch(&mut self) {
258        let next_epoch = self.epoch_state.epoch() + 1;
259        let next_epoch_protocol_version = self.epoch_state.protocol_version();
260        let gas_cost_summary = self.checkpoint_builder.epoch_rolling_gas_cost_summary();
261        let epoch_start_timestamp_ms = self.store.get_clock().timestamp_ms();
262        let next_epoch_system_package_bytes = vec![];
263
264        let kinds = vec![EndOfEpochTransactionKind::new_change_epoch_v2(
265            next_epoch,
266            next_epoch_protocol_version,
267            gas_cost_summary.storage_cost,
268            gas_cost_summary.computation_cost,
269            gas_cost_summary.computation_cost_burned,
270            gas_cost_summary.storage_rebate,
271            gas_cost_summary.non_refundable_storage_fee,
272            epoch_start_timestamp_ms,
273            next_epoch_system_package_bytes,
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    fn process_data_ingestion(
392        &self,
393        checkpoint: VerifiedCheckpoint,
394        checkpoint_contents: CheckpointContents,
395    ) -> anyhow::Result<()> {
396        if let Some(path) = &self.data_ingestion_path {
397            let file_name = format!("{}.chk", checkpoint.sequence_number);
398            let checkpoint_data = self.get_checkpoint_data(checkpoint, checkpoint_contents)?;
399            std::fs::create_dir_all(path)?;
400            let blob = Blob::encode(&checkpoint_data, BlobEncoding::Bcs)?;
401            std::fs::write(path.join(file_name), blob.to_bytes())?;
402        }
403        Ok(())
404    }
405}
406
407pub struct CommitteeWithKeys<'a> {
408    keystore: &'a KeyStore,
409    committee: &'a Committee,
410}
411
412impl<'a> CommitteeWithKeys<'a> {
413    fn new(keystore: &'a KeyStore, committee: &'a Committee) -> Self {
414        Self {
415            keystore,
416            committee,
417        }
418    }
419
420    pub fn keystore(&self) -> &KeyStore {
421        self.keystore
422    }
423}
424
425impl ValidatorKeypairProvider for CommitteeWithKeys<'_> {
426    fn get_validator_key(&self, name: &AuthorityName) -> &dyn Signer<AuthoritySignature> {
427        self.keystore.validator(name).unwrap()
428    }
429
430    fn get_committee(&self) -> &Committee {
431        self.committee
432    }
433}
434
435impl<T, V: store::SimulatorStore> ObjectStore for Simulacrum<T, V> {
436    fn get_object(
437        &self,
438        object_id: &ObjectID,
439    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
440        Ok(store::SimulatorStore::get_object(&self.store, object_id))
441    }
442
443    fn get_object_by_key(
444        &self,
445        object_id: &ObjectID,
446        version: VersionNumber,
447    ) -> Result<Option<Object>, iota_types::storage::error::Error> {
448        self.store.get_object_by_key(object_id, version)
449    }
450}
451
452impl<T, V: store::SimulatorStore> ReadStore for Simulacrum<T, V> {
453    fn get_committee(
454        &self,
455        _epoch: iota_types::committee::EpochId,
456    ) -> iota_types::storage::error::Result<Option<std::sync::Arc<Committee>>> {
457        todo!()
458    }
459
460    fn get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
461        Ok(self.store().get_highest_checkpoint().unwrap())
462    }
463
464    fn get_highest_verified_checkpoint(
465        &self,
466    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
467        todo!()
468    }
469
470    fn get_highest_synced_checkpoint(
471        &self,
472    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
473        todo!()
474    }
475
476    fn get_lowest_available_checkpoint(
477        &self,
478    ) -> iota_types::storage::error::Result<iota_types::messages_checkpoint::CheckpointSequenceNumber>
479    {
480        // TODO wire this up to the underlying sim store, for now this will work since
481        // we never prune the sim store
482        Ok(0)
483    }
484
485    fn get_checkpoint_by_digest(
486        &self,
487        digest: &iota_types::messages_checkpoint::CheckpointDigest,
488    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
489        Ok(self.store().get_checkpoint_by_digest(digest))
490    }
491
492    fn get_checkpoint_by_sequence_number(
493        &self,
494        sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
495    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
496        Ok(self
497            .store()
498            .get_checkpoint_by_sequence_number(sequence_number))
499    }
500
501    fn get_checkpoint_contents_by_digest(
502        &self,
503        digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
504    ) -> iota_types::storage::error::Result<
505        Option<iota_types::messages_checkpoint::CheckpointContents>,
506    > {
507        Ok(self.store().get_checkpoint_contents(digest))
508    }
509
510    fn get_checkpoint_contents_by_sequence_number(
511        &self,
512        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
513    ) -> iota_types::storage::error::Result<
514        Option<iota_types::messages_checkpoint::CheckpointContents>,
515    > {
516        todo!()
517    }
518
519    fn get_transaction(
520        &self,
521        tx_digest: &iota_types::digests::TransactionDigest,
522    ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
523        Ok(self.store().get_transaction(tx_digest).map(Arc::new))
524    }
525
526    fn get_transaction_effects(
527        &self,
528        tx_digest: &iota_types::digests::TransactionDigest,
529    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
530        Ok(self.store().get_transaction_effects(tx_digest))
531    }
532
533    fn get_events(
534        &self,
535        event_digest: &iota_types::digests::TransactionEventsDigest,
536    ) -> iota_types::storage::error::Result<Option<iota_types::effects::TransactionEvents>> {
537        Ok(self.store().get_transaction_events(event_digest))
538    }
539
540    fn get_full_checkpoint_contents_by_sequence_number(
541        &self,
542        _sequence_number: iota_types::messages_checkpoint::CheckpointSequenceNumber,
543    ) -> iota_types::storage::error::Result<
544        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
545    > {
546        todo!()
547    }
548
549    fn get_full_checkpoint_contents(
550        &self,
551        _digest: &iota_types::messages_checkpoint::CheckpointContentsDigest,
552    ) -> iota_types::storage::error::Result<
553        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
554    > {
555        todo!()
556    }
557}
558
559impl<T: Send + Sync, V: store::SimulatorStore + Send + Sync> RestStateReader for Simulacrum<T, V> {
560    fn get_transaction_checkpoint(
561        &self,
562        _digest: &iota_types::digests::TransactionDigest,
563    ) -> iota_types::storage::error::Result<
564        Option<iota_types::messages_checkpoint::CheckpointSequenceNumber>,
565    > {
566        todo!()
567    }
568
569    fn get_lowest_available_checkpoint_objects(
570        &self,
571    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
572        Ok(0)
573    }
574
575    fn get_chain_identifier(
576        &self,
577    ) -> iota_types::storage::error::Result<iota_types::digests::ChainIdentifier> {
578        Ok(self
579            .store()
580            .get_checkpoint_by_sequence_number(0)
581            .unwrap()
582            .digest()
583            .to_owned()
584            .into())
585    }
586
587    fn account_owned_objects_info_iter(
588        &self,
589        _owner: IotaAddress,
590        _cursor: Option<ObjectID>,
591    ) -> iota_types::storage::error::Result<
592        Box<dyn Iterator<Item = iota_types::storage::AccountOwnedObjectInfo> + '_>,
593    > {
594        todo!()
595    }
596
597    fn dynamic_field_iter(
598        &self,
599        _parent: ObjectID,
600        _cursor: Option<ObjectID>,
601    ) -> iota_types::storage::error::Result<
602        Box<
603            dyn Iterator<
604                    Item = (
605                        iota_types::storage::DynamicFieldKey,
606                        iota_types::storage::DynamicFieldIndexInfo,
607                    ),
608                > + '_,
609        >,
610    > {
611        todo!()
612    }
613
614    fn get_coin_info(
615        &self,
616        _coin_type: &StructTag,
617    ) -> iota_types::storage::error::Result<Option<iota_types::storage::CoinInfo>> {
618        todo!()
619    }
620
621    fn get_epoch_last_checkpoint(
622        &self,
623        _epoch_id: iota_types::committee::EpochId,
624    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
625        todo!()
626    }
627}
628
629impl Simulacrum {
630    /// Generate a random transfer transaction.
631    /// TODO: This is here today to make it easier to write tests. But we should
632    /// utilize all the existing code for generating transactions in
633    /// iota-test-transaction-builder by defining a trait
634    /// that both WalletContext and Simulacrum implement. Then we can remove
635    /// this function.
636    pub fn transfer_txn(&mut self, recipient: IotaAddress) -> (Transaction, u64) {
637        let (sender, key) = self.keystore().accounts().next().unwrap();
638        let sender = *sender;
639
640        let object = self
641            .store()
642            .owned_objects(sender)
643            .find(|object| object.is_gas_coin())
644            .unwrap();
645        let gas_coin = GasCoin::try_from(&object).unwrap();
646        let transfer_amount = gas_coin.value() / 2;
647
648        let pt = {
649            let mut builder = ProgrammableTransactionBuilder::new();
650            builder.transfer_iota(recipient, Some(transfer_amount));
651            builder.finish()
652        };
653
654        let kind = TransactionKind::ProgrammableTransaction(pt);
655        let gas_data = GasData {
656            payment: vec![object.compute_object_reference()],
657            owner: sender,
658            price: self.reference_gas_price(),
659            budget: 1_000_000_000,
660        };
661        let tx_data = TransactionData::new_with_gas_data(kind, sender, gas_data);
662        let tx = Transaction::from_data_and_signer(tx_data, vec![key]);
663        (tx, transfer_amount)
664    }
665}
666
667#[cfg(test)]
668mod tests {
669    use std::time::Duration;
670
671    use iota_types::{
672        base_types::IotaAddress, effects::TransactionEffectsAPI, gas_coin::GasCoin,
673        transaction::TransactionDataAPI,
674    };
675    use rand::{SeedableRng, rngs::StdRng};
676
677    use super::*;
678
679    #[test]
680    fn deterministic_genesis() {
681        let rng = StdRng::from_seed([9; 32]);
682        let chain1 = Simulacrum::new_with_rng(rng);
683        let genesis_checkpoint_digest1 = *chain1
684            .store()
685            .get_checkpoint_by_sequence_number(0)
686            .unwrap()
687            .digest();
688
689        let rng = StdRng::from_seed([9; 32]);
690        let chain2 = Simulacrum::new_with_rng(rng);
691        let genesis_checkpoint_digest2 = *chain2
692            .store()
693            .get_checkpoint_by_sequence_number(0)
694            .unwrap()
695            .digest();
696
697        assert_eq!(genesis_checkpoint_digest1, genesis_checkpoint_digest2);
698
699        // Ensure the committees are different when using different seeds
700        let rng = StdRng::from_seed([0; 32]);
701        let chain3 = Simulacrum::new_with_rng(rng);
702
703        assert_ne!(
704            chain1.store().get_committee_by_epoch(0),
705            chain3.store().get_committee_by_epoch(0),
706        );
707    }
708
709    #[test]
710    fn simple() {
711        let steps = 10;
712        let mut chain = Simulacrum::new();
713
714        let clock = chain.store().get_clock();
715        let start_time_ms = clock.timestamp_ms();
716        println!("clock: {:#?}", clock);
717        for _ in 0..steps {
718            chain.advance_clock(Duration::from_millis(1));
719            chain.create_checkpoint();
720            let clock = chain.store().get_clock();
721            println!("clock: {:#?}", clock);
722        }
723        let end_time_ms = chain.store().get_clock().timestamp_ms();
724        assert_eq!(end_time_ms - start_time_ms, steps);
725        dbg!(chain.store().get_highest_checkpoint());
726    }
727
728    #[test]
729    fn simple_epoch() {
730        let steps = 10;
731        let mut chain = Simulacrum::new();
732
733        let start_epoch = chain.store.get_highest_checkpoint().unwrap().epoch;
734        for i in 0..steps {
735            chain.advance_epoch();
736            chain.advance_clock(Duration::from_millis(1));
737            chain.create_checkpoint();
738            println!("{i}");
739        }
740        let end_epoch = chain.store.get_highest_checkpoint().unwrap().epoch;
741        assert_eq!(end_epoch - start_epoch, steps);
742        dbg!(chain.store().get_highest_checkpoint());
743    }
744
745    #[test]
746    fn transfer() {
747        let mut sim = Simulacrum::new();
748        let recipient = IotaAddress::random_for_testing_only();
749        let (tx, transfer_amount) = sim.transfer_txn(recipient);
750
751        let gas_id = tx.data().transaction_data().gas_data().payment[0].0;
752        let effects = sim.execute_transaction(tx).unwrap().0;
753        let gas_summary = effects.gas_cost_summary();
754        let gas_paid = gas_summary.net_gas_usage();
755
756        assert_eq!(
757            (transfer_amount as i64 - gas_paid) as u64,
758            store::SimulatorStore::get_object(sim.store(), &gas_id)
759                .and_then(|object| GasCoin::try_from(&object).ok())
760                .unwrap()
761                .value()
762        );
763
764        assert_eq!(
765            transfer_amount,
766            sim.store()
767                .owned_objects(recipient)
768                .next()
769                .and_then(|object| GasCoin::try_from(&object).ok())
770                .unwrap()
771                .value()
772        );
773
774        let checkpoint = sim.create_checkpoint();
775
776        assert_eq!(&checkpoint.epoch_rolling_gas_cost_summary, gas_summary);
777        assert_eq!(checkpoint.network_total_transactions, 2); // genesis + 1 txn
778    }
779}