iota_transactional_test_runner/
test_adapter.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5//! This module contains the transactional test runner instantiation for the
6//! IOTA adapter
7
8use std::{
9    collections::{BTreeMap, BTreeSet},
10    fmt::{self, Write},
11    path::{Path, PathBuf},
12    sync::Arc,
13    time::Duration,
14};
15
16use anyhow::{Context, anyhow, bail};
17use async_trait::async_trait;
18use bimap::btree::BiBTreeMap;
19use criterion::Criterion;
20use fastcrypto::{
21    ed25519::Ed25519KeyPair,
22    encoding::{Base64, Encoding},
23    traits::ToFromBytes,
24};
25use iota_core::authority::{AuthorityState, test_authority_builder::TestAuthorityBuilder};
26use iota_framework::DEFAULT_FRAMEWORK_PATH;
27use iota_graphql_rpc::test_infra::cluster::SnapshotLagConfig;
28use iota_json_rpc_api::QUERY_MAX_RESULT_LIMIT;
29use iota_json_rpc_types::{
30    DevInspectResults, DryRunTransactionBlockResponse, IotaExecutionStatus,
31    IotaTransactionBlockEffects, IotaTransactionBlockEffectsAPI, IotaTransactionBlockEvents,
32};
33use iota_protocol_config::{Chain, ProtocolConfig};
34use iota_storage::{
35    key_value_store::TransactionKeyValueStore, key_value_store_metrics::KeyValueStoreMetrics,
36};
37use iota_swarm_config::genesis_config::AccountConfig;
38use iota_types::{
39    IOTA_CLOCK_OBJECT_ID, IOTA_DENY_LIST_OBJECT_ID, IOTA_FRAMEWORK_ADDRESS,
40    IOTA_FRAMEWORK_PACKAGE_ID, IOTA_RANDOMNESS_STATE_OBJECT_ID, IOTA_SYSTEM_ADDRESS,
41    IOTA_SYSTEM_PACKAGE_ID, IOTA_SYSTEM_STATE_OBJECT_ID, MOVE_STDLIB_ADDRESS,
42    MOVE_STDLIB_PACKAGE_ID, STARDUST_ADDRESS, STARDUST_PACKAGE_ID,
43    base_types::{
44        IOTA_ADDRESS_LENGTH, IotaAddress, ObjectID, ObjectRef, SequenceNumber, VersionNumber,
45    },
46    committee::EpochId,
47    crypto::{AccountKeyPair, RandomnessRound, get_authority_key_pair, get_key_pair_from_rng},
48    digests::{ConsensusCommitDigest, TransactionDigest, TransactionEventsDigest},
49    effects::{TransactionEffects, TransactionEffectsAPI, TransactionEvents},
50    event::Event,
51    execution_status::ExecutionStatus,
52    gas::GasCostSummary,
53    messages_checkpoint::{
54        CheckpointContents, CheckpointContentsDigest, CheckpointSequenceNumber, VerifiedCheckpoint,
55    },
56    move_package::MovePackage,
57    object::{self, GAS_VALUE_FOR_TESTING, Object, bounded_visitor::BoundedVisitor},
58    programmable_transaction_builder::ProgrammableTransactionBuilder,
59    storage::{ObjectStore, ReadStore, RestStateReader},
60    transaction::{
61        Argument, CallArg, Command, ProgrammableTransaction, Transaction, TransactionData,
62        TransactionDataAPI, TransactionKind, VerifiedTransaction,
63    },
64    utils::{to_sender_signed_transaction, to_sender_signed_transaction_with_multi_signers},
65};
66use move_binary_format::CompiledModule;
67use move_bytecode_utils::module_cache::GetModule;
68use move_command_line_common::files::verify_and_create_named_address_mapping;
69use move_compiler::{
70    Flags, FullyCompiledProgram,
71    editions::{Edition, Flavor},
72    shared::{NumberFormat, NumericalAddress, PackageConfig, PackagePaths},
73};
74use move_core_types::{
75    account_address::AccountAddress,
76    ident_str,
77    identifier::IdentStr,
78    language_storage::{ModuleId, TypeTag},
79    parsing::address::ParsedAddress,
80};
81use move_symbol_pool::Symbol;
82use move_transactional_test_runner::{
83    framework::{
84        CompiledState, MaybeNamedCompiledModule, MoveTestAdapter, compile_any, store_modules,
85    },
86    tasks::{InitCommand, RunCommand, SyntaxChoice, TaskCommand, TaskInput},
87};
88use move_vm_runtime::session::SerializedReturnValues;
89use once_cell::sync::Lazy;
90use rand::{Rng, SeedableRng, rngs::StdRng};
91use tempfile::{NamedTempFile, tempdir};
92
93use crate::{
94    TransactionalAdapter, ValidatorWithFullnode, args::*, offchain_state::OffchainStateReader,
95    programmable_transaction_test_parser::parser::ParsedCommand,
96    simulator_persisted_store::PersistedStore,
97};
98
99#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
100pub enum FakeID {
101    Known(ObjectID),
102    Enumerated(u64, u64),
103}
104
105const DEFAULT_GAS_PRICE: u64 = 1_000;
106
107const WELL_KNOWN_OBJECTS: &[ObjectID] = &[
108    MOVE_STDLIB_PACKAGE_ID,
109    IOTA_FRAMEWORK_PACKAGE_ID,
110    IOTA_SYSTEM_PACKAGE_ID,
111    STARDUST_PACKAGE_ID,
112    IOTA_SYSTEM_STATE_OBJECT_ID,
113    IOTA_CLOCK_OBJECT_ID,
114    IOTA_DENY_LIST_OBJECT_ID,
115    IOTA_RANDOMNESS_STATE_OBJECT_ID,
116];
117// TODO use the file name as a seed
118const RNG_SEED: [u8; 32] = [
119    21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130, 157,
120    179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
121];
122
123const DEFAULT_GAS_BUDGET: u64 = 5_000_000_000;
124const GAS_FOR_TESTING: u64 = GAS_VALUE_FOR_TESTING;
125
126const DEFAULT_CHAIN_START_TIMESTAMP: u64 = 0;
127
128/// Extra args related to configuring the indexer and reader.
129// TODO: the configs are still tied to the indexer crate, eventually we'd like a
130// new command that is more agnostic
131pub struct OffChainConfig {
132    pub snapshot_config: SnapshotLagConfig,
133    pub epochs_to_keep: Option<u64>,
134    /// Dir for simulacrum to write checkpoint files to. To be passed to the
135    /// offchain indexer if it uses file-based ingestion.
136    pub data_ingestion_path: PathBuf,
137    /// URL for the IOTA REST API. To be passed to the offchain indexer if it
138    /// uses the REST API.
139    pub rest_api_url: Option<String>,
140}
141
142struct AdapterInitConfig {
143    additional_mapping: BTreeMap<String, NumericalAddress>,
144    account_names: BTreeSet<String>,
145    protocol_config: ProtocolConfig,
146    is_simulator: bool,
147    custom_validator_account: bool,
148    reference_gas_price: Option<u64>,
149    default_gas_price: Option<u64>,
150    flavor: Option<Flavor>,
151    /// Configuration for offchain state reader read from the file itself, and
152    /// can be passed to the specific indexing and reader flavor.
153    offchain_config: Option<OffChainConfig>,
154}
155
156pub struct IotaTestAdapter {
157    pub(crate) compiled_state: CompiledState,
158    /// For upgrades: maps an upgraded package name to the original package
159    /// name.
160    package_upgrade_mapping: BTreeMap<Symbol, Symbol>,
161    accounts: BTreeMap<String, TestAccount>,
162    default_account: TestAccount,
163    default_syntax: SyntaxChoice,
164    object_enumeration: BiBTreeMap<ObjectID, FakeID>,
165    /// Mapping from task ID to a transaction digest, for use in named variable
166    /// substitution.
167    digest_enumeration: BTreeMap<u64, TransactionDigest>,
168    next_fake: (u64, u64),
169    gas_price: u64,
170    pub(crate) staged_modules: BTreeMap<Symbol, StagedPackage>,
171    is_simulator: bool,
172    /// If `is_simulator` is true, the executor will be a `Simulacrum`, and this
173    /// will be a `RestStateReader` that can be used to spawn the equivalent
174    /// of a fullnode rest api. This can then be used to serve an indexer
175    /// that reads from said rest api service.
176    pub read_replica: Option<Arc<dyn RestStateReader + Send + Sync>>,
177    /// Configuration for offchain state reader read from the file itself, and
178    /// can be passed to the specific indexing and reader flavor.
179    pub offchain_config: Option<OffChainConfig>,
180    /// A trait encapsulating methods to interact with offchain state.
181    pub offchain_reader: Option<Box<dyn OffchainStateReader>>,
182    pub(crate) executor: Box<dyn TransactionalAdapter>,
183}
184
185pub(crate) struct StagedPackage {
186    file: NamedTempFile,
187    syntax: SyntaxChoice,
188    modules: Vec<MaybeNamedCompiledModule>,
189    pub(crate) digest: Vec<u8>,
190}
191
192impl AdapterInitConfig {
193    fn from_args(init_cmd: InitCommand, iota_args: IotaInitArgs) -> Self {
194        let InitCommand { named_addresses } = init_cmd;
195        let IotaInitArgs {
196            accounts,
197            protocol_version,
198            max_gas,
199            move_binary_format_version,
200            simulator,
201            custom_validator_account,
202            reference_gas_price,
203            default_gas_price,
204            snapshot_config,
205            flavor,
206            epochs_to_keep,
207            data_ingestion_path,
208            rest_api_url,
209        } = iota_args;
210
211        let map = verify_and_create_named_address_mapping(named_addresses).unwrap();
212        let accounts = accounts
213            .map(|v| v.into_iter().collect::<BTreeSet<_>>())
214            .unwrap_or_default();
215
216        let mut protocol_config = if let Some(protocol_version) = protocol_version {
217            ProtocolConfig::get_for_version(protocol_version.into(), Chain::Unknown)
218        } else {
219            ProtocolConfig::get_for_max_version_UNSAFE()
220        };
221        if let Some(version) = move_binary_format_version {
222            protocol_config.set_move_binary_format_version_for_testing(version);
223        }
224        if let Some(mx_tx_gas_override) = max_gas {
225            if simulator {
226                panic!("Cannot set max gas in simulator mode");
227            }
228            protocol_config.set_max_tx_gas_for_testing(mx_tx_gas_override)
229        }
230        if custom_validator_account && !simulator {
231            panic!("Can only set custom validator account in simulator mode");
232        }
233        if reference_gas_price.is_some() && !simulator {
234            panic!("Can only set reference gas price in simulator mode");
235        }
236
237        let offchain_config = if simulator {
238            Some(OffChainConfig {
239                snapshot_config,
240                epochs_to_keep,
241                data_ingestion_path: data_ingestion_path.unwrap_or(tempdir().unwrap().keep()),
242                rest_api_url,
243            })
244        } else {
245            None
246        };
247
248        Self {
249            additional_mapping: map,
250            account_names: accounts,
251            protocol_config,
252            is_simulator: simulator,
253            custom_validator_account,
254            reference_gas_price,
255            default_gas_price,
256            flavor,
257            offchain_config,
258        }
259    }
260}
261
262#[derive(Debug)]
263struct TestAccount {
264    address: IotaAddress,
265    key_pair: AccountKeyPair,
266    gas: ObjectID,
267}
268
269#[derive(Debug)]
270struct TxnSummary {
271    created: Vec<ObjectID>,
272    mutated: Vec<ObjectID>,
273    unwrapped: Vec<ObjectID>,
274    deleted: Vec<ObjectID>,
275    unwrapped_then_deleted: Vec<ObjectID>,
276    wrapped: Vec<ObjectID>,
277    unchanged_shared: Vec<ObjectID>,
278    events: Vec<Event>,
279    gas_summary: GasCostSummary,
280}
281
282#[async_trait]
283impl MoveTestAdapter<'_> for IotaTestAdapter {
284    type ExtraPublishArgs = IotaPublishArgs;
285    type ExtraRunArgs = IotaRunArgs;
286    type ExtraInitArgs = IotaInitArgs;
287    type ExtraValueArgs = IotaExtraValueArgs;
288    type Subcommand = IotaSubcommand<Self::ExtraValueArgs, Self::ExtraRunArgs>;
289
290    fn render_command_input(
291        &self,
292        task: &TaskInput<
293            TaskCommand<
294                Self::ExtraInitArgs,
295                Self::ExtraPublishArgs,
296                Self::ExtraValueArgs,
297                Self::ExtraRunArgs,
298                Self::Subcommand,
299            >,
300        >,
301    ) -> Option<String> {
302        match &task.command {
303            TaskCommand::Subcommand(IotaSubcommand::ProgrammableTransaction(..)) => {
304                let data_str = std::fs::read_to_string(task.data.as_ref()?)
305                    .ok()?
306                    .trim()
307                    .to_string();
308                Some(format!("{}\n{}", task.task_text, data_str))
309            }
310            TaskCommand::Init(_, _)
311            | TaskCommand::PrintBytecode(_)
312            | TaskCommand::Publish(_, _)
313            | TaskCommand::Run(_, _)
314            | TaskCommand::Subcommand(..) => None,
315        }
316    }
317
318    fn compiled_state(&mut self) -> &mut CompiledState {
319        &mut self.compiled_state
320    }
321
322    fn default_syntax(&self) -> SyntaxChoice {
323        self.default_syntax
324    }
325
326    async fn init(
327        default_syntax: SyntaxChoice,
328        pre_compiled_deps: Option<Arc<FullyCompiledProgram>>,
329        task_opt: Option<
330            move_transactional_test_runner::tasks::TaskInput<(
331                move_transactional_test_runner::tasks::InitCommand,
332                Self::ExtraInitArgs,
333            )>,
334        >,
335        _path: &Path,
336    ) -> (Self, Option<String>) {
337        let rng = StdRng::from_seed(RNG_SEED);
338        assert!(
339            pre_compiled_deps.is_some(),
340            "Must populate 'pre_compiled_deps' with IOTA framework"
341        );
342
343        // Unpack the init arguments
344        let AdapterInitConfig {
345            additional_mapping,
346            account_names,
347            protocol_config,
348            is_simulator,
349            custom_validator_account,
350            reference_gas_price,
351            default_gas_price,
352            flavor,
353            offchain_config,
354        } = match task_opt.map(|t| t.command) {
355            Some((init_cmd, iota_args)) => AdapterInitConfig::from_args(init_cmd, iota_args),
356            None => AdapterInitConfig::default(),
357        };
358
359        let (
360            executor,
361            AccountSetup {
362                default_account,
363                accounts,
364                named_address_mapping,
365                objects,
366                account_objects,
367            },
368            read_replica,
369        ) = if is_simulator {
370            init_sim_executor(
371                rng,
372                account_names,
373                additional_mapping,
374                &protocol_config,
375                custom_validator_account,
376                reference_gas_price,
377                offchain_config
378                    .as_ref()
379                    .unwrap()
380                    .data_ingestion_path
381                    .clone(),
382            )
383            .await
384        } else {
385            init_val_fullnode_executor(rng, account_names, additional_mapping, &protocol_config)
386                .await
387        };
388
389        let object_ids = objects.iter().map(|obj| obj.id()).collect::<Vec<_>>();
390
391        let mut test_adapter = Self {
392            is_simulator,
393            offchain_reader: None,
394            executor,
395            offchain_config,
396            read_replica,
397            compiled_state: CompiledState::new(
398                named_address_mapping,
399                pre_compiled_deps,
400                Some(NumericalAddress::new(
401                    AccountAddress::ZERO.into_bytes(),
402                    NumberFormat::Hex,
403                )),
404                Some(Edition::DEVELOPMENT),
405                flavor.or(Some(Flavor::Iota)),
406            ),
407            package_upgrade_mapping: BTreeMap::new(),
408            accounts,
409            default_account,
410            default_syntax,
411            object_enumeration: BiBTreeMap::new(),
412            digest_enumeration: BTreeMap::new(),
413            next_fake: (0, 0),
414            // TODO: make this configurable
415            gas_price: default_gas_price.unwrap_or(DEFAULT_GAS_PRICE),
416            staged_modules: BTreeMap::new(),
417        };
418
419        for well_known in WELL_KNOWN_OBJECTS.iter().copied() {
420            test_adapter
421                .object_enumeration
422                .insert(well_known, FakeID::Known(well_known));
423        }
424        let mut output = String::new();
425        for (account, obj_id) in account_objects {
426            let fake = test_adapter.enumerate_fake(obj_id);
427            if !output.is_empty() {
428                output.push_str(", ")
429            }
430            write!(output, "{account}: object({fake})").unwrap()
431        }
432        for object_id in object_ids {
433            test_adapter.enumerate_fake(object_id);
434        }
435        let output = if output.is_empty() {
436            None
437        } else {
438            Some(output)
439        };
440        (test_adapter, output)
441    }
442
443    async fn publish_modules(
444        &mut self,
445        modules: Vec<MaybeNamedCompiledModule>,
446        gas_budget: Option<u64>,
447        extra: Self::ExtraPublishArgs,
448    ) -> anyhow::Result<(Option<String>, Vec<MaybeNamedCompiledModule>)> {
449        self.next_task();
450        let IotaPublishArgs {
451            sender,
452            upgradeable,
453            dependencies,
454            gas_price,
455        } = extra;
456        let named_addr_opt = modules.first().unwrap().named_address;
457        let first_module_name = modules.first().unwrap().module.self_id().name().to_string();
458        let modules_bytes = modules
459            .iter()
460            .map(|m| {
461                let mut module_bytes = vec![];
462                m.module
463                    .serialize_with_version(m.module.version, &mut module_bytes)
464                    .unwrap();
465                Ok(module_bytes)
466            })
467            .collect::<anyhow::Result<_>>()?;
468        let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
469        let mapping = &self.compiled_state.named_address_mapping;
470        let mut dependencies: Vec<_> = dependencies
471            .into_iter()
472            .map(|d| {
473                let Some(addr) = mapping.get(&d) else {
474                    bail!("There is no published module address corresponding to name address {d}");
475                };
476                let id: ObjectID = addr.into_inner().into();
477                Ok(id)
478            })
479            .collect::<Result<_, _>>()?;
480        let gas_price = gas_price.unwrap_or(self.gas_price);
481        // we are assuming that all packages depend on Move Stdlib and IOTA Framework,
482        // so these don't have to be provided explicitly as parameters
483        dependencies.extend([MOVE_STDLIB_PACKAGE_ID, IOTA_FRAMEWORK_PACKAGE_ID]);
484        let data = |sender, gas| {
485            let mut builder = ProgrammableTransactionBuilder::new();
486            if upgradeable {
487                let cap = builder.publish_upgradeable(modules_bytes, dependencies);
488                builder.transfer_arg(sender, cap);
489            } else {
490                builder.publish_immutable(modules_bytes, dependencies);
491            };
492            let pt = builder.finish();
493            TransactionData::new_programmable(sender, gas, pt, gas_budget, gas_price)
494        };
495        let transaction = self.sign_txn(sender, data);
496        let summary = self.execute_txn(transaction).await?;
497        let created_package = summary
498            .created
499            .iter()
500            .find_map(|id| {
501                let object = self.get_object(id, None).unwrap();
502                let package = object.data.try_as_package()?;
503                if package
504                    .serialized_module_map()
505                    .get(&first_module_name)
506                    .is_some()
507                {
508                    Some(*id)
509                } else {
510                    None
511                }
512            })
513            .unwrap();
514        let package_addr = NumericalAddress::new(created_package.into_bytes(), NumberFormat::Hex);
515        if let Some(named_addr) = named_addr_opt {
516            let prev_package = self
517                .compiled_state
518                .named_address_mapping
519                .insert(named_addr.to_string(), package_addr);
520            match prev_package.map(|a| a.into_inner()) {
521                Some(addr) if addr != AccountAddress::ZERO => panic!(
522                    "Cannot reuse named address '{named_addr}' for multiple packages. \
523                It should be set to 0 initially"
524                ),
525                _ => (),
526            }
527        }
528        let output = self.object_summary_output(&summary, /* summarize */ false);
529        let published_modules = self
530            .get_object(&created_package, None)
531            .unwrap()
532            .data
533            .try_as_package()
534            .unwrap()
535            .serialized_module_map()
536            .values()
537            .map(|published_module_bytes| MaybeNamedCompiledModule {
538                named_address: named_addr_opt,
539                module: CompiledModule::deserialize_with_defaults(published_module_bytes).unwrap(),
540                source_map: None,
541            })
542            .collect();
543        Ok((output, published_modules))
544    }
545
546    async fn call_function(
547        &mut self,
548        module_id: &ModuleId,
549        function: &IdentStr,
550        type_args: Vec<TypeTag>,
551        signers: Vec<ParsedAddress>,
552        args: Vec<IotaValue>,
553        gas_budget: Option<u64>,
554        extra: Self::ExtraRunArgs,
555    ) -> anyhow::Result<(Option<String>, SerializedReturnValues)> {
556        self.next_task();
557        let IotaRunArgs { summarize, .. } = extra;
558        let transaction = self.build_function_call_tx(
559            module_id, function, type_args, signers, args, gas_budget, extra,
560        )?;
561        let summary = self.execute_txn(transaction).await?;
562        let output = self.object_summary_output(&summary, summarize);
563        let empty = SerializedReturnValues {
564            mutable_reference_outputs: vec![],
565            return_values: vec![],
566        };
567        Ok((output, empty))
568    }
569
570    async fn handle_subcommand(
571        &mut self,
572        task: TaskInput<Self::Subcommand>,
573    ) -> anyhow::Result<Option<String>> {
574        self.next_task();
575        let TaskInput {
576            command,
577            name,
578            number,
579            start_line,
580            command_lines_stop,
581            stop_line,
582            data,
583            task_text,
584        } = task;
585        macro_rules! get_obj {
586            ($fake_id:ident, $version:expr) => {{
587                let id = match self.fake_to_real_object_id($fake_id) {
588                    None => bail!(
589                        "task {}, lines {}-{}\n{}\n. Unbound fake id {}",
590                        number,
591                        start_line,
592                        command_lines_stop,
593                        task_text,
594                        $fake_id
595                    ),
596                    Some(res) => res,
597                };
598                match self.get_object(&id, $version) {
599                    Err(_) => return Ok(Some(format!("No object at id {}", $fake_id))),
600                    Ok(obj) => obj,
601                }
602            }};
603            ($fake_id:ident) => {{ get_obj!($fake_id, None) }};
604        }
605        match command {
606            IotaSubcommand::RunGraphql(RunGraphqlCommand {
607                show_usage,
608                show_headers,
609                show_service_version,
610                wait_for_checkpoint_pruned,
611                cursors,
612            }) => {
613                let file = data.ok_or_else(|| anyhow::anyhow!("Missing GraphQL query"))?;
614                let contents = std::fs::read_to_string(file.path())?;
615                let offchain_reader = self
616                    .offchain_reader
617                    .as_ref()
618                    .ok_or_else(|| anyhow::anyhow!("Offchain reader not set"))?;
619                let highest_checkpoint =
620                    self.executor.try_get_latest_checkpoint_sequence_number()?;
621                offchain_reader
622                    .wait_for_checkpoint_catchup(highest_checkpoint, Duration::from_secs(60))
623                    .await;
624
625                // wait_for_objects_snapshot_catchup(graphql_client,
626                // Duration::from_secs(180)).await;
627
628                if let Some(checkpoint_to_prune) = wait_for_checkpoint_pruned {
629                    offchain_reader
630                        .wait_for_pruned_checkpoint(checkpoint_to_prune, Duration::from_secs(60))
631                        .await;
632                }
633
634                let interpolated =
635                    self.interpolate_query(&contents, &cursors, highest_checkpoint)?;
636                let resp = offchain_reader
637                    .execute_graphql(interpolated.trim().to_owned(), show_usage)
638                    .await?;
639
640                let mut output = vec![];
641                if show_headers {
642                    output.push(format!("Headers: {:#?}", resp.http_headers.unwrap()));
643                }
644                if show_service_version {
645                    output.push(format!(
646                        "Service version: {}",
647                        resp.service_version.unwrap()
648                    ));
649                }
650                output.push(format!("Response: {}", resp.response_body));
651
652                Ok(Some(output.join("\n")))
653            }
654            IotaSubcommand::ViewCheckpoint => {
655                let latest_chk = self.executor.try_get_latest_checkpoint_sequence_number()?;
656                let chk = self
657                    .executor
658                    .try_get_checkpoint_by_sequence_number(latest_chk)?
659                    .unwrap();
660                Ok(Some(format!("{}", chk.data())))
661            }
662            IotaSubcommand::CreateCheckpoint(CreateCheckpointCommand { count }) => {
663                for _ in 0..count.unwrap_or(1) {
664                    self.executor.create_checkpoint().await?;
665                }
666                let latest_chk = self.executor.try_get_latest_checkpoint_sequence_number()?;
667                Ok(Some(format!("Checkpoint created: {latest_chk}")))
668            }
669            IotaSubcommand::AdvanceEpoch(AdvanceEpochCommand { count }) => {
670                for _ in 0..count.unwrap_or(1) {
671                    self.executor.advance_epoch().await?;
672                }
673                let epoch = self.try_get_latest_epoch_id()?;
674                Ok(Some(format!("Epoch advanced: {epoch}")))
675            }
676            IotaSubcommand::AdvanceClock(AdvanceClockCommand { duration_ns }) => {
677                self.executor
678                    .advance_clock(Duration::from_nanos(duration_ns))
679                    .await?;
680                Ok(None)
681            }
682            IotaSubcommand::SetRandomState(SetRandomStateCommand {
683                randomness_round,
684                random_bytes,
685                randomness_initial_version,
686            }) => {
687                let random_bytes = Base64::decode(&random_bytes)
688                    .map_err(|e| anyhow!("Failed to decode random bytes as Base64: {e}"))?;
689
690                let latest_epoch = self.try_get_latest_epoch_id()?;
691                let tx = VerifiedTransaction::new_randomness_state_update(
692                    latest_epoch,
693                    RandomnessRound(randomness_round),
694                    random_bytes,
695                    SequenceNumber::from_u64(randomness_initial_version),
696                );
697
698                self.execute_txn(tx.into()).await?;
699                Ok(None)
700            }
701            IotaSubcommand::ViewObject(ViewObjectCommand { id: fake_id }) => {
702                let obj = get_obj!(fake_id);
703                Ok(Some(match &obj.data {
704                    object::Data::Move(move_obj) => {
705                        let layout = move_obj.get_layout(&&*self).unwrap();
706                        let move_struct =
707                            BoundedVisitor::deserialize_struct(move_obj.contents(), &layout)
708                                .unwrap();
709
710                        self.stabilize_str(format!(
711                            "Owner: {}\nVersion: {}\nContents: {:#}",
712                            &obj.owner,
713                            obj.version().value(),
714                            move_struct
715                        ))
716                    }
717                    object::Data::Package(package) => {
718                        let num_modules = package.serialized_module_map().len();
719                        let modules = package
720                            .serialized_module_map()
721                            .keys()
722                            .cloned()
723                            .collect::<Vec<_>>()
724                            .join(", ");
725                        assert!(!modules.is_empty());
726                        if num_modules > 1 {
727                            format!("{fake_id}::{{{modules}}}")
728                        } else {
729                            format!("{fake_id}::{modules}")
730                        }
731                    }
732                }))
733            }
734            IotaSubcommand::TransferObject(TransferObjectCommand {
735                id: fake_id,
736                recipient,
737                sender,
738                gas_budget,
739                gas_price,
740            }) => {
741                let mut builder = ProgrammableTransactionBuilder::new();
742                let obj_arg = IotaValue::Object(fake_id, None).into_argument(&mut builder, self)?;
743                let recipient = match self.accounts.get(&recipient) {
744                    Some(test_account) => test_account.address,
745                    None => panic!("Unbound account {recipient}"),
746                };
747                let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
748                let gas_price: u64 = gas_price.unwrap_or(self.gas_price);
749                let transaction = self.sign_txn(sender, |sender, gas| {
750                    let rec_arg = builder.pure(recipient).unwrap();
751                    builder.command(iota_types::transaction::Command::TransferObjects(
752                        vec![obj_arg],
753                        rec_arg,
754                    ));
755                    let pt = builder.finish();
756                    TransactionData::new_programmable(sender, gas, pt, gas_budget, gas_price)
757                });
758                let summary = self.execute_txn(transaction).await?;
759                let output = self.object_summary_output(&summary, /* summarize */ false);
760                Ok(output)
761            }
762            IotaSubcommand::ConsensusCommitPrologue(ConsensusCommitPrologueCommand {
763                timestamp_ms,
764            }) => {
765                let transaction = VerifiedTransaction::new_consensus_commit_prologue_v1(
766                    0,
767                    0,
768                    timestamp_ms,
769                    ConsensusCommitDigest::default(),
770                    Vec::new(),
771                );
772                let summary = self.execute_txn(transaction.into()).await?;
773                let output = self.object_summary_output(&summary, /* summarize */ false);
774                Ok(output)
775            }
776            IotaSubcommand::ProgrammableTransaction(ProgrammableTransactionCommand {
777                sender,
778                sponsor,
779                gas_budget,
780                gas_price,
781                gas_payment,
782                dev_inspect,
783                dry_run,
784                inputs,
785            }) => {
786                if dev_inspect && self.is_simulator() {
787                    bail!("Dev inspect is not supported on simulator mode");
788                }
789
790                if dry_run && dev_inspect {
791                    bail!("Cannot set both dev-inspect and dry-run");
792                }
793
794                let inputs = self.compiled_state().resolve_args(inputs)?;
795                let inputs: Vec<CallArg> = inputs
796                    .into_iter()
797                    .map(|arg| arg.into_call_arg(self))
798                    .collect::<anyhow::Result<_>>()?;
799                let file = data.ok_or_else(|| {
800                    anyhow::anyhow!("Missing commands for programmable transaction")
801                })?;
802                let contents = std::fs::read_to_string(file.path())?;
803                let commands = ParsedCommand::parse_vec(&contents)?;
804                let staged = &self.staged_modules;
805                let state = &self.compiled_state;
806                let commands = commands
807                    .into_iter()
808                    .map(|c| {
809                        c.into_command(
810                            &|p| {
811                                let modules = staged
812                                    .get(&Symbol::from(p))?
813                                    .modules
814                                    .iter()
815                                    .map(|m| {
816                                        let mut buf = vec![];
817                                        m.module
818                                            .serialize_with_version(m.module.version, &mut buf)
819                                            .unwrap();
820                                        buf
821                                    })
822                                    .collect();
823                                Some(modules)
824                            },
825                            &|s| Some(state.resolve_named_address(s)),
826                        )
827                    })
828                    .collect::<anyhow::Result<Vec<Command>>>()?;
829                let summary = if !dev_inspect && !dry_run {
830                    let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
831                    let gas_price = gas_price.unwrap_or(self.gas_price);
832                    let transaction = self.sign_sponsor_txn(
833                        sender,
834                        sponsor,
835                        gas_payment.unwrap_or_default(),
836                        |sender, sponsor, gas| {
837                            TransactionData::new_programmable_allow_sponsor(
838                                sender,
839                                gas,
840                                ProgrammableTransaction { inputs, commands },
841                                gas_budget,
842                                gas_price,
843                                sponsor,
844                            )
845                        },
846                    );
847                    self.execute_txn(transaction).await?
848                } else if dry_run {
849                    let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
850                    let gas_price = gas_price.unwrap_or(self.gas_price);
851                    let sender = self.get_sender(sender);
852                    let sponsor = sponsor.map_or(sender, |a| self.get_sender(Some(a)));
853
854                    let payments = self.get_payments(sponsor, gas_payment.unwrap_or_default());
855
856                    let transaction = TransactionData::new_programmable(
857                        sender.address,
858                        payments,
859                        ProgrammableTransaction { inputs, commands },
860                        gas_budget,
861                        gas_price,
862                    );
863                    self.dry_run(transaction).await?
864                } else {
865                    assert!(
866                        gas_budget.is_none(),
867                        "Meaningless to set gas budget with dev-inspect"
868                    );
869                    let sender_address = self.get_sender(sender).address;
870                    let transaction =
871                        TransactionKind::ProgrammableTransaction(ProgrammableTransaction {
872                            inputs,
873                            commands,
874                        });
875                    self.dev_inspect(sender_address, transaction, gas_price)
876                        .await?
877                };
878                let output = self.object_summary_output(&summary, /* summarize */ false);
879                Ok(output)
880            }
881            IotaSubcommand::UpgradePackage(UpgradePackageCommand {
882                package,
883                upgrade_capability,
884                dependencies,
885                sender,
886                gas_budget,
887                dry_run,
888                syntax,
889                policy,
890                gas_price,
891            }) => {
892                let syntax = syntax.unwrap_or_else(|| self.default_syntax());
893                // zero out the package name
894                let zero =
895                    NumericalAddress::new(AccountAddress::ZERO.into_bytes(), NumberFormat::Hex);
896                let before_upgrade = {
897                    // not binding `m` separately results in some strange async capture error
898                    let m = &mut self.compiled_state.named_address_mapping;
899                    let Some(before) = m.insert(package.clone(), zero) else {
900                        panic!("Unbound package '{package}' for upgrade");
901                    };
902                    before
903                };
904
905                // Override address mappings for compilation when upgrading. Each dependency is
906                // set to its original address (if that dependency had been
907                // upgraded previously). This ensures upgraded dependencies are
908                // resolved during compilation--without this workaround, the compiler will
909                // find multiple definitions of the same module). We persist the original
910                // package name and addresses, which we restore before
911                // performing an upgrade transaction below.
912                let mut original_package_addrs = vec![];
913                for dep in dependencies.iter() {
914                    let named_address_mapping = &mut self.compiled_state.named_address_mapping;
915                    let dep = &Symbol::from(dep.as_str());
916                    let Some(orig_package) = self.package_upgrade_mapping.get(dep) else {
917                        continue;
918                    };
919                    let Some(orig_package_address) =
920                        named_address_mapping.insert(orig_package.to_string(), zero)
921                    else {
922                        continue;
923                    };
924                    original_package_addrs.push((*orig_package, orig_package_address));
925                    let dep_address = named_address_mapping
926                        .insert(dep.to_string(), orig_package_address)
927                        .unwrap_or_else(||
928                            panic!("Internal error: expected dependency {dep} in map when overriding address.")
929                        );
930                    original_package_addrs.push((*dep, dep_address));
931                }
932                let gas_price = gas_price.unwrap_or(self.gas_price);
933
934                let result = compile_any(
935                    self,
936                    "upgrade",
937                    syntax,
938                    name,
939                    number,
940                    start_line,
941                    command_lines_stop,
942                    stop_line,
943                    data,
944                    |adapter, modules| async {
945                        // Restore the original package addresses for dependencies before performing the upgrade.
946                        // This ensures package upgrades are properly linked at their correct addresses
947                        // (previously, addresses referred to the dependency's original package for compilation).
948                        for (name, addr) in original_package_addrs {
949                            adapter
950                                .compiled_state()
951                                .named_address_mapping
952                                .insert(name.to_string(), addr)
953                                .unwrap_or_else(|| panic!("Internal error: expected dependency {name} in map when restoring address."));
954                        }
955
956                        let upgraded_name = modules.first().unwrap().named_address.unwrap();
957                        let package = &Symbol::from(package.as_str());
958                        let original_name = adapter
959                            .package_upgrade_mapping
960                            .get(package)
961                            .unwrap_or(package);
962                        // Persist the upgraded package name with its original package name, so that we can
963                        // refer to the original package name when compiling (see above on overridden addresses).
964                        adapter
965                            .package_upgrade_mapping
966                            .insert(upgraded_name, *original_name);
967
968                        let output = adapter.upgrade_package(
969                            before_upgrade,
970                            &modules,
971                            upgrade_capability,
972                            dependencies,
973                            sender,
974                            gas_budget,
975                            dry_run,
976                            policy,
977                            gas_price,
978                        ).await?;
979                        Ok((output, modules))
980                    },
981                )
982                .await;
983                // if the package name was not updated, reset it to the value before the upgrade
984                let package_addr = self
985                    .compiled_state
986                    .named_address_mapping
987                    .get(&package)
988                    .unwrap();
989                if package_addr == &zero {
990                    self.compiled_state
991                        .named_address_mapping
992                        .insert(package, before_upgrade);
993                }
994                let (warnings_opt, output, data, modules) = result?;
995                store_modules(self, syntax, data, modules);
996                Ok(merge_output(warnings_opt, output))
997            }
998            IotaSubcommand::StagePackage(StagePackageCommand {
999                syntax,
1000                dependencies,
1001            }) => {
1002                let syntax = syntax.unwrap_or_else(|| self.default_syntax());
1003                let (warnings_opt, output, data, modules) = compile_any(
1004                    self,
1005                    "upgrade",
1006                    syntax,
1007                    name,
1008                    number,
1009                    start_line,
1010                    command_lines_stop,
1011                    stop_line,
1012                    data,
1013                    |_adapter, modules| async { Ok((None, modules)) },
1014                )
1015                .await?;
1016                assert!(!modules.is_empty());
1017                let Some(package_name) = modules.first().unwrap().named_address else {
1018                    bail!("Staged modules must have a named address");
1019                };
1020                for m in &modules {
1021                    let Some(named_addr) = &m.named_address else {
1022                        bail!("Staged modules must have a named address");
1023                    };
1024                    if named_addr != &package_name {
1025                        bail!(
1026                            "Staged modules must have the same named address, \
1027                            {package_name} != {named_addr}"
1028                        );
1029                    }
1030                }
1031                let dependencies =
1032                    self.get_dependency_ids(dependencies, /* include_std */ true)?;
1033                let module_bytes = modules
1034                    .iter()
1035                    .map(|m| {
1036                        let mut buf = vec![];
1037                        m.module
1038                            .serialize_with_version(m.module.version, &mut buf)
1039                            .unwrap();
1040                        buf
1041                    })
1042                    .collect::<Vec<_>>();
1043                let digest = MovePackage::compute_digest_for_modules_and_deps(
1044                    module_bytes.iter(),
1045                    &dependencies,
1046                )
1047                .to_vec();
1048                let staged = StagedPackage {
1049                    file: data,
1050                    syntax,
1051                    modules,
1052                    digest,
1053                };
1054                let prev = self.staged_modules.insert(package_name, staged);
1055                if prev.is_some() {
1056                    panic!("Package '{package_name}' already staged")
1057                }
1058                Ok(merge_output(warnings_opt, output))
1059            }
1060            IotaSubcommand::SetAddress(SetAddressCommand { address, input }) => {
1061                let address_sym = &Symbol::from(address.as_str());
1062                let state = self.compiled_state();
1063                let input = input.into_concrete_value(&|s| Some(state.resolve_named_address(s)))?;
1064                let (value, package) = match input {
1065                    IotaValue::Object(fake_id, version) => {
1066                        let id = match self.fake_to_real_object_id(fake_id) {
1067                            Some(id) => id,
1068                            None => bail!("INVALID TEST. Unknown object, object({})", fake_id),
1069                        };
1070                        let obj = self.get_object(&id, version)?;
1071                        let package = obj.data.try_as_package().map(|package| {
1072                            package
1073                                .serialized_module_map()
1074                                .values()
1075                                .map(|published_module_bytes| {
1076                                    let module = CompiledModule::deserialize_with_defaults(
1077                                        published_module_bytes,
1078                                    )
1079                                    .unwrap();
1080                                    MaybeNamedCompiledModule {
1081                                        named_address: Some(*address_sym),
1082                                        module,
1083                                        source_map: None,
1084                                    }
1085                                })
1086                                .collect()
1087                        });
1088                        let value: AccountAddress = id.into();
1089                        (value, package)
1090                    }
1091                    IotaValue::MoveValue(v) => {
1092                        let bytes = v.simple_serialize().unwrap();
1093                        let value: AccountAddress = bcs::from_bytes(&bytes)?;
1094                        (value, None)
1095                    }
1096                    IotaValue::Digest(_) => bail!("digest is not supported as an input"),
1097                    IotaValue::ObjVec(_) => bail!("obj vec is not supported as an input"),
1098                    IotaValue::Receiving(_, _) => bail!("receiving is not supported as an input"),
1099                    IotaValue::ImmShared(_, _) => {
1100                        bail!("read-only shared object is not supported as an input")
1101                    }
1102                };
1103                let value = NumericalAddress::new(value.into_bytes(), NumberFormat::Hex);
1104                self.compiled_state
1105                    .named_address_mapping
1106                    .insert(address, value);
1107
1108                let res = package.and_then(|p| Some((p, self.staged_modules.remove(address_sym)?)));
1109                if let Some((package, staged)) = res {
1110                    let StagedPackage {
1111                        file,
1112                        syntax,
1113                        modules: _,
1114                        digest: _,
1115                    } = staged;
1116                    store_modules(self, syntax, file, package)
1117                }
1118
1119                Ok(None)
1120            }
1121            IotaSubcommand::Bench(
1122                RunCommand {
1123                    signers,
1124                    args,
1125                    type_args,
1126                    gas_budget,
1127                    syntax,
1128                    name,
1129                },
1130                extra_args,
1131            ) => {
1132                let (raw_addr, module_name, name) = name.unwrap();
1133
1134                assert!(
1135                    syntax.is_none(),
1136                    "syntax flag meaningless with function execution"
1137                );
1138
1139                let addr = self.compiled_state().resolve_address(&raw_addr);
1140                let module_id = ModuleId::new(addr, module_name);
1141                let type_args = self.compiled_state().resolve_type_args(type_args)?;
1142                let args = self.compiled_state().resolve_args(args)?;
1143
1144                let tx = self
1145                    .build_function_call_tx(
1146                        &module_id,
1147                        name.as_ident_str(),
1148                        type_args.clone(),
1149                        signers.clone(),
1150                        args.clone(),
1151                        gas_budget,
1152                        extra_args.clone(),
1153                    )
1154                    .unwrap();
1155
1156                let objects = self.executor.read_input_objects(tx.clone()).await?;
1157
1158                // only run benchmarks in release mode
1159                if !cfg!(debug_assertions) {
1160                    let mut c = Criterion::default();
1161
1162                    c.bench_function("benchmark_tx", |b| {
1163                        let tx = tx.clone();
1164                        let objects = objects.clone();
1165                        b.iter(|| {
1166                            self.executor
1167                                .prepare_txn(tx.clone(), objects.clone())
1168                                .unwrap();
1169                        })
1170                    });
1171                }
1172
1173                // Run the tx for real after the benchmark, so that its effects are persisted
1174                // and available to subsequent commands
1175                self.call_function(
1176                    &module_id,
1177                    name.as_ident_str(),
1178                    type_args,
1179                    signers,
1180                    args,
1181                    gas_budget,
1182                    extra_args,
1183                )
1184                .await?;
1185                Ok(merge_output(None, None))
1186            }
1187        }
1188    }
1189
1190    /// Process the error string such that it's less dependent on specific
1191    /// addresses or object IDs. Instead, they are replaced by the account
1192    /// names or fake IDs as much as possible. This reduces the effort of
1193    /// updating tests when something changed.
1194    async fn process_error(&self, error: anyhow::Error) -> anyhow::Error {
1195        let mut err = error.to_string();
1196        for (name, account) in &self.accounts {
1197            let addr = account.address.to_string();
1198            let replace = format!("@{name}");
1199            err = err.replace(&addr, &replace);
1200            // Also match without 0x since different error messages may use different
1201            // format.
1202            err = err.replace(&addr[2..], &replace);
1203        }
1204        for (id, fake_id) in &self.object_enumeration {
1205            let id = id.to_string();
1206            let replace = format!("object({fake_id})");
1207            err = err.replace(&id, &replace);
1208            // Also match without 0x since different error messages may use different
1209            // format.
1210            err = err.replace(&id[2..], &replace);
1211        }
1212        anyhow!(err)
1213    }
1214}
1215
1216fn merge_output(left: Option<String>, right: Option<String>) -> Option<String> {
1217    match (left, right) {
1218        (None, right) => right,
1219        (left, None) => left,
1220        (Some(mut left), Some(right)) => {
1221            left.push_str(&right);
1222            Some(left)
1223        }
1224    }
1225}
1226
1227impl IotaTestAdapter {
1228    pub fn with_offchain_reader(&mut self, offchain_reader: Box<dyn OffchainStateReader>) {
1229        self.offchain_reader = Some(offchain_reader);
1230    }
1231
1232    pub fn is_simulator(&self) -> bool {
1233        self.is_simulator
1234    }
1235
1236    pub fn executor(&self) -> &dyn TransactionalAdapter {
1237        &*self.executor
1238    }
1239
1240    pub fn into_executor(self) -> Box<dyn TransactionalAdapter> {
1241        self.executor
1242    }
1243
1244    fn named_variables(&self) -> BTreeMap<String, String> {
1245        let mut variables = BTreeMap::new();
1246
1247        let named_addrs = self
1248            .compiled_state
1249            .named_address_mapping
1250            .iter()
1251            .map(|(name, addr)| (name.clone(), format!("{addr:#02x}")));
1252
1253        for (name, addr) in named_addrs {
1254            let addr = addr.to_string();
1255
1256            // Required variant
1257            variables.insert(name.to_owned(), addr.clone());
1258            // Optional variant
1259            let name = name.to_string() + "_opt";
1260            variables.insert(name.clone(), addr.clone());
1261        }
1262
1263        for (oid, fid) in &self.object_enumeration {
1264            if let FakeID::Enumerated(x, y) = fid {
1265                variables.insert(format!("obj_{x}_{y}"), oid.to_string());
1266                variables.insert(format!("obj_{x}_{y}_opt"), oid.to_string());
1267            }
1268        }
1269
1270        for (tid, digest) in &self.digest_enumeration {
1271            variables.insert(format!("digest_{tid}"), digest.to_string());
1272        }
1273
1274        variables
1275    }
1276
1277    fn interpolate_contents(
1278        &self,
1279        contents: &str,
1280        variables: &BTreeMap<String, String>,
1281    ) -> anyhow::Result<String> {
1282        let mut interpolated_contents = contents.to_string();
1283
1284        let re = regex::Regex::new(r"@\{([^\}]+)\}").unwrap();
1285
1286        let unique_vars = re
1287            .captures_iter(contents)
1288            .filter_map(|c| c.get(1).map(|m| m.as_str().to_string()))
1289            .collect::<std::collections::HashSet<_>>();
1290
1291        for var_name in unique_vars {
1292            let Some(value) = variables.get(&var_name) else {
1293                bail!("Unknown variable: {var_name}\nAllowed variable mappings are {variables:#?}");
1294            };
1295
1296            let pattern = format!("@{{{var_name}}}");
1297            interpolated_contents = interpolated_contents.replace(&pattern, value);
1298        }
1299
1300        Ok(interpolated_contents)
1301    }
1302
1303    fn encode_cursor(&self, cursor: &str) -> anyhow::Result<String> {
1304        // Cursor format is either bcs(object_id,n1,n2,...) or a json value,
1305        // in which case we just return its base64 encoding.
1306        let Some(args) = cursor
1307            .strip_prefix("bcs(")
1308            .and_then(|c| c.strip_suffix(")"))
1309        else {
1310            // To comply with how `iota-graphql-rpc` decodes the json cursor
1311            // (see `iota_graphql_rpc::types::cursor::JsonCursor`).
1312            //
1313            // This traces back to `async_graphql = 7.0.7` that uses no padding for
1314            // encoding/decoding.
1315            return Ok(base64::Engine::encode(
1316                &base64::engine::general_purpose::URL_SAFE_NO_PAD,
1317                cursor,
1318            ));
1319        };
1320
1321        let mut parts = args.split(",");
1322
1323        let id: ObjectID = parts
1324            .next()
1325            .context("bcs(...) cursors must have at least one argument")?
1326            .trim()
1327            .parse()?;
1328
1329        let mut bytes = bcs::to_bytes(&id.to_vec())?;
1330        for part in parts {
1331            let n: u64 = part.trim().parse()?;
1332            bytes.extend(bcs::to_bytes(&n)?);
1333        }
1334
1335        Ok(Base64::encode(bytes))
1336    }
1337
1338    fn interpolate_query(
1339        &self,
1340        contents: &str,
1341        cursors: &[String],
1342        highest_checkpoint: u64,
1343    ) -> anyhow::Result<String> {
1344        // First collect all the variable mappings
1345        let mut variables = self.named_variables();
1346        variables.insert(
1347            "highest_checkpoint".to_string(),
1348            highest_checkpoint.to_string(),
1349        );
1350
1351        // Then interpolate the cursors which may reference objects
1352        for (idx, s) in cursors.iter().enumerate() {
1353            let interpolated_cursor = self.interpolate_contents(s, &variables)?;
1354            let encoded_cursor = self.encode_cursor(&interpolated_cursor)?;
1355
1356            // Add the encoded cursor to the variables map because they may get used in the
1357            // query.
1358            variables.insert(format!("cursor_{idx}"), encoded_cursor);
1359        }
1360        self.interpolate_contents(contents, &variables)
1361    }
1362
1363    async fn upgrade_package(
1364        &mut self,
1365        before_upgrade: NumericalAddress,
1366        modules: &[MaybeNamedCompiledModule],
1367        upgrade_capability: FakeID,
1368        dependencies: Vec<String>,
1369        sender: String,
1370        gas_budget: Option<u64>,
1371        dry_run: bool,
1372        policy: u8,
1373        gas_price: u64,
1374    ) -> anyhow::Result<Option<String>> {
1375        let modules_bytes = modules
1376            .iter()
1377            .map(|m| {
1378                let mut module_bytes = vec![];
1379                m.module
1380                    .serialize_with_version(m.module.version, &mut module_bytes)?;
1381                Ok(module_bytes)
1382            })
1383            .collect::<anyhow::Result<Vec<Vec<u8>>>>()?;
1384        let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
1385
1386        let dependencies = self.get_dependency_ids(dependencies, /* include_std */ true)?;
1387
1388        let mut builder = ProgrammableTransactionBuilder::new();
1389
1390        // Argument::Input(0)
1391        IotaValue::Object(upgrade_capability, None).into_argument(&mut builder, self)?;
1392        let upgrade_arg = builder.pure(policy).unwrap();
1393        let digest: Vec<u8> =
1394            MovePackage::compute_digest_for_modules_and_deps(&modules_bytes, &dependencies).into();
1395        let digest_arg = builder.pure(digest).unwrap();
1396
1397        let upgrade_ticket = builder.programmable_move_call(
1398            IOTA_FRAMEWORK_PACKAGE_ID,
1399            ident_str!("package").to_owned(),
1400            ident_str!("authorize_upgrade").to_owned(),
1401            vec![],
1402            vec![Argument::Input(0), upgrade_arg, digest_arg],
1403        );
1404
1405        let package_id = before_upgrade.into_inner().into();
1406        let upgrade_receipt =
1407            builder.upgrade(package_id, upgrade_ticket, dependencies, modules_bytes);
1408
1409        builder.programmable_move_call(
1410            IOTA_FRAMEWORK_PACKAGE_ID,
1411            ident_str!("package").to_owned(),
1412            ident_str!("commit_upgrade").to_owned(),
1413            vec![],
1414            vec![Argument::Input(0), upgrade_receipt],
1415        );
1416
1417        let pt = builder.finish();
1418
1419        let summary = if dry_run {
1420            let transaction = TransactionData::new_programmable(
1421                self.get_sender(Some(sender)).address,
1422                vec![],
1423                pt,
1424                gas_budget,
1425                gas_price,
1426            );
1427            self.dry_run(transaction).await?
1428        } else {
1429            let data = |sender, gas| {
1430                TransactionData::new_programmable(sender, gas, pt, gas_budget, gas_price)
1431            };
1432            let transaction = self.sign_txn(Some(sender), data);
1433            self.execute_txn(transaction).await?
1434        };
1435        let created_package = summary
1436            .created
1437            .iter()
1438            .find_map(|id| {
1439                let object = self.get_object(id, None).unwrap();
1440                let package = object.data.try_as_package()?;
1441                Some(package.id())
1442            })
1443            .unwrap();
1444        let package_addr = NumericalAddress::new(created_package.into_bytes(), NumberFormat::Hex);
1445        if let Some(new_package_name) = modules[0].named_address {
1446            let prev_package = self
1447                .compiled_state
1448                .named_address_mapping
1449                .insert(new_package_name.to_string(), package_addr);
1450            match prev_package.map(|a| a.into_inner()) {
1451                Some(addr) if addr != AccountAddress::ZERO => panic!(
1452                    "Cannot reuse named address '{new_package_name}' for multiple packages. \
1453                It should be set to 0 initially"
1454                ),
1455                _ => (),
1456            }
1457        }
1458        let output = self.object_summary_output(&summary, /* summarize */ false);
1459        Ok(output)
1460    }
1461
1462    fn sign_txn(
1463        &self,
1464        sender: Option<String>,
1465        txn_data: impl FnOnce(
1466            // sender
1467            IotaAddress,
1468            // gas
1469            Vec<ObjectRef>,
1470        ) -> TransactionData,
1471    ) -> Transaction {
1472        self.sign_sponsor_txn(sender, None, vec![], move |sender, _, gas| {
1473            txn_data(sender, gas)
1474        })
1475    }
1476
1477    fn get_payments(&self, sponsor: &TestAccount, payments: Vec<FakeID>) -> Vec<ObjectRef> {
1478        let payments = if payments.is_empty() {
1479            vec![sponsor.gas]
1480        } else {
1481            payments
1482                .into_iter()
1483                .map(|payment| {
1484                    self.fake_to_real_object_id(payment)
1485                        .expect("Could not find specified payment object")
1486                })
1487                .collect::<Vec<ObjectID>>()
1488        };
1489
1490        payments
1491            .into_iter()
1492            .map(|payment| {
1493                self.get_object(&payment, None)
1494                    .unwrap()
1495                    .compute_object_reference()
1496            })
1497            .collect()
1498    }
1499
1500    fn sign_sponsor_txn(
1501        &self,
1502        sender: Option<String>,
1503        sponsor: Option<String>,
1504        payment: Vec<FakeID>,
1505        txn_data: impl FnOnce(
1506            // sender
1507            IotaAddress,
1508            // sponsor
1509            IotaAddress,
1510            // gas
1511            Vec<ObjectRef>,
1512        ) -> TransactionData,
1513    ) -> Transaction {
1514        let sender = self.get_sender(sender);
1515        let sponsor = sponsor.map_or(sender, |a| self.get_sender(Some(a)));
1516
1517        let payment_refs = self.get_payments(sponsor, payment);
1518
1519        let data = txn_data(sender.address, sponsor.address, payment_refs);
1520
1521        if sender.address == sponsor.address {
1522            to_sender_signed_transaction(data, &sender.key_pair)
1523        } else {
1524            to_sender_signed_transaction_with_multi_signers(
1525                data,
1526                vec![&sender.key_pair, &sponsor.key_pair],
1527            )
1528        }
1529    }
1530
1531    fn get_sender(&self, sender: Option<String>) -> &TestAccount {
1532        match sender {
1533            Some(n) => match self.accounts.get(&n) {
1534                Some(test_account) => test_account,
1535                None => panic!("Unbound account {n}"),
1536            },
1537            None => &self.default_account,
1538        }
1539    }
1540
1541    fn build_function_call_tx(
1542        &mut self,
1543        module_id: &ModuleId,
1544        function: &IdentStr,
1545        type_args: Vec<TypeTag>,
1546        signers: Vec<ParsedAddress>,
1547        args: Vec<IotaValue>,
1548        gas_budget: Option<u64>,
1549        extra: IotaRunArgs,
1550    ) -> anyhow::Result<Transaction> {
1551        assert!(signers.is_empty(), "signers are not used");
1552        let IotaRunArgs {
1553            sender, gas_price, ..
1554        } = extra;
1555        let mut builder = ProgrammableTransactionBuilder::new();
1556        let arguments = args
1557            .into_iter()
1558            .map(|arg| arg.into_argument(&mut builder, self))
1559            .collect::<anyhow::Result<_>>()?;
1560        let package_id = ObjectID::from(*module_id.address());
1561
1562        let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
1563        let gas_price = gas_price.unwrap_or(self.gas_price);
1564        let data = |sender, gas| {
1565            builder.command(Command::move_call(
1566                package_id,
1567                module_id.name().to_owned(),
1568                function.to_owned(),
1569                type_args,
1570                arguments,
1571            ));
1572            let pt = builder.finish();
1573            TransactionData::new_programmable(sender, gas, pt, gas_budget, gas_price)
1574        };
1575        Ok(self.sign_txn(sender, data))
1576    }
1577
1578    async fn execute_txn(&mut self, transaction: Transaction) -> anyhow::Result<TxnSummary> {
1579        let with_shared = transaction
1580            .data()
1581            .intent_message()
1582            .value
1583            .contains_shared_object();
1584        let (effects, error_opt) = self.executor.execute_txn(transaction).await?;
1585        let digest = effects.transaction_digest();
1586
1587        // Try to assign `digest_$task` to this transaction's digest -- panic if a
1588        // transaction has already been set. Currently each task executes at
1589        // most one transaction, and everything is fine. This panic triggering
1590        // will be an early warning that we need to do something
1591        // more sophisticated.
1592        let task = self.next_fake.0;
1593        if let Some(prev) = self.digest_enumeration.insert(task, *digest) {
1594            panic!(
1595                "Task {task} executed two transactions (expected at most one): {prev}, {digest}"
1596            );
1597        }
1598
1599        let mut created_ids: Vec<_> = effects
1600            .created()
1601            .iter()
1602            .map(|((id, _, _), _)| *id)
1603            .collect();
1604        let mut mutated_ids: Vec<_> = effects
1605            .mutated()
1606            .iter()
1607            .map(|((id, _, _), _)| *id)
1608            .collect();
1609        let mut unwrapped_ids: Vec<_> = effects
1610            .unwrapped()
1611            .iter()
1612            .map(|((id, _, _), _)| *id)
1613            .collect();
1614        let mut deleted_ids: Vec<_> = effects.deleted().iter().map(|(id, _, _)| *id).collect();
1615        let mut unwrapped_then_deleted_ids: Vec<_> = effects
1616            .unwrapped_then_deleted()
1617            .iter()
1618            .map(|(id, _, _)| *id)
1619            .collect();
1620        let mut wrapped_ids: Vec<_> = effects.wrapped().iter().map(|(id, _, _)| *id).collect();
1621        let gas_summary = effects.gas_cost_summary();
1622
1623        // make sure objects that have previously not been in storage get assigned a
1624        // fake id.
1625        let mut might_need_fake_id: Vec<_> = created_ids
1626            .iter()
1627            .chain(unwrapped_ids.iter())
1628            .copied()
1629            .collect();
1630
1631        // Use a stable sort before assigning fake ids, so test output remains stable.
1632        might_need_fake_id.sort_by_key(|id| self.get_object_sorting_key(id));
1633        for id in might_need_fake_id {
1634            self.enumerate_fake(id);
1635        }
1636
1637        let mut unchanged_shared_ids = effects
1638            .unchanged_shared_objects()
1639            .iter()
1640            .map(|(id, _)| *id)
1641            .collect::<Vec<_>>();
1642
1643        // Treat unwrapped objects as writes (even though sometimes this is the first
1644        // time we can refer to them at their id in storage).
1645
1646        // sort by fake id
1647        created_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1648        mutated_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1649        unwrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1650        deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1651        unwrapped_then_deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1652        wrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1653        unchanged_shared_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1654
1655        match effects.status() {
1656            ExecutionStatus::Success => {
1657                let events = self
1658                    .executor
1659                    .query_tx_events_asc(digest, *QUERY_MAX_RESULT_LIMIT)
1660                    .await?;
1661                Ok(TxnSummary {
1662                    events,
1663                    gas_summary: gas_summary.clone(),
1664                    created: created_ids,
1665                    mutated: mutated_ids,
1666                    unwrapped: unwrapped_ids,
1667                    deleted: deleted_ids,
1668                    unwrapped_then_deleted: unwrapped_then_deleted_ids,
1669                    wrapped: wrapped_ids,
1670                    unchanged_shared: unchanged_shared_ids,
1671                })
1672            }
1673            ExecutionStatus::Failure { error, command } => {
1674                let execution_msg = if with_shared {
1675                    format!("Debug of error: {error:?} at command {command:?}")
1676                } else {
1677                    format!("Execution Error: {}", error_opt.unwrap())
1678                };
1679                bail!(self.stabilize_str(format!(
1680                    "Transaction Effects Status: {error}\n{execution_msg}",
1681                )))
1682            }
1683        }
1684    }
1685
1686    async fn dry_run(&mut self, transaction: TransactionData) -> anyhow::Result<TxnSummary> {
1687        let digest = transaction.digest();
1688        let results = self
1689            .executor
1690            .dry_run_transaction_block(transaction, digest)
1691            .await?;
1692        let DryRunTransactionBlockResponse {
1693            effects, events, ..
1694        } = results;
1695
1696        self.tx_summary_from_effects(effects, events)
1697    }
1698
1699    async fn dev_inspect(
1700        &mut self,
1701        sender: IotaAddress,
1702        transaction_kind: TransactionKind,
1703        gas_price: Option<u64>,
1704    ) -> anyhow::Result<TxnSummary> {
1705        let results = self
1706            .executor
1707            .dev_inspect_transaction_block(sender, transaction_kind, gas_price)
1708            .await?;
1709        let DevInspectResults {
1710            effects, events, ..
1711        } = results;
1712        self.tx_summary_from_effects(effects, events)
1713    }
1714
1715    fn tx_summary_from_effects(
1716        &mut self,
1717        effects: IotaTransactionBlockEffects,
1718        events: IotaTransactionBlockEvents,
1719    ) -> anyhow::Result<TxnSummary> {
1720        if let IotaExecutionStatus::Failure { error } = effects.status() {
1721            bail!(self.stabilize_str(format!(
1722                "Transaction Effects Status: {error}\nExecution Error: {error}",
1723            )));
1724        }
1725        let mut created_ids: Vec<_> = effects.created().iter().map(|o| o.object_id()).collect();
1726        let mut mutated_ids: Vec<_> = effects.mutated().iter().map(|o| o.object_id()).collect();
1727        let mut unwrapped_ids: Vec<_> = effects.unwrapped().iter().map(|o| o.object_id()).collect();
1728        let mut deleted_ids: Vec<_> = effects.deleted().iter().map(|o| o.object_id).collect();
1729        let mut unwrapped_then_deleted_ids: Vec<_> = effects
1730            .unwrapped_then_deleted()
1731            .iter()
1732            .map(|o| o.object_id)
1733            .collect();
1734        let mut wrapped_ids: Vec<_> = effects.wrapped().iter().map(|o| o.object_id).collect();
1735        let gas_summary = effects.gas_cost_summary();
1736
1737        // make sure objects that have previously not been in storage get assigned a
1738        // fake id.
1739        let mut might_need_fake_id: Vec<_> = created_ids
1740            .iter()
1741            .chain(unwrapped_ids.iter())
1742            .copied()
1743            .collect();
1744
1745        // Use a stable sort before assigning fake ids, so test output remains stable.
1746        might_need_fake_id.sort_by_key(|id| self.get_object_sorting_key(id));
1747        for id in might_need_fake_id {
1748            self.enumerate_fake(id);
1749        }
1750
1751        // Treat unwrapped objects as writes (even though sometimes this is the first
1752        // time we can refer to them at their id in storage).
1753
1754        // sort by fake id
1755        created_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1756        mutated_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1757        unwrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1758        deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1759        unwrapped_then_deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1760        wrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1761
1762        let events = events
1763            .data
1764            .into_iter()
1765            .map(|iota_event| iota_event.into())
1766            .collect();
1767
1768        Ok(TxnSummary {
1769            events,
1770            gas_summary: gas_summary.clone(),
1771            created: created_ids,
1772            mutated: mutated_ids,
1773            unwrapped: unwrapped_ids,
1774            deleted: deleted_ids,
1775            unwrapped_then_deleted: unwrapped_then_deleted_ids,
1776            wrapped: wrapped_ids,
1777            // TODO: Properly propagate unchanged shared objects in dev_inspect.
1778            unchanged_shared: vec![],
1779        })
1780    }
1781
1782    fn get_object(&self, id: &ObjectID, version: Option<SequenceNumber>) -> anyhow::Result<Object> {
1783        let obj_res = if let Some(v) = version {
1784            ObjectStore::try_get_object_by_key(&*self.executor, id, v)
1785        } else {
1786            ObjectStore::try_get_object(&*self.executor, id)
1787        };
1788        match obj_res {
1789            Ok(Some(obj)) => Ok(obj),
1790            Ok(None) | Err(_) => Err(anyhow!("INVALID TEST! Unable to find object {id}")),
1791        }
1792    }
1793
1794    // stable way of sorting objects by type. Does not however, produce a stable
1795    // sorting between objects of the same type
1796    fn get_object_sorting_key(&self, id: &ObjectID) -> String {
1797        match &self.get_object(id, None).unwrap().data {
1798            object::Data::Move(obj) => self.stabilize_str(format!("{}", obj.type_())),
1799            object::Data::Package(pkg) => pkg
1800                .serialized_module_map()
1801                .keys()
1802                .map(|s| s.as_str())
1803                .collect::<Vec<_>>()
1804                .join(","),
1805        }
1806    }
1807
1808    pub(crate) fn fake_to_real_object_id(&self, fake_id: FakeID) -> Option<ObjectID> {
1809        self.object_enumeration.get_by_right(&fake_id).copied()
1810    }
1811
1812    pub(crate) fn real_to_fake_object_id(&self, id: &ObjectID) -> Option<FakeID> {
1813        self.object_enumeration.get_by_left(id).copied()
1814    }
1815
1816    fn enumerate_fake(&mut self, id: ObjectID) -> FakeID {
1817        if let Some(fake) = self.object_enumeration.get_by_left(&id) {
1818            return *fake;
1819        }
1820        let (task, i) = self.next_fake;
1821        let fake_id = FakeID::Enumerated(task, i);
1822        self.object_enumeration.insert(id, fake_id);
1823
1824        self.next_fake = (task, i + 1);
1825        fake_id
1826    }
1827
1828    fn object_summary_output(
1829        &self,
1830        TxnSummary {
1831            events,
1832            gas_summary,
1833            created,
1834            mutated,
1835            unwrapped,
1836            deleted,
1837            unwrapped_then_deleted,
1838            wrapped,
1839            unchanged_shared,
1840        }: &TxnSummary,
1841        summarize: bool,
1842    ) -> Option<String> {
1843        let mut out = String::new();
1844        if !events.is_empty() {
1845            write!(out, "events: {}", self.list_events(events, summarize)).unwrap();
1846        }
1847        if !created.is_empty() {
1848            if !out.is_empty() {
1849                out.push('\n')
1850            }
1851            write!(out, "created: {}", self.list_objs(created, summarize)).unwrap();
1852        }
1853        if !mutated.is_empty() {
1854            if !out.is_empty() {
1855                out.push('\n')
1856            }
1857            write!(out, "mutated: {}", self.list_objs(mutated, summarize)).unwrap();
1858        }
1859        if !unwrapped.is_empty() {
1860            if !out.is_empty() {
1861                out.push('\n')
1862            }
1863            write!(out, "unwrapped: {}", self.list_objs(unwrapped, summarize)).unwrap();
1864        }
1865        if !deleted.is_empty() {
1866            if !out.is_empty() {
1867                out.push('\n')
1868            }
1869            write!(out, "deleted: {}", self.list_objs(deleted, summarize)).unwrap();
1870        }
1871        if !unwrapped_then_deleted.is_empty() {
1872            if !out.is_empty() {
1873                out.push('\n')
1874            }
1875            write!(
1876                out,
1877                "unwrapped_then_deleted: {}",
1878                self.list_objs(unwrapped_then_deleted, summarize)
1879            )
1880            .unwrap();
1881        }
1882        if !wrapped.is_empty() {
1883            if !out.is_empty() {
1884                out.push('\n')
1885            }
1886            write!(out, "wrapped: {}", self.list_objs(wrapped, summarize)).unwrap();
1887        }
1888        if !unchanged_shared.is_empty() {
1889            if !out.is_empty() {
1890                out.push('\n')
1891            }
1892            write!(
1893                out,
1894                "unchanged_shared: {}",
1895                self.list_objs(unchanged_shared, summarize)
1896            )
1897            .unwrap();
1898        }
1899        out.push('\n');
1900        write!(out, "gas summary: {gas_summary}").unwrap();
1901
1902        if out.is_empty() { None } else { Some(out) }
1903    }
1904
1905    fn list_events(&self, events: &[Event], summarize: bool) -> String {
1906        if summarize {
1907            return format!("{}", events.len());
1908        }
1909        events
1910            .iter()
1911            .map(|event| self.stabilize_str(format!("{event:?}")))
1912            .collect::<Vec<_>>()
1913            .join(", ")
1914    }
1915
1916    fn list_objs(&self, objs: &[ObjectID], summarize: bool) -> String {
1917        if summarize {
1918            return format!("{}", objs.len());
1919        }
1920        objs.iter()
1921            .map(|id| match self.real_to_fake_object_id(id) {
1922                None => "object(_)".to_string(),
1923                Some(FakeID::Known(id)) => {
1924                    let id: AccountAddress = id.into();
1925                    format!("0x{id:x}")
1926                }
1927                Some(fake) => format!("object({fake})"),
1928            })
1929            .collect::<Vec<_>>()
1930            .join(", ")
1931    }
1932
1933    fn stabilize_str(&self, input: impl AsRef<str>) -> String {
1934        fn candidate_is_hex(s: &str) -> bool {
1935            const HEX_STR_LENGTH: usize = IOTA_ADDRESS_LENGTH * 2;
1936            let n = s.len();
1937            (s.starts_with("0x") && n >= 3) || n == HEX_STR_LENGTH
1938        }
1939        let mut hex_candidate = String::new();
1940        let mut result = String::new();
1941        let mut chars = input.as_ref().chars().peekable();
1942        let mut cur = chars.next();
1943        while let Some(c) = cur {
1944            match c {
1945                '0' if hex_candidate.is_empty() && matches!(chars.peek(), Some('x')) => {
1946                    let c = chars.next().unwrap();
1947                    assert!(c == 'x');
1948                    hex_candidate.push_str("0x");
1949                }
1950                '0'..='9' | 'a'..='f' | 'A'..='F' => hex_candidate.push(c),
1951                _ => {
1952                    if candidate_is_hex(&hex_candidate) {
1953                        result.push_str(&self.remap_hex_str(hex_candidate));
1954                        hex_candidate = String::new();
1955                    } else {
1956                        result.push_str(&hex_candidate);
1957                        if !hex_candidate.is_empty() {
1958                            hex_candidate = String::new();
1959                        }
1960                    }
1961                    result.push(c);
1962                }
1963            }
1964            cur = chars.next();
1965        }
1966        if candidate_is_hex(&hex_candidate) {
1967            result.push_str(&self.remap_hex_str(hex_candidate));
1968        } else {
1969            result.push_str(&hex_candidate);
1970        }
1971        result
1972    }
1973
1974    fn remap_hex_str(&self, hex_str: String) -> String {
1975        let hex_str = if hex_str.starts_with("0x") {
1976            hex_str
1977        } else {
1978            format!("0x{hex_str}")
1979        };
1980        let parsed = AccountAddress::from_hex_literal(&hex_str).unwrap();
1981        if let Some((known, _)) = self
1982            .compiled_state
1983            .named_address_mapping
1984            .iter()
1985            .find(|(_name, addr)| addr.into_inner() == parsed)
1986        {
1987            return known.clone();
1988        }
1989        match self.real_to_fake_object_id(&parsed.into()) {
1990            None => "_".to_string(),
1991            Some(FakeID::Known(id)) => {
1992                let id: AccountAddress = id.into();
1993                format!("0x{id:x}")
1994            }
1995            Some(fake) => format!("fake({fake})"),
1996        }
1997    }
1998
1999    fn next_task(&mut self) {
2000        self.next_fake = (self.next_fake.0 + 1, 0)
2001    }
2002
2003    fn get_dependency_ids(
2004        &self,
2005        dependencies: Vec<String>,
2006        include_std: bool,
2007    ) -> anyhow::Result<Vec<ObjectID>> {
2008        let mut dependencies: Vec<_> = dependencies
2009            .into_iter()
2010            .map(|d| {
2011                let Some(addr) = self.compiled_state.named_address_mapping.get(&d) else {
2012                    bail!("There is no published module address corresponding to name address {d}");
2013                };
2014                let id: ObjectID = addr.into_inner().into();
2015                Ok(id)
2016            })
2017            .collect::<Result<_, _>>()?;
2018        // we are assuming that all packages depend on Move Stdlib and IOTA Framework,
2019        // so these don't have to be provided explicitly as parameters
2020        if include_std {
2021            dependencies.extend([MOVE_STDLIB_PACKAGE_ID, IOTA_FRAMEWORK_PACKAGE_ID]);
2022        }
2023        Ok(dependencies)
2024    }
2025}
2026
2027impl<'a> GetModule for &'a IotaTestAdapter {
2028    type Error = anyhow::Error;
2029
2030    type Item = &'a CompiledModule;
2031
2032    fn get_module_by_id(&self, id: &ModuleId) -> anyhow::Result<Option<Self::Item>, Self::Error> {
2033        Ok(Some(
2034            self.compiled_state
2035                .dep_modules()
2036                .find(|m| &m.self_id() == id)
2037                .unwrap_or_else(|| panic!("Internal error: Unbound module {id}")),
2038        ))
2039    }
2040}
2041
2042impl fmt::Display for FakeID {
2043    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2044        match self {
2045            FakeID::Known(id) => {
2046                let addr: AccountAddress = (*id).into();
2047                write!(f, "0x{addr:x}")
2048            }
2049            FakeID::Enumerated(task, i) => write!(f, "{task},{i}"),
2050        }
2051    }
2052}
2053
2054impl Default for AdapterInitConfig {
2055    fn default() -> Self {
2056        Self {
2057            additional_mapping: BTreeMap::new(),
2058            account_names: BTreeSet::new(),
2059            protocol_config: ProtocolConfig::get_for_max_version_UNSAFE(),
2060            is_simulator: false,
2061            custom_validator_account: false,
2062            reference_gas_price: None,
2063            default_gas_price: None,
2064            flavor: None,
2065            offchain_config: None,
2066        }
2067    }
2068}
2069
2070static NAMED_ADDRESSES: Lazy<BTreeMap<String, NumericalAddress>> = Lazy::new(|| {
2071    let mut map = move_stdlib::move_stdlib_named_addresses();
2072    assert!(map.get("std").unwrap().into_inner() == MOVE_STDLIB_ADDRESS);
2073    // TODO fix IOTA framework constants
2074    map.insert(
2075        "iota".to_string(),
2076        NumericalAddress::new(
2077            IOTA_FRAMEWORK_ADDRESS.into_bytes(),
2078            move_compiler::shared::NumberFormat::Hex,
2079        ),
2080    );
2081    map.insert(
2082        "iota_system".to_string(),
2083        NumericalAddress::new(
2084            IOTA_SYSTEM_ADDRESS.into_bytes(),
2085            move_compiler::shared::NumberFormat::Hex,
2086        ),
2087    );
2088    map.insert(
2089        "stardust".to_string(),
2090        NumericalAddress::new(
2091            STARDUST_ADDRESS.into_bytes(),
2092            move_compiler::shared::NumberFormat::Hex,
2093        ),
2094    );
2095    map
2096});
2097
2098pub static PRE_COMPILED: Lazy<FullyCompiledProgram> = Lazy::new(|| {
2099    // TODO invoke package system? Or otherwise pull the versions for these packages
2100    // as per their actual Move.toml files. They way they are treated here is
2101    // odd, too, though.
2102    let iota_files: &Path = Path::new(DEFAULT_FRAMEWORK_PATH);
2103    let iota_system_sources = {
2104        let mut buf = iota_files.to_path_buf();
2105        buf.extend(["packages", "iota-system", "sources"]);
2106        buf.to_string_lossy().to_string()
2107    };
2108    let iota_sources = {
2109        let mut buf = iota_files.to_path_buf();
2110        buf.extend(["packages", "iota-framework", "sources"]);
2111        buf.to_string_lossy().to_string()
2112    };
2113    let iota_deps = {
2114        let mut buf = iota_files.to_path_buf();
2115        buf.extend(["packages", "move-stdlib", "sources"]);
2116        buf.to_string_lossy().to_string()
2117    };
2118    let config = PackageConfig {
2119        edition: Edition::E2024_BETA,
2120        flavor: Flavor::Iota,
2121        ..Default::default()
2122    };
2123    let fully_compiled_res = move_compiler::construct_pre_compiled_lib(
2124        vec![PackagePaths {
2125            name: Some(("iota-framework".into(), config)),
2126            paths: vec![iota_system_sources, iota_sources, iota_deps],
2127            named_address_map: NAMED_ADDRESSES.clone(),
2128        }],
2129        None,
2130        Flags::empty(),
2131        None,
2132    )
2133    .unwrap();
2134    match fully_compiled_res {
2135        Err((files, diags)) => {
2136            eprintln!("!!!IOTA framework failed to compile!!!");
2137            move_compiler::diagnostics::report_diagnostics(&files, diags)
2138        }
2139        Ok(res) => res,
2140    }
2141});
2142
2143async fn create_validator_fullnode(
2144    protocol_config: &ProtocolConfig,
2145    objects: &[Object],
2146) -> (Arc<AuthorityState>, Arc<AuthorityState>) {
2147    let builder = TestAuthorityBuilder::new()
2148        .with_protocol_config(protocol_config.clone())
2149        .with_starting_objects(objects);
2150    let state = builder.clone().build().await;
2151    let fullnode_key_pair = get_authority_key_pair().1;
2152    let fullnode = builder.with_keypair(&fullnode_key_pair).build().await;
2153    (state, fullnode)
2154}
2155
2156async fn create_val_fullnode_executor(
2157    protocol_config: &ProtocolConfig,
2158    objects: &[Object],
2159) -> ValidatorWithFullnode {
2160    let (validator, fullnode) = create_validator_fullnode(protocol_config, objects).await;
2161
2162    let metrics = KeyValueStoreMetrics::new_for_tests();
2163    let kv_store = Arc::new(TransactionKeyValueStore::new(
2164        "rocksdb",
2165        metrics,
2166        validator.clone(),
2167    ));
2168    ValidatorWithFullnode {
2169        validator,
2170        fullnode,
2171        kv_store,
2172    }
2173}
2174
2175struct AccountSetup {
2176    pub default_account: TestAccount,
2177    pub named_address_mapping: BTreeMap<String, NumericalAddress>,
2178    pub objects: Vec<Object>,
2179    pub account_objects: BTreeMap<String, ObjectID>,
2180    pub accounts: BTreeMap<String, TestAccount>,
2181}
2182
2183/// Create the executor for a validator with a fullnode
2184/// The issue with this executor is we cannot control the checkpoint
2185/// and epoch creation process
2186async fn init_val_fullnode_executor(
2187    mut rng: StdRng,
2188    account_names: BTreeSet<String>,
2189    additional_mapping: BTreeMap<String, NumericalAddress>,
2190    protocol_config: &ProtocolConfig,
2191) -> (
2192    Box<dyn TransactionalAdapter>,
2193    AccountSetup,
2194    Option<Arc<dyn RestStateReader + Send + Sync>>,
2195) {
2196    // Initial list of named addresses with specified values
2197    let mut named_address_mapping = NAMED_ADDRESSES.clone();
2198    let mut account_objects = BTreeMap::new();
2199    let mut accounts = BTreeMap::new();
2200    let mut objects = vec![];
2201
2202    // Closure to create accounts with gas objects of value `GAS_FOR_TESTING`
2203    let mut mk_account = || {
2204        let (address, key_pair) = get_key_pair_from_rng(&mut rng);
2205        let obj = Object::with_id_owner_gas_for_testing(
2206            ObjectID::new(rng.gen()),
2207            address,
2208            GAS_FOR_TESTING,
2209        );
2210        let test_account = TestAccount {
2211            address,
2212            key_pair,
2213            gas: obj.id(),
2214        };
2215        objects.push(obj);
2216        test_account
2217    };
2218
2219    // For each named IOTA account without an address value, create an account with
2220    // an address and a gas object
2221    for n in account_names {
2222        let test_account = mk_account();
2223        account_objects.insert(n.clone(), test_account.gas);
2224        accounts.insert(n, test_account);
2225    }
2226
2227    // Make a default account with a gas object
2228    let default_account = mk_account();
2229
2230    let executor = Box::new(create_val_fullnode_executor(protocol_config, &objects).await);
2231
2232    update_named_address_mapping(
2233        &mut named_address_mapping,
2234        &accounts,
2235        additional_mapping,
2236        &*executor,
2237    )
2238    .await;
2239
2240    let acc_setup = AccountSetup {
2241        default_account,
2242        named_address_mapping,
2243        objects,
2244        account_objects,
2245        accounts,
2246    };
2247    (executor, acc_setup, None)
2248}
2249
2250/// Create an executor using a simulator
2251/// This means we can control the checkpoint, epoch creation process and
2252/// manually advance clock as needed
2253async fn init_sim_executor(
2254    mut rng: StdRng,
2255    account_names: BTreeSet<String>,
2256    additional_mapping: BTreeMap<String, NumericalAddress>,
2257    protocol_config: &ProtocolConfig,
2258    custom_validator_account: bool,
2259    reference_gas_price: Option<u64>,
2260    data_ingestion_path: PathBuf,
2261) -> (
2262    Box<dyn TransactionalAdapter>,
2263    AccountSetup,
2264    Option<Arc<dyn RestStateReader + Send + Sync>>,
2265) {
2266    // Initial list of named addresses with specified values
2267    let mut named_address_mapping = NAMED_ADDRESSES.clone();
2268    let mut account_objects = BTreeMap::new();
2269    let mut account_kps = BTreeMap::new();
2270    let mut accounts = BTreeMap::new();
2271    let mut objects = vec![];
2272
2273    // For each named IOTA account without an address value, create a key pair
2274    for n in account_names {
2275        let test_account = get_key_pair_from_rng(&mut rng);
2276        account_kps.insert(n, test_account);
2277    }
2278
2279    // Make a default account keypair
2280    let default_account_kp = get_key_pair_from_rng(&mut rng);
2281
2282    let (mut validator_addr, mut validator_key, mut key_copy) = (None, None, None);
2283    if custom_validator_account {
2284        // Make a validator account with a gas object
2285        let (a, b): (IotaAddress, Ed25519KeyPair) = get_key_pair_from_rng(&mut rng);
2286
2287        key_copy = Some(
2288            Ed25519KeyPair::from_bytes(b.as_bytes())
2289                .expect("FATAL: recovering key from bytes failed"),
2290        );
2291        validator_addr = Some(a);
2292        validator_key = Some(b);
2293    }
2294
2295    let mut acc_cfgs = account_kps
2296        .values()
2297        .map(|acc| AccountConfig {
2298            address: Some(acc.0),
2299            gas_amounts: vec![GAS_FOR_TESTING],
2300        })
2301        .collect::<Vec<_>>();
2302    acc_cfgs.push(AccountConfig {
2303        address: Some(default_account_kp.0),
2304        gas_amounts: vec![GAS_FOR_TESTING],
2305    });
2306
2307    if let Some(v_addr) = validator_addr {
2308        acc_cfgs.push(AccountConfig {
2309            address: Some(v_addr),
2310            gas_amounts: vec![GAS_FOR_TESTING],
2311        });
2312    }
2313
2314    // Create the simulator with the specific account configs, which also crates
2315    // objects
2316
2317    let (mut sim, read_replica) =
2318        PersistedStore::new_sim_replica_with_protocol_version_and_accounts(
2319            rng,
2320            DEFAULT_CHAIN_START_TIMESTAMP,
2321            protocol_config.version,
2322            acc_cfgs,
2323            key_copy.map(|q| vec![q]),
2324            reference_gas_price,
2325            None,
2326        );
2327
2328    sim.set_data_ingestion_path(data_ingestion_path.clone());
2329
2330    // Get the actual object values from the simulator
2331    for (name, (addr, kp)) in account_kps {
2332        let o = sim.store().owned_objects(addr).next().unwrap();
2333        objects.push(o.clone());
2334        account_objects.insert(name.clone(), o.id());
2335
2336        accounts.insert(
2337            name.to_owned(),
2338            TestAccount {
2339                address: addr,
2340                key_pair: kp,
2341                gas: o.id(),
2342            },
2343        );
2344    }
2345    let o = sim
2346        .store()
2347        .owned_objects(default_account_kp.0)
2348        .next()
2349        .unwrap();
2350    let default_account = TestAccount {
2351        address: default_account_kp.0,
2352        key_pair: default_account_kp.1,
2353        gas: o.id(),
2354    };
2355    objects.push(o.clone());
2356
2357    if let (Some(v_addr), Some(v_key)) = (validator_addr, validator_key) {
2358        let o = sim.store().owned_objects(v_addr).next().unwrap();
2359        let validator_account = TestAccount {
2360            address: v_addr,
2361            key_pair: v_key,
2362            gas: o.id(),
2363        };
2364        objects.push(o.clone());
2365        account_objects.insert("validator_0".to_string(), o.id());
2366        accounts.insert("validator_0".to_string(), validator_account);
2367    }
2368
2369    let sim = Box::new(sim);
2370    update_named_address_mapping(
2371        &mut named_address_mapping,
2372        &accounts,
2373        additional_mapping,
2374        &*sim,
2375    )
2376    .await;
2377
2378    (
2379        sim,
2380        AccountSetup {
2381            default_account,
2382            named_address_mapping,
2383            objects,
2384            account_objects,
2385            accounts,
2386        },
2387        Some(Arc::new(read_replica)),
2388    )
2389}
2390
2391async fn update_named_address_mapping(
2392    named_address_mapping: &mut BTreeMap<String, NumericalAddress>,
2393    accounts: &BTreeMap<String, TestAccount>,
2394    additional_mapping: BTreeMap<String, NumericalAddress>,
2395    trans_adapter: &dyn TransactionalAdapter,
2396) {
2397    let active_val_addrs: BTreeMap<_, _> = trans_adapter
2398        .get_active_validator_addresses()
2399        .await
2400        .expect("Failed to get validator addresses")
2401        .iter()
2402        .enumerate()
2403        .map(|(idx, addr)| (format!("validator_{idx}"), *addr))
2404        .collect();
2405
2406    // For mappings where the address is specified, populate the named address
2407    // mapping
2408    let additional_mapping = additional_mapping
2409        .into_iter()
2410        .chain(accounts.iter().map(|(n, test_account)| {
2411            let addr = NumericalAddress::new(test_account.address.to_inner(), NumberFormat::Hex);
2412            (n.clone(), addr)
2413        }))
2414        .chain(active_val_addrs.iter().map(|(n, addr)| {
2415            let addr = NumericalAddress::new(addr.to_inner(), NumberFormat::Hex);
2416            (n.clone(), addr)
2417        }));
2418    // Extend the mappings of all named addresses with values
2419    for (name, addr) in additional_mapping {
2420        if (named_address_mapping.contains_key(&name)
2421            && (named_address_mapping.get(&name) != Some(&addr)))
2422            || name == "iota"
2423        {
2424            panic!("Invalid init. The named address '{name}' is reserved or duplicated")
2425        }
2426        named_address_mapping.insert(name, addr);
2427    }
2428}
2429
2430impl ObjectStore for IotaTestAdapter {
2431    fn try_get_object(
2432        &self,
2433        object_id: &ObjectID,
2434    ) -> iota_types::storage::error::Result<Option<Object>> {
2435        ObjectStore::try_get_object(&*self.executor, object_id)
2436    }
2437
2438    fn try_get_object_by_key(
2439        &self,
2440        object_id: &ObjectID,
2441        version: VersionNumber,
2442    ) -> iota_types::storage::error::Result<Option<Object>> {
2443        ObjectStore::try_get_object_by_key(&*self.executor, object_id, version)
2444    }
2445}
2446
2447impl ReadStore for IotaTestAdapter {
2448    fn try_get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
2449        self.executor.try_get_latest_epoch_id()
2450    }
2451
2452    fn try_get_committee(
2453        &self,
2454        epoch: iota_types::committee::EpochId,
2455    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::committee::Committee>>> {
2456        self.executor.try_get_committee(epoch)
2457    }
2458
2459    fn try_get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
2460        ReadStore::try_get_latest_checkpoint(&self.executor)
2461    }
2462
2463    fn try_get_highest_verified_checkpoint(
2464        &self,
2465    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
2466        self.executor.try_get_highest_verified_checkpoint()
2467    }
2468
2469    fn try_get_highest_synced_checkpoint(
2470        &self,
2471    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
2472        self.executor.try_get_highest_synced_checkpoint()
2473    }
2474
2475    fn try_get_lowest_available_checkpoint(
2476        &self,
2477    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
2478        self.executor.try_get_lowest_available_checkpoint()
2479    }
2480
2481    fn try_get_checkpoint_by_digest(
2482        &self,
2483        digest: &iota_types::messages_checkpoint::CheckpointDigest,
2484    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
2485        self.executor.try_get_checkpoint_by_digest(digest)
2486    }
2487
2488    fn try_get_checkpoint_by_sequence_number(
2489        &self,
2490        sequence_number: CheckpointSequenceNumber,
2491    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
2492        self.executor
2493            .try_get_checkpoint_by_sequence_number(sequence_number)
2494    }
2495
2496    fn try_get_checkpoint_contents_by_digest(
2497        &self,
2498        digest: &CheckpointContentsDigest,
2499    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
2500        self.executor.try_get_checkpoint_contents_by_digest(digest)
2501    }
2502
2503    fn try_get_checkpoint_contents_by_sequence_number(
2504        &self,
2505        sequence_number: CheckpointSequenceNumber,
2506    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
2507        self.executor
2508            .try_get_checkpoint_contents_by_sequence_number(sequence_number)
2509    }
2510
2511    fn try_get_transaction(
2512        &self,
2513        tx_digest: &TransactionDigest,
2514    ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
2515        self.executor.try_get_transaction(tx_digest)
2516    }
2517
2518    fn try_get_transaction_effects(
2519        &self,
2520        tx_digest: &TransactionDigest,
2521    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
2522        self.executor.try_get_transaction_effects(tx_digest)
2523    }
2524
2525    fn try_get_events(
2526        &self,
2527        event_digest: &TransactionEventsDigest,
2528    ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
2529        self.executor.try_get_events(event_digest)
2530    }
2531
2532    fn try_get_full_checkpoint_contents_by_sequence_number(
2533        &self,
2534        sequence_number: CheckpointSequenceNumber,
2535    ) -> iota_types::storage::error::Result<
2536        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
2537    > {
2538        self.executor
2539            .try_get_full_checkpoint_contents_by_sequence_number(sequence_number)
2540    }
2541
2542    fn try_get_full_checkpoint_contents(
2543        &self,
2544        digest: &CheckpointContentsDigest,
2545    ) -> iota_types::storage::error::Result<
2546        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
2547    > {
2548        self.executor.try_get_full_checkpoint_contents(digest)
2549    }
2550}