diff --git a/Cargo.lock b/Cargo.lock index d54155c..adf5400 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5823,6 +5823,7 @@ dependencies = [ "serde_json", "serde_json_any_key", "sui-execution", + "sui-json-rpc-types", "sui-types", "tempfile", "tokio", diff --git a/crates/movy-fuzz/src/executor.rs b/crates/movy-fuzz/src/executor.rs index 5632af1..766dc44 100644 --- a/crates/movy-fuzz/src/executor.rs +++ b/crates/movy-fuzz/src/executor.rs @@ -166,6 +166,24 @@ where stage_idx, success, }; + + if log::log_enabled!(log::Level::Debug) { + for ev in events.iter() { + if let Some((st, ev)) = state.fuzz_state().decode_sui_event(ev)? { + log::debug!( + "Event: {}({})", + st.to_canonical_string(true), + serde_json::to_string(&ev) + .unwrap_or_else(|e| format!("json err({}): {:?}", e, ev)) + ); + } else { + log::debug!( + "Event {} missing for decoding", + ev.type_.to_canonical_string(true) + ); + } + } + } let events: Vec<_> = events.into_iter().map(|e| e.into()).collect(); // Expose preliminary outcome so oracles can inspect events. diff --git a/crates/movy-fuzz/src/meta.rs b/crates/movy-fuzz/src/meta.rs index b8b2ffb..d426a6e 100644 --- a/crates/movy-fuzz/src/meta.rs +++ b/crates/movy-fuzz/src/meta.rs @@ -649,98 +649,6 @@ impl FuzzMetadata { }) } - pub fn iter_functions( - &self, - ) -> impl Iterator< - Item = ( - &MoveAddress, // package_addr - &String, // module_name - &MoveModuleAbi, // module_data - &String, // func_name - &MoveFunctionAbi, // func_data - ), - > { - self.abis.iter().flat_map(|(package_addr, package_meta)| { - package_meta.modules.iter().flat_map(move |module_data| { - module_data.functions.iter().map(move |func_data| { - ( - package_addr, - &module_data.module_id.module_name, - module_data, - &func_data.name, - func_data, - ) - }) - }) - }) - } - - pub fn get_package_metadata(&self, package_id: &MoveAddress) -> Option<&MovePackageAbi> { - self.testing_abis.get( - self.module_address_to_package - .get(package_id) - .unwrap_or(package_id), - ) - } - - pub fn get_function( - &self, - package_id: &MoveAddress, - module: &str, - function: &str, - ) -> Option<&MoveFunctionAbi> { - self.get_package_metadata(package_id) - .and_then(|pkg| { - pkg.modules - .iter() - .find(|m| m.module_id.module_name == module) - }) - .and_then(|module| module.functions.iter().find(|f| f.name == function)) - } - - pub fn get_struct( - &self, - package_id: &MoveAddress, - module: &str, - struct_name: &str, - ) -> Option<&MoveStructAbi> { - self.get_package_metadata(package_id) - .and_then(|pkg| { - pkg.modules - .iter() - .find(|m| m.module_id.module_name == module) - }) - .and_then(|module| module.structs.iter().find(|s| s.struct_name == struct_name)) - } - - pub fn get_enum( - &self, - package_id: &MoveAddress, - module: &str, - enum_name: &str, - ) -> Option<&MoveStructAbi> { - self.get_package_metadata(package_id) - .and_then(|pkg| { - pkg.modules - .iter() - .find(|m| m.module_id.module_name == module) - }) - .and_then(|module| module.structs.iter().find(|s| s.struct_name == enum_name)) - } - - pub fn get_abilities( - &self, - package_id: &MoveAddress, - module: &str, - struct_name: &str, - ) -> Option { - self.get_struct(package_id, module, struct_name) - .map(|s| s.abilities) - .or(self - .get_enum(package_id, module, struct_name) - .map(|e| e.abilities)) - } - pub fn generate_magic_number_pool(&self) -> BTreeSet> { BTreeSet::new() } diff --git a/crates/movy-fuzz/src/mutators/object_data.rs b/crates/movy-fuzz/src/mutators/object_data.rs index e744e98..485d597 100644 --- a/crates/movy-fuzz/src/mutators/object_data.rs +++ b/crates/movy-fuzz/src/mutators/object_data.rs @@ -899,7 +899,7 @@ where .flat_map(|type_tag| { meta_state .type_graph - .find_consumers(&MoveAbiSignatureToken::from_type_tag(type_tag)) + .find_consumers(&MoveAbiSignatureToken::from_type_tag_lossy(type_tag)) .iter() .map(|(module_id, consumer_function)| { FunctionIdent::new( diff --git a/crates/movy-replay/Cargo.toml b/crates/movy-replay/Cargo.toml index 9c42d3b..6002a7f 100644 --- a/crates/movy-replay/Cargo.toml +++ b/crates/movy-replay/Cargo.toml @@ -41,6 +41,7 @@ mdbx-derive = {workspace = true} z3 = {workspace = true } tokio-stream = {workspace = true} tempfile = {workspace = true} +sui-json-rpc-types = {workspace = true} movy-analysis = {workspace = true} movy-types = {workspace = true} diff --git a/crates/movy-replay/src/meta.rs b/crates/movy-replay/src/meta.rs index fc18610..7aca14b 100644 --- a/crates/movy-replay/src/meta.rs +++ b/crates/movy-replay/src/meta.rs @@ -7,16 +7,28 @@ use crate::{ db::{ObjectStoreCachedStore, ObjectStoreInfo}, env::SuiTestingEnv, }; +use color_eyre::eyre::eyre; +use move_core_types::{ + annotated_value::{MoveDatatypeLayout, MoveStruct, MoveValue}, + language_storage::StructTag, +}; use movy_analysis::type_graph::MoveTypeGraph; use movy_sui::database::cache::ObjectSuiStoreCommit; use movy_types::{ - abi::{MoveAbility, MovePackageAbi}, + abi::{ + MoveAbiSignatureToken, MoveAbility, MoveFunctionAbi, MoveModuleAbi, MoveModuleId, + MovePackageAbi, MoveStructAbi, + }, error::MovyError, input::{FunctionIdent, MoveAddress, MoveStructTag, MoveTypeTag}, }; use serde::{Deserialize, Serialize}; use serde_json_any_key::*; -use sui_types::storage::{BackingPackageStore, BackingStore, ObjectStore}; +use sui_json_rpc_types::type_and_fields_from_move_event_data; +use sui_types::{ + event::Event, + storage::{BackingPackageStore, BackingStore, ObjectStore}, +}; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Metadata { @@ -28,6 +40,8 @@ pub struct Metadata { pub module_address_to_package: BTreeMap, pub ability_to_type_tag: BTreeMap>, pub function_name_to_idents: BTreeMap>, + #[serde(with = "any_key_map")] + pub structs_mapping: BTreeMap<(MoveModuleId, String), MoveStructAbi>, } pub struct TargetPackages { @@ -36,7 +50,44 @@ pub struct TargetPackages { } impl Metadata { + pub fn iter_functions( + &self, + ) -> impl Iterator< + Item = ( + &MoveAddress, // package_addr + &String, // module_name + &MoveModuleAbi, // module_data + &String, // func_name + &MoveFunctionAbi, // func_data + ), + > { + self.abis.iter().flat_map(|(package_addr, package_meta)| { + package_meta.modules.iter().flat_map(move |module_data| { + module_data.functions.iter().map(move |func_data| { + ( + package_addr, + &module_data.module_id.module_name, + module_data, + &func_data.name, + func_data, + ) + }) + }) + }) + } + pub fn get_package_metadata(&self, package_id: &MoveAddress) -> Option<&MovePackageAbi> { + self.testing_abis.get( + self.module_address_to_package + .get(package_id) + .unwrap_or(package_id), + ) + } + + pub fn get_original_package_metadata( + &self, + package_id: &MoveAddress, + ) -> Option<&MovePackageAbi> { self.abis.get( self.module_address_to_package .get(package_id) @@ -44,6 +95,103 @@ impl Metadata { ) } + pub fn get_function( + &self, + package_id: &MoveAddress, + module: &str, + function: &str, + ) -> Option<&MoveFunctionAbi> { + self.get_package_metadata(package_id) + .and_then(|pkg| { + pkg.modules + .iter() + .find(|m| m.module_id.module_name == module) + }) + .and_then(|module| module.functions.iter().find(|f| f.name == function)) + } + + pub fn get_struct( + &self, + package_id: &MoveAddress, + module: &str, + struct_name: &str, + ) -> Option<&MoveStructAbi> { + self.get_package_metadata(package_id) + .and_then(|pkg| { + pkg.modules + .iter() + .find(|m| m.module_id.module_name == module) + }) + .and_then(|module| module.structs.iter().find(|s| s.struct_name == struct_name)) + } + + pub fn get_enum( + &self, + package_id: &MoveAddress, + module: &str, + enum_name: &str, + ) -> Option<&MoveStructAbi> { + self.get_package_metadata(package_id) + .and_then(|pkg| { + pkg.modules + .iter() + .find(|m| m.module_id.module_name == module) + }) + .and_then(|module| module.structs.iter().find(|s| s.struct_name == enum_name)) + } + + pub fn get_abilities( + &self, + package_id: &MoveAddress, + module: &str, + struct_name: &str, + ) -> Option { + self.get_struct(package_id, module, struct_name) + .map(|s| s.abilities) + .or(self + .get_enum(package_id, module, struct_name) + .map(|e| e.abilities)) + } + + pub fn decode_sui_event( + &self, + event: &Event, + ) -> Result, MovyError> { + log::debug!("Decoding event {}", event.type_.to_canonical_string(true)); + let id: MoveAddress = event.type_.address.into(); + if let Some(st) = + self.get_struct(&id, event.type_.module.as_str(), event.type_.name.as_str()) + { + let mut typs = vec![]; + for ty in event.type_.type_params.iter() { + let ty = MoveTypeTag::from(ty.clone()); + let abi_ty = MoveAbiSignatureToken::from_type_tag_lossy(&ty); + if let Some(typ) = abi_ty.to_move_type_layout(&[], &self.structs_mapping) { + typs.push(typ); + } else { + log::debug!("decode_event: abi_ty {} is mising", &abi_ty); + } + } + if let Some(layout) = st.to_move_struct_layout(&typs, &self.structs_mapping) { + let e = Event::move_event_to_move_value( + &event.contents, + MoveDatatypeLayout::Struct(Box::new(layout)), + )?; + return Ok(Some(type_and_fields_from_move_event_data(e)?)); + } else { + log::debug!( + "can not convert to move struct layout for {} and {:?}", + st.struct_name, + &typs + ); + } + } else { + log::debug!("the event struct is not known"); + } + + Ok(None) + } + pub async fn from_env_filtered( env: &SuiTestingEnv, local_abis: BTreeMap, @@ -150,6 +298,16 @@ impl Metadata { type_graph.add_package(package); } + let mut structs_mapping = BTreeMap::new(); + for (pkg_id, pkg) in testing_abis.iter() { + for md in pkg.modules.iter() { + for st in md.structs.iter() { + structs_mapping + .insert((md.module_id.clone(), st.struct_name.clone()), st.clone()); + } + } + } + let meta = Metadata { type_graph, abis, @@ -161,6 +319,7 @@ impl Metadata { .map(|(ability, tags)| (ability, tags.into_iter().collect())) .collect(), function_name_to_idents, + structs_mapping, }; Ok(meta) } diff --git a/crates/movy-replay/src/tracer/fuzz.rs b/crates/movy-replay/src/tracer/fuzz.rs index d780c5a..6a4b8d1 100644 --- a/crates/movy-replay/src/tracer/fuzz.rs +++ b/crates/movy-replay/src/tracer/fuzz.rs @@ -209,6 +209,7 @@ where frame.module.name(), &frame.function_name ); + log::debug!("Entering {}", &package); self.current_functions.push(FunctionIdent::new( &(*frame.module.address()).into(), &frame.module.name().to_string(), diff --git a/crates/movy-types/src/abi.rs b/crates/movy-types/src/abi.rs index 946db3a..7c152eb 100644 --- a/crates/movy-types/src/abi.rs +++ b/crates/movy-types/src/abi.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, fmt::Display, str::FromStr}; +use std::{collections::BTreeMap, fmt::Display, ops::Deref, str::FromStr}; use alloy_primitives::{U128, U256}; use color_eyre::eyre::eyre; @@ -7,11 +7,15 @@ use move_binary_format::{ CompiledModule, file_format::{ AbilitySet, DatatypeHandleIndex, DatatypeTyParameter, FunctionDefinition, FunctionHandle, - ModuleHandle, SignatureToken, Visibility, + ModuleHandle, SignatureToken, StructDefinition, Visibility, }, }; +use move_core_types::{ + annotated_value::MoveTypeLayout, + annotated_value::{MoveFieldLayout, MoveStructLayout}, +}; use serde::{Deserialize, Serialize}; -use sui_types::{base_types::ObjectID, object::Object}; +use sui_types::{Identifier, base_types::ObjectID, object::Object}; use crate::{ error::MovyError, @@ -119,14 +123,20 @@ impl From for MoveStructTypeParameters { } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct MoveStructAbi { +pub struct MoveStructField { + pub name: String, + pub ty: MoveAbiSignatureToken, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct MoveStructHandle { pub module_id: MoveModuleId, pub struct_name: String, pub abilities: MoveAbility, pub type_parameters: Vec, } -impl MoveStructAbi { +impl MoveStructHandle { pub fn from_module_idx(idx: DatatypeHandleIndex, module: &CompiledModule) -> Self { let dty = module.datatype_handle_at(idx); let sname = module.identifier_at(dty.name).to_string(); @@ -151,6 +161,66 @@ impl MoveStructAbi { } } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct MoveStructAbi { + pub handle: MoveStructHandle, + pub fields: Vec, +} + +impl Deref for MoveStructAbi { + type Target = MoveStructHandle; + fn deref(&self) -> &Self::Target { + &self.handle + } +} + +impl MoveStructAbi { + pub fn to_move_struct_layout( + &self, + typs: &[MoveTypeLayout], + struct_defs: &BTreeMap<(MoveModuleId, String), MoveStructAbi>, + ) -> Option { + let mut fields = vec![]; + + for fd in self.fields.iter() { + let ty = fd.ty.to_move_type_layout(typs, struct_defs)?; + let field = MoveFieldLayout::new(Identifier::new(fd.name.clone()).ok()?, ty); + fields.push(field); + } + Some(MoveStructLayout::new( + MoveStructTag { + address: self.module_id.module_address.clone(), + module: self.module_id.module_name.clone(), + name: self.struct_name.clone(), + tys: vec![], + } + .try_into() + .ok()?, + fields, + )) + } + pub fn from_module_def(def: &StructDefinition, module: &CompiledModule) -> Self { + let handle = MoveStructHandle::from_module_idx(def.struct_handle, module); + let tys = handle + .type_parameters + .iter() + .map(|v| v.constraints.clone()) + .collect_vec(); + let mut fields = vec![]; + for fd in def.fields().into_iter().flatten() { + let ty = MoveAbiSignatureToken::from_sui_token_module(&fd.signature.0, &tys, module); + let tyname = module.identifier_at(fd.name).to_string(); + let field = MoveStructField { + name: tyname, + ty: ty, + }; + fields.push(field); + } + + Self { handle, fields } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum MoveAbiSignatureToken { Bool, @@ -163,8 +233,8 @@ pub enum MoveAbiSignatureToken { Address, Signer, Vector(Box), - Struct(MoveStructAbi), - StructInstantiation(MoveStructAbi, Vec), + Struct(MoveStructHandle), + StructInstantiation(MoveStructHandle, Vec), TypeParameter(u16, MoveAbility), Reference(Box), MutableReference(Box), @@ -300,6 +370,62 @@ impl MoveAbiSignatureToken { _ => None, } } + + pub fn to_move_type_layout( + &self, + typs: &[MoveTypeLayout], + struct_defs: &BTreeMap<(MoveModuleId, String), MoveStructAbi>, + ) -> Option { + match self { + MoveAbiSignatureToken::Address => Some(MoveTypeLayout::Address), + MoveAbiSignatureToken::Signer => Some(MoveTypeLayout::Signer), + MoveAbiSignatureToken::Bool => Some(MoveTypeLayout::Bool), + MoveAbiSignatureToken::U8 => Some(MoveTypeLayout::U8), + MoveAbiSignatureToken::U16 => Some(MoveTypeLayout::U16), + MoveAbiSignatureToken::U32 => Some(MoveTypeLayout::U32), + MoveAbiSignatureToken::U64 => Some(MoveTypeLayout::U64), + MoveAbiSignatureToken::U128 => Some(MoveTypeLayout::U128), + MoveAbiSignatureToken::U256 => Some(MoveTypeLayout::U256), + MoveAbiSignatureToken::Struct(st) => { + let tp = (st.module_id.clone(), st.struct_name.clone()); + let st = struct_defs.get(&tp)?; + + let st = st.to_move_struct_layout(typs, struct_defs)?; + Some(MoveTypeLayout::Struct(Box::new(st))) + } + MoveAbiSignatureToken::StructInstantiation(st, insts) => { + let tp = (st.module_id.clone(), st.struct_name.clone()); + let st = struct_defs.get(&tp)?; + + let mut new_typs = vec![]; + + for inst in insts { + let typ = inst.to_move_type_layout(typs, struct_defs)?; + new_typs.push(typ); + } + + let st = st.to_move_struct_layout(&new_typs, struct_defs)?; + Some(MoveTypeLayout::Struct(Box::new(st))) + } + MoveAbiSignatureToken::TypeParameter(idx, _) => match typs.get(*idx as usize) { + Some(ty) => Some(ty.clone()), + None => { + log::trace!("type parameter {} missing from typs", idx); + None + } + }, + MoveAbiSignatureToken::Vector(v) => Some(MoveTypeLayout::Vector(Box::new( + v.to_move_type_layout(typs, struct_defs)?, + ))), + MoveAbiSignatureToken::Reference(rf) => { + Some(rf.to_move_type_layout(typs, struct_defs)?) + } + MoveAbiSignatureToken::MutableReference(rf) => { + Some(rf.to_move_type_layout(typs, struct_defs)?) + } + } + } + pub fn to_type_tag(&self) -> Option { match self { Self::Bool => Some(MoveTypeTag::Bool), @@ -337,6 +463,11 @@ impl MoveAbiSignatureToken { tys: &Vec, module: &CompiledModule, ) -> Self { + log::trace!( + "from_sui_token_module, value is {:?}, tys is {:?}", + value, + tys + ); match value { SignatureToken::Address => Self::Address, SignatureToken::Bool => Self::Bool, @@ -351,12 +482,12 @@ impl MoveAbiSignatureToken { Self::Vector(Box::new(Self::from_sui_token_module(v, tys, module))) } SignatureToken::Datatype(dty) => { - let st = MoveStructAbi::from_module_idx(*dty, module); + let st = MoveStructHandle::from_module_idx(*dty, module); Self::Struct(st) } SignatureToken::DatatypeInstantiation(v) => { let (dty, insts) = *(v.clone()); - let st = MoveStructAbi::from_module_idx(dty, module); + let st = MoveStructHandle::from_module_idx(dty, module); let insts = insts .into_iter() .map(|v| Self::from_sui_token_module(&v, tys, module)) @@ -745,13 +876,14 @@ impl MoveAbiSignatureToken { MoveAbiSignatureToken::TypeParameter(index, _) => ty_args .get(index) .cloned() - .map(|ty_tag| MoveAbiSignatureToken::from_type_tag(&ty_tag)) + .map(|ty_tag| MoveAbiSignatureToken::from_type_tag_lossy(&ty_tag)) .unwrap_or(self.clone()), _ => self.clone(), } } - pub fn from_type_tag(ty: &MoveTypeTag) -> Self + // TODO: Review the soundness + pub fn from_type_tag_lossy(ty: &MoveTypeTag) -> Self where Self: Sized, { @@ -765,12 +897,12 @@ impl MoveAbiSignatureToken { MoveTypeTag::U128 => MoveAbiSignatureToken::U128, MoveTypeTag::U256 => MoveAbiSignatureToken::U256, MoveTypeTag::Signer => MoveAbiSignatureToken::Signer, - MoveTypeTag::Vector(inner) => { - MoveAbiSignatureToken::Vector(Box::new(MoveAbiSignatureToken::from_type_tag(inner))) - } + MoveTypeTag::Vector(inner) => MoveAbiSignatureToken::Vector(Box::new( + MoveAbiSignatureToken::from_type_tag_lossy(inner), + )), MoveTypeTag::Struct(tag) => { if tag.tys.is_empty() { - MoveAbiSignatureToken::Struct(MoveStructAbi { + MoveAbiSignatureToken::Struct(MoveStructHandle { module_id: MoveModuleId { module_address: tag.address, module_name: tag.module.clone(), @@ -783,10 +915,10 @@ impl MoveAbiSignatureToken { let type_arguments = tag .tys .iter() - .map(MoveAbiSignatureToken::from_type_tag) + .map(MoveAbiSignatureToken::from_type_tag_lossy) .collect(); MoveAbiSignatureToken::StructInstantiation( - MoveStructAbi { + MoveStructHandle { module_id: MoveModuleId { module_address: tag.address, module_name: tag.module.clone(), @@ -818,7 +950,7 @@ impl MoveAbiSignatureToken { impl From for MoveAbiSignatureToken { fn from(value: MoveTypeTag) -> Self { - Self::from_type_tag(&value) + Self::from_type_tag_lossy(&value) } } @@ -1058,7 +1190,7 @@ impl MoveModuleAbi { let mut structs = vec![]; for st in module.struct_defs() { - structs.push(MoveStructAbi::from_module_idx(st.struct_handle, module)); + structs.push(MoveStructAbi::from_module_def(st, module)); } let mut functions = vec![]; @@ -1089,7 +1221,7 @@ impl MovePackageAbi { for md in self.modules.iter_mut() { md.module_id.module_address = address; for st in md.structs.iter_mut() { - st.module_id.module_address = address; + st.handle.module_id.module_address = address; } for fc in md.functions.iter_mut() { for ty in fc.parameters.iter_mut() { diff --git a/test-data/counter/tests/movy.move b/test-data/counter/tests/movy.move index 656765d..dcc20bc 100644 --- a/test-data/counter/tests/movy.move +++ b/test-data/counter/tests/movy.move @@ -6,6 +6,7 @@ use counter::counter::{Self, Counter}; use movy::context::Self; use movy::oracle::crash_because; use sui::bag::Self; +use movy::log::log_keyed_u64; #[test] public fun movy_init( @@ -22,7 +23,6 @@ public fun movy_init( { let mut counter_val = ts::take_shared(&scenario); counter::increment(&mut counter_val, 0); - assert!(counter::value(&counter_val) == 1, 0); ts::return_shared(counter_val); }; @@ -47,6 +47,7 @@ public fun movy_pre_ptb( let (ctr_id, val) = extract_counter(ctr); let state = context::borrow_mut_state(movy); bag::add(state, ctr_id, val); + log_keyed_u64(b"pre-ptb".to_string(), val); } #[test] @@ -57,6 +58,7 @@ public fun movy_post_ptb( let (ctr_id, new_val) = extract_counter(ctr); let state = context::borrow_state(movy); let previous_val = bag::borrow(state, ctr_id); + log_keyed_u64(b"post-ptb".to_string(), new_val); if (*previous_val > new_val) { crash_because(b"Counter should be always increasing".to_string()); } @@ -72,6 +74,7 @@ public fun movy_pre_increment( let (ctr_id, val) = extract_counter(ctr); let state = context::borrow_mut_state(movy); bag::add(state, ctr_id, val); + log_keyed_u64(b"post-increment".to_string(), val); } #[test] @@ -83,6 +86,7 @@ public fun movy_post_increment( let (ctr_id, new_val) = extract_counter(ctr); let state = context::borrow_state(movy); let previous_val = bag::borrow(state, ctr_id); + log_keyed_u64(b"post-increment".to_string(), new_val); if (*previous_val + n != new_val) { crash_because(b"Increment does not correctly inreases internal value.".to_string()); }