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::{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    BRIDGE_ADDRESS, 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            objects_snapshot_min_checkpoint_lag,
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            let snapshot_config =
239                SnapshotLagConfig::new(objects_snapshot_min_checkpoint_lag, Some(1));
240            Some(OffChainConfig {
241                snapshot_config,
242                epochs_to_keep,
243                data_ingestion_path: data_ingestion_path.unwrap_or(tempdir().unwrap().into_path()),
244                rest_api_url,
245            })
246        } else {
247            None
248        };
249
250        Self {
251            additional_mapping: map,
252            account_names: accounts,
253            protocol_config,
254            is_simulator: simulator,
255            custom_validator_account,
256            reference_gas_price,
257            default_gas_price,
258            flavor,
259            offchain_config,
260        }
261    }
262}
263
264#[derive(Debug)]
265struct TestAccount {
266    address: IotaAddress,
267    key_pair: AccountKeyPair,
268    gas: ObjectID,
269}
270
271#[derive(Debug)]
272struct TxnSummary {
273    created: Vec<ObjectID>,
274    mutated: Vec<ObjectID>,
275    unwrapped: Vec<ObjectID>,
276    deleted: Vec<ObjectID>,
277    unwrapped_then_deleted: Vec<ObjectID>,
278    wrapped: Vec<ObjectID>,
279    unchanged_shared: Vec<ObjectID>,
280    events: Vec<Event>,
281    gas_summary: GasCostSummary,
282}
283
284#[async_trait]
285impl MoveTestAdapter<'_> for IotaTestAdapter {
286    type ExtraPublishArgs = IotaPublishArgs;
287    type ExtraRunArgs = IotaRunArgs;
288    type ExtraInitArgs = IotaInitArgs;
289    type ExtraValueArgs = IotaExtraValueArgs;
290    type Subcommand = IotaSubcommand<Self::ExtraValueArgs, Self::ExtraRunArgs>;
291
292    fn render_command_input(
293        &self,
294        task: &TaskInput<
295            TaskCommand<
296                Self::ExtraInitArgs,
297                Self::ExtraPublishArgs,
298                Self::ExtraValueArgs,
299                Self::ExtraRunArgs,
300                Self::Subcommand,
301            >,
302        >,
303    ) -> Option<String> {
304        match &task.command {
305            TaskCommand::Subcommand(IotaSubcommand::ProgrammableTransaction(..)) => {
306                let data_str = std::fs::read_to_string(task.data.as_ref()?)
307                    .ok()?
308                    .trim()
309                    .to_string();
310                Some(format!("{}\n{}", task.task_text, data_str))
311            }
312            TaskCommand::Init(_, _)
313            | TaskCommand::PrintBytecode(_)
314            | TaskCommand::Publish(_, _)
315            | TaskCommand::Run(_, _)
316            | TaskCommand::Subcommand(..) => None,
317        }
318    }
319
320    fn compiled_state(&mut self) -> &mut CompiledState {
321        &mut self.compiled_state
322    }
323
324    fn default_syntax(&self) -> SyntaxChoice {
325        self.default_syntax
326    }
327
328    async fn init(
329        default_syntax: SyntaxChoice,
330        pre_compiled_deps: Option<Arc<FullyCompiledProgram>>,
331        task_opt: Option<
332            move_transactional_test_runner::tasks::TaskInput<(
333                move_transactional_test_runner::tasks::InitCommand,
334                Self::ExtraInitArgs,
335            )>,
336        >,
337        _path: &Path,
338    ) -> (Self, Option<String>) {
339        let rng = StdRng::from_seed(RNG_SEED);
340        assert!(
341            pre_compiled_deps.is_some(),
342            "Must populate 'pre_compiled_deps' with IOTA framework"
343        );
344
345        // Unpack the init arguments
346        let AdapterInitConfig {
347            additional_mapping,
348            account_names,
349            protocol_config,
350            is_simulator,
351            custom_validator_account,
352            reference_gas_price,
353            default_gas_price,
354            flavor,
355            offchain_config,
356        } = match task_opt.map(|t| t.command) {
357            Some((init_cmd, iota_args)) => AdapterInitConfig::from_args(init_cmd, iota_args),
358            None => AdapterInitConfig::default(),
359        };
360
361        let (
362            executor,
363            AccountSetup {
364                default_account,
365                accounts,
366                named_address_mapping,
367                objects,
368                account_objects,
369            },
370            read_replica,
371        ) = if is_simulator {
372            init_sim_executor(
373                rng,
374                account_names,
375                additional_mapping,
376                &protocol_config,
377                custom_validator_account,
378                reference_gas_price,
379                offchain_config
380                    .as_ref()
381                    .unwrap()
382                    .data_ingestion_path
383                    .clone(),
384            )
385            .await
386        } else {
387            init_val_fullnode_executor(rng, account_names, additional_mapping, &protocol_config)
388                .await
389        };
390
391        let object_ids = objects.iter().map(|obj| obj.id()).collect::<Vec<_>>();
392
393        let mut test_adapter = Self {
394            is_simulator,
395            offchain_reader: None,
396            executor,
397            offchain_config,
398            read_replica,
399            compiled_state: CompiledState::new(
400                named_address_mapping,
401                pre_compiled_deps,
402                Some(NumericalAddress::new(
403                    AccountAddress::ZERO.into_bytes(),
404                    NumberFormat::Hex,
405                )),
406                Some(Edition::DEVELOPMENT),
407                flavor.or(Some(Flavor::Iota)),
408            ),
409            package_upgrade_mapping: BTreeMap::new(),
410            accounts,
411            default_account,
412            default_syntax,
413            object_enumeration: BiBTreeMap::new(),
414            digest_enumeration: BTreeMap::new(),
415            next_fake: (0, 0),
416            // TODO: make this configurable
417            gas_price: default_gas_price.unwrap_or(DEFAULT_GAS_PRICE),
418            staged_modules: BTreeMap::new(),
419        };
420
421        for well_known in WELL_KNOWN_OBJECTS.iter().copied() {
422            test_adapter
423                .object_enumeration
424                .insert(well_known, FakeID::Known(well_known));
425        }
426        let mut output = String::new();
427        for (account, obj_id) in account_objects {
428            let fake = test_adapter.enumerate_fake(obj_id);
429            if !output.is_empty() {
430                output.push_str(", ")
431            }
432            write!(output, "{}: object({})", account, fake).unwrap()
433        }
434        for object_id in object_ids {
435            test_adapter.enumerate_fake(object_id);
436        }
437        let output = if output.is_empty() {
438            None
439        } else {
440            Some(output)
441        };
442        (test_adapter, output)
443    }
444
445    async fn publish_modules(
446        &mut self,
447        modules: Vec<MaybeNamedCompiledModule>,
448        gas_budget: Option<u64>,
449        extra: Self::ExtraPublishArgs,
450    ) -> anyhow::Result<(Option<String>, Vec<MaybeNamedCompiledModule>)> {
451        self.next_task();
452        let IotaPublishArgs {
453            sender,
454            upgradeable,
455            dependencies,
456            gas_price,
457        } = extra;
458        let named_addr_opt = modules.first().unwrap().named_address;
459        let first_module_name = modules.first().unwrap().module.self_id().name().to_string();
460        let modules_bytes = modules
461            .iter()
462            .map(|m| {
463                let mut module_bytes = vec![];
464                m.module
465                    .serialize_with_version(m.module.version, &mut module_bytes)
466                    .unwrap();
467                Ok(module_bytes)
468            })
469            .collect::<anyhow::Result<_>>()?;
470        let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
471        let mapping = &self.compiled_state.named_address_mapping;
472        let mut dependencies: Vec<_> = dependencies
473            .into_iter()
474            .map(|d| {
475                let Some(addr) = mapping.get(&d) else {
476                    bail!("There is no published module address corresponding to name address {d}");
477                };
478                let id: ObjectID = addr.into_inner().into();
479                Ok(id)
480            })
481            .collect::<Result<_, _>>()?;
482        let gas_price = gas_price.unwrap_or(self.gas_price);
483        // we are assuming that all packages depend on Move Stdlib and IOTA Framework,
484        // so these don't have to be provided explicitly as parameters
485        dependencies.extend([MOVE_STDLIB_PACKAGE_ID, IOTA_FRAMEWORK_PACKAGE_ID]);
486        let data = |sender, gas| {
487            let mut builder = ProgrammableTransactionBuilder::new();
488            if upgradeable {
489                let cap = builder.publish_upgradeable(modules_bytes, dependencies);
490                builder.transfer_arg(sender, cap);
491            } else {
492                builder.publish_immutable(modules_bytes, dependencies);
493            };
494            let pt = builder.finish();
495            TransactionData::new_programmable(sender, vec![gas], pt, gas_budget, gas_price)
496        };
497        let transaction = self.sign_txn(sender, data);
498        let summary = self.execute_txn(transaction).await?;
499        let created_package = summary
500            .created
501            .iter()
502            .find_map(|id| {
503                let object = self.get_object(id, None).unwrap();
504                let package = object.data.try_as_package()?;
505                if package
506                    .serialized_module_map()
507                    .get(&first_module_name)
508                    .is_some()
509                {
510                    Some(*id)
511                } else {
512                    None
513                }
514            })
515            .unwrap();
516        let package_addr = NumericalAddress::new(created_package.into_bytes(), NumberFormat::Hex);
517        if let Some(named_addr) = named_addr_opt {
518            let prev_package = self
519                .compiled_state
520                .named_address_mapping
521                .insert(named_addr.to_string(), package_addr);
522            match prev_package.map(|a| a.into_inner()) {
523                Some(addr) if addr != AccountAddress::ZERO => panic!(
524                    "Cannot reuse named address '{}' for multiple packages. \
525                It should be set to 0 initially",
526                    named_addr
527                ),
528                _ => (),
529            }
530        }
531        let output = self.object_summary_output(&summary, /* summarize */ false);
532        let published_modules = self
533            .get_object(&created_package, None)
534            .unwrap()
535            .data
536            .try_as_package()
537            .unwrap()
538            .serialized_module_map()
539            .iter()
540            .map(|(_, published_module_bytes)| MaybeNamedCompiledModule {
541                named_address: named_addr_opt,
542                module: CompiledModule::deserialize_with_defaults(published_module_bytes).unwrap(),
543                source_map: None,
544            })
545            .collect();
546        Ok((output, published_modules))
547    }
548
549    async fn call_function(
550        &mut self,
551        module_id: &ModuleId,
552        function: &IdentStr,
553        type_args: Vec<TypeTag>,
554        signers: Vec<ParsedAddress>,
555        args: Vec<IotaValue>,
556        gas_budget: Option<u64>,
557        extra: Self::ExtraRunArgs,
558    ) -> anyhow::Result<(Option<String>, SerializedReturnValues)> {
559        self.next_task();
560        let IotaRunArgs { summarize, .. } = extra;
561        let transaction = self.build_function_call_tx(
562            module_id, function, type_args, signers, args, gas_budget, extra,
563        )?;
564        let summary = self.execute_txn(transaction).await?;
565        let output = self.object_summary_output(&summary, summarize);
566        let empty = SerializedReturnValues {
567            mutable_reference_outputs: vec![],
568            return_values: vec![],
569        };
570        Ok((output, empty))
571    }
572
573    async fn handle_subcommand(
574        &mut self,
575        task: TaskInput<Self::Subcommand>,
576    ) -> anyhow::Result<Option<String>> {
577        self.next_task();
578        let TaskInput {
579            command,
580            name,
581            number,
582            start_line,
583            command_lines_stop,
584            stop_line,
585            data,
586            task_text,
587        } = task;
588        macro_rules! get_obj {
589            ($fake_id:ident, $version:expr) => {{
590                let id = match self.fake_to_real_object_id($fake_id) {
591                    None => bail!(
592                        "task {}, lines {}-{}\n{}\n. Unbound fake id {}",
593                        number,
594                        start_line,
595                        command_lines_stop,
596                        task_text,
597                        $fake_id
598                    ),
599                    Some(res) => res,
600                };
601                match self.get_object(&id, $version) {
602                    Err(_) => return Ok(Some(format!("No object at id {}", $fake_id))),
603                    Ok(obj) => obj,
604                }
605            }};
606            ($fake_id:ident) => {{ get_obj!($fake_id, None) }};
607        }
608        match command {
609            IotaSubcommand::RunGraphql(RunGraphqlCommand {
610                show_usage,
611                show_headers,
612                show_service_version,
613                wait_for_checkpoint_pruned,
614                cursors,
615            }) => {
616                let file = data.ok_or_else(|| anyhow::anyhow!("Missing GraphQL query"))?;
617                let contents = std::fs::read_to_string(file.path())?;
618                let offchain_reader = self
619                    .offchain_reader
620                    .as_ref()
621                    .ok_or_else(|| anyhow::anyhow!("Offchain reader not set"))?;
622                let highest_checkpoint = self.executor.get_latest_checkpoint_sequence_number()?;
623                offchain_reader
624                    .wait_for_checkpoint_catchup(highest_checkpoint, Duration::from_secs(60))
625                    .await;
626
627                // wait_for_objects_snapshot_catchup(graphql_client,
628                // Duration::from_secs(180)).await;
629
630                if let Some(checkpoint_to_prune) = wait_for_checkpoint_pruned {
631                    offchain_reader
632                        .wait_for_pruned_checkpoint(checkpoint_to_prune, Duration::from_secs(60))
633                        .await;
634                }
635
636                let interpolated =
637                    self.interpolate_query(&contents, &cursors, highest_checkpoint)?;
638                let resp = offchain_reader
639                    .execute_graphql(interpolated.trim().to_owned(), show_usage)
640                    .await?;
641
642                let mut output = vec![];
643                if show_headers {
644                    output.push(format!("Headers: {:#?}", resp.http_headers.unwrap()));
645                }
646                if show_service_version {
647                    output.push(format!(
648                        "Service version: {}",
649                        resp.service_version.unwrap()
650                    ));
651                }
652                output.push(format!("Response: {}", resp.response_body));
653
654                Ok(Some(output.join("\n")))
655            }
656            IotaSubcommand::ViewCheckpoint => {
657                let latest_chk = self.executor.get_latest_checkpoint_sequence_number()?;
658                let chk = self
659                    .executor
660                    .get_checkpoint_by_sequence_number(latest_chk)?
661                    .unwrap();
662                Ok(Some(format!("{}", chk.data())))
663            }
664            IotaSubcommand::CreateCheckpoint(CreateCheckpointCommand { count }) => {
665                for _ in 0..count.unwrap_or(1) {
666                    self.executor.create_checkpoint().await?;
667                }
668                let latest_chk = self.executor.get_latest_checkpoint_sequence_number()?;
669                Ok(Some(format!("Checkpoint created: {}", latest_chk)))
670            }
671            IotaSubcommand::AdvanceEpoch(AdvanceEpochCommand { count }) => {
672                for _ in 0..count.unwrap_or(1) {
673                    self.executor.advance_epoch().await?;
674                }
675                let epoch = self.get_latest_epoch_id()?;
676                Ok(Some(format!("Epoch advanced: {epoch}")))
677            }
678            IotaSubcommand::AdvanceClock(AdvanceClockCommand { duration_ns }) => {
679                self.executor
680                    .advance_clock(Duration::from_nanos(duration_ns))
681                    .await?;
682                Ok(None)
683            }
684            IotaSubcommand::SetRandomState(SetRandomStateCommand {
685                randomness_round,
686                random_bytes,
687                randomness_initial_version,
688            }) => {
689                let random_bytes = Base64::decode(&random_bytes)
690                    .map_err(|e| anyhow!("Failed to decode random bytes as Base64: {e}"))?;
691
692                let latest_epoch = self.get_latest_epoch_id()?;
693                let tx = VerifiedTransaction::new_randomness_state_update(
694                    latest_epoch,
695                    RandomnessRound(randomness_round),
696                    random_bytes,
697                    SequenceNumber::from_u64(randomness_initial_version),
698                );
699
700                self.execute_txn(tx.into()).await?;
701                Ok(None)
702            }
703            IotaSubcommand::ViewObject(ViewObjectCommand { id: fake_id }) => {
704                let obj = get_obj!(fake_id);
705                Ok(Some(match &obj.data {
706                    object::Data::Move(move_obj) => {
707                        let layout = move_obj.get_layout(&&*self).unwrap();
708                        let move_struct =
709                            BoundedVisitor::deserialize_struct(move_obj.contents(), &layout)
710                                .unwrap();
711
712                        self.stabilize_str(format!(
713                            "Owner: {}\nVersion: {}\nContents: {:#}",
714                            &obj.owner,
715                            obj.version().value(),
716                            move_struct
717                        ))
718                    }
719                    object::Data::Package(package) => {
720                        let num_modules = package.serialized_module_map().len();
721                        let modules = package
722                            .serialized_module_map()
723                            .keys()
724                            .cloned()
725                            .collect::<Vec<_>>()
726                            .join(", ");
727                        assert!(!modules.is_empty());
728                        if num_modules > 1 {
729                            format!("{}::{{{}}}", fake_id, modules)
730                        } else {
731                            format!("{}::{}", fake_id, modules)
732                        }
733                    }
734                }))
735            }
736            IotaSubcommand::TransferObject(TransferObjectCommand {
737                id: fake_id,
738                recipient,
739                sender,
740                gas_budget,
741                gas_price,
742            }) => {
743                let mut builder = ProgrammableTransactionBuilder::new();
744                let obj_arg = IotaValue::Object(fake_id, None).into_argument(&mut builder, self)?;
745                let recipient = match self.accounts.get(&recipient) {
746                    Some(test_account) => test_account.address,
747                    None => panic!("Unbound account {}", recipient),
748                };
749                let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
750                let gas_price: u64 = gas_price.unwrap_or(self.gas_price);
751                let transaction = self.sign_txn(sender, |sender, gas| {
752                    let rec_arg = builder.pure(recipient).unwrap();
753                    builder.command(iota_types::transaction::Command::TransferObjects(
754                        vec![obj_arg],
755                        rec_arg,
756                    ));
757                    let pt = builder.finish();
758                    TransactionData::new_programmable(sender, vec![gas], pt, gas_budget, gas_price)
759                });
760                let summary = self.execute_txn(transaction).await?;
761                let output = self.object_summary_output(&summary, /* summarize */ false);
762                Ok(output)
763            }
764            IotaSubcommand::ConsensusCommitPrologue(ConsensusCommitPrologueCommand {
765                timestamp_ms,
766            }) => {
767                let transaction = VerifiedTransaction::new_consensus_commit_prologue_v1(
768                    0,
769                    0,
770                    timestamp_ms,
771                    ConsensusCommitDigest::default(),
772                    Vec::new(),
773                );
774                let summary = self.execute_txn(transaction.into()).await?;
775                let output = self.object_summary_output(&summary, /* summarize */ false);
776                Ok(output)
777            }
778            IotaSubcommand::ProgrammableTransaction(ProgrammableTransactionCommand {
779                sender,
780                sponsor,
781                gas_budget,
782                gas_price,
783                gas_payment,
784                dev_inspect,
785                dry_run,
786                inputs,
787            }) => {
788                if dev_inspect && self.is_simulator() {
789                    bail!("Dev inspect is not supported on simulator mode");
790                }
791
792                if dry_run && dev_inspect {
793                    bail!("Cannot set both dev-inspect and dry-run");
794                }
795
796                let inputs = self.compiled_state().resolve_args(inputs)?;
797                let inputs: Vec<CallArg> = inputs
798                    .into_iter()
799                    .map(|arg| arg.into_call_arg(self))
800                    .collect::<anyhow::Result<_>>()?;
801                let file = data.ok_or_else(|| {
802                    anyhow::anyhow!("Missing commands for programmable transaction")
803                })?;
804                let contents = std::fs::read_to_string(file.path())?;
805                let commands = ParsedCommand::parse_vec(&contents)?;
806                let staged = &self.staged_modules;
807                let state = &self.compiled_state;
808                let commands = commands
809                    .into_iter()
810                    .map(|c| {
811                        c.into_command(
812                            &|p| {
813                                let modules = staged
814                                    .get(&Symbol::from(p))?
815                                    .modules
816                                    .iter()
817                                    .map(|m| {
818                                        let mut buf = vec![];
819                                        m.module
820                                            .serialize_with_version(m.module.version, &mut buf)
821                                            .unwrap();
822                                        buf
823                                    })
824                                    .collect();
825                                Some(modules)
826                            },
827                            &|s| Some(state.resolve_named_address(s)),
828                        )
829                    })
830                    .collect::<anyhow::Result<Vec<Command>>>()?;
831                let summary = if !dev_inspect && !dry_run {
832                    let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
833                    let gas_price = gas_price.unwrap_or(self.gas_price);
834                    let transaction = self.sign_sponsor_txn(
835                        sender,
836                        sponsor,
837                        gas_payment,
838                        |sender, sponsor, gas| {
839                            TransactionData::new_programmable_allow_sponsor(
840                                sender,
841                                vec![gas],
842                                ProgrammableTransaction { inputs, commands },
843                                gas_budget,
844                                gas_price,
845                                sponsor,
846                            )
847                        },
848                    );
849                    self.execute_txn(transaction).await?
850                } else if dry_run {
851                    let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
852                    let gas_price = gas_price.unwrap_or(self.gas_price);
853                    let sender = self.get_sender(sender);
854                    let sponsor = sponsor.map_or(sender, |a| self.get_sender(Some(a)));
855
856                    let payment = self.get_payment(sponsor, gas_payment);
857
858                    let transaction = TransactionData::new_programmable(
859                        sender.address,
860                        vec![payment],
861                        ProgrammableTransaction { inputs, commands },
862                        gas_budget,
863                        gas_price,
864                    );
865                    self.dry_run(transaction).await?
866                } else {
867                    assert!(
868                        gas_budget.is_none(),
869                        "Meaningless to set gas budget with dev-inspect"
870                    );
871                    let sender_address = self.get_sender(sender).address;
872                    let transaction =
873                        TransactionKind::ProgrammableTransaction(ProgrammableTransaction {
874                            inputs,
875                            commands,
876                        });
877                    self.dev_inspect(sender_address, transaction, gas_price)
878                        .await?
879                };
880                let output = self.object_summary_output(&summary, /* summarize */ false);
881                Ok(output)
882            }
883            IotaSubcommand::UpgradePackage(UpgradePackageCommand {
884                package,
885                upgrade_capability,
886                dependencies,
887                sender,
888                gas_budget,
889                syntax,
890                policy,
891                gas_price,
892            }) => {
893                let syntax = syntax.unwrap_or_else(|| self.default_syntax());
894                // zero out the package name
895                let zero =
896                    NumericalAddress::new(AccountAddress::ZERO.into_bytes(), NumberFormat::Hex);
897                let before_upgrade = {
898                    // not binding `m` separately results in some strange async capture error
899                    let m = &mut self.compiled_state.named_address_mapping;
900                    let Some(before) = m.insert(package.clone(), zero) else {
901                        panic!("Unbound package '{package}' for upgrade");
902                    };
903                    before
904                };
905
906                // Override address mappings for compilation when upgrading. Each dependency is
907                // set to its original address (if that dependency had been
908                // upgraded previously). This ensures upgraded dependencies are
909                // resolved during compilation--without this workaround, the compiler will
910                // find multiple definitions of the same module). We persist the original
911                // package name and addresses, which we restore before
912                // performing an upgrade transaction below.
913                let mut original_package_addrs = vec![];
914                for dep in dependencies.iter() {
915                    let named_address_mapping = &mut self.compiled_state.named_address_mapping;
916                    let dep = &Symbol::from(dep.as_str());
917                    let Some(orig_package) = self.package_upgrade_mapping.get(dep) else {
918                        continue;
919                    };
920                    let Some(orig_package_address) =
921                        named_address_mapping.insert(orig_package.to_string(), zero)
922                    else {
923                        continue;
924                    };
925                    original_package_addrs.push((*orig_package, orig_package_address));
926                    let dep_address = named_address_mapping
927                        .insert(dep.to_string(), orig_package_address)
928                        .unwrap_or_else(||
929                            panic!("Internal error: expected dependency {dep} in map when overriding address.")
930                        );
931                    original_package_addrs.push((*dep, dep_address));
932                }
933                let gas_price = gas_price.unwrap_or(self.gas_price);
934
935                let result = compile_any(
936                    self,
937                    "upgrade",
938                    syntax,
939                    name,
940                    number,
941                    start_line,
942                    command_lines_stop,
943                    stop_line,
944                    data,
945                    |adapter, modules| async {
946                        // Restore the original package addresses for dependencies before performing the upgrade.
947                        // This ensures package upgrades are properly linked at their correct addresses
948                        // (previously, addresses referred to the dependency's original package for compilation).
949                        for (name, addr) in original_package_addrs {
950                            adapter
951                                .compiled_state()
952                                .named_address_mapping
953                                .insert(name.to_string(), addr)
954                                .unwrap_or_else(|| panic!("Internal error: expected dependency {name} in map when restoring address."));
955                        }
956
957                        let upgraded_name = modules.first().unwrap().named_address.unwrap();
958                        let package = &Symbol::from(package.as_str());
959                        let original_name = adapter
960                            .package_upgrade_mapping
961                            .get(package)
962                            .unwrap_or(package);
963                        // Persist the upgraded package name with its original package name, so that we can
964                        // refer to the original package name when compiling (see above on overridden addresses).
965                        adapter
966                            .package_upgrade_mapping
967                            .insert(upgraded_name, *original_name);
968
969                        let output = adapter.upgrade_package(
970                            before_upgrade,
971                            &modules,
972                            upgrade_capability,
973                            dependencies,
974                            sender,
975                            gas_budget,
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                                .iter()
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(
1245        &self,
1246        cursors: &[String],
1247        highest_checkpoint: u64,
1248    ) -> BTreeMap<String, String> {
1249        let mut variables = BTreeMap::new();
1250        let mut objects_mapping: BTreeMap<String, Vec<u8>> = BTreeMap::new();
1251
1252        let named_addrs = self
1253            .compiled_state
1254            .named_address_mapping
1255            .iter()
1256            .map(|(name, addr)| (name.clone(), format!("{:#02x}", addr)));
1257
1258        for (name, addr) in named_addrs {
1259            let addr = addr.to_string();
1260
1261            // Required variant
1262            variables.insert(name.to_owned(), addr.clone());
1263            // Optional variant
1264            let name = name.to_string() + "_opt";
1265            variables.insert(name.clone(), addr.clone());
1266        }
1267
1268        for (oid, fid) in &self.object_enumeration {
1269            if let FakeID::Enumerated(x, y) = fid {
1270                objects_mapping.insert(format!("obj_{x}_{y}"), oid.to_vec());
1271                variables.insert(format!("obj_{x}_{y}"), oid.to_string());
1272                variables.insert(format!("obj_{x}_{y}_opt"), oid.to_string());
1273            }
1274        }
1275
1276        for (tid, digest) in &self.digest_enumeration {
1277            variables.insert(format!("digest_{tid}"), digest.to_string());
1278        }
1279
1280        for (idx, s) in cursors.iter().enumerate() {
1281            // an object cursor may be either @{obj_x_y} or @{obj_x_y,n}
1282            // if the former, then use highest_checkpoint
1283            if s.starts_with("@{obj_") && s.ends_with('}') {
1284                let end_of_key = s.find(',').unwrap_or(s.len() - 1);
1285                let obj_lookup = s[2..end_of_key].to_string();
1286
1287                let obj_id = objects_mapping.get(&obj_lookup).unwrap_or_else(|| {
1288                    panic!(
1289                        "Unknown object lookup: {}\nAllowed variable mappings are {:#?}",
1290                        obj_lookup, variables
1291                    )
1292                });
1293
1294                let checkpoint = if end_of_key == s.len() - 1 {
1295                    highest_checkpoint
1296                } else {
1297                    s[end_of_key + 1..s.len() - 1].parse::<u64>().unwrap()
1298                };
1299
1300                let bcsd = bcs::to_bytes(&(obj_id.clone(), checkpoint)).unwrap_or_default();
1301                let base64d = Base64::encode(bcsd);
1302
1303                variables.insert(format!("cursor_{idx}"), base64d);
1304            } else {
1305                use base64::Engine;
1306
1307                // To comply with how `iota-graphql-rpc` decodes the json cursor
1308                // (see `iota_graphql_rpc::types::cursor::JsonCursor`).
1309                //
1310                // This traces back to `async_graphql = 7.0.7` that uses no padding for
1311                // encoding/decoding.
1312                let base64d = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(s);
1313
1314                variables.insert(format!("cursor_{idx}"), base64d);
1315            }
1316        }
1317
1318        variables
1319    }
1320
1321    fn interpolate_query(
1322        &self,
1323        contents: &str,
1324        cursors: &[String],
1325        highest_checkpoint: u64,
1326    ) -> anyhow::Result<String> {
1327        let variables = self.named_variables(cursors, highest_checkpoint);
1328        let mut interpolated_query = contents.to_string();
1329
1330        let re = regex::Regex::new(r"@\{([^\}]+)\}").unwrap();
1331
1332        let mut unique_vars = std::collections::HashSet::new();
1333
1334        // Collect unique variables
1335        for cap in re.captures_iter(contents) {
1336            if let Some(var_name) = cap.get(1) {
1337                unique_vars.insert(var_name.as_str());
1338            }
1339        }
1340
1341        for var_name in unique_vars {
1342            let Some(value) = variables.get(var_name) else {
1343                return Err(anyhow!(
1344                    "Unknown variable: {}\nAllowed variable mappings are {:#?}",
1345                    var_name,
1346                    variables
1347                ));
1348            };
1349
1350            let pattern = format!("@{{{}}}", var_name);
1351            interpolated_query = interpolated_query.replace(&pattern, value);
1352        }
1353
1354        Ok(interpolated_query)
1355    }
1356
1357    async fn upgrade_package(
1358        &mut self,
1359        before_upgrade: NumericalAddress,
1360        modules: &[MaybeNamedCompiledModule],
1361        upgrade_capability: FakeID,
1362        dependencies: Vec<String>,
1363        sender: String,
1364        gas_budget: Option<u64>,
1365        policy: u8,
1366        gas_price: u64,
1367    ) -> anyhow::Result<Option<String>> {
1368        let modules_bytes = modules
1369            .iter()
1370            .map(|m| {
1371                let mut module_bytes = vec![];
1372                m.module
1373                    .serialize_with_version(m.module.version, &mut module_bytes)?;
1374                Ok(module_bytes)
1375            })
1376            .collect::<anyhow::Result<Vec<Vec<u8>>>>()?;
1377        let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
1378
1379        let dependencies = self.get_dependency_ids(dependencies, /* include_std */ true)?;
1380
1381        let mut builder = ProgrammableTransactionBuilder::new();
1382
1383        // Argument::Input(0)
1384        IotaValue::Object(upgrade_capability, None).into_argument(&mut builder, self)?;
1385        let upgrade_arg = builder.pure(policy).unwrap();
1386        let digest: Vec<u8> =
1387            MovePackage::compute_digest_for_modules_and_deps(&modules_bytes, &dependencies).into();
1388        let digest_arg = builder.pure(digest).unwrap();
1389
1390        let upgrade_ticket = builder.programmable_move_call(
1391            IOTA_FRAMEWORK_PACKAGE_ID,
1392            ident_str!("package").to_owned(),
1393            ident_str!("authorize_upgrade").to_owned(),
1394            vec![],
1395            vec![Argument::Input(0), upgrade_arg, digest_arg],
1396        );
1397
1398        let package_id = before_upgrade.into_inner().into();
1399        let upgrade_receipt =
1400            builder.upgrade(package_id, upgrade_ticket, dependencies, modules_bytes);
1401
1402        builder.programmable_move_call(
1403            IOTA_FRAMEWORK_PACKAGE_ID,
1404            ident_str!("package").to_owned(),
1405            ident_str!("commit_upgrade").to_owned(),
1406            vec![],
1407            vec![Argument::Input(0), upgrade_receipt],
1408        );
1409
1410        let pt = builder.finish();
1411
1412        let data = |sender, gas| {
1413            TransactionData::new_programmable(sender, vec![gas], pt, gas_budget, gas_price)
1414        };
1415
1416        let transaction = self.sign_txn(Some(sender), data);
1417        let summary = self.execute_txn(transaction).await?;
1418        let created_package = summary
1419            .created
1420            .iter()
1421            .find_map(|id| {
1422                let object = self.get_object(id, None).unwrap();
1423                let package = object.data.try_as_package()?;
1424                Some(package.id())
1425            })
1426            .unwrap();
1427        let package_addr = NumericalAddress::new(created_package.into_bytes(), NumberFormat::Hex);
1428        if let Some(new_package_name) = modules[0].named_address {
1429            let prev_package = self
1430                .compiled_state
1431                .named_address_mapping
1432                .insert(new_package_name.to_string(), package_addr);
1433            match prev_package.map(|a| a.into_inner()) {
1434                Some(addr) if addr != AccountAddress::ZERO => panic!(
1435                    "Cannot reuse named address '{}' for multiple packages. \
1436                It should be set to 0 initially",
1437                    new_package_name
1438                ),
1439                _ => (),
1440            }
1441        }
1442        let output = self.object_summary_output(&summary, /* summarize */ false);
1443        Ok(output)
1444    }
1445
1446    fn sign_txn(
1447        &self,
1448        sender: Option<String>,
1449        txn_data: impl FnOnce(
1450            // sender
1451            IotaAddress,
1452            // gas
1453            ObjectRef,
1454        ) -> TransactionData,
1455    ) -> Transaction {
1456        self.sign_sponsor_txn(sender, None, None, move |sender, _, gas| {
1457            txn_data(sender, gas)
1458        })
1459    }
1460
1461    fn get_payment(&self, sponsor: &TestAccount, payment: Option<FakeID>) -> ObjectRef {
1462        let payment = if let Some(payment) = payment {
1463            self.fake_to_real_object_id(payment)
1464                .expect("Could not find specified payment object")
1465        } else {
1466            sponsor.gas
1467        };
1468
1469        self.get_object(&payment, None)
1470            .unwrap()
1471            .compute_object_reference()
1472    }
1473
1474    fn sign_sponsor_txn(
1475        &self,
1476        sender: Option<String>,
1477        sponsor: Option<String>,
1478        payment: Option<FakeID>,
1479        txn_data: impl FnOnce(
1480            // sender
1481            IotaAddress,
1482            // sponsor
1483            IotaAddress,
1484            // gas
1485            ObjectRef,
1486        ) -> TransactionData,
1487    ) -> Transaction {
1488        let sender = self.get_sender(sender);
1489        let sponsor = sponsor.map_or(sender, |a| self.get_sender(Some(a)));
1490
1491        let payment_ref = self.get_payment(sponsor, payment);
1492
1493        let data = txn_data(sender.address, sponsor.address, payment_ref);
1494        if sender.address == sponsor.address {
1495            to_sender_signed_transaction(data, &sender.key_pair)
1496        } else {
1497            to_sender_signed_transaction_with_multi_signers(
1498                data,
1499                vec![&sender.key_pair, &sponsor.key_pair],
1500            )
1501        }
1502    }
1503
1504    fn get_sender(&self, sender: Option<String>) -> &TestAccount {
1505        match sender {
1506            Some(n) => match self.accounts.get(&n) {
1507                Some(test_account) => test_account,
1508                None => panic!("Unbound account {}", n),
1509            },
1510            None => &self.default_account,
1511        }
1512    }
1513
1514    fn build_function_call_tx(
1515        &mut self,
1516        module_id: &ModuleId,
1517        function: &IdentStr,
1518        type_args: Vec<TypeTag>,
1519        signers: Vec<ParsedAddress>,
1520        args: Vec<IotaValue>,
1521        gas_budget: Option<u64>,
1522        extra: IotaRunArgs,
1523    ) -> anyhow::Result<Transaction> {
1524        assert!(signers.is_empty(), "signers are not used");
1525        let IotaRunArgs {
1526            sender, gas_price, ..
1527        } = extra;
1528        let mut builder = ProgrammableTransactionBuilder::new();
1529        let arguments = args
1530            .into_iter()
1531            .map(|arg| arg.into_argument(&mut builder, self))
1532            .collect::<anyhow::Result<_>>()?;
1533        let package_id = ObjectID::from(*module_id.address());
1534
1535        let gas_budget = gas_budget.unwrap_or(DEFAULT_GAS_BUDGET);
1536        let gas_price = gas_price.unwrap_or(self.gas_price);
1537        let data = |sender, gas| {
1538            builder.command(Command::move_call(
1539                package_id,
1540                module_id.name().to_owned(),
1541                function.to_owned(),
1542                type_args,
1543                arguments,
1544            ));
1545            let pt = builder.finish();
1546            TransactionData::new_programmable(sender, vec![gas], pt, gas_budget, gas_price)
1547        };
1548        Ok(self.sign_txn(sender, data))
1549    }
1550
1551    async fn execute_txn(&mut self, transaction: Transaction) -> anyhow::Result<TxnSummary> {
1552        let with_shared = transaction
1553            .data()
1554            .intent_message()
1555            .value
1556            .contains_shared_object();
1557        let (effects, error_opt) = self.executor.execute_txn(transaction).await?;
1558        let digest = effects.transaction_digest();
1559
1560        // Try to assign `digest_$task` to this transaction's digest -- panic if a
1561        // transaction has already been set. Currently each task executes at
1562        // most one transaction, and everything is fine. This panic triggering
1563        // will be an early warning that we need to do something
1564        // more sophisticated.
1565        let task = self.next_fake.0;
1566        if let Some(prev) = self.digest_enumeration.insert(task, *digest) {
1567            panic!(
1568                "Task {task} executed two transactions (expected at most one): {prev}, {digest}"
1569            );
1570        }
1571
1572        let mut created_ids: Vec<_> = effects
1573            .created()
1574            .iter()
1575            .map(|((id, _, _), _)| *id)
1576            .collect();
1577        let mut mutated_ids: Vec<_> = effects
1578            .mutated()
1579            .iter()
1580            .map(|((id, _, _), _)| *id)
1581            .collect();
1582        let mut unwrapped_ids: Vec<_> = effects
1583            .unwrapped()
1584            .iter()
1585            .map(|((id, _, _), _)| *id)
1586            .collect();
1587        let mut deleted_ids: Vec<_> = effects.deleted().iter().map(|(id, _, _)| *id).collect();
1588        let mut unwrapped_then_deleted_ids: Vec<_> = effects
1589            .unwrapped_then_deleted()
1590            .iter()
1591            .map(|(id, _, _)| *id)
1592            .collect();
1593        let mut wrapped_ids: Vec<_> = effects.wrapped().iter().map(|(id, _, _)| *id).collect();
1594        let gas_summary = effects.gas_cost_summary();
1595
1596        // make sure objects that have previously not been in storage get assigned a
1597        // fake id.
1598        let mut might_need_fake_id: Vec<_> = created_ids
1599            .iter()
1600            .chain(unwrapped_ids.iter())
1601            .copied()
1602            .collect();
1603
1604        // Use a stable sort before assigning fake ids, so test output remains stable.
1605        might_need_fake_id.sort_by_key(|id| self.get_object_sorting_key(id));
1606        for id in might_need_fake_id {
1607            self.enumerate_fake(id);
1608        }
1609
1610        let mut unchanged_shared_ids = effects
1611            .unchanged_shared_objects()
1612            .iter()
1613            .map(|(id, _)| *id)
1614            .collect::<Vec<_>>();
1615
1616        // Treat unwrapped objects as writes (even though sometimes this is the first
1617        // time we can refer to them at their id in storage).
1618
1619        // sort by fake id
1620        created_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1621        mutated_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1622        unwrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1623        deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1624        unwrapped_then_deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1625        wrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1626        unchanged_shared_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1627
1628        match effects.status() {
1629            ExecutionStatus::Success => {
1630                let events = self
1631                    .executor
1632                    .query_tx_events_asc(digest, *QUERY_MAX_RESULT_LIMIT)
1633                    .await?;
1634                Ok(TxnSummary {
1635                    events,
1636                    gas_summary: gas_summary.clone(),
1637                    created: created_ids,
1638                    mutated: mutated_ids,
1639                    unwrapped: unwrapped_ids,
1640                    deleted: deleted_ids,
1641                    unwrapped_then_deleted: unwrapped_then_deleted_ids,
1642                    wrapped: wrapped_ids,
1643                    unchanged_shared: unchanged_shared_ids,
1644                })
1645            }
1646            ExecutionStatus::Failure { error, command } => {
1647                let execution_msg = if with_shared {
1648                    format!("Debug of error: {error:?} at command {command:?}")
1649                } else {
1650                    format!("Execution Error: {}", error_opt.unwrap())
1651                };
1652                Err(anyhow::anyhow!(self.stabilize_str(format!(
1653                    "Transaction Effects Status: {error}\n{execution_msg}",
1654                ))))
1655            }
1656        }
1657    }
1658
1659    async fn dry_run(&mut self, transaction: TransactionData) -> anyhow::Result<TxnSummary> {
1660        let digest = transaction.digest();
1661        let results = self
1662            .executor
1663            .dry_run_transaction_block(transaction, digest)
1664            .await?;
1665        let DryRunTransactionBlockResponse {
1666            effects, events, ..
1667        } = results;
1668
1669        self.tx_summary_from_effects(effects, events)
1670    }
1671
1672    async fn dev_inspect(
1673        &mut self,
1674        sender: IotaAddress,
1675        transaction_kind: TransactionKind,
1676        gas_price: Option<u64>,
1677    ) -> anyhow::Result<TxnSummary> {
1678        let results = self
1679            .executor
1680            .dev_inspect_transaction_block(sender, transaction_kind, gas_price)
1681            .await?;
1682        let DevInspectResults {
1683            effects, events, ..
1684        } = results;
1685        self.tx_summary_from_effects(effects, events)
1686    }
1687
1688    fn tx_summary_from_effects(
1689        &mut self,
1690        effects: IotaTransactionBlockEffects,
1691        events: IotaTransactionBlockEvents,
1692    ) -> anyhow::Result<TxnSummary> {
1693        if let IotaExecutionStatus::Failure { error } = effects.status() {
1694            return Err(anyhow::anyhow!(self.stabilize_str(format!(
1695                "Transaction Effects Status: {error}\nExecution Error: {error}",
1696            ))));
1697        }
1698        let mut created_ids: Vec<_> = effects.created().iter().map(|o| o.object_id()).collect();
1699        let mut mutated_ids: Vec<_> = effects.mutated().iter().map(|o| o.object_id()).collect();
1700        let mut unwrapped_ids: Vec<_> = effects.unwrapped().iter().map(|o| o.object_id()).collect();
1701        let mut deleted_ids: Vec<_> = effects.deleted().iter().map(|o| o.object_id).collect();
1702        let mut unwrapped_then_deleted_ids: Vec<_> = effects
1703            .unwrapped_then_deleted()
1704            .iter()
1705            .map(|o| o.object_id)
1706            .collect();
1707        let mut wrapped_ids: Vec<_> = effects.wrapped().iter().map(|o| o.object_id).collect();
1708        let gas_summary = effects.gas_cost_summary();
1709
1710        // make sure objects that have previously not been in storage get assigned a
1711        // fake id.
1712        let mut might_need_fake_id: Vec<_> = created_ids
1713            .iter()
1714            .chain(unwrapped_ids.iter())
1715            .copied()
1716            .collect();
1717
1718        // Use a stable sort before assigning fake ids, so test output remains stable.
1719        might_need_fake_id.sort_by_key(|id| self.get_object_sorting_key(id));
1720        for id in might_need_fake_id {
1721            self.enumerate_fake(id);
1722        }
1723
1724        // Treat unwrapped objects as writes (even though sometimes this is the first
1725        // time we can refer to them at their id in storage).
1726
1727        // sort by fake id
1728        created_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1729        mutated_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1730        unwrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1731        deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1732        unwrapped_then_deleted_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1733        wrapped_ids.sort_by_key(|id| self.real_to_fake_object_id(id));
1734
1735        let events = events
1736            .data
1737            .into_iter()
1738            .map(|iota_event| iota_event.into())
1739            .collect();
1740
1741        Ok(TxnSummary {
1742            events,
1743            gas_summary: gas_summary.clone(),
1744            created: created_ids,
1745            mutated: mutated_ids,
1746            unwrapped: unwrapped_ids,
1747            deleted: deleted_ids,
1748            unwrapped_then_deleted: unwrapped_then_deleted_ids,
1749            wrapped: wrapped_ids,
1750            // TODO: Properly propagate unchanged shared objects in dev_inspect.
1751            unchanged_shared: vec![],
1752        })
1753    }
1754
1755    fn get_object(&self, id: &ObjectID, version: Option<SequenceNumber>) -> anyhow::Result<Object> {
1756        let obj_res = if let Some(v) = version {
1757            ObjectStore::get_object_by_key(&*self.executor, id, v)
1758        } else {
1759            ObjectStore::get_object(&*self.executor, id)
1760        };
1761        match obj_res {
1762            Ok(Some(obj)) => Ok(obj),
1763            Ok(None) | Err(_) => Err(anyhow!("INVALID TEST! Unable to find object {id}")),
1764        }
1765    }
1766
1767    // stable way of sorting objects by type. Does not however, produce a stable
1768    // sorting between objects of the same type
1769    fn get_object_sorting_key(&self, id: &ObjectID) -> String {
1770        match &self.get_object(id, None).unwrap().data {
1771            object::Data::Move(obj) => self.stabilize_str(format!("{}", obj.type_())),
1772            object::Data::Package(pkg) => pkg
1773                .serialized_module_map()
1774                .keys()
1775                .map(|s| s.as_str())
1776                .collect::<Vec<_>>()
1777                .join(","),
1778        }
1779    }
1780
1781    pub(crate) fn fake_to_real_object_id(&self, fake_id: FakeID) -> Option<ObjectID> {
1782        self.object_enumeration.get_by_right(&fake_id).copied()
1783    }
1784
1785    pub(crate) fn real_to_fake_object_id(&self, id: &ObjectID) -> Option<FakeID> {
1786        self.object_enumeration.get_by_left(id).copied()
1787    }
1788
1789    fn enumerate_fake(&mut self, id: ObjectID) -> FakeID {
1790        if let Some(fake) = self.object_enumeration.get_by_left(&id) {
1791            return *fake;
1792        }
1793        let (task, i) = self.next_fake;
1794        let fake_id = FakeID::Enumerated(task, i);
1795        self.object_enumeration.insert(id, fake_id);
1796
1797        self.next_fake = (task, i + 1);
1798        fake_id
1799    }
1800
1801    fn object_summary_output(
1802        &self,
1803        TxnSummary {
1804            events,
1805            gas_summary,
1806            created,
1807            mutated,
1808            unwrapped,
1809            deleted,
1810            unwrapped_then_deleted,
1811            wrapped,
1812            unchanged_shared,
1813        }: &TxnSummary,
1814        summarize: bool,
1815    ) -> Option<String> {
1816        let mut out = String::new();
1817        if !events.is_empty() {
1818            write!(out, "events: {}", self.list_events(events, summarize)).unwrap();
1819        }
1820        if !created.is_empty() {
1821            if !out.is_empty() {
1822                out.push('\n')
1823            }
1824            write!(out, "created: {}", self.list_objs(created, summarize)).unwrap();
1825        }
1826        if !mutated.is_empty() {
1827            if !out.is_empty() {
1828                out.push('\n')
1829            }
1830            write!(out, "mutated: {}", self.list_objs(mutated, summarize)).unwrap();
1831        }
1832        if !unwrapped.is_empty() {
1833            if !out.is_empty() {
1834                out.push('\n')
1835            }
1836            write!(out, "unwrapped: {}", self.list_objs(unwrapped, summarize)).unwrap();
1837        }
1838        if !deleted.is_empty() {
1839            if !out.is_empty() {
1840                out.push('\n')
1841            }
1842            write!(out, "deleted: {}", self.list_objs(deleted, summarize)).unwrap();
1843        }
1844        if !unwrapped_then_deleted.is_empty() {
1845            if !out.is_empty() {
1846                out.push('\n')
1847            }
1848            write!(
1849                out,
1850                "unwrapped_then_deleted: {}",
1851                self.list_objs(unwrapped_then_deleted, summarize)
1852            )
1853            .unwrap();
1854        }
1855        if !wrapped.is_empty() {
1856            if !out.is_empty() {
1857                out.push('\n')
1858            }
1859            write!(out, "wrapped: {}", self.list_objs(wrapped, summarize)).unwrap();
1860        }
1861        if !unchanged_shared.is_empty() {
1862            if !out.is_empty() {
1863                out.push('\n')
1864            }
1865            write!(
1866                out,
1867                "unchanged_shared: {}",
1868                self.list_objs(unchanged_shared, summarize)
1869            )
1870            .unwrap();
1871        }
1872        out.push('\n');
1873        write!(out, "gas summary: {}", gas_summary).unwrap();
1874
1875        if out.is_empty() { None } else { Some(out) }
1876    }
1877
1878    fn list_events(&self, events: &[Event], summarize: bool) -> String {
1879        if summarize {
1880            return format!("{}", events.len());
1881        }
1882        events
1883            .iter()
1884            .map(|event| self.stabilize_str(format!("{:?}", event)))
1885            .collect::<Vec<_>>()
1886            .join(", ")
1887    }
1888
1889    fn list_objs(&self, objs: &[ObjectID], summarize: bool) -> String {
1890        if summarize {
1891            return format!("{}", objs.len());
1892        }
1893        objs.iter()
1894            .map(|id| match self.real_to_fake_object_id(id) {
1895                None => "object(_)".to_string(),
1896                Some(FakeID::Known(id)) => {
1897                    let id: AccountAddress = id.into();
1898                    format!("0x{id:x}")
1899                }
1900                Some(fake) => format!("object({})", fake),
1901            })
1902            .collect::<Vec<_>>()
1903            .join(", ")
1904    }
1905
1906    fn stabilize_str(&self, input: impl AsRef<str>) -> String {
1907        fn candidate_is_hex(s: &str) -> bool {
1908            const HEX_STR_LENGTH: usize = IOTA_ADDRESS_LENGTH * 2;
1909            let n = s.len();
1910            (s.starts_with("0x") && n >= 3) || n == HEX_STR_LENGTH
1911        }
1912        let mut hex_candidate = String::new();
1913        let mut result = String::new();
1914        let mut chars = input.as_ref().chars().peekable();
1915        let mut cur = chars.next();
1916        while let Some(c) = cur {
1917            match c {
1918                '0' if hex_candidate.is_empty() && matches!(chars.peek(), Some('x')) => {
1919                    let c = chars.next().unwrap();
1920                    assert!(c == 'x');
1921                    hex_candidate.push_str("0x");
1922                }
1923                '0'..='9' | 'a'..='f' | 'A'..='F' => hex_candidate.push(c),
1924                _ => {
1925                    if candidate_is_hex(&hex_candidate) {
1926                        result.push_str(&self.remap_hex_str(hex_candidate));
1927                        hex_candidate = String::new();
1928                    } else {
1929                        result.push_str(&hex_candidate);
1930                        if !hex_candidate.is_empty() {
1931                            hex_candidate = String::new();
1932                        }
1933                    }
1934                    result.push(c);
1935                }
1936            }
1937            cur = chars.next();
1938        }
1939        if candidate_is_hex(&hex_candidate) {
1940            result.push_str(&self.remap_hex_str(hex_candidate));
1941        } else {
1942            result.push_str(&hex_candidate);
1943        }
1944        result
1945    }
1946
1947    fn remap_hex_str(&self, hex_str: String) -> String {
1948        let hex_str = if hex_str.starts_with("0x") {
1949            hex_str
1950        } else {
1951            format!("0x{}", hex_str)
1952        };
1953        let parsed = AccountAddress::from_hex_literal(&hex_str).unwrap();
1954        if let Some((known, _)) = self
1955            .compiled_state
1956            .named_address_mapping
1957            .iter()
1958            .find(|(_name, addr)| addr.into_inner() == parsed)
1959        {
1960            return known.clone();
1961        }
1962        match self.real_to_fake_object_id(&parsed.into()) {
1963            None => "_".to_string(),
1964            Some(FakeID::Known(id)) => {
1965                let id: AccountAddress = id.into();
1966                format!("0x{id:x}")
1967            }
1968            Some(fake) => format!("fake({})", fake),
1969        }
1970    }
1971
1972    fn next_task(&mut self) {
1973        self.next_fake = (self.next_fake.0 + 1, 0)
1974    }
1975
1976    fn get_dependency_ids(
1977        &self,
1978        dependencies: Vec<String>,
1979        include_std: bool,
1980    ) -> anyhow::Result<Vec<ObjectID>> {
1981        let mut dependencies: Vec<_> = dependencies
1982            .into_iter()
1983            .map(|d| {
1984                let Some(addr) = self.compiled_state.named_address_mapping.get(&d) else {
1985                    bail!("There is no published module address corresponding to name address {d}");
1986                };
1987                let id: ObjectID = addr.into_inner().into();
1988                Ok(id)
1989            })
1990            .collect::<Result<_, _>>()?;
1991        // we are assuming that all packages depend on Move Stdlib and IOTA Framework,
1992        // so these don't have to be provided explicitly as parameters
1993        if include_std {
1994            dependencies.extend([MOVE_STDLIB_PACKAGE_ID, IOTA_FRAMEWORK_PACKAGE_ID]);
1995        }
1996        Ok(dependencies)
1997    }
1998}
1999
2000impl<'a> GetModule for &'a IotaTestAdapter {
2001    type Error = anyhow::Error;
2002
2003    type Item = &'a CompiledModule;
2004
2005    fn get_module_by_id(&self, id: &ModuleId) -> anyhow::Result<Option<Self::Item>, Self::Error> {
2006        Ok(Some(
2007            self.compiled_state
2008                .dep_modules()
2009                .find(|m| &m.self_id() == id)
2010                .unwrap_or_else(|| panic!("Internal error: Unbound module {}", id)),
2011        ))
2012    }
2013}
2014
2015impl fmt::Display for FakeID {
2016    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2017        match self {
2018            FakeID::Known(id) => {
2019                let addr: AccountAddress = (*id).into();
2020                write!(f, "0x{:x}", addr)
2021            }
2022            FakeID::Enumerated(task, i) => write!(f, "{},{}", task, i),
2023        }
2024    }
2025}
2026
2027impl Default for AdapterInitConfig {
2028    fn default() -> Self {
2029        Self {
2030            additional_mapping: BTreeMap::new(),
2031            account_names: BTreeSet::new(),
2032            protocol_config: ProtocolConfig::get_for_max_version_UNSAFE(),
2033            is_simulator: false,
2034            custom_validator_account: false,
2035            reference_gas_price: None,
2036            default_gas_price: None,
2037            flavor: None,
2038            offchain_config: None,
2039        }
2040    }
2041}
2042
2043static NAMED_ADDRESSES: Lazy<BTreeMap<String, NumericalAddress>> = Lazy::new(|| {
2044    let mut map = move_stdlib::move_stdlib_named_addresses();
2045    assert!(map.get("std").unwrap().into_inner() == MOVE_STDLIB_ADDRESS);
2046    // TODO fix IOTA framework constants
2047    map.insert(
2048        "iota".to_string(),
2049        NumericalAddress::new(
2050            IOTA_FRAMEWORK_ADDRESS.into_bytes(),
2051            move_compiler::shared::NumberFormat::Hex,
2052        ),
2053    );
2054    map.insert(
2055        "iota_system".to_string(),
2056        NumericalAddress::new(
2057            IOTA_SYSTEM_ADDRESS.into_bytes(),
2058            move_compiler::shared::NumberFormat::Hex,
2059        ),
2060    );
2061    map.insert(
2062        "stardust".to_string(),
2063        NumericalAddress::new(
2064            STARDUST_ADDRESS.into_bytes(),
2065            move_compiler::shared::NumberFormat::Hex,
2066        ),
2067    );
2068    map.insert(
2069        "bridge".to_string(),
2070        NumericalAddress::new(
2071            BRIDGE_ADDRESS.into_bytes(),
2072            move_compiler::shared::NumberFormat::Hex,
2073        ),
2074    );
2075    map
2076});
2077
2078pub static PRE_COMPILED: Lazy<FullyCompiledProgram> = Lazy::new(|| {
2079    // TODO invoke package system? Or otherwise pull the versions for these packages
2080    // as per their actual Move.toml files. They way they are treated here is
2081    // odd, too, though.
2082    let iota_files: &Path = Path::new(DEFAULT_FRAMEWORK_PATH);
2083    let iota_system_sources = {
2084        let mut buf = iota_files.to_path_buf();
2085        buf.extend(["packages", "iota-system", "sources"]);
2086        buf.to_string_lossy().to_string()
2087    };
2088    let iota_sources = {
2089        let mut buf = iota_files.to_path_buf();
2090        buf.extend(["packages", "iota-framework", "sources"]);
2091        buf.to_string_lossy().to_string()
2092    };
2093    let iota_deps = {
2094        let mut buf = iota_files.to_path_buf();
2095        buf.extend(["packages", "move-stdlib", "sources"]);
2096        buf.to_string_lossy().to_string()
2097    };
2098    let config = PackageConfig {
2099        edition: Edition::E2024_BETA,
2100        flavor: Flavor::Iota,
2101        ..Default::default()
2102    };
2103    let bridge_sources = {
2104        let mut buf = iota_files.to_path_buf();
2105        buf.extend(["packages", "bridge", "sources"]);
2106        buf.to_string_lossy().to_string()
2107    };
2108    let fully_compiled_res = move_compiler::construct_pre_compiled_lib(
2109        vec![PackagePaths {
2110            name: Some(("iota-framework".into(), config)),
2111            paths: vec![iota_system_sources, iota_sources, iota_deps, bridge_sources],
2112            named_address_map: NAMED_ADDRESSES.clone(),
2113        }],
2114        None,
2115        Flags::empty(),
2116        None,
2117    )
2118    .unwrap();
2119    match fully_compiled_res {
2120        Err((files, diags)) => {
2121            eprintln!("!!!IOTA framework failed to compile!!!");
2122            move_compiler::diagnostics::report_diagnostics(&files, diags)
2123        }
2124        Ok(res) => res,
2125    }
2126});
2127
2128async fn create_validator_fullnode(
2129    protocol_config: &ProtocolConfig,
2130    objects: &[Object],
2131) -> (Arc<AuthorityState>, Arc<AuthorityState>) {
2132    let builder = TestAuthorityBuilder::new()
2133        .with_protocol_config(protocol_config.clone())
2134        .with_starting_objects(objects);
2135    let state = builder.clone().build().await;
2136    let fullnode_key_pair = get_authority_key_pair().1;
2137    let fullnode = builder.with_keypair(&fullnode_key_pair).build().await;
2138    (state, fullnode)
2139}
2140
2141async fn create_val_fullnode_executor(
2142    protocol_config: &ProtocolConfig,
2143    objects: &[Object],
2144) -> ValidatorWithFullnode {
2145    let (validator, fullnode) = create_validator_fullnode(protocol_config, objects).await;
2146
2147    let metrics = KeyValueStoreMetrics::new_for_tests();
2148    let kv_store = Arc::new(TransactionKeyValueStore::new(
2149        "rocksdb",
2150        metrics,
2151        validator.clone(),
2152    ));
2153    ValidatorWithFullnode {
2154        validator,
2155        fullnode,
2156        kv_store,
2157    }
2158}
2159
2160struct AccountSetup {
2161    pub default_account: TestAccount,
2162    pub named_address_mapping: BTreeMap<String, NumericalAddress>,
2163    pub objects: Vec<Object>,
2164    pub account_objects: BTreeMap<String, ObjectID>,
2165    pub accounts: BTreeMap<String, TestAccount>,
2166}
2167
2168/// Create the executor for a validator with a fullnode
2169/// The issue with this executor is we cannot control the checkpoint
2170/// and epoch creation process
2171async fn init_val_fullnode_executor(
2172    mut rng: StdRng,
2173    account_names: BTreeSet<String>,
2174    additional_mapping: BTreeMap<String, NumericalAddress>,
2175    protocol_config: &ProtocolConfig,
2176) -> (
2177    Box<dyn TransactionalAdapter>,
2178    AccountSetup,
2179    Option<Arc<dyn RestStateReader + Send + Sync>>,
2180) {
2181    // Initial list of named addresses with specified values
2182    let mut named_address_mapping = NAMED_ADDRESSES.clone();
2183    let mut account_objects = BTreeMap::new();
2184    let mut accounts = BTreeMap::new();
2185    let mut objects = vec![];
2186
2187    // Closure to create accounts with gas objects of value `GAS_FOR_TESTING`
2188    let mut mk_account = || {
2189        let (address, key_pair) = get_key_pair_from_rng(&mut rng);
2190        let obj = Object::with_id_owner_gas_for_testing(
2191            ObjectID::new(rng.gen()),
2192            address,
2193            GAS_FOR_TESTING,
2194        );
2195        let test_account = TestAccount {
2196            address,
2197            key_pair,
2198            gas: obj.id(),
2199        };
2200        objects.push(obj);
2201        test_account
2202    };
2203
2204    // For each named IOTA account without an address value, create an account with
2205    // an address and a gas object
2206    for n in account_names {
2207        let test_account = mk_account();
2208        account_objects.insert(n.clone(), test_account.gas);
2209        accounts.insert(n, test_account);
2210    }
2211
2212    // Make a default account with a gas object
2213    let default_account = mk_account();
2214
2215    let executor = Box::new(create_val_fullnode_executor(protocol_config, &objects).await);
2216
2217    update_named_address_mapping(
2218        &mut named_address_mapping,
2219        &accounts,
2220        additional_mapping,
2221        &*executor,
2222    )
2223    .await;
2224
2225    let acc_setup = AccountSetup {
2226        default_account,
2227        named_address_mapping,
2228        objects,
2229        account_objects,
2230        accounts,
2231    };
2232    (executor, acc_setup, None)
2233}
2234
2235/// Create an executor using a simulator
2236/// This means we can control the checkpoint, epoch creation process and
2237/// manually advance clock as needed
2238async fn init_sim_executor(
2239    mut rng: StdRng,
2240    account_names: BTreeSet<String>,
2241    additional_mapping: BTreeMap<String, NumericalAddress>,
2242    protocol_config: &ProtocolConfig,
2243    custom_validator_account: bool,
2244    reference_gas_price: Option<u64>,
2245    data_ingestion_path: PathBuf,
2246) -> (
2247    Box<dyn TransactionalAdapter>,
2248    AccountSetup,
2249    Option<Arc<dyn RestStateReader + Send + Sync>>,
2250) {
2251    // Initial list of named addresses with specified values
2252    let mut named_address_mapping = NAMED_ADDRESSES.clone();
2253    let mut account_objects = BTreeMap::new();
2254    let mut account_kps = BTreeMap::new();
2255    let mut accounts = BTreeMap::new();
2256    let mut objects = vec![];
2257
2258    // For each named IOTA account without an address value, create a key pair
2259    for n in account_names {
2260        let test_account = get_key_pair_from_rng(&mut rng);
2261        account_kps.insert(n, test_account);
2262    }
2263
2264    // Make a default account keypair
2265    let default_account_kp = get_key_pair_from_rng(&mut rng);
2266
2267    let (mut validator_addr, mut validator_key, mut key_copy) = (None, None, None);
2268    if custom_validator_account {
2269        // Make a validator account with a gas object
2270        let (a, b): (IotaAddress, Ed25519KeyPair) = get_key_pair_from_rng(&mut rng);
2271
2272        key_copy = Some(
2273            Ed25519KeyPair::from_bytes(b.as_bytes())
2274                .expect("FATAL: recovering key from bytes failed"),
2275        );
2276        validator_addr = Some(a);
2277        validator_key = Some(b);
2278    }
2279
2280    let mut acc_cfgs = account_kps
2281        .values()
2282        .map(|acc| AccountConfig {
2283            address: Some(acc.0),
2284            gas_amounts: vec![GAS_FOR_TESTING],
2285        })
2286        .collect::<Vec<_>>();
2287    acc_cfgs.push(AccountConfig {
2288        address: Some(default_account_kp.0),
2289        gas_amounts: vec![GAS_FOR_TESTING],
2290    });
2291
2292    if let Some(v_addr) = validator_addr {
2293        acc_cfgs.push(AccountConfig {
2294            address: Some(v_addr),
2295            gas_amounts: vec![GAS_FOR_TESTING],
2296        });
2297    }
2298
2299    // Create the simulator with the specific account configs, which also crates
2300    // objects
2301
2302    let (mut sim, read_replica) =
2303        PersistedStore::new_sim_replica_with_protocol_version_and_accounts(
2304            rng,
2305            DEFAULT_CHAIN_START_TIMESTAMP,
2306            protocol_config.version,
2307            acc_cfgs,
2308            key_copy.map(|q| vec![q]),
2309            reference_gas_price,
2310            None,
2311        );
2312
2313    sim.set_data_ingestion_path(data_ingestion_path.clone());
2314
2315    // Get the actual object values from the simulator
2316    for (name, (addr, kp)) in account_kps {
2317        let o = sim.store().owned_objects(addr).next().unwrap();
2318        objects.push(o.clone());
2319        account_objects.insert(name.clone(), o.id());
2320
2321        accounts.insert(
2322            name.to_owned(),
2323            TestAccount {
2324                address: addr,
2325                key_pair: kp,
2326                gas: o.id(),
2327            },
2328        );
2329    }
2330    let o = sim
2331        .store()
2332        .owned_objects(default_account_kp.0)
2333        .next()
2334        .unwrap();
2335    let default_account = TestAccount {
2336        address: default_account_kp.0,
2337        key_pair: default_account_kp.1,
2338        gas: o.id(),
2339    };
2340    objects.push(o.clone());
2341
2342    if let (Some(v_addr), Some(v_key)) = (validator_addr, validator_key) {
2343        let o = sim.store().owned_objects(v_addr).next().unwrap();
2344        let validator_account = TestAccount {
2345            address: v_addr,
2346            key_pair: v_key,
2347            gas: o.id(),
2348        };
2349        objects.push(o.clone());
2350        account_objects.insert("validator_0".to_string(), o.id());
2351        accounts.insert("validator_0".to_string(), validator_account);
2352    }
2353
2354    let sim = Box::new(sim);
2355    update_named_address_mapping(
2356        &mut named_address_mapping,
2357        &accounts,
2358        additional_mapping,
2359        &*sim,
2360    )
2361    .await;
2362
2363    (
2364        sim,
2365        AccountSetup {
2366            default_account,
2367            named_address_mapping,
2368            objects,
2369            account_objects,
2370            accounts,
2371        },
2372        Some(Arc::new(read_replica)),
2373    )
2374}
2375
2376async fn update_named_address_mapping(
2377    named_address_mapping: &mut BTreeMap<String, NumericalAddress>,
2378    accounts: &BTreeMap<String, TestAccount>,
2379    additional_mapping: BTreeMap<String, NumericalAddress>,
2380    trans_adapter: &dyn TransactionalAdapter,
2381) {
2382    let active_val_addrs: BTreeMap<_, _> = trans_adapter
2383        .get_active_validator_addresses()
2384        .await
2385        .expect("Failed to get validator addresses")
2386        .iter()
2387        .enumerate()
2388        .map(|(idx, addr)| (format!("validator_{idx}"), *addr))
2389        .collect();
2390
2391    // For mappings where the address is specified, populate the named address
2392    // mapping
2393    let additional_mapping = additional_mapping
2394        .into_iter()
2395        .chain(accounts.iter().map(|(n, test_account)| {
2396            let addr = NumericalAddress::new(test_account.address.to_inner(), NumberFormat::Hex);
2397            (n.clone(), addr)
2398        }))
2399        .chain(active_val_addrs.iter().map(|(n, addr)| {
2400            let addr = NumericalAddress::new(addr.to_inner(), NumberFormat::Hex);
2401            (n.clone(), addr)
2402        }));
2403    // Extend the mappings of all named addresses with values
2404    for (name, addr) in additional_mapping {
2405        if (named_address_mapping.contains_key(&name)
2406            && (named_address_mapping.get(&name) != Some(&addr)))
2407            || name == "iota"
2408        {
2409            panic!(
2410                "Invalid init. The named address '{}' is reserved or duplicated",
2411                name
2412            )
2413        }
2414        named_address_mapping.insert(name, addr);
2415    }
2416}
2417
2418impl ObjectStore for IotaTestAdapter {
2419    fn get_object(
2420        &self,
2421        object_id: &ObjectID,
2422    ) -> iota_types::storage::error::Result<Option<Object>> {
2423        ObjectStore::get_object(&*self.executor, object_id)
2424    }
2425
2426    fn get_object_by_key(
2427        &self,
2428        object_id: &ObjectID,
2429        version: VersionNumber,
2430    ) -> iota_types::storage::error::Result<Option<Object>> {
2431        ObjectStore::get_object_by_key(&*self.executor, object_id, version)
2432    }
2433}
2434
2435impl ReadStore for IotaTestAdapter {
2436    fn get_latest_epoch_id(&self) -> iota_types::storage::error::Result<EpochId> {
2437        self.executor.get_latest_epoch_id()
2438    }
2439
2440    fn get_committee(
2441        &self,
2442        epoch: iota_types::committee::EpochId,
2443    ) -> iota_types::storage::error::Result<Option<Arc<iota_types::committee::Committee>>> {
2444        self.executor.get_committee(epoch)
2445    }
2446
2447    fn get_latest_checkpoint(&self) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
2448        ReadStore::get_latest_checkpoint(&self.executor)
2449    }
2450
2451    fn get_highest_verified_checkpoint(
2452        &self,
2453    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
2454        self.executor.get_highest_verified_checkpoint()
2455    }
2456
2457    fn get_highest_synced_checkpoint(
2458        &self,
2459    ) -> iota_types::storage::error::Result<VerifiedCheckpoint> {
2460        self.executor.get_highest_synced_checkpoint()
2461    }
2462
2463    fn get_lowest_available_checkpoint(
2464        &self,
2465    ) -> iota_types::storage::error::Result<CheckpointSequenceNumber> {
2466        self.executor.get_lowest_available_checkpoint()
2467    }
2468
2469    fn get_checkpoint_by_digest(
2470        &self,
2471        digest: &iota_types::messages_checkpoint::CheckpointDigest,
2472    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
2473        self.executor.get_checkpoint_by_digest(digest)
2474    }
2475
2476    fn get_checkpoint_by_sequence_number(
2477        &self,
2478        sequence_number: CheckpointSequenceNumber,
2479    ) -> iota_types::storage::error::Result<Option<VerifiedCheckpoint>> {
2480        self.executor
2481            .get_checkpoint_by_sequence_number(sequence_number)
2482    }
2483
2484    fn get_checkpoint_contents_by_digest(
2485        &self,
2486        digest: &CheckpointContentsDigest,
2487    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
2488        self.executor.get_checkpoint_contents_by_digest(digest)
2489    }
2490
2491    fn get_checkpoint_contents_by_sequence_number(
2492        &self,
2493        sequence_number: CheckpointSequenceNumber,
2494    ) -> iota_types::storage::error::Result<Option<CheckpointContents>> {
2495        self.executor
2496            .get_checkpoint_contents_by_sequence_number(sequence_number)
2497    }
2498
2499    fn get_transaction(
2500        &self,
2501        tx_digest: &TransactionDigest,
2502    ) -> iota_types::storage::error::Result<Option<Arc<VerifiedTransaction>>> {
2503        self.executor.get_transaction(tx_digest)
2504    }
2505
2506    fn get_transaction_effects(
2507        &self,
2508        tx_digest: &TransactionDigest,
2509    ) -> iota_types::storage::error::Result<Option<TransactionEffects>> {
2510        self.executor.get_transaction_effects(tx_digest)
2511    }
2512
2513    fn get_events(
2514        &self,
2515        event_digest: &TransactionEventsDigest,
2516    ) -> iota_types::storage::error::Result<Option<TransactionEvents>> {
2517        self.executor.get_events(event_digest)
2518    }
2519
2520    fn get_full_checkpoint_contents_by_sequence_number(
2521        &self,
2522        sequence_number: CheckpointSequenceNumber,
2523    ) -> iota_types::storage::error::Result<
2524        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
2525    > {
2526        self.executor
2527            .get_full_checkpoint_contents_by_sequence_number(sequence_number)
2528    }
2529
2530    fn get_full_checkpoint_contents(
2531        &self,
2532        digest: &CheckpointContentsDigest,
2533    ) -> iota_types::storage::error::Result<
2534        Option<iota_types::messages_checkpoint::FullCheckpointContents>,
2535    > {
2536        self.executor.get_full_checkpoint_contents(digest)
2537    }
2538}