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