From 084c6367be3ca0dea9026edba87f8125cef66ac1 Mon Sep 17 00:00:00 2001 From: libr Date: Wed, 4 Feb 2026 22:51:44 +0800 Subject: [PATCH 1/5] feat: linkage fix (wip) --- crates/movy-fuzz/src/operations/sui_fuzz.rs | 5 +- crates/movy-fuzz/src/operations/sui_replay.rs | 7 +- crates/movy-replay/src/env.rs | 3 +- crates/movy-replay/src/exec.rs | 110 +++++++++++++++++- crates/movy-sui/src/compile.rs | 15 ++- crates/movy/src/sui/fuzz.rs | 2 +- 6 files changed, 130 insertions(+), 12 deletions(-) diff --git a/crates/movy-fuzz/src/operations/sui_fuzz.rs b/crates/movy-fuzz/src/operations/sui_fuzz.rs index 87d65b2..8ab4fa6 100644 --- a/crates/movy-fuzz/src/operations/sui_fuzz.rs +++ b/crates/movy-fuzz/src/operations/sui_fuzz.rs @@ -32,8 +32,7 @@ use movy_replay::exec::SuiExecutor; use movy_replay::tracer::oracle::{CouldDisabledOralce, SuiGeneralOracle}; use movy_sui::database::cache::{CachedStore, ObjectSuiStoreCommit}; use movy_types::error::MovyError; -use sui_types::storage::BackingStore; -use sui_types::storage::{BackingPackageStore, ObjectStore}; +use sui_types::storage::{BackingPackageStore, BackingStore, ObjectStore}; pub fn oracles( typed_bug_abort: bool, @@ -71,6 +70,7 @@ where + ObjectSuiStoreCommit + BackingStore + BackingPackageStore + + BackingPackageStore + Clone + 'static, { @@ -245,6 +245,7 @@ pub fn fuzz( + ObjectStoreInfo + ObjectSuiStoreCommit + BackingStore + + BackingPackageStore + Clone + 'static, >, diff --git a/crates/movy-fuzz/src/operations/sui_replay.rs b/crates/movy-fuzz/src/operations/sui_replay.rs index 0c64946..c79ebb1 100644 --- a/crates/movy-fuzz/src/operations/sui_replay.rs +++ b/crates/movy-fuzz/src/operations/sui_replay.rs @@ -38,7 +38,12 @@ pub fn sui_plain_replay_seed( trace: bool, ) -> Result<(), MovyError> where - T: ObjectStore + BackingStore + ObjectSuiStoreCommit + ObjectStoreMintObject + ObjectStoreInfo, + T: ObjectStore + + BackingStore + + BackingPackageStore + + ObjectSuiStoreCommit + + ObjectStoreMintObject + + ObjectStoreInfo, { let inner = env.into_inner(); let executor = SuiExecutor::new(inner)?; diff --git a/crates/movy-replay/src/env.rs b/crates/movy-replay/src/env.rs index b60f9a4..f6db290 100644 --- a/crates/movy-replay/src/env.rs +++ b/crates/movy-replay/src/env.rs @@ -122,7 +122,8 @@ impl< let abi_result = SuiCompiledPackage::build_all_unpublished_from_folder(path, false)?; let mut non_test_abi = abi_result.abi()?; log::info!("Compiling {} with test mode...", path.display()); - let compiled_result = SuiCompiledPackage::build_all_unpublished_from_folder(path, true)?; + let mut compiled_result = SuiCompiledPackage::build_all_unpublished_from_folder(path, true)?; + compiled_result.ensure_immediate_deps(); let package_names = compiled_result.package_names.clone(); let compiled_result = compiled_result.movy_mock()?; log::debug!( diff --git a/crates/movy-replay/src/exec.rs b/crates/movy-replay/src/exec.rs index d472651..965d5c4 100644 --- a/crates/movy-replay/src/exec.rs +++ b/crates/movy-replay/src/exec.rs @@ -1,4 +1,9 @@ -use std::{ops::Deref, str::FromStr, sync::Arc}; +use std::{ + collections::{BTreeMap, BTreeSet, VecDeque}, + ops::Deref, + str::FromStr, + sync::Arc, +}; use color_eyre::eyre::eyre; use itertools::Itertools; @@ -16,7 +21,7 @@ use sui_types::{ inner_temporary_store::InnerTemporaryStore, metrics::LimitsMetrics, object::Owner, - storage::{BackingStore, ObjectStore, WriteKind}, + storage::{BackingPackageStore, BackingStore, ObjectStore, WriteKind}, supported_protocol_versions::{Chain, ProtocolConfig}, transaction::{ Argument, CallArg, CheckedInputObjects, Command, InputObjectKind, ObjectReadResult, @@ -59,7 +64,12 @@ impl Deref for ExecutionTracedResults { impl SuiExecutor where - T: ObjectStore + BackingStore + ObjectSuiStoreCommit + ObjectStoreMintObject + ObjectStoreInfo, + T: ObjectStore + + BackingStore + + BackingPackageStore + + ObjectSuiStoreCommit + + ObjectStoreMintObject + + ObjectStoreInfo, { pub fn new(db: T) -> Result { let protocol_config: ProtocolConfig = @@ -214,7 +224,7 @@ where ) -> Result, MovyError> { let gas = self.db.get_move_object_info(gas.into())?.sui_reference(); let tx_kind = TransactionKind::ProgrammableTransaction(ptb.clone()); - let tx_data = TransactionData::new(tx_kind, sender, gas, 1_000_000_000, 1); + let tx_data = TransactionData::new(tx_kind, sender, gas, 100_000_000_000, 1); self.run_tx_trace(tx_data, epoch, epoch_ms, tracer) } @@ -278,6 +288,96 @@ where } } } + let mut canonical_by_original: BTreeMap = BTreeMap::new(); + for dep in dependencies.iter() { + let mut original_id = *dep; + let mut upgraded_id = *dep; + let mut upgraded_version = 0u64; + if let Some(object) = self.db.get_object(dep) { + if let Some(pkg) = object.data.try_as_package() { + original_id = pkg.original_package_id(); + if let Some(info) = pkg.linkage_table().get(&original_id) { + upgraded_id = info.upgraded_id; + upgraded_version = info.upgraded_version.value(); + } else { + upgraded_version = object.version().value(); + } + } else { + upgraded_version = object.version().value(); + } + } + let entry = canonical_by_original + .entry(original_id) + .or_insert((upgraded_id, upgraded_version)); + if upgraded_version > entry.1 { + *entry = (upgraded_id, upgraded_version); + } + } + let canonical_deps: Vec = + canonical_by_original.values().map(|v| v.0).collect(); + debug!( + "Publish deps list (normalized): {}", + canonical_deps.iter().map(|v| v.to_string()).join(", ") + ); + + let mut all_deps: BTreeSet = dependencies.iter().copied().collect(); + let mut queue: VecDeque = dependencies.iter().copied().collect(); + while let Some(dep) = queue.pop_front() { + let mut obj = self.db.get_object(&dep); + if obj.is_none() { + if let Ok(Some(pkg)) = self.db.get_package_object(&dep) { + obj = Some(pkg.into()); + } + } + let Some(object) = obj else { continue }; + let Some(pkg) = object.data.try_as_package() else { continue }; + for info in pkg.linkage_table().values() { + let id = info.upgraded_id; + if all_deps.insert(id) { + queue.push_back(id); + } + } + } + + let mut canonical_by_original: BTreeMap = BTreeMap::new(); + for dep in all_deps.iter() { + let mut obj = self.db.get_object(dep); + if obj.is_none() { + if let Ok(Some(pkg)) = self.db.get_package_object(dep) { + obj = Some(pkg.into()); + } + } + if let Some(object) = obj { + if let Some(pkg) = object.data.try_as_package() { + let original_id = pkg.original_package_id(); + let (upgraded_id, upgraded_version) = + if let Some(info) = pkg.linkage_table().get(&original_id) { + (info.upgraded_id, info.upgraded_version.value()) + } else { + (*dep, object.version().value()) + }; + let entry = canonical_by_original + .entry(original_id) + .or_insert((upgraded_id, upgraded_version)); + if upgraded_version > entry.1 { + *entry = (upgraded_id, upgraded_version); + } + } else { + canonical_by_original + .entry(*dep) + .or_insert((*dep, object.version().value())); + } + } else { + canonical_by_original.entry(*dep).or_insert((*dep, 0)); + } + } + let canonical_deps: Vec = + canonical_by_original.values().map(|v| v.0).collect(); + debug!( + "Publish deps list (normalized): {}", + canonical_deps.iter().map(|v| v.to_string()).join(", ") + ); + let mut modules_bytes = vec![]; for module in &modules { let mut buf = vec![]; @@ -288,7 +388,7 @@ where let ptb = ProgrammableTransaction { inputs: vec![CallArg::Pure(bcs::to_bytes(&admin)?)], commands: vec![ - Command::Publish(modules_bytes, dependencies.clone()), // This produces an upgrade cap + Command::Publish(modules_bytes, canonical_deps.clone()), // This produces an upgrade cap Command::TransferObjects(vec![Argument::Result(0)], Argument::Input(0)), ], }; diff --git a/crates/movy-sui/src/compile.rs b/crates/movy-sui/src/compile.rs index 7e59d3d..054ac1b 100644 --- a/crates/movy-sui/src/compile.rs +++ b/crates/movy-sui/src/compile.rs @@ -64,6 +64,18 @@ impl SuiCompiledPackage { pub fn abi(&self) -> Result { MovePackageAbi::from_sui_id_and_modules(self.package_id, self.all_modules_iter()) } + pub fn ensure_immediate_deps(&mut self) { + let mut deps: BTreeSet = self.dependencies.iter().copied().collect(); + for md in &self.modules { + for dep in md.immediate_dependencies() { + let id: ObjectID = (*dep.address()).into(); + if id != self.package_id && id != ObjectID::ZERO { + deps.insert(id); + } + } + } + self.dependencies = deps.into_iter().collect(); + } } impl SuiCompiledPackage { @@ -121,8 +133,7 @@ impl SuiCompiledPackage { published_dependencies: self.published_dependencies.clone(), }; - let mut deps: BTreeSet = - self.published_dependencies.clone().into_iter().collect(); + let mut deps: BTreeSet = self.dependencies.clone().into_iter().collect(); for md in self.modules.iter() { let md = Self::mock_module(md); // deps.extend( diff --git a/crates/movy/src/sui/fuzz.rs b/crates/movy/src/sui/fuzz.rs index f0803d2..80c0fd4 100644 --- a/crates/movy/src/sui/fuzz.rs +++ b/crates/movy/src/sui/fuzz.rs @@ -198,7 +198,7 @@ impl SuiFuzzArgs { MoveTypeTag::from_str("0x2::sui::SUI").unwrap(), MoveOwner::AddressOwner(self.deployer), gas_id.into(), - 100_000_000_000, + 10_000_000_000_000, )?; let testing_env = SuiTestingEnv::new(env); testing_env.mock_testing_std()?; From b8254e3cd009e1c111fc1a276a41a005a571f602 Mon Sep 17 00:00:00 2001 From: libr Date: Fri, 6 Feb 2026 12:31:36 +0800 Subject: [PATCH 2/5] feat: linkage fix --- Cargo.lock | 91 +------ crates/movy-replay/src/env.rs | 464 +++++++++++++++++++++++++++++++-- crates/movy-replay/src/exec.rs | 53 +++- crates/movy-sui/Cargo.toml | 3 +- crates/movy-sui/src/compile.rs | 181 +++++++++++-- 5 files changed, 667 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d13d7c..73c903a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1024,7 +1024,6 @@ dependencies = [ [[package]] name = "bin-version" version = "1.60.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "const-str 0.5.7", "git-version", @@ -1682,7 +1681,6 @@ checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" [[package]] name = "consensus-config" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "fastcrypto", "mysten-network", @@ -1694,7 +1692,6 @@ dependencies = [ [[package]] name = "consensus-types" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "base64 0.21.7", "consensus-config", @@ -2673,7 +2670,6 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enum-compat-util" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "serde_yaml", ] @@ -4197,7 +4193,6 @@ dependencies = [ [[package]] name = "jsonrpc" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "serde", "serde_json", @@ -5001,12 +4996,10 @@ dependencies = [ [[package]] name = "move-abstract-interpreter" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" [[package]] name = "move-abstract-interpreter-v2" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-binary-format", ] @@ -5014,12 +5007,10 @@ dependencies = [ [[package]] name = "move-abstract-stack" version = "0.0.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" [[package]] name = "move-binary-format" version = "0.0.3" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "enum-compat-util", @@ -5035,12 +5026,10 @@ dependencies = [ [[package]] name = "move-borrow-graph" version = "0.0.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" [[package]] name = "move-bytecode-source-map" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -5056,7 +5045,6 @@ dependencies = [ [[package]] name = "move-bytecode-utils" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "indexmap 2.12.1", @@ -5069,7 +5057,6 @@ dependencies = [ [[package]] name = "move-bytecode-verifier" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-interpreter", "move-abstract-stack", @@ -5085,7 +5072,6 @@ dependencies = [ [[package]] name = "move-bytecode-verifier-meter" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-binary-format", "move-core-types", @@ -5095,7 +5081,6 @@ dependencies = [ [[package]] name = "move-bytecode-verifier-v0" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-interpreter-v2", "move-abstract-stack", @@ -5110,7 +5095,6 @@ dependencies = [ [[package]] name = "move-bytecode-verifier-v1" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-interpreter-v2", "move-abstract-stack", @@ -5125,7 +5109,6 @@ dependencies = [ [[package]] name = "move-bytecode-verifier-v2" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-interpreter-v2", "move-abstract-stack", @@ -5140,7 +5123,6 @@ dependencies = [ [[package]] name = "move-command-line-common" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -5161,7 +5143,6 @@ dependencies = [ [[package]] name = "move-compiler" version = "0.0.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -5197,7 +5178,6 @@ dependencies = [ [[package]] name = "move-core-types" version = "0.0.4" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -5222,7 +5202,6 @@ dependencies = [ [[package]] name = "move-coverage" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -5247,7 +5226,6 @@ dependencies = [ [[package]] name = "move-disassembler" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -5268,7 +5246,6 @@ dependencies = [ [[package]] name = "move-docgen" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "clap", @@ -5291,7 +5268,6 @@ dependencies = [ [[package]] name = "move-ir-to-bytecode" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "codespan-reporting", @@ -5309,7 +5285,6 @@ dependencies = [ [[package]] name = "move-ir-to-bytecode-syntax" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "hex", @@ -5322,7 +5297,6 @@ dependencies = [ [[package]] name = "move-ir-types" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "hex", "move-command-line-common", @@ -5335,7 +5309,6 @@ dependencies = [ [[package]] name = "move-model" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "codespan", @@ -5359,7 +5332,6 @@ dependencies = [ [[package]] name = "move-model-2" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -5383,7 +5355,6 @@ dependencies = [ [[package]] name = "move-package" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "clap", @@ -5419,7 +5390,6 @@ dependencies = [ [[package]] name = "move-proc-macros" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "enum-compat-util", "quote", @@ -5429,7 +5399,6 @@ dependencies = [ [[package]] name = "move-regex-borrow-graph" version = "0.0.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "itertools 0.10.5", "move-binary-format", @@ -5440,7 +5409,6 @@ dependencies = [ [[package]] name = "move-stackless-bytecode" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "codespan", "codespan-reporting", @@ -5461,7 +5429,6 @@ dependencies = [ [[package]] name = "move-stdlib-natives" version = "0.1.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "hex", "move-binary-format", @@ -5476,7 +5443,6 @@ dependencies = [ [[package]] name = "move-stdlib-natives-v0" version = "0.1.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "hex", "move-binary-format", @@ -5491,7 +5457,6 @@ dependencies = [ [[package]] name = "move-stdlib-natives-v1" version = "0.1.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "hex", "move-binary-format", @@ -5506,7 +5471,6 @@ dependencies = [ [[package]] name = "move-stdlib-natives-v2" version = "0.1.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "hex", "move-binary-format", @@ -5521,7 +5485,6 @@ dependencies = [ [[package]] name = "move-symbol-pool" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "once_cell", "phf", @@ -5531,7 +5494,6 @@ dependencies = [ [[package]] name = "move-trace-format" version = "0.0.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-binary-format", "move-core-types", @@ -5544,7 +5506,6 @@ dependencies = [ [[package]] name = "move-vm-config" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-binary-format", "once_cell", @@ -5553,7 +5514,6 @@ dependencies = [ [[package]] name = "move-vm-profiler" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-vm-config", "once_cell", @@ -5565,7 +5525,6 @@ dependencies = [ [[package]] name = "move-vm-runtime" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "better_any", "fail", @@ -5585,7 +5544,6 @@ dependencies = [ [[package]] name = "move-vm-runtime-v0" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "better_any", "fail", @@ -5603,7 +5561,6 @@ dependencies = [ [[package]] name = "move-vm-runtime-v1" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "better_any", "fail", @@ -5621,7 +5578,6 @@ dependencies = [ [[package]] name = "move-vm-runtime-v2" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "better_any", "fail", @@ -5639,7 +5595,6 @@ dependencies = [ [[package]] name = "move-vm-stack" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-bytecode-verifier", "move-vm-types", @@ -5648,7 +5603,6 @@ dependencies = [ [[package]] name = "move-vm-test-utils" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "move-binary-format", @@ -5662,7 +5616,6 @@ dependencies = [ [[package]] name = "move-vm-types" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "move-binary-format", @@ -5675,7 +5628,6 @@ dependencies = [ [[package]] name = "move-vm-types-v0" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "move-binary-format", @@ -5687,7 +5639,6 @@ dependencies = [ [[package]] name = "move-vm-types-v1" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "move-binary-format", @@ -5699,7 +5650,6 @@ dependencies = [ [[package]] name = "move-vm-types-v2" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "move-binary-format", @@ -5866,6 +5816,7 @@ dependencies = [ "mdbx-derive", "move-binary-format", "move-compiler", + "move-core-types", "move-package", "move-trace-format", "move-vm-runtime", @@ -5990,7 +5941,6 @@ dependencies = [ [[package]] name = "mysten-common" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "antithesis_sdk", "anyhow", @@ -6013,7 +5963,6 @@ dependencies = [ [[package]] name = "mysten-metrics" version = "0.7.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "async-trait", "axum", @@ -6034,7 +5983,6 @@ dependencies = [ [[package]] name = "mysten-network" version = "0.2.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anemo", "anemo-tower", @@ -7265,7 +7213,6 @@ dependencies = [ [[package]] name = "prometheus-closure-metric" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "prometheus", @@ -8720,7 +8667,6 @@ dependencies = [ [[package]] name = "shared-crypto" version = "0.0.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "eyre", @@ -9129,7 +9075,6 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "sui-adapter-latest" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -9162,7 +9107,6 @@ dependencies = [ [[package]] name = "sui-adapter-v0" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -9190,7 +9134,6 @@ dependencies = [ [[package]] name = "sui-adapter-v1" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -9217,7 +9160,6 @@ dependencies = [ [[package]] name = "sui-adapter-v2" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -9244,7 +9186,6 @@ dependencies = [ [[package]] name = "sui-config" version = "0.0.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anemo", "anyhow", @@ -9277,7 +9218,6 @@ dependencies = [ [[package]] name = "sui-enum-compat-util" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "serde_yaml", ] @@ -9285,7 +9225,6 @@ dependencies = [ [[package]] name = "sui-execution" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-interpreter", "move-abstract-interpreter-v2", @@ -9324,7 +9263,6 @@ dependencies = [ [[package]] name = "sui-framework" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "move-binary-format", @@ -9338,7 +9276,6 @@ dependencies = [ [[package]] name = "sui-framework-snapshot" version = "1.60.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -9353,7 +9290,6 @@ dependencies = [ [[package]] name = "sui-http" version = "0.0.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bytes", "http", @@ -9373,7 +9309,6 @@ dependencies = [ [[package]] name = "sui-json" version = "0.0.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -9390,7 +9325,6 @@ dependencies = [ [[package]] name = "sui-json-rpc-api" version = "0.0.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "fastcrypto", @@ -9410,7 +9344,6 @@ dependencies = [ [[package]] name = "sui-json-rpc-types" version = "0.0.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "bcs", @@ -9443,7 +9376,6 @@ dependencies = [ [[package]] name = "sui-keys" version = "0.0.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "async-trait", @@ -9469,7 +9401,6 @@ dependencies = [ [[package]] name = "sui-macros" version = "0.7.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "futures", "once_cell", @@ -9480,7 +9411,6 @@ dependencies = [ [[package]] name = "sui-move-build" version = "1.60.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "fastcrypto", @@ -9505,7 +9435,6 @@ dependencies = [ [[package]] name = "sui-move-natives-latest" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "better_any", @@ -9528,7 +9457,6 @@ dependencies = [ [[package]] name = "sui-move-natives-v0" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "better_any", @@ -9549,7 +9477,6 @@ dependencies = [ [[package]] name = "sui-move-natives-v1" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "better_any", @@ -9570,7 +9497,6 @@ dependencies = [ [[package]] name = "sui-move-natives-v2" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "better_any", @@ -9591,7 +9517,6 @@ dependencies = [ [[package]] name = "sui-open-rpc" version = "1.60.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "bcs", "schemars 0.8.22", @@ -9603,7 +9528,6 @@ dependencies = [ [[package]] name = "sui-open-rpc-macros" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "derive-syn-parse", "itertools 0.13.0", @@ -9616,7 +9540,6 @@ dependencies = [ [[package]] name = "sui-package-management" version = "1.60.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "move-core-types", @@ -9634,7 +9557,6 @@ dependencies = [ [[package]] name = "sui-package-resolver" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "async-trait", "bcs", @@ -9652,7 +9574,6 @@ dependencies = [ [[package]] name = "sui-proc-macros" version = "0.7.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "msim-macros", "proc-macro2", @@ -9664,7 +9585,6 @@ dependencies = [ [[package]] name = "sui-protocol-config" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "clap", "fastcrypto", @@ -9682,7 +9602,6 @@ dependencies = [ [[package]] name = "sui-protocol-config-macros" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "proc-macro2", "quote", @@ -9713,7 +9632,6 @@ dependencies = [ [[package]] name = "sui-sdk" version = "1.60.1" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "async-trait", @@ -9767,7 +9685,6 @@ dependencies = [ [[package]] name = "sui-transaction-builder" version = "0.0.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anyhow", "async-trait", @@ -9784,7 +9701,6 @@ dependencies = [ [[package]] name = "sui-types" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "anemo", "anyhow", @@ -9860,7 +9776,6 @@ dependencies = [ [[package]] name = "sui-verifier-latest" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-stack", "move-binary-format", @@ -9876,7 +9791,6 @@ dependencies = [ [[package]] name = "sui-verifier-v0" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-stack", "move-binary-format", @@ -9892,7 +9806,6 @@ dependencies = [ [[package]] name = "sui-verifier-v1" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-stack", "move-binary-format", @@ -9907,7 +9820,6 @@ dependencies = [ [[package]] name = "sui-verifier-v2" version = "0.1.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "move-abstract-stack", "move-binary-format", @@ -10769,7 +10681,6 @@ dependencies = [ [[package]] name = "typed-store-error" version = "0.4.0" -source = "git+https://github.com/wtdcode/sui?branch=v1.60.1-fuzz#713a04540246ec1b21d599213da1a775b34b375b" dependencies = [ "serde", "thiserror 1.0.69", diff --git a/crates/movy-replay/src/env.rs b/crates/movy-replay/src/env.rs index f6db290..d3154c0 100644 --- a/crates/movy-replay/src/env.rs +++ b/crates/movy-replay/src/env.rs @@ -1,11 +1,12 @@ use std::{ - collections::{BTreeMap, BTreeSet}, + collections::{BTreeMap, BTreeSet, VecDeque}, path::Path, str::FromStr, }; use color_eyre::eyre::eyre; use itertools::Itertools; +use move_binary_format::{CompiledModule, file_format_common::VERSION_6}; use movy_sui::{ compile::SuiCompiledPackage, database::cache::ObjectSuiStoreCommit, @@ -18,12 +19,14 @@ use movy_types::{ }; use sui_types::{ Identifier, - base_types::{ObjectID, SequenceNumber}, + base_types::ObjectID, digests::TransactionDigest, effects::TransactionEffectsAPI, + move_package::MovePackage, object::Object, programmable_transaction_builder::ProgrammableTransactionBuilder, storage::{BackingPackageStore, BackingStore, ObjectStore}, + supported_protocol_versions::ProtocolConfig, }; use crate::{ @@ -36,6 +39,56 @@ pub struct SuiTestingEnv { db: T, } + + +fn external_module_refs( + modules: impl Iterator, +) -> BTreeMap> { + let mut out: BTreeMap> = BTreeMap::new(); + for module in modules { + let self_idx = module.self_handle_idx(); + for (idx, h) in module.module_handles.iter().enumerate() { + if (idx as u16) == self_idx.0 { + continue; + } + let addr = ObjectID::from(*module.address_identifier_at(h.address)); + if addr == ObjectID::ZERO { + continue; + } + let name = module.identifier_at(h.name).to_string(); + out.entry(addr).or_default().insert(name); + } + } + out +} + +fn compiled_module_map<'a>( + modules: impl Iterator, + protocol_config: &ProtocolConfig, +) -> Result>, MovyError> { + let mut map = BTreeMap::new(); + for module in modules { + let mut bytes = vec![]; + let version = if protocol_config.move_binary_format_version() > VERSION_6 { + module.version + } else { + VERSION_6 + }; + module.serialize_with_version(version, &mut bytes)?; + map.insert(module.name().to_string(), bytes); + } + Ok(map) +} + +fn package_modules_match( + compiled: &SuiCompiledPackage, + pkg: &MovePackage, + protocol_config: &ProtocolConfig, +) -> Result { + let compiled_map = compiled_module_map(compiled.all_modules_iter(), protocol_config)?; + Ok(&compiled_map == pkg.serialized_module_map()) +} + impl SuiTestingEnv { pub fn inner(&self) -> &T { &self.db @@ -119,25 +172,404 @@ impl< gas: ObjectID, ) -> Result<(MoveAddress, MovePackageAbi, MovePackageAbi, Vec), MovyError> { log::info!("Compiling {} with non-test mode...", path.display()); - let abi_result = SuiCompiledPackage::build_all_unpublished_from_folder(path, false)?; - let mut non_test_abi = abi_result.abi()?; + let mut abi_result = SuiCompiledPackage::build_all_unpublished_from_folder(path, false)?; log::info!("Compiling {} with test mode...", path.display()); - let mut compiled_result = SuiCompiledPackage::build_all_unpublished_from_folder(path, true)?; + let mut compiled_result = + SuiCompiledPackage::build_all_unpublished_from_folder(path, true)?; compiled_result.ensure_immediate_deps(); let package_names = compiled_result.package_names.clone(); - let compiled_result = compiled_result.movy_mock()?; - log::debug!( - "test modules are {}", - compiled_result - .test_modules() - .iter() - .map(|v| v.self_id().name().to_string()) - .join(", ") - ); let mut executor = SuiExecutor::new(&self.db)?; - let address = - executor.deploy_contract(epoch, epoch_ms, deployer.into(), gas, compiled_result)?; + let expected_id = abi_result.package_id; + let address = if expected_id != ObjectID::ZERO { + log::info!( + "published-at detected: {}. Checking local store for existing package...", + expected_id + ); + if let Some(object) = self.db.get_object(&expected_id) { + log::info!( + "package {} exists in store, verifying modules...", + expected_id + ); + let pkg = object + .data + .try_as_package() + .ok_or_else(|| eyre!("Expected package data"))?; + if !package_modules_match(&abi_result, pkg, &executor.protocol_config)? { + log::warn!("package {} modules mismatch", expected_id); + return Err(eyre!("package {} modules mismatch", expected_id).into()); + } + log::info!("package {} modules match; using as deps", expected_id); + expected_id + } else { + log::info!( + "package {} not found. Forcing publish at address...", + expected_id + ); + let compiled_result = compiled_result.movy_mock()?; + log::debug!( + "test modules are {}", + compiled_result + .test_modules() + .iter() + .map(|v| v.self_id().name().to_string()) + .join(", ") + ); + executor.force_deploy_contract_at(expected_id, compiled_result)? + } + } else { + log::debug!("published-at is not set; entering auto-publish + rewrite path"); + log::debug!( + "unpublished_dep_order size={} deps={}", + compiled_result.unpublished_dep_order().len(), + compiled_result + .unpublished_dep_order() + .iter() + .cloned() + .collect::>() + .join(",") + ); + log::debug!( + "unpublished_dep_modules keys size={} keys={}", + compiled_result.unpublished_dep_modules().len(), + compiled_result + .unpublished_dep_modules() + .keys() + .cloned() + .collect::>() + .join(",") + ); + let mut zero_module_addr_map: BTreeMap = BTreeMap::new(); + if !compiled_result.unpublished_dep_order().is_empty() { + // Best-effort: ensure any dependency packages referenced by a dep publish exist in the store. + // Otherwise `deploy_contract` will fail with LINKER_ERROR ("Cannot find ModuleId ... in data cache"). + let mut ensured: BTreeSet = BTreeSet::new(); + + // Only zero-address packages require name-based rewriting. + // Non-zero unpublished deps can be force-published at their compiled address, + // which avoids ambiguity when different packages share module names. + for dep_name in compiled_result.unpublished_dep_order().iter() { + log::debug!("dep publish begin {}", dep_name); + let Some(modules) = compiled_result.unpublished_dep_modules().get(dep_name) + else { + log::debug!( + "ERROR: missing modules for dep {} (available keys={})", + dep_name, + compiled_result + .unpublished_dep_modules() + .keys() + .cloned() + .collect::>() + .join(",") + ); + return Err(eyre!("missing modules for dep {}", dep_name).into()); + }; + if modules.is_empty() { + log::debug!("ERROR: empty modules for dep {}", dep_name); + return Err(eyre!("empty modules for dep {}", dep_name).into()); + } + + let dep_self_addr = ObjectID::from(*modules[0].address()); + let mut dep_pkg = + SuiCompiledPackage::new_unpublished(dep_name.clone(), modules.clone()); + if dep_self_addr == ObjectID::ZERO && !zero_module_addr_map.is_empty() { + if let Err(e) = dep_pkg.rewrite_deps_by_module_name(&zero_module_addr_map) { + log::debug!( + "ERROR: rewrite deps by module name failed for dep {}: {:?}", + dep_name, e + ); + return Err(e); + } + } + dep_pkg.ensure_immediate_deps(); + + // Ensure dependency packages exist for this dep before publishing it. + // This is especially important for zero-address deps that must use `deploy_contract`. + let mut queue: VecDeque = + dep_pkg.dependencies().iter().copied().collect(); + while let Some(addr) = queue.pop_front() { + if addr == ObjectID::ZERO { + continue; + } + if !ensured.insert(addr) { + continue; + } + let exists = self.db.get_package_object(&addr).ok().flatten().is_some() + || self.db.get_object(&addr).is_some(); + if exists { + continue; + } + log::debug!( + "dep {} missing in store (required by {}); trying force publish from compiled deps...", + addr, dep_name + ); + + let Some(candidates) = compiled_result.dep_modules_by_addr().get(&addr) + else { + log::debug!( + "no compiled dep modules recorded for addr {} (required by {})", + addr, dep_name + ); + continue; + }; + if candidates.len() != 1 { + log::debug!( + "ambiguous dep candidates for addr {} (required by {}): {}", + addr, + dep_name, + candidates.keys().cloned().collect::>().join(",") + ); + continue; + } + let (cand_name, cand_modules) = candidates.iter().next().unwrap(); + if cand_modules.is_empty() { + log::debug!( + "dep candidate {} for addr {} has 0 modules (required by {})", + cand_name, addr, dep_name + ); + continue; + } + + let mut cand_pkg = SuiCompiledPackage::new_unpublished( + cand_name.clone(), + cand_modules.clone(), + ); + if ObjectID::from(*cand_modules[0].address()) == ObjectID::ZERO + && !zero_module_addr_map.is_empty() + { + if let Err(e) = + cand_pkg.rewrite_deps_by_module_name(&zero_module_addr_map) + { + log::debug!( + "ERROR: rewrite deps by module name failed for candidate {} at {}: {:?}", + cand_name, addr, e + ); + return Err(e); + } + } + cand_pkg.ensure_immediate_deps(); + for d in cand_pkg.dependencies().iter().copied() { + queue.push_back(d); + } + let cand_pkg = match cand_pkg.movy_mock() { + Ok(v) => v, + Err(e) => { + log::debug!( + "ERROR: movy_mock failed for candidate {} at {}: {:?}", + cand_name, addr, e + ); + return Err(e); + } + }; + if let Err(e) = executor.force_deploy_contract_at(addr, cand_pkg) { + log::debug!( + "ERROR: force publish candidate {} at {} failed: {:?}", + cand_name, addr, e + ); + return Err(e); + } + log::debug!( + "forced publish dep {} from package {} (required by {})", + addr, cand_name, dep_name + ); + } + + let dep_pkg = dep_pkg.movy_mock()?; + let dep_address = if dep_self_addr != ObjectID::ZERO { + if self.db.get_object(&dep_self_addr).is_some() { + dep_self_addr + } else { + match executor.force_deploy_contract_at(dep_self_addr, dep_pkg) { + Ok(v) => v, + Err(e) => { + log::debug!( + "ERROR: force publish dep {} at {} failed: {:?}", + dep_name, dep_self_addr, e + ); + return Err(e); + } + } + } + } else { + match executor.deploy_contract( + epoch, + epoch_ms, + deployer.into(), + gas, + dep_pkg, + ) { + Ok(v) => v, + Err(e) => { + log::debug!( + "ERROR: deploy dep {} (self=0x0) failed: {:?}", + dep_name, e + ); + return Err(e); + } + } + }; + + log::debug!( + "dep publish {}: self={} -> published={}", + dep_name, dep_self_addr, dep_address + ); + + if dep_self_addr == ObjectID::ZERO { + for md in modules.iter() { + let name = md.name().to_string(); + if let Some(prev) = + zero_module_addr_map.insert(name.clone(), dep_address) + { + if prev != dep_address { + log::debug!( + "ERROR: duplicate zero-address module name {} mapped to both {} and {}", + name, prev, dep_address + ); + return Err(eyre!( + "duplicate zero-address module name {} mapped to both {} and {}", + name, + prev, + dep_address + ) + .into()); + } + } + } + } + } + + if !zero_module_addr_map.is_empty() { + log::debug!( + "zero-address module map size={}", + zero_module_addr_map.len() + ); + compiled_result.rewrite_deps_by_module_name(&zero_module_addr_map)?; + abi_result.rewrite_deps_by_module_name(&zero_module_addr_map)?; + } + + log::debug!("dependency auto-publish finished"); + } else { + log::debug!("no unpublished deps detected; skip auto-publish deps"); + } + + compiled_result.ensure_immediate_deps(); + + // Ensure every dependency package exists in the store before publishing root. + // This catches the "address is set but package doesn't exist (yet)" case. + for dep in compiled_result.dependencies().iter().copied() { + let exists = self.db.get_package_object(&dep).ok().flatten().is_some() + || self.db.get_object(&dep).is_some(); + if exists { + continue; + } + + log::debug!( + "dep {} missing in store; trying to force publish from compiled deps...", + dep + ); + + let Some(candidates) = compiled_result.dep_modules_by_addr().get(&dep) else { + log::debug!( + "no compiled dep modules recorded for addr {}", + dep + ); + continue; + }; + if candidates.len() != 1 { + log::debug!( + "ambiguous dep candidates for addr {}: {}", + dep, + candidates.keys().cloned().collect::>().join(",") + ); + continue; + } + let (dep_name, modules) = candidates.iter().next().unwrap(); + if modules.is_empty() { + log::debug!( + "dep candidate {} for addr {} has 0 modules", + dep_name, dep + ); + continue; + } + + let mut dep_pkg = + SuiCompiledPackage::new_unpublished(dep_name.clone(), modules.clone()); + if ObjectID::from(*modules[0].address()) == ObjectID::ZERO + && !zero_module_addr_map.is_empty() + { + dep_pkg.rewrite_deps_by_module_name(&zero_module_addr_map)?; + } + dep_pkg.ensure_immediate_deps(); + let dep_pkg = dep_pkg.movy_mock()?; + executor.force_deploy_contract_at(dep, dep_pkg)?; + log::debug!( + "forced publish missing dep {} from package {}", + dep, dep_name + ); + } + + log::debug!("scanning root external refs..."); + let refs = external_module_refs(compiled_result.all_modules_iter().cloned()); + log::debug!("root external refs count={}", refs.len()); + for (addr, names) in refs.iter() { + log::debug!( + "ref addr={} modules={}", + addr, + names.iter().cloned().collect::>().join(",") + ); + // Best-effort: check package presence and required modules. + let pkg_obj = self + .db + .get_package_object(addr) + .ok() + .flatten() + .map(|p| p.object().clone()) + .or_else(|| self.db.get_object(addr)); + if let Some(obj) = pkg_obj { + if let Some(pkg) = obj.data.try_as_package() { + for m in names { + if !pkg.serialized_module_map().contains_key(m) { + log::debug!( + "missing module {} in package {} (version={})", + m, + addr, + obj.version().value() + ); + } + } + } else { + log::debug!("dep {} exists but not a package", addr); + } + } else { + log::debug!("dep package {} not found in store", addr); + } + } + log::debug!( + "root deps list size={} deps={}", + compiled_result.dependencies().len(), + compiled_result + .dependencies() + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(",") + ); + log::debug!("root external refs scan done"); + + let compiled_result = compiled_result.movy_mock()?; + log::debug!( + "test modules are {}", + compiled_result + .test_modules() + .iter() + .map(|v| v.self_id().name().to_string()) + .join(", ") + ); + log::debug!("about to publish root package"); + executor.deploy_contract(epoch, epoch_ms, deployer.into(), gas, compiled_result)? + }; + + let mut non_test_abi = abi_result.abi()?; // In search of any deploy functions let mut abi = self.db.get_package_info(address.into())?.unwrap(); diff --git a/crates/movy-replay/src/exec.rs b/crates/movy-replay/src/exec.rs index 965d5c4..13f7ff7 100644 --- a/crates/movy-replay/src/exec.rs +++ b/crates/movy-replay/src/exec.rs @@ -16,11 +16,13 @@ use sui_types::{ TypeTag, base_types::{ObjectID, SuiAddress}, committee::ProtocolVersion, + digests::TransactionDigest, effects::{TransactionEffects, TransactionEffectsAPI}, gas::SuiGasStatus, inner_temporary_store::InnerTemporaryStore, metrics::LimitsMetrics, - object::Owner, + move_package::MovePackage, + object::{Object, Owner}, storage::{BackingPackageStore, BackingStore, ObjectStore, WriteKind}, supported_protocol_versions::{Chain, ProtocolConfig}, transaction::{ @@ -253,6 +255,22 @@ where self.run_ptb_with_gas(ptb, epoch, epoch_ms, sender, gas_ref.0, tracer) } + fn load_dependency_package(&self, dep: &ObjectID) -> Result { + let mut obj = self.db.get_object(dep); + if obj.is_none() { + if let Ok(Some(pkg)) = self.db.get_package_object(dep) { + obj = Some(pkg.into()); + } + } + let Some(object) = obj else { + return Err(eyre!("package {} not found", dep).into()); + }; + let Some(pkg) = object.data.try_as_package() else { + return Err(eyre!("object {} is not a package", dep).into()); + }; + Ok(pkg.clone()) + } + pub fn deploy_contract( &mut self, epoch: u64, @@ -422,4 +440,37 @@ where Err(eyre!("fail to deploy").into()) } } + + pub fn force_deploy_contract_at( + &mut self, + package_id: ObjectID, + project: SuiCompiledPackage, + ) -> Result { + log::info!("force publish package at {}", package_id); + let (modules, dependencies) = project.into_deployment(); + let mut dep_packages = Vec::new(); + for dep in dependencies.iter() { + if *dep == package_id { + continue; + } + dep_packages.push(self.load_dependency_package(dep)?); + } + let object = Object::new_package( + &modules, + TransactionDigest::genesis_marker(), + &self.protocol_config, + dep_packages.iter(), + )?; + if object.id() != package_id { + return Err(eyre!( + "forced publish id mismatch: expected {}, got {}", + package_id, + object.id() + ) + .into()); + } + log::info!("force publish success at {}", package_id); + self.db.commit_single_object(object)?; + Ok(package_id) + } } diff --git a/crates/movy-sui/Cargo.toml b/crates/movy-sui/Cargo.toml index 96b75c2..5d9c6a3 100644 --- a/crates/movy-sui/Cargo.toml +++ b/crates/movy-sui/Cargo.toml @@ -33,6 +33,7 @@ url = {workspace = true} auto_impl = {workspace = true} move-compiler = {workspace = true} move-binary-format = {workspace = true} +move-core-types = {workspace = true} move-package = {workspace = true} sui-move-build = {workspace = true} sui-package-management = { workspace = true } @@ -45,4 +46,4 @@ sui-sdk = {workspace = true} movy-types = {workspace = true} [build-dependencies] -cynic-codegen = { version = "3" } \ No newline at end of file +cynic-codegen = { version = "3" } diff --git a/crates/movy-sui/src/compile.rs b/crates/movy-sui/src/compile.rs index 054ac1b..16c9e59 100644 --- a/crates/movy-sui/src/compile.rs +++ b/crates/movy-sui/src/compile.rs @@ -1,10 +1,18 @@ -use std::{collections::BTreeSet, io::Write, path::Path}; +use std::{ + collections::{BTreeMap, BTreeSet}, + io::Write, + path::Path, +}; use color_eyre::eyre::eyre; use itertools::Itertools; use log::{debug, trace}; -use move_binary_format::CompiledModule; +use move_binary_format::{ + CompiledModule, + file_format::{AddressIdentifierIndex, ModuleHandleIndex}, +}; use move_compiler::editions::Flavor; +use move_core_types::account_address::AccountAddress; use move_package::{ resolution::resolution_graph::ResolvedGraph, source_package::layout::SourcePackageLayout, }; @@ -52,12 +60,51 @@ pub struct SuiCompiledPackage { modules: Vec, dependencies: Vec, published_dependencies: Vec, + #[serde(default)] + unpublished_dep_modules: BTreeMap>, + #[serde(default)] + unpublished_dep_order: Vec, + #[serde(skip, default)] + dep_modules_by_addr: BTreeMap>>, } impl SuiCompiledPackage { pub fn all_modules_iter(&self) -> impl Iterator { self.modules.iter() } + pub fn dependencies(&self) -> &[ObjectID] { + &self.dependencies + } + pub fn unpublished_dep_order(&self) -> &[String] { + &self.unpublished_dep_order + } + pub fn unpublished_dep_modules(&self) -> &BTreeMap> { + &self.unpublished_dep_modules + } + pub fn dep_modules_by_addr( + &self, + ) -> &BTreeMap>> { + &self.dep_modules_by_addr + } + pub fn new_unpublished(package_name: String, modules: Vec) -> Self { + Self { + package_id: ObjectID::ZERO, + package_name: package_name.clone(), + package_names: vec![package_name], + modules, + dependencies: vec![], + published_dependencies: vec![], + unpublished_dep_modules: BTreeMap::new(), + unpublished_dep_order: vec![], + dep_modules_by_addr: BTreeMap::new(), + } + } + pub fn rewrite_deps_by_module_name( + &mut self, + module_addr_map: &BTreeMap, + ) -> Result<(), MovyError> { + rewrite_modules_by_name(&mut self.modules, module_addr_map) + } pub fn into_deployment(self) -> (Vec, Vec) { (self.modules.into_iter().collect(), self.dependencies) } @@ -131,9 +178,12 @@ impl SuiCompiledPackage { modules: vec![], dependencies: vec![], published_dependencies: self.published_dependencies.clone(), + unpublished_dep_modules: self.unpublished_dep_modules.clone(), + unpublished_dep_order: self.unpublished_dep_order.clone(), + dep_modules_by_addr: self.dep_modules_by_addr.clone(), }; - let mut deps: BTreeSet = self.dependencies.clone().into_iter().collect(); + let deps: BTreeSet = self.dependencies.clone().into_iter().collect(); for md in self.modules.iter() { let md = Self::mock_module(md); // deps.extend( @@ -172,24 +222,70 @@ impl SuiCompiledPackage { .compiled_package_info .package_name .to_string(); - let mut package_names = BTreeSet::new(); - package_names.insert(package_name.clone()); + let unpublished_deps = &artifacts.dependency_ids.unpublished; + let mut dep_modules_by_addr: BTreeMap>> = + BTreeMap::new(); for (dep_name, unit) in artifacts.package.deps_compiled_units.iter() { - let module_addr: MoveAddress = (*unit.unit.module.self_id().address()).into(); - if ObjectID::from(module_addr) == root_address { - package_names.insert(dep_name.to_string()); + let addr: ObjectID = ObjectID::from(*unit.unit.module.self_id().address()); + dep_modules_by_addr + .entry(addr) + .or_default() + .entry(dep_name.to_string()) + .or_default() + .push(unit.unit.module.clone()); + } + let mut unpublished_dep_modules: BTreeMap> = BTreeMap::new(); + for (dep_name, unit) in artifacts.package.deps_compiled_units.iter() { + if unpublished_deps.contains(dep_name) { + unpublished_dep_modules + .entry(dep_name.to_string()) + .or_default() + .push(unit.unit.module.clone()); + } + } + let mut unpublished_dep_order = Vec::new(); + // `DependencyGraph::topological_order()` orders a package *before* its dependencies + // (i.e. root first). For publishing, we need the reverse: dependencies first. + for dep_name in artifacts + .dependency_graph + .topological_order() + .into_iter() + .rev() + { + if dep_name == artifacts.package.compiled_package_info.package_name { + continue; + } + if unpublished_deps.contains(&dep_name) { + unpublished_dep_order.push(dep_name.to_string()); } } + let mut package_names = BTreeSet::new(); + package_names.insert(package_name.clone()); + let modules = if root_address == ObjectID::ZERO { + debug!("Root address is zero; using root modules only"); + artifacts + .package + .root_modules() + .map(|m| m.unit.module.clone()) + .collect::>() + } else { + for (dep_name, unit) in artifacts.package.deps_compiled_units.iter() { + let module_addr: MoveAddress = (*unit.unit.module.self_id().address()).into(); + if ObjectID::from(module_addr) == root_address { + package_names.insert(dep_name.to_string()); + } + } + artifacts + .package + .all_compiled_units() + .filter(|m| { + debug!("Compiled module address: {}", m.address.into_inner()); + root_address == m.address.into_inner().into() + }) + .map(|m| m.module.clone()) + .collect::>() + }; let package_names = package_names.into_iter().collect::>(); - let modules = artifacts - .package - .all_compiled_units() - .filter(|m| { - debug!("Compiled module address: {}", m.address.into_inner()); - root_address == m.address.into_inner().into() - }) - .map(|m| m.module.clone()) - .collect::>(); if modules.len() == 0 { return Err(eyre!( "Compiling {} yields 0 modules for root {}", @@ -234,6 +330,9 @@ impl SuiCompiledPackage { .values() .cloned() .collect(), + unpublished_dep_modules, + unpublished_dep_order, + dep_modules_by_addr, }) } @@ -265,6 +364,54 @@ impl SuiCompiledPackage { } } +fn rewrite_modules_by_name( + modules: &mut [CompiledModule], + module_addr_map: &BTreeMap, +) -> Result<(), MovyError> { + for module in modules.iter_mut() { + rewrite_module_by_name(module, module_addr_map)?; + } + Ok(()) +} + +fn rewrite_module_by_name( + module: &mut CompiledModule, + module_addr_map: &BTreeMap, +) -> Result<(), MovyError> { + let mut addr_index_map: BTreeMap = BTreeMap::new(); + for (idx, addr) in module.address_identifiers.iter().enumerate() { + addr_index_map.insert(ObjectID::from(*addr), AddressIdentifierIndex(idx as u16)); + } + + let self_handle_idx = module.self_handle_idx(); + for idx in 0..module.module_handles.len() { + let handle_idx = ModuleHandleIndex(idx as u16); + if handle_idx == self_handle_idx { + continue; + } + let handle = module.module_handles[idx].clone(); + let current_addr = *module.address_identifier_at(handle.address); + if current_addr != AccountAddress::ZERO { + continue; + } + let name = module.identifier_at(handle.name).to_string(); + let Some(new_addr) = module_addr_map.get(&name) else { + continue; + }; + let addr_idx = if let Some(existing) = addr_index_map.get(new_addr) { + *existing + } else { + let addr = AccountAddress::from(*new_addr); + module.address_identifiers.push(addr); + let new_idx = AddressIdentifierIndex((module.address_identifiers.len() - 1) as u16); + addr_index_map.insert(*new_addr, new_idx); + new_idx + }; + module.module_handles[idx].address = addr_idx; + } + Ok(()) +} + #[cfg(test)] mod test { use std::{ From 8406cf86de0439564652fdb5c9ede6ce5ac1e6f9 Mon Sep 17 00:00:00 2001 From: libr Date: Fri, 6 Feb 2026 16:04:22 +0800 Subject: [PATCH 3/5] feat: add package address override functionality and refactor dependencies handling --- Cargo.toml | 96 ++++++++++---------- crates/movy-replay/src/env.rs | 158 ++++++++++++++++++++++++++------- crates/movy-replay/src/exec.rs | 10 +-- crates/movy-sui/src/compile.rs | 14 +++ crates/movy/src/sui/env.rs | 87 +++++++++++++++++- 5 files changed, 278 insertions(+), 87 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5b39c9b..5c94de1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,59 +61,59 @@ tonic = "0.14" url = "2.5.7" # Sui dependencies -move-binary-format = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-trace-format = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-package = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-compiler = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-disassembler = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-ir-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-core-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-vm-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-vm-stack = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-vm-runtime = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz", features = ["tracing", "testing"]} -move-model = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -move-stackless-bytecode = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-move-build = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-json-rpc-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-package-management = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-sdk = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-config = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-storage = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-snapshot = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-core = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -sui-execution = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz", features = ["testing"]} -shared-crypto = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} -# sui-adapter-v2 = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-binary-format = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-trace-format = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-package = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-compiler = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-disassembler = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-ir-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-core-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-vm-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-vm-stack = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-vm-runtime = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz", features = ["tracing", "testing"]} +# move-model = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# move-stackless-bytecode = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-move-build = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-json-rpc-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-types = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-package-management = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-sdk = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-config = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-storage = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-snapshot = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-core = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# sui-execution = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz", features = ["testing"]} +# shared-crypto = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} +# # sui-adapter-v2 = {git = "https://github.com/wtdcode/sui", branch = "v1.60.1-fuzz"} sui-rpc = { git = "https://github.com/MystenLabs/sui-rust-sdk.git", rev = "fb62af78b30f5dc64eeaec0094ab95b5ce5b7ce2" } sui-sdk-types = { git = "https://github.com/MystenLabs/sui-rust-sdk.git", rev = "fb62af78b30f5dc64eeaec0094ab95b5ce5b7ce2" } fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "09f86974195ec85d8aae386b1909d341d3ccfe52"} # sui use git dependency =/ -# move-stackless-bytecode = {path = "../sui/external-crates/move/crates/move-stackless-bytecode"} -# move-model = {path = "../sui/external-crates/move/crates/move-model"} -# move-binary-format = {path = "../sui/external-crates/move/crates/move-binary-format"} -# move-trace-format = {path = "../sui/external-crates/move/crates/move-trace-format"} -# move-package = {path = "../sui/external-crates/move/crates/move-package"} -# move-compiler = {path = "../sui/external-crates/move/crates/move-compiler"} -# move-disassembler = {path = "../sui/external-crates/move/crates/move-disassembler"} -# move-ir-types = {path = "../sui/external-crates/move/crates/move-ir-types"} -# move-core-types = {path = "../sui/external-crates/move/crates/move-core-types"} -# move-vm-types = {path = "../sui/external-crates/move/crates/move-vm-types"} -# move-vm-stack = {path = "../sui/external-crates/move/crates/move-vm-stack"} -# move-vm-runtime = {path = "../sui/external-crates/move/crates/move-vm-runtime", features = ["tracing", "testing"]} -# sui-move-build = {path = "../sui/crates/sui-move-build"} -# sui-json-rpc-types = {path = "../sui/crates/sui-json-rpc-types"} -# sui-types = {path = "../sui/crates/sui-types"} -# sui-package-management = {path = "../sui/crates/sui-package-management"} -# sui-sdk = {path = "../sui/crates/sui-sdk"} -# sui-config = {path = "../sui/crates/sui-config"} -# sui-storage = {path = "../sui/crates/sui-storage"} -# sui-snapshot = {path = "../sui/crates/sui-snapshot"} -# sui-core = {path = "../sui/crates/sui-core"} -# sui-execution = {path = "../sui/sui-execution", features = ["testing"]} -# # sui-adapter-v2 = {path = "../sui/sui-execution/v2/sui-adapter/"} -# shared-crypto = {path = "../sui/crates/shared-crypto"} +move-stackless-bytecode = {path = "../sui/external-crates/move/crates/move-stackless-bytecode"} +move-model = {path = "../sui/external-crates/move/crates/move-model"} +move-binary-format = {path = "../sui/external-crates/move/crates/move-binary-format"} +move-trace-format = {path = "../sui/external-crates/move/crates/move-trace-format"} +move-package = {path = "../sui/external-crates/move/crates/move-package"} +move-compiler = {path = "../sui/external-crates/move/crates/move-compiler"} +move-disassembler = {path = "../sui/external-crates/move/crates/move-disassembler"} +move-ir-types = {path = "../sui/external-crates/move/crates/move-ir-types"} +move-core-types = {path = "../sui/external-crates/move/crates/move-core-types"} +move-vm-types = {path = "../sui/external-crates/move/crates/move-vm-types"} +move-vm-stack = {path = "../sui/external-crates/move/crates/move-vm-stack"} +move-vm-runtime = {path = "../sui/external-crates/move/crates/move-vm-runtime", features = ["tracing", "testing"]} +sui-move-build = {path = "../sui/crates/sui-move-build"} +sui-json-rpc-types = {path = "../sui/crates/sui-json-rpc-types"} +sui-types = {path = "../sui/crates/sui-types"} +sui-package-management = {path = "../sui/crates/sui-package-management"} +sui-sdk = {path = "../sui/crates/sui-sdk"} +sui-config = {path = "../sui/crates/sui-config"} +sui-storage = {path = "../sui/crates/sui-storage"} +sui-snapshot = {path = "../sui/crates/sui-snapshot"} +sui-core = {path = "../sui/crates/sui-core"} +sui-execution = {path = "../sui/sui-execution", features = ["testing"]} +# sui-adapter-v2 = {path = "../sui/sui-execution/v2/sui-adapter/"} +shared-crypto = {path = "../sui/crates/shared-crypto"} # Aptos dependencies diff --git a/crates/movy-replay/src/env.rs b/crates/movy-replay/src/env.rs index d3154c0..7bc5418 100644 --- a/crates/movy-replay/src/env.rs +++ b/crates/movy-replay/src/env.rs @@ -39,7 +39,14 @@ pub struct SuiTestingEnv { db: T, } - +#[derive(Clone, Debug)] +pub struct PackageAddressOverride { + /// The address used in type tags / module IDs (the "original package id" in Sui upgrade terms). + /// If unset, `published_at` is used for both roles. + pub original: Option, + /// The storage package object ID (the "published-at" / upgraded package id). + pub published_at: MoveAddress, +} fn external_module_refs( modules: impl Iterator, @@ -170,6 +177,7 @@ impl< epoch: u64, epoch_ms: u64, gas: ObjectID, + package_address_overrides: Option<&BTreeMap>, ) -> Result<(MoveAddress, MovePackageAbi, MovePackageAbi, Vec), MovyError> { log::info!("Compiling {} with non-test mode...", path.display()); let mut abi_result = SuiCompiledPackage::build_all_unpublished_from_folder(path, false)?; @@ -238,6 +246,7 @@ impl< .collect::>() .join(",") ); + let mut original_to_storage: BTreeMap = BTreeMap::new(); let mut zero_module_addr_map: BTreeMap = BTreeMap::new(); if !compiled_result.unpublished_dep_order().is_empty() { // Best-effort: ensure any dependency packages referenced by a dep publish exist in the store. @@ -269,13 +278,65 @@ impl< } let dep_self_addr = ObjectID::from(*modules[0].address()); + + if let Some(ov) = package_address_overrides.and_then(|m| m.get(dep_name)) { + let original = ov.original.unwrap_or(ov.published_at); + let original_id = ObjectID::from(original); + let storage_id = ObjectID::from(ov.published_at); + + let exists = self + .db + .get_package_object(&storage_id) + .ok() + .flatten() + .is_some() + || self.db.get_object(&storage_id).is_some(); + if !exists { + return Err(eyre!( + "--package-address {}:{} not found in store; add it to --onchains first", + dep_name, + storage_id + ) + .into()); + } + + log::info!( + "Using package {} original={} published-at={} (--package-address)", + dep_name, + original_id, + storage_id + ); + original_to_storage.insert(original_id, storage_id); + + if dep_self_addr == ObjectID::ZERO { + for md in modules.iter() { + let name = md.name().to_string(); + if let Some(prev) = + zero_module_addr_map.insert(name.clone(), original_id) + { + if prev != original_id { + return Err(eyre!( + "duplicate zero-address module name {} mapped to both {} and {}", + name, + prev, + original_id + ) + .into()); + } + } + } + } + continue; + } + let mut dep_pkg = SuiCompiledPackage::new_unpublished(dep_name.clone(), modules.clone()); if dep_self_addr == ObjectID::ZERO && !zero_module_addr_map.is_empty() { if let Err(e) = dep_pkg.rewrite_deps_by_module_name(&zero_module_addr_map) { log::debug!( "ERROR: rewrite deps by module name failed for dep {}: {:?}", - dep_name, e + dep_name, + e ); return Err(e); } @@ -300,14 +361,16 @@ impl< } log::debug!( "dep {} missing in store (required by {}); trying force publish from compiled deps...", - addr, dep_name + addr, + dep_name ); let Some(candidates) = compiled_result.dep_modules_by_addr().get(&addr) else { log::debug!( "no compiled dep modules recorded for addr {} (required by {})", - addr, dep_name + addr, + dep_name ); continue; }; @@ -324,7 +387,9 @@ impl< if cand_modules.is_empty() { log::debug!( "dep candidate {} for addr {} has 0 modules (required by {})", - cand_name, addr, dep_name + cand_name, + addr, + dep_name ); continue; } @@ -341,7 +406,9 @@ impl< { log::debug!( "ERROR: rewrite deps by module name failed for candidate {} at {}: {:?}", - cand_name, addr, e + cand_name, + addr, + e ); return Err(e); } @@ -355,7 +422,9 @@ impl< Err(e) => { log::debug!( "ERROR: movy_mock failed for candidate {} at {}: {:?}", - cand_name, addr, e + cand_name, + addr, + e ); return Err(e); } @@ -363,13 +432,17 @@ impl< if let Err(e) = executor.force_deploy_contract_at(addr, cand_pkg) { log::debug!( "ERROR: force publish candidate {} at {} failed: {:?}", - cand_name, addr, e + cand_name, + addr, + e ); return Err(e); } log::debug!( "forced publish dep {} from package {} (required by {})", - addr, cand_name, dep_name + addr, + cand_name, + dep_name ); } @@ -384,7 +457,9 @@ impl< Err(e) => { log::debug!( "ERROR: force publish dep {} at {} failed: {:?}", - dep_name, dep_self_addr, e + dep_name, + dep_self_addr, + e ); return Err(e); } @@ -402,7 +477,8 @@ impl< Err(e) => { log::debug!( "ERROR: deploy dep {} (self=0x0) failed: {:?}", - dep_name, e + dep_name, + e ); return Err(e); } @@ -411,7 +487,9 @@ impl< log::debug!( "dep publish {}: self={} -> published={}", - dep_name, dep_self_addr, dep_address + dep_name, + dep_self_addr, + dep_address ); if dep_self_addr == ObjectID::ZERO { @@ -423,7 +501,9 @@ impl< if prev != dep_address { log::debug!( "ERROR: duplicate zero-address module name {} mapped to both {} and {}", - name, prev, dep_address + name, + prev, + dep_address ); return Err(eyre!( "duplicate zero-address module name {} mapped to both {} and {}", @@ -453,6 +533,7 @@ impl< } compiled_result.ensure_immediate_deps(); + compiled_result.rewrite_dependency_storage_ids(&original_to_storage); // Ensure every dependency package exists in the store before publishing root. // This catches the "address is set but package doesn't exist (yet)" case. @@ -469,10 +550,7 @@ impl< ); let Some(candidates) = compiled_result.dep_modules_by_addr().get(&dep) else { - log::debug!( - "no compiled dep modules recorded for addr {}", - dep - ); + log::debug!("no compiled dep modules recorded for addr {}", dep); continue; }; if candidates.len() != 1 { @@ -485,10 +563,7 @@ impl< } let (dep_name, modules) = candidates.iter().next().unwrap(); if modules.is_empty() { - log::debug!( - "dep candidate {} for addr {} has 0 modules", - dep_name, dep - ); + log::debug!("dep candidate {} for addr {} has 0 modules", dep_name, dep); continue; } @@ -504,7 +579,8 @@ impl< executor.force_deploy_contract_at(dep, dep_pkg)?; log::debug!( "forced publish missing dep {} from package {}", - dep, dep_name + dep, + dep_name ); } @@ -512,19 +588,37 @@ impl< let refs = external_module_refs(compiled_result.all_modules_iter().cloned()); log::debug!("root external refs count={}", refs.len()); for (addr, names) in refs.iter() { - log::debug!( - "ref addr={} modules={}", - addr, - names.iter().cloned().collect::>().join(",") - ); + let mapped_storage = if self.db.get_object(addr).is_none() + && self.db.get_package_object(addr).ok().flatten().is_none() + { + original_to_storage.get(addr).copied() + } else { + None + }; + let check_addr = mapped_storage.unwrap_or(*addr); + + if let Some(storage) = mapped_storage { + log::debug!( + "ref addr={} (mapped to storage {}) modules={}", + addr, + storage, + names.iter().cloned().collect::>().join(",") + ); + } else { + log::debug!( + "ref addr={} modules={}", + addr, + names.iter().cloned().collect::>().join(",") + ); + } // Best-effort: check package presence and required modules. let pkg_obj = self .db - .get_package_object(addr) + .get_package_object(&check_addr) .ok() .flatten() .map(|p| p.object().clone()) - .or_else(|| self.db.get_object(addr)); + .or_else(|| self.db.get_object(&check_addr)); if let Some(obj) = pkg_obj { if let Some(pkg) = obj.data.try_as_package() { for m in names { @@ -532,16 +626,16 @@ impl< log::debug!( "missing module {} in package {} (version={})", m, - addr, + check_addr, obj.version().value() ); } } } else { - log::debug!("dep {} exists but not a package", addr); + log::debug!("dep {} exists but not a package", check_addr); } } else { - log::debug!("dep package {} not found in store", addr); + log::debug!("dep package {} not found in store", check_addr); } } log::debug!( diff --git a/crates/movy-replay/src/exec.rs b/crates/movy-replay/src/exec.rs index 13f7ff7..e2c84a5 100644 --- a/crates/movy-replay/src/exec.rs +++ b/crates/movy-replay/src/exec.rs @@ -331,8 +331,7 @@ where *entry = (upgraded_id, upgraded_version); } } - let canonical_deps: Vec = - canonical_by_original.values().map(|v| v.0).collect(); + let canonical_deps: Vec = canonical_by_original.values().map(|v| v.0).collect(); debug!( "Publish deps list (normalized): {}", canonical_deps.iter().map(|v| v.to_string()).join(", ") @@ -348,7 +347,9 @@ where } } let Some(object) = obj else { continue }; - let Some(pkg) = object.data.try_as_package() else { continue }; + let Some(pkg) = object.data.try_as_package() else { + continue; + }; for info in pkg.linkage_table().values() { let id = info.upgraded_id; if all_deps.insert(id) { @@ -389,8 +390,7 @@ where canonical_by_original.entry(*dep).or_insert((*dep, 0)); } } - let canonical_deps: Vec = - canonical_by_original.values().map(|v| v.0).collect(); + let canonical_deps: Vec = canonical_by_original.values().map(|v| v.0).collect(); debug!( "Publish deps list (normalized): {}", canonical_deps.iter().map(|v| v.to_string()).join(", ") diff --git a/crates/movy-sui/src/compile.rs b/crates/movy-sui/src/compile.rs index 16c9e59..130f199 100644 --- a/crates/movy-sui/src/compile.rs +++ b/crates/movy-sui/src/compile.rs @@ -75,6 +75,20 @@ impl SuiCompiledPackage { pub fn dependencies(&self) -> &[ObjectID] { &self.dependencies } + pub fn rewrite_dependency_storage_ids(&mut self, id_map: &BTreeMap) { + if id_map.is_empty() { + return; + } + let mut out: BTreeSet = BTreeSet::new(); + for dep in self.dependencies.iter().copied() { + if let Some(mapped) = id_map.get(&dep) { + out.insert(*mapped); + } else { + out.insert(dep); + } + } + self.dependencies = out.into_iter().collect(); + } pub fn unpublished_dep_order(&self) -> &[String] { &self.unpublished_dep_order } diff --git a/crates/movy/src/sui/env.rs b/crates/movy/src/sui/env.rs index a76cd7b..9245685 100644 --- a/crates/movy/src/sui/env.rs +++ b/crates/movy/src/sui/env.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use movy_fuzz::meta::FuzzFunctionScore; use movy_replay::{ db::{ObjectStoreCachedStore, ObjectStoreInfo}, - env::SuiTestingEnv, + env::{PackageAddressOverride, SuiTestingEnv}, }; use movy_sui::{ compile::SuiCompiledPackage, database::cache::ObjectSuiStoreCommit, rpc::graphql::GraphQlClient, @@ -38,6 +38,12 @@ pub struct SuiTargetArgs { pub objects: Option>, #[arg(short, long, help = "Local packages to build.")] pub locals: Option>, + #[arg( + long, + value_delimiter = ',', + help = "Override package address mapping. Form: Name:0xPUBLISHED_AT or Name:0xORIGINAL@0xPUBLISHED_AT. Example: --package-address governance:0x03..@0x92.." + )] + pub package_address: Option>, } impl SuiTargetArgs { @@ -78,6 +84,27 @@ impl SuiTargetArgs { { let mut target_packages = Vec::new(); let mut local_name_map = BTreeMap::new(); + let mut package_address_overrides: BTreeMap = + BTreeMap::new(); + for ov in self.package_address.iter().flatten() { + if package_address_overrides + .insert( + ov.package.clone(), + PackageAddressOverride { + original: ov.original, + published_at: ov.published_at, + }, + ) + .is_some() + { + log::warn!("Duplicate --package-address for {}", ov.package); + } + } + let package_address_overrides = if package_address_overrides.is_empty() { + None + } else { + Some(package_address_overrides) + }; for onchain in self.onchains.iter().flatten() { log::info!("Deploying onchain address {} to env...", onchain); env.deploy_address(*onchain).await?; @@ -96,8 +123,17 @@ impl SuiTargetArgs { let mut local_abis = vec![]; for local in self.locals.iter().flatten() { log::info!("Deploying the local package at {}", local.display()); + let overrides_ref = package_address_overrides.as_ref(); let (target_package, testing_abi, abi, package_names) = env - .load_local(local, deployer, attacker, epoch, epoch_ms, gas.into()) + .load_local( + local, + deployer, + attacker, + epoch, + epoch_ms, + gas.into(), + overrides_ref, + ) .await?; for name in package_names.iter() { local_name_map.insert(name.clone(), target_package); @@ -113,6 +149,53 @@ impl SuiTargetArgs { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PackageAddressOverrideArg { + pub package: String, + pub original: Option, + pub published_at: MoveAddress, +} + +impl std::str::FromStr for PackageAddressOverrideArg { + type Err = MovyError; + fn from_str(s: &str) -> Result { + let (pkg, rest) = s.split_once(':').ok_or_else(|| { + MovyError::InvalidIdentifier(format!("Invalid package address: {}", s)) + })?; + let package = pkg.trim(); + if package.is_empty() { + return Err(MovyError::InvalidIdentifier(format!( + "Invalid package address: {}", + s + ))); + } + + let rest = rest.trim(); + if let Some((orig, published)) = rest.split_once('@').or_else(|| rest.split_once('=')) { + let original = MoveAddress::from_str(orig.trim()).map_err(|_| { + MovyError::InvalidIdentifier(format!("Invalid package address: {}", s)) + })?; + let published_at = MoveAddress::from_str(published.trim()).map_err(|_| { + MovyError::InvalidIdentifier(format!("Invalid package address: {}", s)) + })?; + Ok(Self { + package: package.to_string(), + original: Some(original), + published_at, + }) + } else { + let published_at = MoveAddress::from_str(rest).map_err(|_| { + MovyError::InvalidIdentifier(format!("Invalid package address: {}", s)) + })?; + Ok(Self { + package: package.to_string(), + original: None, + published_at, + }) + } + } +} + #[derive(Args, Clone, Debug, Serialize, Deserialize)] pub struct FuzzTargetArgs { #[arg(long, value_delimiter = ',', help = "Include specific packages")] From f652f5b8288a0e0e9deb2300fdcc30d706c6727e Mon Sep 17 00:00:00 2001 From: LiBr Date: Fri, 6 Feb 2026 16:15:00 +0800 Subject: [PATCH 4/5] refactor: simplify dependency normalization in deploy_contract --- crates/movy-replay/src/exec.rs | 61 +++++++++------------------------- 1 file changed, 16 insertions(+), 45 deletions(-) diff --git a/crates/movy-replay/src/exec.rs b/crates/movy-replay/src/exec.rs index e2c84a5..37f1963 100644 --- a/crates/movy-replay/src/exec.rs +++ b/crates/movy-replay/src/exec.rs @@ -306,47 +306,18 @@ where } } } - let mut canonical_by_original: BTreeMap = BTreeMap::new(); - for dep in dependencies.iter() { - let mut original_id = *dep; - let mut upgraded_id = *dep; - let mut upgraded_version = 0u64; - if let Some(object) = self.db.get_object(dep) { - if let Some(pkg) = object.data.try_as_package() { - original_id = pkg.original_package_id(); - if let Some(info) = pkg.linkage_table().get(&original_id) { - upgraded_id = info.upgraded_id; - upgraded_version = info.upgraded_version.value(); - } else { - upgraded_version = object.version().value(); - } - } else { - upgraded_version = object.version().value(); - } - } - let entry = canonical_by_original - .entry(original_id) - .or_insert((upgraded_id, upgraded_version)); - if upgraded_version > entry.1 { - *entry = (upgraded_id, upgraded_version); - } - } - let canonical_deps: Vec = canonical_by_original.values().map(|v| v.0).collect(); - debug!( - "Publish deps list (normalized): {}", - canonical_deps.iter().map(|v| v.to_string()).join(", ") - ); - let mut all_deps: BTreeSet = dependencies.iter().copied().collect(); let mut queue: VecDeque = dependencies.iter().copied().collect(); while let Some(dep) = queue.pop_front() { - let mut obj = self.db.get_object(&dep); - if obj.is_none() { - if let Ok(Some(pkg)) = self.db.get_package_object(&dep) { - obj = Some(pkg.into()); - } - } - let Some(object) = obj else { continue }; + let Some(object) = self.db.get_object(&dep).or_else(|| { + self.db + .get_package_object(&dep) + .ok() + .flatten() + .map(Into::into) + }) else { + continue; + }; let Some(pkg) = object.data.try_as_package() else { continue; }; @@ -360,13 +331,13 @@ where let mut canonical_by_original: BTreeMap = BTreeMap::new(); for dep in all_deps.iter() { - let mut obj = self.db.get_object(dep); - if obj.is_none() { - if let Ok(Some(pkg)) = self.db.get_package_object(dep) { - obj = Some(pkg.into()); - } - } - if let Some(object) = obj { + if let Some(object) = self.db.get_object(dep).or_else(|| { + self.db + .get_package_object(dep) + .ok() + .flatten() + .map(Into::into) + }) { if let Some(pkg) = object.data.try_as_package() { let original_id = pkg.original_package_id(); let (upgraded_id, upgraded_version) = From 4a58ab439ebb5d621bc55becd948198135643554 Mon Sep 17 00:00:00 2001 From: LiBr Date: Fri, 6 Feb 2026 16:23:37 +0800 Subject: [PATCH 5/5] refactor: deduplicate zero-address module mapping logic --- crates/movy-replay/src/env.rs | 82 ++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/crates/movy-replay/src/env.rs b/crates/movy-replay/src/env.rs index 7bc5418..ded51d1 100644 --- a/crates/movy-replay/src/env.rs +++ b/crates/movy-replay/src/env.rs @@ -96,6 +96,28 @@ fn package_modules_match( Ok(&compiled_map == pkg.serialized_module_map()) } +fn record_zero_address_modules( + module_addr_map: &mut BTreeMap, + modules: &[CompiledModule], + package_addr: ObjectID, +) -> Result<(), MovyError> { + for module in modules.iter() { + let name = module.name().to_string(); + if let Some(prev) = module_addr_map.insert(name.clone(), package_addr) + && prev != package_addr + { + return Err(eyre!( + "duplicate zero-address module name {} mapped to both {} and {}", + name, + prev, + package_addr + ) + .into()); + } + } + Ok(()) +} + impl SuiTestingEnv { pub fn inner(&self) -> &T { &self.db @@ -278,6 +300,7 @@ impl< } let dep_self_addr = ObjectID::from(*modules[0].address()); + let dep_is_zero_addr = dep_self_addr == ObjectID::ZERO; if let Some(ov) = package_address_overrides.and_then(|m| m.get(dep_name)) { let original = ov.original.unwrap_or(ov.published_at); @@ -308,30 +331,19 @@ impl< ); original_to_storage.insert(original_id, storage_id); - if dep_self_addr == ObjectID::ZERO { - for md in modules.iter() { - let name = md.name().to_string(); - if let Some(prev) = - zero_module_addr_map.insert(name.clone(), original_id) - { - if prev != original_id { - return Err(eyre!( - "duplicate zero-address module name {} mapped to both {} and {}", - name, - prev, - original_id - ) - .into()); - } - } - } + if dep_is_zero_addr { + record_zero_address_modules( + &mut zero_module_addr_map, + modules, + original_id, + )?; } continue; } let mut dep_pkg = SuiCompiledPackage::new_unpublished(dep_name.clone(), modules.clone()); - if dep_self_addr == ObjectID::ZERO && !zero_module_addr_map.is_empty() { + if dep_is_zero_addr && !zero_module_addr_map.is_empty() { if let Err(e) = dep_pkg.rewrite_deps_by_module_name(&zero_module_addr_map) { log::debug!( "ERROR: rewrite deps by module name failed for dep {}: {:?}", @@ -492,28 +504,18 @@ impl< dep_address ); - if dep_self_addr == ObjectID::ZERO { - for md in modules.iter() { - let name = md.name().to_string(); - if let Some(prev) = - zero_module_addr_map.insert(name.clone(), dep_address) - { - if prev != dep_address { - log::debug!( - "ERROR: duplicate zero-address module name {} mapped to both {} and {}", - name, - prev, - dep_address - ); - return Err(eyre!( - "duplicate zero-address module name {} mapped to both {} and {}", - name, - prev, - dep_address - ) - .into()); - } - } + if dep_is_zero_addr { + if let Err(e) = record_zero_address_modules( + &mut zero_module_addr_map, + modules, + dep_address, + ) { + log::debug!( + "ERROR: zero-address module map update failed for dep {}: {:?}", + dep_name, + e + ); + return Err(e); } } }