1use 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];
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 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 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 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 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, 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 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, 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, 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, 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 let zero =
896 NumericalAddress::new(AccountAddress::ZERO.into_bytes(), NumberFormat::Hex);
897 let before_upgrade = {
898 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 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 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 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 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 .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 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(
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 variables.insert(name.to_owned(), addr.clone());
1263 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 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 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 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, true)?;
1380
1381 let mut builder = ProgrammableTransactionBuilder::new();
1382
1383 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, false);
1443 Ok(output)
1444 }
1445
1446 fn sign_txn(
1447 &self,
1448 sender: Option<String>,
1449 txn_data: impl FnOnce(
1450 IotaAddress,
1452 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 IotaAddress,
1482 IotaAddress,
1484 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 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 let mut might_need_fake_id: Vec<_> = created_ids
1599 .iter()
1600 .chain(unwrapped_ids.iter())
1601 .copied()
1602 .collect();
1603
1604 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 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 let mut might_need_fake_id: Vec<_> = created_ids
1713 .iter()
1714 .chain(unwrapped_ids.iter())
1715 .copied()
1716 .collect();
1717
1718 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 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 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 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 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 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 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
2168async 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 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 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 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 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
2235async 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 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 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 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 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 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 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 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 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}