diff --git a/.github/workflows/forge-stable.yaml b/.github/workflows/forge-stable.yaml index e9841704cff90..aea8a24b6c8f0 100644 --- a/.github/workflows/forge-stable.yaml +++ b/.github/workflows/forge-stable.yaml @@ -49,10 +49,8 @@ on: description: The number of test jobs to run in parallel. If not specified, defaults to 1 default: 1 - # NOTE: to support testing different branches on different schedules, you need to specify the cron schedule in the 'determine-test-branch' step as well below - # Reference: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule schedule: - - cron: "0 22 * * 0,2,4" # The main branch cadence. This runs every Sun,Tues,Thurs + - cron: "0 22 * * 1-5" # The main branch cadence. This runs every Mon-Fri pull_request: paths: - ".github/workflows/forge-stable.yaml" @@ -147,17 +145,11 @@ jobs: id: determine-test-branch # NOTE: the schedule cron MUST match the one in the 'on.schedule.cron' section above run: | + BRANCH="" if [[ "${{ github.event_name }}" == "schedule" ]]; then - if [[ "${{ github.event.schedule }}" == "0 22 * * 0,2,4" ]]; then - echo "Branch: main" - echo "BRANCH=main" >> $GITHUB_OUTPUT - else - echo "Unknown schedule: ${{ github.event.schedule }}" - exit 1 - fi + echo "BRANCH=main" >> $GITHUB_OUTPUT elif [[ "${{ github.event_name }}" == "push" ]]; then - echo "Branch: ${{ github.ref_name }}" - echo "BRANCH=${{ github.ref_name }}" >> $GITHUB_OUTPUT + echo "BRANCH=${{ github.ref_name }}" >> $GITHUB_OUTPUT # on workflow_dispatch, this will simply use the inputs.GIT_SHA given (or the default) elif [[ -n "${{ inputs.GIT_SHA }}" ]]; then echo "BRANCH=${{ inputs.GIT_SHA }}" >> $GITHUB_OUTPUT @@ -165,6 +157,7 @@ jobs: else echo "BRANCH=${{ github.head_ref }}" >> $GITHUB_OUTPUT fi + echo "Branch: $(grep BRANCH= $GITHUB_OUTPUT)" # Use the branch hash instead of the full branch name to stay under kubernetes namespace length limit - name: Hash the branch id: hash-branch diff --git a/Cargo.lock b/Cargo.lock index feb02cd1e0d53..6248ce77ab1d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1923,6 +1923,7 @@ dependencies = [ "move-prover", "move-prover-boogie-backend", "move-prover-bytecode-pipeline", + "move-prover-lab", "move-stackless-bytecode", "move-unit-test", "move-vm-runtime", @@ -1942,6 +1943,7 @@ dependencies = [ "tempfile", "thiserror", "tiny-keccak", + "toml 0.7.8", ] [[package]] @@ -11651,7 +11653,6 @@ dependencies = [ "codespan-reporting", "datatest-stable", "itertools 0.13.0", - "log", "move-binary-format", "move-core-types", "move-model", @@ -11663,6 +11664,23 @@ dependencies = [ "walkdir", ] +[[package]] +name = "move-prover-lab" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "clap 4.5.21", + "codespan-reporting", + "itertools 0.13.0", + "move-model", + "move-prover", + "move-prover-boogie-backend", + "move-prover-bytecode-pipeline", + "plotters", + "z3tracer", +] + [[package]] name = "move-prover-test-utils" version = "0.1.0" @@ -13983,26 +14001,6 @@ dependencies = [ "protobuf-codegen", ] -[[package]] -name = "prover-lab" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "clap 4.5.21", - "codespan-reporting", - "itertools 0.13.0", - "log", - "move-compiler", - "move-compiler-v2", - "move-model", - "move-prover", - "move-prover-boogie-backend", - "move-prover-bytecode-pipeline", - "plotters", - "z3tracer", -] - [[package]] name = "psl-types" version = "2.0.11" diff --git a/Cargo.toml b/Cargo.toml index 85473f6bb3de5..fe006c548de84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -875,6 +875,7 @@ move-prover = { path = "third_party/move/move-prover" } move-prover-boogie-backend = { path = "third_party/move/move-prover/boogie-backend" } move-prover-bytecode-pipeline = { path = "third_party/move/move-prover/bytecode-pipeline" } move-prover-test-utils = { path = "third_party/move/move-prover/test-utils" } +move-prover-lab = { path = "third_party/move/move-prover/lab" } aptos-move-stdlib = { path = "aptos-move/framework/move-stdlib" } aptos-table-natives = { path = "aptos-move/framework/table-natives" } move-resource-viewer = { path = "third_party/move/tools/move-resource-viewer" } diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs index 62a09562f1bdf..5c51fa9ee314b 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs @@ -212,7 +212,7 @@ crate::gas_schedule::macros::define_gas_parameters!( [ max_execution_gas_gov: InternalGas, { RELEASE_V1_13.. => "max_execution_gas.gov" }, - 4_300_000_000, + 4_000_000_000, ], [ max_io_gas: InternalGas, diff --git a/aptos-move/aptos-vm-types/src/module_and_script_storage/state_view_adapter.rs b/aptos-move/aptos-vm-types/src/module_and_script_storage/state_view_adapter.rs index 64627c634797d..40eec5457f640 100644 --- a/aptos-move/aptos-vm-types/src/module_and_script_storage/state_view_adapter.rs +++ b/aptos-move/aptos-vm-types/src/module_and_script_storage/state_view_adapter.rs @@ -15,17 +15,20 @@ use move_binary_format::{ CompiledModule, }; use move_core_types::{ - account_address::AccountAddress, identifier::IdentStr, language_storage::ModuleId, + account_address::AccountAddress, + identifier::IdentStr, + language_storage::{ModuleId, TypeTag}, metadata::Metadata, }; use move_vm_runtime::{ ambassador_impl_CodeStorage, ambassador_impl_ModuleStorage, ambassador_impl_WithRuntimeEnvironment, AsUnsyncCodeStorage, BorrowedOrOwned, CodeStorage, - Module, ModuleStorage, RuntimeEnvironment, Script, UnsyncCodeStorage, UnsyncModuleStorage, - WithRuntimeEnvironment, + Function, Module, ModuleStorage, RuntimeEnvironment, Script, UnsyncCodeStorage, + UnsyncModuleStorage, WithRuntimeEnvironment, }; use move_vm_types::{ code::{ModuleBytesStorage, ModuleCode}, + loaded_data::runtime_types::{StructType, Type}, module_storage_error, }; use std::{ops::Deref, sync::Arc}; diff --git a/aptos-move/aptos-vm-types/src/resolver.rs b/aptos-move/aptos-vm-types/src/resolver.rs index 99e72df4c8140..38bcef0f69a24 100644 --- a/aptos-move/aptos-vm-types/src/resolver.rs +++ b/aptos-move/aptos-vm-types/src/resolver.rs @@ -203,33 +203,27 @@ pub trait StateStorageView { /// TODO: audit and reconsider the default implementation (e.g. should not /// resolve AggregatorV2 via the state-view based default implementation, as it /// doesn't provide a value exchange functionality). -pub trait TExecutorView: +pub trait TExecutorView: TResourceView + TModuleView + TAggregatorV1View - + TDelayedFieldView + + TDelayedFieldView + StateStorageView { } -impl TExecutorView for A where +impl TExecutorView for A where A: TResourceView + TModuleView + TAggregatorV1View - + TDelayedFieldView + + TDelayedFieldView + StateStorageView { } -pub trait ExecutorView: - TExecutorView -{ -} +pub trait ExecutorView: TExecutorView {} -impl ExecutorView for T where - T: TExecutorView -{ -} +impl ExecutorView for T where T: TExecutorView {} pub trait ResourceGroupView: TResourceGroupView diff --git a/aptos-move/aptos-vm/src/block_executor/mod.rs b/aptos-move/aptos-vm/src/block_executor/mod.rs index 168b1bb4a3719..ad3918657a568 100644 --- a/aptos-move/aptos-vm/src/block_executor/mod.rs +++ b/aptos-move/aptos-vm/src/block_executor/mod.rs @@ -354,7 +354,7 @@ impl BlockExecutorTransactionOutput for AptosTransactionOutput { .materialized_size() } - fn get_write_summary(&self) -> HashSet> { + fn get_write_summary(&self) -> HashSet> { let vm_output = self.vm_output.lock(); let output = vm_output .as_ref() diff --git a/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs b/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs index e20b94c89c68c..736e2327f953d 100644 --- a/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs +++ b/aptos-move/aptos-vm/src/move_vm_ext/session/mod.rs @@ -38,10 +38,10 @@ use move_core_types::{ vm_status::StatusCode, }; use move_vm_runtime::{ - move_vm::MoveVM, native_extensions::NativeContextExtensions, session::Session, ModuleStorage, - VerifiedModuleBundle, + move_vm::MoveVM, native_extensions::NativeContextExtensions, session::Session, + AsFunctionValueExtension, ModuleStorage, VerifiedModuleBundle, }; -use move_vm_types::{value_serde::serialize_and_allow_delayed_values, values::Value}; +use move_vm_types::{value_serde::ValueSerDeContext, values::Value}; use std::{ collections::BTreeMap, ops::{Deref, DerefMut}, @@ -127,6 +127,7 @@ impl<'r, 'l> SessionExt<'r, 'l> { module_storage: &impl ModuleStorage, ) -> VMResult<(VMChangeSet, ModuleWriteSet)> { let move_vm = self.inner.get_move_vm(); + let function_extension = module_storage.as_function_value_extension(); let resource_converter = |value: Value, layout: MoveTypeLayout, @@ -136,13 +137,17 @@ impl<'r, 'l> SessionExt<'r, 'l> { // We allow serialization of native values here because we want to // temporarily store native values (via encoding to ensure deterministic // gas charging) in block storage. - serialize_and_allow_delayed_values(&value, &layout)? + ValueSerDeContext::new() + .with_delayed_fields_serde() + .with_func_args_deserialization(&function_extension) + .serialize(&value, &layout)? .map(|bytes| (bytes.into(), Some(Arc::new(layout)))) } else { // Otherwise, there should be no native values so ensure // serialization fails here if there are any. - value - .simple_serialize(&layout) + ValueSerDeContext::new() + .with_func_args_deserialization(&function_extension) + .serialize(&value, &layout)? .map(|bytes| (bytes.into(), None)) }; serialization_result.ok_or_else(|| { @@ -165,7 +170,7 @@ impl<'r, 'l> SessionExt<'r, 'l> { let table_context: NativeTableContext = extensions.remove(); let table_change_set = table_context - .into_change_set() + .into_change_set(&function_extension) .map_err(|e| e.finish(Location::Undefined))?; let aggregator_context: NativeAggregatorContext = extensions.remove(); diff --git a/aptos-move/block-executor/src/captured_reads.rs b/aptos-move/block-executor/src/captured_reads.rs index 7a61f5e3fc16c..4835eaa2c4b63 100644 --- a/aptos-move/block-executor/src/captured_reads.rs +++ b/aptos-move/block-executor/src/captured_reads.rs @@ -1,10 +1,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::{ - code_cache_global::GlobalModuleCache, types::InputOutputKey, - value_exchange::filter_value_for_exchange, -}; +use crate::{code_cache_global::GlobalModuleCache, types::InputOutputKey, view::LatestView}; use anyhow::bail; use aptos_aggregator::{ delta_math::DeltaHistory, @@ -21,15 +18,18 @@ use aptos_mvhashmap::{ }; use aptos_types::{ error::{code_invariant_error, PanicError, PanicOr}, - executable::ModulePath, - state_store::state_value::StateValueMetadata, + executable::{Executable, ModulePath}, + state_store::{state_value::StateValueMetadata, TStateView}, transaction::BlockExecutableTransaction as Transaction, write_set::TransactionWrite, }; use aptos_vm_types::resolver::ResourceGroupSize; use derivative::Derivative; use move_core_types::value::MoveTypeLayout; -use move_vm_types::code::{ModuleCode, SyncModuleCache, WithAddress, WithName, WithSize}; +use move_vm_types::{ + code::{ModuleCode, SyncModuleCache, WithAddress, WithName, WithSize}, + delayed_values::delayed_field_id::DelayedFieldID, +}; use std::{ collections::{ hash_map::{ @@ -325,7 +325,7 @@ pub enum CacheRead { pub(crate) struct CapturedReads { data_reads: HashMap>, group_reads: HashMap>, - delayed_field_reads: HashMap, + delayed_field_reads: HashMap, #[deprecated] pub(crate) deprecated_module_reads: Vec, @@ -359,9 +359,13 @@ where S: WithSize, { // Return an iterator over the captured reads. - pub(crate) fn get_read_values_with_delayed_fields( + pub(crate) fn get_read_values_with_delayed_fields< + SV: TStateView, + X: Executable, + >( &self, - delayed_write_set_ids: &HashSet, + view: &LatestView, + delayed_write_set_ids: &HashSet, skip: &HashSet, ) -> Result)>, PanicError> { self.data_reads @@ -372,7 +376,7 @@ where } if let DataRead::Versioned(_version, value, Some(layout)) = data_read { - filter_value_for_exchange::(value, layout, delayed_write_set_ids, key) + view.filter_value_for_exchange(value, layout, delayed_write_set_ids, key) } else { None } @@ -511,7 +515,7 @@ where pub(crate) fn capture_delayed_field_read( &mut self, - id: T::Identifier, + id: DelayedFieldID, update: bool, read: DelayedFieldRead, ) -> Result<(), PanicOr> { @@ -571,7 +575,7 @@ where pub(crate) fn get_delayed_field_by_kind( &self, - id: &T::Identifier, + id: &DelayedFieldID, min_kind: DelayedFieldReadKind, ) -> Option { self.delayed_field_reads @@ -718,7 +722,7 @@ where // (as it internally uses read_latest_predicted_value to get the current value). pub(crate) fn validate_delayed_field_reads( &self, - delayed_fields: &dyn TVersionedDelayedFieldView, + delayed_fields: &dyn TVersionedDelayedFieldView, idx_to_validate: TxnIndex, ) -> Result { if self.delayed_field_speculative_failure { @@ -779,9 +783,7 @@ where K: Hash + Eq + Ord + Clone + WithAddress + WithName, VC: Deref>, { - pub(crate) fn get_read_summary( - &self, - ) -> HashSet> { + pub(crate) fn get_read_summary(&self) -> HashSet> { let mut ret = HashSet::new(); for (key, read) in &self.data_reads { if let DataRead::Versioned(_, _, _) = read { @@ -822,7 +824,7 @@ where pub(crate) struct UnsyncReadSet { pub(crate) resource_reads: HashSet, pub(crate) group_reads: HashMap>, - pub(crate) delayed_field_reads: HashSet, + pub(crate) delayed_field_reads: HashSet, #[deprecated] pub(crate) deprecated_module_reads: HashSet, @@ -839,9 +841,7 @@ where self.module_reads.insert(key); } - pub(crate) fn get_read_summary( - &self, - ) -> HashSet> { + pub(crate) fn get_read_summary(&self) -> HashSet> { let mut ret = HashSet::new(); for key in &self.resource_reads { ret.insert(InputOutputKey::Resource(key.clone())); @@ -1067,7 +1067,6 @@ mod test { impl Transaction for TestTransactionType { type Event = MockEvent; - type Identifier = DelayedFieldID; type Key = KeyType; type Tag = u32; type Value = ValueType; diff --git a/aptos-move/block-executor/src/executor.rs b/aptos-move/block-executor/src/executor.rs index c0bd5ec56967e..b98b68559583c 100644 --- a/aptos-move/block-executor/src/executor.rs +++ b/aptos-move/block-executor/src/executor.rs @@ -59,7 +59,7 @@ use fail::fail_point; use move_binary_format::CompiledModule; use move_core_types::{language_storage::ModuleId, value::MoveTypeLayout, vm_status::StatusCode}; use move_vm_runtime::{Module, RuntimeEnvironment, WithRuntimeEnvironment}; -use move_vm_types::code::ModuleCache; +use move_vm_types::{code::ModuleCache, delayed_values::delayed_field_id::DelayedFieldID}; use num_cpus; use rayon::ThreadPool; use std::{ @@ -115,7 +115,7 @@ where incarnation: Incarnation, signature_verified_block: &TP, last_input_output: &TxnLastInputOutput, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, executor: &E, base_view: &S, global_module_cache: &GlobalModuleCache< @@ -406,7 +406,7 @@ where Module, AptosModuleExtension, >, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, scheduler: &Scheduler, ) -> bool { let _timer = TASK_VALIDATE_SECONDS.start_timer(); @@ -436,7 +436,7 @@ where fn update_transaction_on_abort( txn_idx: TxnIndex, last_input_output: &TxnLastInputOutput, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, runtime_environment: &RuntimeEnvironment, ) { counters::SPECULATIVE_ABORT_COUNT.inc(); @@ -490,7 +490,7 @@ where valid: bool, validation_wave: Wave, last_input_output: &TxnLastInputOutput, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, scheduler: &Scheduler, runtime_environment: &RuntimeEnvironment, ) -> Result { @@ -520,7 +520,7 @@ where /// returns false (indicating that transaction needs to be re-executed). fn validate_and_commit_delayed_fields( txn_idx: TxnIndex, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, last_input_output: &TxnLastInputOutput, ) -> Result { let read_set = last_input_output @@ -563,7 +563,7 @@ where &self, block_gas_limit_type: &BlockGasLimitType, scheduler: &Scheduler, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, scheduler_task: &mut SchedulerTask, last_input_output: &TxnLastInputOutput, shared_commit_state: &ExplicitSyncWrapper>, @@ -766,7 +766,7 @@ where Module, AptosModuleExtension, >, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, scheduler: &Scheduler, runtime_environment: &RuntimeEnvironment, ) -> Result<(), PanicError> { @@ -788,7 +788,7 @@ where fn materialize_aggregator_v1_delta_writes( txn_idx: TxnIndex, last_input_output: &TxnLastInputOutput, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, base_view: &S, ) -> Vec<(T::Key, WriteOp)> { // Materialize all the aggregator v1 deltas. @@ -840,7 +840,7 @@ where fn materialize_txn_commit( &self, txn_idx: TxnIndex, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, scheduler: &Scheduler, start_shared_counter: u32, shared_counter: &AtomicU32, @@ -953,7 +953,7 @@ where environment: &AptosEnvironment, block: &TP, last_input_output: &TxnLastInputOutput, - versioned_cache: &MVHashMap, + versioned_cache: &MVHashMap, scheduler: &Scheduler, // TODO: should not need to pass base view. base_view: &S, @@ -1274,7 +1274,7 @@ where Module, AptosModuleExtension, >, - unsync_map: &UnsyncMap, + unsync_map: &UnsyncMap, output: &E::Output, resource_write_set: Vec<(T::Key, Arc, Option>)>, ) -> Result<(), SequentialBlockExecutionError> { diff --git a/aptos-move/block-executor/src/limit_processor.rs b/aptos-move/block-executor/src/limit_processor.rs index b687da43e1e39..1b60348d45462 100644 --- a/aptos-move/block-executor/src/limit_processor.rs +++ b/aptos-move/block-executor/src/limit_processor.rs @@ -281,7 +281,6 @@ mod test { proptest_types::types::{KeyType, MockEvent, MockTransaction}, types::InputOutputKey, }; - use move_vm_types::delayed_values::delayed_field_id::DelayedFieldID; use std::collections::HashSet; // TODO: add tests for accumulate_fee_statement / compute_conflict_multiplier for different BlockGasLimitType configs @@ -363,15 +362,13 @@ mod test { assert!(processor.should_end_block_parallel()); } - fn to_map( - reads: &[InputOutputKey], - ) -> HashSet, u32, DelayedFieldID>> { + fn to_map(reads: &[InputOutputKey]) -> HashSet, u32>> { reads .iter() .map(|key| match key { InputOutputKey::Resource(k) => InputOutputKey::Resource(KeyType(*k, false)), InputOutputKey::Group(k, t) => InputOutputKey::Group(KeyType(*k, false), *t), - InputOutputKey::DelayedField(i) => InputOutputKey::DelayedField((*i).into()), + InputOutputKey::DelayedField(i) => InputOutputKey::DelayedField(*i), }) .collect() } diff --git a/aptos-move/block-executor/src/proptest_types/types.rs b/aptos-move/block-executor/src/proptest_types/types.rs index d594f134c2829..bc5ee4395e11d 100644 --- a/aptos-move/block-executor/src/proptest_types/types.rs +++ b/aptos-move/block-executor/src/proptest_types/types.rs @@ -440,7 +440,6 @@ impl< > Transaction for MockTransaction { type Event = E; - type Identifier = DelayedFieldID; type Key = K; type Tag = u32; type Value = ValueType; @@ -848,7 +847,7 @@ where fn execute_transaction( &self, - view: &(impl TExecutorView + view: &(impl TExecutorView + TResourceGroupView + AptosCodeStorage), txn: &Self::Txn, @@ -1112,12 +1111,7 @@ where self.deltas.clone() } - fn delayed_field_change_set( - &self, - ) -> BTreeMap< - ::Identifier, - DelayedChange<::Identifier>, - > { + fn delayed_field_change_set(&self) -> BTreeMap> { // TODO[agg_v2](tests): add aggregators V2 to the proptest? BTreeMap::new() } @@ -1263,7 +1257,6 @@ where crate::types::InputOutputKey< ::Key, ::Tag, - ::Identifier, >, > { HashSet::new() diff --git a/aptos-move/block-executor/src/task.rs b/aptos-move/block-executor/src/task.rs index 3599420e28c3f..4efdc14d7160a 100644 --- a/aptos-move/block-executor/src/task.rs +++ b/aptos-move/block-executor/src/task.rs @@ -21,6 +21,7 @@ use aptos_vm_types::{ resolver::{ResourceGroupSize, TExecutorView, TResourceGroupView}, }; use move_core_types::{value::MoveTypeLayout, vm_status::StatusCode}; +use move_vm_types::delayed_values::delayed_field_id::DelayedFieldID; use std::{ collections::{BTreeMap, HashSet}, fmt::Debug, @@ -77,7 +78,6 @@ pub trait ExecutorTask: Sync { ::Key, ::Tag, MoveTypeLayout, - ::Identifier, ::Value, > + TResourceGroupView< GroupKey = ::Key, @@ -118,12 +118,7 @@ pub trait TransactionOutput: Send + Sync + Debug { fn aggregator_v1_delta_set(&self) -> Vec<(::Key, DeltaOp)>; /// Get the delayed field changes of a transaction from its output. - fn delayed_field_change_set( - &self, - ) -> BTreeMap< - ::Identifier, - DelayedChange<::Identifier>, - >; + fn delayed_field_change_set(&self) -> BTreeMap>; fn reads_needing_delayed_field_exchange( &self, @@ -204,11 +199,5 @@ pub trait TransactionOutput: Send + Sync + Debug { fn get_write_summary( &self, - ) -> HashSet< - InputOutputKey< - ::Key, - ::Tag, - ::Identifier, - >, - >; + ) -> HashSet::Key, ::Tag>>; } diff --git a/aptos-move/block-executor/src/txn_last_input_output.rs b/aptos-move/block-executor/src/txn_last_input_output.rs index fcfbde7452aa7..0907d81202c72 100644 --- a/aptos-move/block-executor/src/txn_last_input_output.rs +++ b/aptos-move/block-executor/src/txn_last_input_output.rs @@ -25,6 +25,7 @@ use dashmap::DashSet; use move_binary_format::CompiledModule; use move_core_types::{language_storage::ModuleId, value::MoveTypeLayout}; use move_vm_runtime::{Module, RuntimeEnvironment}; +use move_vm_types::delayed_values::delayed_field_id::DelayedFieldID; use std::{ collections::{BTreeMap, HashSet}, fmt::Debug, @@ -356,7 +357,7 @@ impl, E: Debug + Send + Clone> pub(crate) fn delayed_field_keys( &self, txn_idx: TxnIndex, - ) -> Option> { + ) -> Option> { self.outputs[txn_idx as usize] .load() .as_ref() @@ -462,7 +463,7 @@ impl, E: Debug + Send + Clone> pub(crate) fn get_write_summary( &self, txn_idx: TxnIndex, - ) -> HashSet> { + ) -> HashSet> { match self.outputs[txn_idx as usize] .load_full() .expect("Output must exist") diff --git a/aptos-move/block-executor/src/types.rs b/aptos-move/block-executor/src/types.rs index 8e4c987d91fe5..cd162d271925b 100644 --- a/aptos-move/block-executor/src/types.rs +++ b/aptos-move/block-executor/src/types.rs @@ -2,24 +2,25 @@ // SPDX-License-Identifier: Apache-2.0 use aptos_types::transaction::BlockExecutableTransaction as Transaction; +use move_vm_types::delayed_values::delayed_field_id::DelayedFieldID; use std::{collections::HashSet, fmt}; #[derive(Eq, Hash, PartialEq, Debug)] -pub enum InputOutputKey { +pub enum InputOutputKey { Resource(K), Group(K, T), - DelayedField(I), + DelayedField(DelayedFieldID), } pub struct ReadWriteSummary { - reads: HashSet>, - writes: HashSet>, + reads: HashSet>, + writes: HashSet>, } impl ReadWriteSummary { pub fn new( - reads: HashSet>, - writes: HashSet>, + reads: HashSet>, + writes: HashSet>, ) -> Self { Self { reads, writes } } @@ -29,7 +30,7 @@ impl ReadWriteSummary { } pub fn collapse_resource_group_conflicts(self) -> Self { - let collapse = |k: InputOutputKey| match k { + let collapse = |k: InputOutputKey| match k { InputOutputKey::Resource(k) => InputOutputKey::Resource(k), InputOutputKey::Group(k, _) => InputOutputKey::Resource(k), InputOutputKey::DelayedField(id) => InputOutputKey::DelayedField(id), diff --git a/aptos-move/block-executor/src/value_exchange.rs b/aptos-move/block-executor/src/value_exchange.rs index e28ec35dddd03..0855164565ee3 100644 --- a/aptos-move/block-executor/src/value_exchange.rs +++ b/aptos-move/block-executor/src/value_exchange.rs @@ -17,9 +17,10 @@ use aptos_types::{ use bytes::Bytes; use move_binary_format::errors::PartialVMResult; use move_core_types::value::{IdentifierMappingKind, MoveTypeLayout}; +use move_vm_runtime::AsFunctionValueExtension; use move_vm_types::{ - delayed_values::delayed_field_id::{ExtractWidth, TryFromMoveValue}, - value_serde::{deserialize_and_allow_delayed_values, ValueToIdentifierMapping}, + delayed_values::delayed_field_id::{DelayedFieldID, ExtractWidth, TryFromMoveValue}, + value_serde::{ValueSerDeContext, ValueToIdentifierMapping}, value_traversal::find_identifiers_in_value, values::Value, }; @@ -35,7 +36,7 @@ pub(crate) struct TemporaryValueToIdentifierMapping< txn_idx: TxnIndex, // These are the delayed field keys that were touched when utilizing this mapping // to replace ids with values or values with ids - delayed_field_ids: RefCell>, + delayed_field_ids: RefCell>, } impl<'a, T: Transaction, S: TStateView, X: Executable> @@ -49,11 +50,11 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> } } - fn generate_delayed_field_id(&self, width: u32) -> T::Identifier { + fn generate_delayed_field_id(&self, width: u32) -> DelayedFieldID { self.latest_view.generate_delayed_field_id(width) } - pub fn into_inner(self) -> HashSet { + pub fn into_inner(self) -> HashSet { self.delayed_field_ids.into_inner() } } @@ -64,14 +65,12 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> impl<'a, T: Transaction, S: TStateView, X: Executable> ValueToIdentifierMapping for TemporaryValueToIdentifierMapping<'a, T, S, X> { - type Identifier = T::Identifier; - fn value_to_identifier( &self, kind: &IdentifierMappingKind, layout: &MoveTypeLayout, value: Value, - ) -> PartialVMResult { + ) -> PartialVMResult { let (base_value, width) = DelayedFieldValue::try_from_move_value(layout, value, kind)?; let id = self.generate_delayed_field_id(width); match &self.latest_view.latest_view { @@ -85,7 +84,7 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> ValueToIden fn identifier_to_value( &self, layout: &MoveTypeLayout, - identifier: Self::Identifier, + identifier: DelayedFieldID, ) -> PartialVMResult { self.delayed_field_ids.borrow_mut().insert(identifier); let delayed_field = match &self.latest_view.latest_view { @@ -106,68 +105,87 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> ValueToIden } } -// Given bytes, where values were already exchanged with identifiers, -// return a list of identifiers present in it. -fn extract_identifiers_from_value( - bytes: &Bytes, - layout: &MoveTypeLayout, -) -> anyhow::Result> { - // TODO[agg_v2](optimize): this performs 2 traversals of a value: - // 1) deserialize, - // 2) find identifiers to populate the set. - // See if can cache identifiers in advance, or combine it with - // deserialization. - let value = deserialize_and_allow_delayed_values(bytes, layout) - .ok_or_else(|| anyhow::anyhow!("Failed to deserialize resource during id replacement"))?; - - let mut identifiers = HashSet::new(); - find_identifiers_in_value(&value, &mut identifiers)?; - // TODO[agg_v2](cleanup): ugly way of converting delayed ids to generic type params. - Ok(identifiers.into_iter().map(T::Identifier::from).collect()) -} +impl<'a, T, S, X> LatestView<'a, T, S, X> +where + T: Transaction, + S: TStateView, + X: Executable, +{ + /// Given bytes, where values were already exchanged with identifiers, returns a list of + /// identifiers present in it. + fn extract_identifiers_from_value( + &self, + bytes: &Bytes, + layout: &MoveTypeLayout, + ) -> anyhow::Result> { + // TODO[agg_v2](optimize): this performs 2 traversals of a value: + // 1) deserialize, + // 2) find identifiers to populate the set. + // See if can cache identifiers in advance, or combine it with + // deserialization. + let function_value_extension = self.as_function_value_extension(); + let value = ValueSerDeContext::new() + .with_func_args_deserialization(&function_value_extension) + .with_delayed_fields_serde() + .deserialize(bytes, layout) + .ok_or_else(|| { + anyhow::anyhow!("Failed to deserialize resource during id replacement") + })?; -// Deletion returns a PanicError. -pub(crate) fn does_value_need_exchange( - value: &T::Value, - layout: &MoveTypeLayout, - delayed_write_set_ids: &HashSet, -) -> Result { - if let Some(bytes) = value.bytes() { - extract_identifiers_from_value::(bytes, layout) - .map(|identifiers_in_read| !delayed_write_set_ids.is_disjoint(&identifiers_in_read)) - .map_err(|e| code_invariant_error(format!("Identifier extraction failed with {:?}", e))) - } else { - // Deletion returns an error. - Err(code_invariant_error( - "Delete shouldn't be in values considered for exchange", - )) + let mut identifiers = HashSet::new(); + find_identifiers_in_value(&value, &mut identifiers)?; + // TODO[agg_v2](cleanup): ugly way of converting delayed ids to generic type params. + Ok(identifiers.into_iter().map(DelayedFieldID::from).collect()) } -} -// Exclude deletions, and values that do not contain any delayed field IDs that were written to. -pub(crate) fn filter_value_for_exchange( - value: &T::Value, - layout: &Arc, - delayed_write_set_ids: &HashSet, - key: &T::Key, -) -> Option)), PanicError>> { - if value.is_deletion() { - None - } else { - does_value_need_exchange::(value, layout, delayed_write_set_ids).map_or_else( - |e| Some(Err(e)), - |needs_exchange| { - needs_exchange.then(|| { - Ok(( - key.clone(), - ( - value.as_state_value_metadata().unwrap().clone(), - value.write_op_size().write_len().unwrap(), - layout.clone(), - ), - )) + // Deletion returns a PanicError. + pub(crate) fn does_value_need_exchange( + &self, + value: &T::Value, + layout: &MoveTypeLayout, + delayed_write_set_ids: &HashSet, + ) -> Result { + if let Some(bytes) = value.bytes() { + self.extract_identifiers_from_value(bytes, layout) + .map(|identifiers_in_read| !delayed_write_set_ids.is_disjoint(&identifiers_in_read)) + .map_err(|e| { + code_invariant_error(format!("Identifier extraction failed with {:?}", e)) }) - }, - ) + } else { + // Deletion returns an error. + Err(code_invariant_error( + "Delete shouldn't be in values considered for exchange", + )) + } + } + + // Exclude deletions, and values that do not contain any delayed field IDs that were written to. + pub(crate) fn filter_value_for_exchange( + &self, + value: &T::Value, + layout: &Arc, + delayed_write_set_ids: &HashSet, + key: &T::Key, + ) -> Option)), PanicError>> { + if value.is_deletion() { + None + } else { + self.does_value_need_exchange(value, layout, delayed_write_set_ids) + .map_or_else( + |e| Some(Err(e)), + |needs_exchange| { + needs_exchange.then(|| { + Ok(( + key.clone(), + ( + value.as_state_value_metadata().unwrap().clone(), + value.write_op_size().write_len().unwrap(), + layout.clone(), + ), + )) + }) + }, + ) + } } } diff --git a/aptos-move/block-executor/src/view.rs b/aptos-move/block-executor/src/view.rs index 7f4b3f3daa053..38d9ef7d30647 100644 --- a/aptos-move/block-executor/src/view.rs +++ b/aptos-move/block-executor/src/view.rs @@ -11,9 +11,7 @@ use crate::{ code_cache_global::GlobalModuleCache, counters, scheduler::{DependencyResult, DependencyStatus, Scheduler, TWaitForDependency}, - value_exchange::{ - does_value_need_exchange, filter_value_for_exchange, TemporaryValueToIdentifierMapping, - }, + value_exchange::TemporaryValueToIdentifierMapping, }; use aptos_aggregator::{ bounded_math::{ok_overflow, BoundedMath, SignedU128}, @@ -57,13 +55,10 @@ use move_binary_format::{ CompiledModule, }; use move_core_types::{language_storage::ModuleId, value::MoveTypeLayout, vm_status::StatusCode}; -use move_vm_runtime::{Module, RuntimeEnvironment}; +use move_vm_runtime::{AsFunctionValueExtension, Module, RuntimeEnvironment}; use move_vm_types::{ - delayed_values::delayed_field_id::ExtractUniqueIndex, - value_serde::{ - deserialize_and_allow_delayed_values, deserialize_and_replace_values_with_ids, - serialize_and_allow_delayed_values, serialize_and_replace_ids_with_values, - }, + delayed_values::delayed_field_id::{DelayedFieldID, ExtractUniqueIndex}, + value_serde::ValueSerDeContext, }; use std::{ cell::RefCell, @@ -163,7 +158,7 @@ trait ResourceGroupState { } pub(crate) struct ParallelState<'a, T: Transaction, X: Executable> { - pub(crate) versioned_map: &'a MVHashMap, + pub(crate) versioned_map: &'a MVHashMap, scheduler: &'a Scheduler, start_counter: u32, counter: &'a AtomicU32, @@ -175,9 +170,9 @@ fn get_delayed_field_value_impl( captured_reads: &RefCell< CapturedReads, >, - versioned_delayed_fields: &dyn TVersionedDelayedFieldView, + versioned_delayed_fields: &dyn TVersionedDelayedFieldView, wait_for: &dyn TWaitForDependency, - id: &T::Identifier, + id: &DelayedFieldID, txn_idx: TxnIndex, ) -> Result> { // We expect only DelayedFieldReadKind::Value (which is set from this function), @@ -315,9 +310,9 @@ fn delayed_field_try_add_delta_outcome_impl( captured_reads: &RefCell< CapturedReads, >, - versioned_delayed_fields: &dyn TVersionedDelayedFieldView, + versioned_delayed_fields: &dyn TVersionedDelayedFieldView, wait_for: &dyn TWaitForDependency, - id: &T::Identifier, + id: &DelayedFieldID, base_delta: &SignedU128, delta: &SignedU128, max_value: u128, @@ -451,7 +446,7 @@ fn wait_for_dependency( impl<'a, T: Transaction, X: Executable> ParallelState<'a, T, X> { pub(crate) fn new( - shared_map: &'a MVHashMap, + shared_map: &'a MVHashMap, shared_scheduler: &'a Scheduler, start_shared_counter: u32, shared_counter: &'a AtomicU32, @@ -465,7 +460,11 @@ impl<'a, T: Transaction, X: Executable> ParallelState<'a, T, X> { } } - pub(crate) fn set_delayed_field_value(&self, id: T::Identifier, base_value: DelayedFieldValue) { + pub(crate) fn set_delayed_field_value( + &self, + id: DelayedFieldID, + base_value: DelayedFieldValue, + ) { self.versioned_map .delayed_fields() .set_base_value(id, base_value) @@ -787,7 +786,7 @@ impl<'a, T: Transaction, X: Executable> ResourceGroupState for ParallelState< } pub(crate) struct SequentialState<'a, T: Transaction> { - pub(crate) unsync_map: &'a UnsyncMap, + pub(crate) unsync_map: &'a UnsyncMap, pub(crate) read_set: RefCell>, pub(crate) start_counter: u32, pub(crate) counter: &'a RefCell, @@ -797,7 +796,7 @@ pub(crate) struct SequentialState<'a, T: Transaction> { impl<'a, T: Transaction> SequentialState<'a, T> { pub fn new( - unsync_map: &'a UnsyncMap, + unsync_map: &'a UnsyncMap, start_counter: u32, counter: &'a RefCell, ) -> Self { @@ -810,11 +809,15 @@ impl<'a, T: Transaction> SequentialState<'a, T> { } } - pub(crate) fn set_delayed_field_value(&self, id: T::Identifier, base_value: DelayedFieldValue) { + pub(crate) fn set_delayed_field_value( + &self, + id: DelayedFieldID, + base_value: DelayedFieldValue, + ) { self.unsync_map.set_base_delayed_field(id, base_value) } - pub(crate) fn read_delayed_field(&self, id: T::Identifier) -> Option { + pub(crate) fn read_delayed_field(&self, id: DelayedFieldID) -> Option { self.unsync_map.fetch_delayed_field(&id) } } @@ -1020,7 +1023,7 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< } #[cfg(test)] - fn get_read_summary(&self) -> HashSet> { + fn get_read_summary(&self) -> HashSet> { match &self.latest_view { ViewState::Sync(state) => state.captured_reads.borrow().get_read_summary(), ViewState::Unsync(state) => state.read_set.borrow().get_read_summary(), @@ -1130,20 +1133,28 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< &self, state_value: StateValue, layout: &MoveTypeLayout, - ) -> anyhow::Result<(StateValue, HashSet)> { + ) -> anyhow::Result<(StateValue, HashSet)> { let mapping = TemporaryValueToIdentifierMapping::new(self, self.txn_idx); + let function_value_extension = self.as_function_value_extension(); + state_value .map_bytes(|bytes| { // This call will replace all occurrences of aggregator / snapshot // values with unique identifiers with the same type layout. // The values are stored in aggregators multi-version data structure, // see the actual trait implementation for more details. - let patched_value = - deserialize_and_replace_values_with_ids(bytes.as_ref(), layout, &mapping) - .ok_or_else(|| { - anyhow::anyhow!("Failed to deserialize resource during id replacement") - })?; - serialize_and_allow_delayed_values(&patched_value, layout)? + let patched_value = ValueSerDeContext::new() + .with_delayed_fields_replacement(&mapping) + .with_func_args_deserialization(&function_value_extension) + .deserialize(bytes.as_ref(), layout) + .ok_or_else(|| { + anyhow::anyhow!("Failed to deserialize resource during id replacement") + })?; + + ValueSerDeContext::new() + .with_delayed_fields_serde() + .with_func_args_deserialization(&function_value_extension) + .serialize(&patched_value, layout)? .ok_or_else(|| { anyhow::anyhow!( "Failed to serialize value {} after id replacement", @@ -1161,17 +1172,26 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< &self, bytes: &Bytes, layout: &MoveTypeLayout, - ) -> anyhow::Result<(Bytes, HashSet)> { + ) -> anyhow::Result<(Bytes, HashSet)> { // This call will replace all occurrences of aggregator / snapshot // identifiers with values with the same type layout. - let value = deserialize_and_allow_delayed_values(bytes, layout).ok_or_else(|| { - anyhow::anyhow!( - "Failed to deserialize resource during id replacement: {:?}", - bytes - ) - })?; + let function_value_extension = self.as_function_value_extension(); + let value = ValueSerDeContext::new() + .with_func_args_deserialization(&function_value_extension) + .with_delayed_fields_serde() + .deserialize(bytes, layout) + .ok_or_else(|| { + anyhow::anyhow!( + "Failed to deserialize resource during id replacement: {:?}", + bytes + ) + })?; + let mapping = TemporaryValueToIdentifierMapping::new(self, self.txn_idx); - let patched_bytes = serialize_and_replace_ids_with_values(&value, layout, &mapping) + let patched_bytes = ValueSerDeContext::new() + .with_delayed_fields_replacement(&mapping) + .with_func_args_deserialization(&function_value_extension) + .serialize(&value, layout)? .ok_or_else(|| anyhow::anyhow!("Failed to serialize resource during id replacement"))? .into(); Ok((patched_bytes, mapping.into_inner())) @@ -1180,8 +1200,8 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< fn get_reads_needing_exchange_sequential( &self, read_set: &HashSet, - unsync_map: &UnsyncMap, - delayed_write_set_ids: &HashSet, + unsync_map: &UnsyncMap, + delayed_write_set_ids: &HashSet, skip: &HashSet, ) -> Result)>, PanicError> { read_set @@ -1192,14 +1212,13 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< } match unsync_map.fetch_data(key) { - Some(ValueWithLayout::Exchanged(value, Some(layout))) => { - filter_value_for_exchange::( + Some(ValueWithLayout::Exchanged(value, Some(layout))) => self + .filter_value_for_exchange( value.as_ref(), &layout, delayed_write_set_ids, key, - ) - }, + ), Some(ValueWithLayout::Exchanged(_, None)) => None, Some(ValueWithLayout::RawFromStorage(_)) => Some(Err(code_invariant_error( "Cannot exchange value that was not exchanged before", @@ -1213,7 +1232,7 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< fn get_group_reads_needing_exchange_parallel( &self, parallel_state: &ParallelState<'a, T, X>, - delayed_write_set_ids: &HashSet, + delayed_write_set_ids: &HashSet, skip: &HashSet, ) -> PartialVMResult> { let reads_with_delayed_fields = parallel_state @@ -1233,12 +1252,9 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< let mut resources_needing_delayed_field_exchange = false; for data_read in inner_reads.values() { if let DataRead::Versioned(_version, value, Some(layout)) = data_read { - let needs_exchange = does_value_need_exchange::( - value, - layout.as_ref(), - delayed_write_set_ids, - ) - .map_err(PartialVMError::from)?; + let needs_exchange = self + .does_value_need_exchange(value, layout.as_ref(), delayed_write_set_ids) + .map_err(PartialVMError::from)?; if needs_exchange { resources_needing_delayed_field_exchange = true; @@ -1277,8 +1293,8 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< fn get_group_reads_needing_exchange_sequential( &self, group_read_set: &HashMap>, - unsync_map: &UnsyncMap, - delayed_write_set_ids: &HashSet, + unsync_map: &UnsyncMap, + delayed_write_set_ids: &HashSet, skip: &HashSet, ) -> PartialVMResult> { group_read_set @@ -1293,7 +1309,7 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> LatestView< if let ValueWithLayout::Exchanged(value, Some(layout)) = value_with_layout { - let needs_exchange = does_value_need_exchange::( + let needs_exchange = self.does_value_need_exchange( &value, layout.as_ref(), delayed_write_set_ids, @@ -1659,7 +1675,7 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> TAggregator impl<'a, T: Transaction, S: TStateView, X: Executable> TDelayedFieldView for LatestView<'a, T, S, X> { - type Identifier = T::Identifier; + type Identifier = DelayedFieldID; type ResourceGroupTag = T::Tag; type ResourceKey = T::Key; @@ -1770,7 +1786,7 @@ impl<'a, T: Transaction, S: TStateView, X: Executable> TDelayedFie ViewState::Sync(state) => state .captured_reads .borrow() - .get_read_values_with_delayed_fields(delayed_write_set_ids, skip), + .get_read_values_with_delayed_fields(self, delayed_write_set_ids, skip), ViewState::Unsync(state) => { let read_set = state.read_set.borrow(); self.get_reads_needing_exchange_sequential( @@ -1836,7 +1852,6 @@ mod test { write_set::TransactionWrite, }; use aptos_vm_types::resolver::TResourceView; - use bytes::Bytes; use claims::{assert_err_eq, assert_none, assert_ok_eq, assert_some_eq}; use move_core_types::value::{IdentifierMappingKind, MoveStructLayout, MoveTypeLayout}; use move_vm_types::{ @@ -1902,7 +1917,6 @@ mod test { impl BlockExecutableTransaction for TestTransactionType { type Event = MockEvent; - type Identifier = DelayedFieldID; type Key = KeyType; type Tag = u32; type Value = ValueType; @@ -2479,7 +2493,13 @@ mod test { } fn create_state_value(value: &Value, layout: &MoveTypeLayout) -> StateValue { - StateValue::new_legacy(value.simple_serialize(layout).unwrap().into()) + StateValue::new_legacy( + ValueSerDeContext::new() + .serialize(value, layout) + .unwrap() + .unwrap() + .into(), + ) } #[derive(Clone)] @@ -2520,7 +2540,7 @@ mod test { Value::u64(2), Value::u64(3), ])); - let state_value = StateValue::new_legacy(value.simple_serialize(&layout).unwrap().into()); + let state_value = create_state_value(&value, &layout); let (patched_state_value, identifiers) = latest_view .replace_values_with_identifiers(state_value.clone(), &layout) .unwrap(); @@ -2546,8 +2566,7 @@ mod test { let storage_layout = create_struct_layout(create_aggregator_storage_layout(MoveTypeLayout::U64)); let value = create_struct_value(create_aggregator_value_u64(25, 30)); - let state_value = - StateValue::new_legacy(value.simple_serialize(&storage_layout).unwrap().into()); + let state_value = create_state_value(&value, &storage_layout); let layout = create_struct_layout(create_aggregator_layout_u64()); let (patched_state_value, identifiers) = latest_view @@ -2585,8 +2604,7 @@ mod test { create_aggregator_value_u64(35, 65), create_aggregator_value_u64(0, 20), ])); - let state_value = - StateValue::new_legacy(value.simple_serialize(&storage_layout).unwrap().into()); + let state_value = create_state_value(&value, &storage_layout); let layout = create_struct_layout(create_vector_layout(create_aggregator_layout_u64())); let (patched_state_value, identifiers) = latest_view @@ -2619,12 +2637,7 @@ mod test { ])])); assert_eq!( patched_state_value, - StateValue::new_legacy( - patched_value - .simple_serialize(&storage_layout) - .unwrap() - .into() - ) + create_state_value(&patched_value, &storage_layout), ); let (final_state_value, identifiers) = latest_view .replace_identifiers_with_values(patched_state_value.bytes(), &layout) @@ -2649,8 +2662,7 @@ mod test { create_snapshot_value(Value::u128(35)), create_snapshot_value(Value::u128(0)), ])); - let state_value = - StateValue::new_legacy(value.simple_serialize(&storage_layout).unwrap().into()); + let state_value = create_state_value(&value, &storage_layout); let layout = create_struct_layout(create_vector_layout(create_snapshot_layout( MoveTypeLayout::U128, @@ -2682,12 +2694,7 @@ mod test { ])])); assert_eq!( patched_state_value, - StateValue::new_legacy( - patched_value - .simple_serialize(&storage_layout) - .unwrap() - .into() - ) + create_state_value(&patched_value, &storage_layout), ); let (final_state_value, identifiers2) = latest_view .replace_identifiers_with_values(patched_state_value.bytes(), &layout) @@ -2712,8 +2719,7 @@ mod test { create_derived_value("ab", 55), create_derived_value("c", 50), ])); - let state_value = - StateValue::new_legacy(value.simple_serialize(&storage_layout).unwrap().into()); + let state_value = create_state_value(&value, &storage_layout); let layout = create_struct_layout(create_vector_layout(create_derived_string_layout())); let (patched_state_value, identifiers) = latest_view @@ -2744,12 +2750,7 @@ mod test { ])])); assert_eq!( patched_state_value, - StateValue::new_legacy( - patched_value - .simple_serialize(&storage_layout) - .unwrap() - .into() - ) + create_state_value(&patched_value, &storage_layout), ); let (final_state_value, identifiers2) = latest_view .replace_identifiers_with_values(patched_state_value.bytes(), &layout) @@ -3100,18 +3101,13 @@ mod test { #[test] fn test_read_operations() { - let state_value_3 = StateValue::new_legacy(Bytes::from( - Value::u64(12321) - .simple_serialize(&MoveTypeLayout::U64) - .unwrap(), - )); + let state_value_3 = create_state_value(&Value::u64(12321), &MoveTypeLayout::U64); let mut data = HashMap::new(); data.insert(KeyType::(3, false), state_value_3.clone()); let storage_layout = create_struct_layout(create_aggregator_storage_layout(MoveTypeLayout::U64)); let value = create_struct_value(create_aggregator_value_u64(25, 30)); - let state_value_4 = - StateValue::new_legacy(value.simple_serialize(&storage_layout).unwrap().into()); + let state_value_4 = create_state_value(&value, &storage_layout); data.insert(KeyType::(4, false), state_value_4); let start_counter = 1000; @@ -3150,12 +3146,7 @@ mod test { ); let patched_value = create_struct_value(create_aggregator_value_u64(id.as_u64(), 30)); - let state_value_4 = StateValue::new_legacy( - patched_value - .simple_serialize(&storage_layout) - .unwrap() - .into(), - ); + let state_value_4 = create_state_value(&patched_value, &storage_layout); assert_eq!( views .get_resource_state_value(&KeyType::(4, false), Some(&layout)) @@ -3177,8 +3168,11 @@ mod test { let captured_reads = views.latest_view_par.take_parallel_reads(); assert!(captured_reads.validate_data_reads(holder.versioned_map.data(), 1)); // TODO(aggr_v2): what's up with this test case? - let _read_set_with_delayed_fields = - captured_reads.get_read_values_with_delayed_fields(&HashSet::new(), &HashSet::new()); + let _read_set_with_delayed_fields = captured_reads.get_read_values_with_delayed_fields( + &views.latest_view_par, + &HashSet::new(), + &HashSet::new(), + ); // TODO[agg_v2](test): This prints // read: (KeyType(4, false), Versioned(Err(StorageVersion), Some(Struct(Runtime([Struct(Runtime([Tagged(IdentifierMapping(Aggregator), U64), U64]))]))))) diff --git a/aptos-move/framework/Cargo.toml b/aptos-move/framework/Cargo.toml index fb7d4cb4d4495..35946a9a6bc76 100644 --- a/aptos-move/framework/Cargo.toml +++ b/aptos-move/framework/Cargo.toml @@ -60,6 +60,7 @@ move-package = { workspace = true } move-prover = { workspace = true } move-prover-boogie-backend = { workspace = true } move-prover-bytecode-pipeline = { workspace = true } +move-prover-lab = { workspace = true } move-stackless-bytecode = { workspace = true } move-vm-runtime = { workspace = true } move-vm-types = { workspace = true } @@ -78,6 +79,7 @@ smallvec = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } tiny-keccak = { workspace = true } +toml = { workspace = true } [dev-dependencies] aptos-aggregator = { workspace = true, features = ["testing"] } diff --git a/aptos-move/framework/aptos-framework/doc/aptos_governance.md b/aptos-move/framework/aptos-framework/doc/aptos_governance.md index ef3a011b5ed74..c7c97bebe112c 100644 --- a/aptos-move/framework/aptos-framework/doc/aptos_governance.md +++ b/aptos-move/framework/aptos-framework/doc/aptos_governance.md @@ -39,6 +39,7 @@ on a proposal multiple times as long as the total voting power of these votes do - [Function `get_required_proposer_stake`](#0x1_aptos_governance_get_required_proposer_stake) - [Function `has_entirely_voted`](#0x1_aptos_governance_has_entirely_voted) - [Function `get_remaining_voting_power`](#0x1_aptos_governance_get_remaining_voting_power) +- [Function `assert_proposal_expiration`](#0x1_aptos_governance_assert_proposal_expiration) - [Function `create_proposal`](#0x1_aptos_governance_create_proposal) - [Function `create_proposal_v2`](#0x1_aptos_governance_create_proposal_v2) - [Function `create_proposal_v2_impl`](#0x1_aptos_governance_create_proposal_v2_impl) @@ -74,6 +75,7 @@ on a proposal multiple times as long as the total voting power of these votes do - [Function `get_required_proposer_stake`](#@Specification_1_get_required_proposer_stake) - [Function `has_entirely_voted`](#@Specification_1_has_entirely_voted) - [Function `get_remaining_voting_power`](#@Specification_1_get_remaining_voting_power) + - [Function `assert_proposal_expiration`](#@Specification_1_assert_proposal_expiration) - [Function `create_proposal`](#@Specification_1_create_proposal) - [Function `create_proposal_v2`](#@Specification_1_create_proposal_v2) - [Function `create_proposal_v2_impl`](#@Specification_1_create_proposal_v2_impl) @@ -758,6 +760,16 @@ Partial voting feature hasn't been properly initialized. + + +The proposal has expired. + + +
const EPROPOSAL_EXPIRED: u64 = 15;
+
+ + + Proposal is not ready to be resolved. Waiting on time or votes @@ -1160,6 +1172,43 @@ Note: a stake pool's voting power on a proposal could increase over time(e.g. re + + + + +## Function `assert_proposal_expiration` + + + +
public fun assert_proposal_expiration(stake_pool: address, proposal_id: u64)
+
+ + + +
+Implementation + + +
public fun assert_proposal_expiration(stake_pool: address, proposal_id: u64) {
+    assert_voting_initialization();
+    let proposal_expiration = voting::get_proposal_expiration_secs<GovernanceProposal>(
+        @aptos_framework,
+        proposal_id
+    );
+    // The voter's stake needs to be locked up at least as long as the proposal's expiration.
+    assert!(
+        proposal_expiration <= stake::get_lockup_secs(stake_pool),
+        error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP),
+    );
+    assert!(
+        timestamp::now_seconds() <= proposal_expiration,
+        error::invalid_argument(EPROPOSAL_EXPIRED),
+    );
+}
+
+ + +
@@ -1498,15 +1547,7 @@ cannot vote on the proposal even after partial governance voting is enabled. let voter_address = signer::address_of(voter); assert!(stake::get_delegated_voter(stake_pool) == voter_address, error::invalid_argument(ENOT_DELEGATED_VOTER)); - // The voter's stake needs to be locked up at least as long as the proposal's expiration. - let proposal_expiration = voting::get_proposal_expiration_secs<GovernanceProposal>( - @aptos_framework, - proposal_id - ); - assert!( - stake::get_lockup_secs(stake_pool) >= proposal_expiration, - error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP), - ); + assert_proposal_expiration(stake_pool, proposal_id); // If a stake pool has already voted on a proposal before partial governance voting is enabled, // `get_remaining_voting_power` returns 0. @@ -2435,6 +2476,28 @@ Abort if structs have already been created. + + +### Function `assert_proposal_expiration` + + +
public fun assert_proposal_expiration(stake_pool: address, proposal_id: u64)
+
+ + + + +
include VotingInitializationAbortIfs;
+include voting::AbortsIfNotContainProposalID<GovernanceProposal>{voting_forum_address: @aptos_framework};
+let proposal_expiration = voting::spec_get_proposal_expiration_secs<GovernanceProposal>(@aptos_framework, proposal_id);
+aborts_if !stake::stake_pool_exists(stake_pool);
+aborts_if proposal_expiration > stake::spec_get_lockup_secs(stake_pool);
+aborts_if !exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
+aborts_if timestamp::now_seconds() > proposal_expiration;
+
+ + + ### Function `create_proposal` diff --git a/aptos-move/framework/aptos-framework/doc/delegation_pool.md b/aptos-move/framework/aptos-framework/doc/delegation_pool.md index 268d0618fb722..f9c83df1ff80f 100644 --- a/aptos-move/framework/aptos-framework/doc/delegation_pool.md +++ b/aptos-move/framework/aptos-framework/doc/delegation_pool.md @@ -2955,6 +2955,7 @@ Vote on a proposal with a voter's voting power. To successfully vote, the follow if (voting_power > remaining_voting_power) { voting_power = remaining_voting_power; }; + aptos_governance::assert_proposal_expiration(pool_address, proposal_id); assert!(voting_power > 0, error::invalid_argument(ENO_VOTING_POWER)); let governance_records = borrow_global_mut<GovernanceRecords>(pool_address); diff --git a/aptos-move/framework/aptos-framework/doc/stake.md b/aptos-move/framework/aptos-framework/doc/stake.md index 53d815ae1ed8a..dedfe15027641 100644 --- a/aptos-move/framework/aptos-framework/doc/stake.md +++ b/aptos-move/framework/aptos-framework/doc/stake.md @@ -4565,148 +4565,6 @@ Returns validator's next epoch voting power, including pending_active, active, a - - - - -
fun spec_validator_index_upper_bound(): u64 {
-   len(global<ValidatorPerformance>(@aptos_framework).validators)
-}
-
- - - - - - - -
fun spec_has_stake_pool(a: address): bool {
-   exists<StakePool>(a)
-}
-
- - - - - - - -
fun spec_has_validator_config(a: address): bool {
-   exists<ValidatorConfig>(a)
-}
-
- - - - - - - -
fun spec_rewards_amount(
-   stake_amount: u64,
-   num_successful_proposals: u64,
-   num_total_proposals: u64,
-   rewards_rate: u64,
-   rewards_rate_denominator: u64,
-): u64;
-
- - - - - - - -
fun spec_contains(validators: vector<ValidatorInfo>, addr: address): bool {
-   exists i in 0..len(validators): validators[i].addr == addr
-}
-
- - - - - - - -
fun spec_is_current_epoch_validator(pool_address: address): bool {
-   let validator_set = global<ValidatorSet>(@aptos_framework);
-   !spec_contains(validator_set.pending_active, pool_address)
-       && (spec_contains(validator_set.active_validators, pool_address)
-       || spec_contains(validator_set.pending_inactive, pool_address))
-}
-
- - - - - - - -
schema ResourceRequirement {
-    requires exists<AptosCoinCapabilities>(@aptos_framework);
-    requires exists<ValidatorPerformance>(@aptos_framework);
-    requires exists<ValidatorSet>(@aptos_framework);
-    requires exists<StakingConfig>(@aptos_framework);
-    requires exists<StakingRewardsConfig>(@aptos_framework) || !features::spec_periodical_reward_rate_decrease_enabled();
-    requires exists<timestamp::CurrentTimeMicroseconds>(@aptos_framework);
-}
-
- - - - - - - -
fun spec_get_reward_rate_1(config: StakingConfig): num {
-   if (features::spec_periodical_reward_rate_decrease_enabled()) {
-       let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
-       if (epoch_rewards_rate.value == 0) {
-           0
-       } else {
-           let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
-           let denominator = if (denominator_0 > MAX_U64) {
-               MAX_U64
-           } else {
-               denominator_0
-           };
-           let nominator = aptos_std::fixed_point64::spec_multiply_u128(denominator, epoch_rewards_rate);
-           nominator
-       }
-   } else {
-           config.rewards_rate
-   }
-}
-
- - - - - - - -
fun spec_get_reward_rate_2(config: StakingConfig): num {
-   if (features::spec_periodical_reward_rate_decrease_enabled()) {
-       let epoch_rewards_rate = global<staking_config::StakingRewardsConfig>(@aptos_framework).rewards_rate;
-       if (epoch_rewards_rate.value == 0) {
-           1
-       } else {
-           let denominator_0 = aptos_std::fixed_point64::spec_divide_u128(staking_config::MAX_REWARDS_RATE, epoch_rewards_rate);
-           let denominator = if (denominator_0 > MAX_U64) {
-               MAX_U64
-           } else {
-               denominator_0
-           };
-           denominator
-       }
-   } else {
-           config.rewards_rate_denominator
-   }
-}
-
- - - ### Resource `ValidatorSet` @@ -5492,158 +5350,6 @@ Returns validator's next epoch voting power, including pending_active, active, a - - - - -
schema AddStakeWithCapAbortsIfAndEnsures {
-    owner_cap: OwnerCapability;
-    amount: u64;
-    let pool_address = owner_cap.pool_address;
-    aborts_if !exists<StakePool>(pool_address);
-    let config = global<staking_config::StakingConfig>(@aptos_framework);
-    let validator_set = global<ValidatorSet>(@aptos_framework);
-    let voting_power_increase_limit = config.voting_power_increase_limit;
-    let post post_validator_set = global<ValidatorSet>(@aptos_framework);
-    let update_voting_power_increase = amount != 0 && (spec_contains(validator_set.active_validators, pool_address)
-                                                       || spec_contains(validator_set.pending_active, pool_address));
-    aborts_if update_voting_power_increase && validator_set.total_joining_power + amount > MAX_U128;
-    ensures update_voting_power_increase ==> post_validator_set.total_joining_power == validator_set.total_joining_power + amount;
-    aborts_if update_voting_power_increase && validator_set.total_voting_power > 0
-            && validator_set.total_voting_power * voting_power_increase_limit > MAX_U128;
-    aborts_if update_voting_power_increase && validator_set.total_voting_power > 0
-            && validator_set.total_joining_power + amount > validator_set.total_voting_power * voting_power_increase_limit / 100;
-    let stake_pool = global<StakePool>(pool_address);
-    let post post_stake_pool = global<StakePool>(pool_address);
-    let value_pending_active = stake_pool.pending_active.value;
-    let value_active = stake_pool.active.value;
-    ensures amount != 0 && spec_is_current_epoch_validator(pool_address) ==> post_stake_pool.pending_active.value == value_pending_active + amount;
-    ensures amount != 0 && !spec_is_current_epoch_validator(pool_address) ==> post_stake_pool.active.value == value_active + amount;
-    let maximum_stake = config.maximum_stake;
-    let value_pending_inactive = stake_pool.pending_inactive.value;
-    let next_epoch_voting_power = value_pending_active + value_active + value_pending_inactive;
-    let voting_power = next_epoch_voting_power + amount;
-    aborts_if amount != 0 && voting_power > MAX_U64;
-    aborts_if amount != 0 && voting_power > maximum_stake;
-}
-
- - - - - - - -
schema AddStakeAbortsIfAndEnsures {
-    owner: signer;
-    amount: u64;
-    let owner_address = signer::address_of(owner);
-    aborts_if !exists<OwnerCapability>(owner_address);
-    let owner_cap = global<OwnerCapability>(owner_address);
-    include AddStakeWithCapAbortsIfAndEnsures { owner_cap };
-}
-
- - - - - - - -
fun spec_is_allowed(account: address): bool {
-   if (!exists<AllowedValidators>(@aptos_framework)) {
-       true
-   } else {
-       let allowed = global<AllowedValidators>(@aptos_framework);
-       contains(allowed.accounts, account)
-   }
-}
-
- - - - - - - -
fun spec_find_validator(v: vector<ValidatorInfo>, addr: address): Option<u64>;
-
- - - - - - - -
fun spec_validators_are_initialized(validators: vector<ValidatorInfo>): bool {
-   forall i in 0..len(validators):
-       spec_has_stake_pool(validators[i].addr) &&
-           spec_has_validator_config(validators[i].addr)
-}
-
- - - - - - - -
fun spec_validators_are_initialized_addrs(addrs: vector<address>): bool {
-   forall i in 0..len(addrs):
-       spec_has_stake_pool(addrs[i]) &&
-           spec_has_validator_config(addrs[i])
-}
-
- - - - - - - -
fun spec_validator_indices_are_valid(validators: vector<ValidatorInfo>): bool {
-   spec_validator_indices_are_valid_addr(validators, spec_validator_index_upper_bound()) &&
-       spec_validator_indices_are_valid_config(validators, spec_validator_index_upper_bound())
-}
-
- - - - - - - -
fun spec_validator_indices_are_valid_addr(validators: vector<ValidatorInfo>, upper_bound: u64): bool {
-   forall i in 0..len(validators):
-       global<ValidatorConfig>(validators[i].addr).validator_index < upper_bound
-}
-
- - - - - - - -
fun spec_validator_indices_are_valid_config(validators: vector<ValidatorInfo>, upper_bound: u64): bool {
-   forall i in 0..len(validators):
-       validators[i].config.validator_index < upper_bound
-}
-
- - - - - - - -
fun spec_validator_indices_active_pending_inactive(validator_set: ValidatorSet): bool {
-   len(validator_set.pending_inactive) + len(validator_set.active_validators) == spec_validator_index_upper_bound()
-}
-
- - - ### Function `update_stake_pool` @@ -5755,6 +5461,17 @@ Returns validator's next epoch voting power, including pending_active, active, a + + + + +
fun spec_get_lockup_secs(pool_address: address): u64 {
+   global<StakePool>(pool_address).locked_until_secs
+}
+
+ + + ### Function `calculate_rewards_amount` diff --git a/aptos-move/framework/aptos-framework/sources/aptos_governance.move b/aptos-move/framework/aptos-framework/sources/aptos_governance.move index fb1469bc7e684..f5de768d05864 100644 --- a/aptos-move/framework/aptos-framework/sources/aptos_governance.move +++ b/aptos-move/framework/aptos-framework/sources/aptos_governance.move @@ -62,6 +62,8 @@ module aptos_framework::aptos_governance { const EPARTIAL_VOTING_NOT_INITIALIZED: u64 = 13; /// The proposal in the argument is not a partial voting proposal. const ENOT_PARTIAL_VOTING_PROPOSAL: u64 = 14; + /// The proposal has expired. + const EPROPOSAL_EXPIRED: u64 = 15; /// This matches the same enum const in voting. We have to duplicate it as Move doesn't have support for enums yet. const PROPOSAL_STATE_SUCCEEDED: u64 = 1; @@ -331,6 +333,23 @@ module aptos_framework::aptos_governance { get_voting_power(stake_pool) - used_voting_power } + public fun assert_proposal_expiration(stake_pool: address, proposal_id: u64) { + assert_voting_initialization(); + let proposal_expiration = voting::get_proposal_expiration_secs( + @aptos_framework, + proposal_id + ); + // The voter's stake needs to be locked up at least as long as the proposal's expiration. + assert!( + proposal_expiration <= stake::get_lockup_secs(stake_pool), + error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP), + ); + assert!( + timestamp::now_seconds() <= proposal_expiration, + error::invalid_argument(EPROPOSAL_EXPIRED), + ); + } + /// Create a single-step proposal with the backing `stake_pool`. /// @param execution_hash Required. This is the hash of the resolution script. When the proposal is resolved, /// only the exact script with matching hash can be successfully executed. @@ -512,15 +531,7 @@ module aptos_framework::aptos_governance { let voter_address = signer::address_of(voter); assert!(stake::get_delegated_voter(stake_pool) == voter_address, error::invalid_argument(ENOT_DELEGATED_VOTER)); - // The voter's stake needs to be locked up at least as long as the proposal's expiration. - let proposal_expiration = voting::get_proposal_expiration_secs( - @aptos_framework, - proposal_id - ); - assert!( - stake::get_lockup_secs(stake_pool) >= proposal_expiration, - error::invalid_argument(EINSUFFICIENT_STAKE_LOCKUP), - ); + assert_proposal_expiration(stake_pool, proposal_id); // If a stake pool has already voted on a proposal before partial governance voting is enabled, // `get_remaining_voting_power` returns 0. @@ -963,7 +974,7 @@ module aptos_framework::aptos_governance { } #[test(aptos_framework = @aptos_framework, proposer = @0x123, voter_1 = @0x234, voter_2 = @345)] - #[expected_failure(abort_code = 0x10004, location = aptos_framework::voting)] + #[expected_failure(abort_code = 65541, location = aptos_framework::aptos_governance)] public entry fun test_cannot_double_vote( aptos_framework: signer, proposer: signer, @@ -975,7 +986,7 @@ module aptos_framework::aptos_governance { create_proposal( &proposer, signer::address_of(&proposer), - b"", + b"0", b"", b"", ); @@ -986,7 +997,54 @@ module aptos_framework::aptos_governance { } #[test(aptos_framework = @aptos_framework, proposer = @0x123, voter_1 = @0x234, voter_2 = @345)] - #[expected_failure(abort_code = 0x10004, location = aptos_framework::voting)] + #[expected_failure(abort_code = 65551, location = aptos_framework::aptos_governance)] + public entry fun test_cannot_vote_for_expired_proposal( + aptos_framework: signer, + proposer: signer, + voter_1: signer, + voter_2: signer, + ) acquires ApprovedExecutionHashes, GovernanceConfig, GovernanceResponsbility, VotingRecords, VotingRecordsV2, GovernanceEvents { + setup_partial_voting_with_initialized_stake(&aptos_framework, &proposer, &voter_1, &voter_2); + + create_proposal( + &proposer, + signer::address_of(&proposer), + b"0", + b"", + b"", + ); + + timestamp::fast_forward_seconds(2000); + stake::end_epoch(); + + // Should abort because the proposal has expired. + vote(&voter_1, signer::address_of(&voter_1), 0, true); + } + + #[test(aptos_framework = @aptos_framework, proposer = @0x123, voter_1 = @0x234, voter_2 = @0x345)] + #[expected_failure(abort_code = 65539, location = aptos_framework::aptos_governance)] + public entry fun test_cannot_vote_due_to_insufficient_stake_lockup( + aptos_framework: signer, + proposer: signer, + voter_1: signer, + voter_2: signer, + ) acquires ApprovedExecutionHashes, GovernanceConfig, GovernanceResponsbility, VotingRecords, VotingRecordsV2, GovernanceEvents { + setup_partial_voting_with_initialized_stake(&aptos_framework, &proposer, &voter_1, &voter_2); + + create_proposal( + &proposer, + signer::address_of(&proposer), + b"0", + b"", + b"", + ); + + // Should abort due to insufficient stake lockup. + vote(&voter_1, signer::address_of(&voter_1), 0, true); + } + + #[test(aptos_framework = @aptos_framework, proposer = @0x123, voter_1 = @0x234, voter_2 = @345)] + #[expected_failure(abort_code = 65541, location = aptos_framework::aptos_governance)] public entry fun test_cannot_double_vote_with_different_voter_addresses( aptos_framework: signer, proposer: signer, @@ -998,7 +1056,7 @@ module aptos_framework::aptos_governance { create_proposal( &proposer, signer::address_of(&proposer), - b"", + b"0", b"", b"", ); diff --git a/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move b/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move index c3d18d593533c..3ea2572dd8fea 100644 --- a/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move +++ b/aptos-move/framework/aptos-framework/sources/aptos_governance.spec.move @@ -832,6 +832,16 @@ spec aptos_framework::aptos_governance { include VotingInitializationAbortIfs; } + spec assert_proposal_expiration(stake_pool: address, proposal_id: u64) { + include VotingInitializationAbortIfs; + include voting::AbortsIfNotContainProposalID{voting_forum_address: @aptos_framework}; + let proposal_expiration = voting::spec_get_proposal_expiration_secs(@aptos_framework, proposal_id); + aborts_if !stake::stake_pool_exists(stake_pool); + aborts_if proposal_expiration > stake::spec_get_lockup_secs(stake_pool); + aborts_if !exists(@aptos_framework); + aborts_if timestamp::now_seconds() > proposal_expiration; + } + spec force_end_epoch(aptos_framework: &signer) { use aptos_framework::reconfiguration_with_dkg; use std::signer; diff --git a/aptos-move/framework/aptos-framework/sources/delegation_pool.move b/aptos-move/framework/aptos-framework/sources/delegation_pool.move index 0e21967dcb53b..3b5cc7600f881 100644 --- a/aptos-move/framework/aptos-framework/sources/delegation_pool.move +++ b/aptos-move/framework/aptos-framework/sources/delegation_pool.move @@ -954,6 +954,7 @@ module aptos_framework::delegation_pool { if (voting_power > remaining_voting_power) { voting_power = remaining_voting_power; }; + aptos_governance::assert_proposal_expiration(pool_address, proposal_id); assert!(voting_power > 0, error::invalid_argument(ENO_VOTING_POWER)); let governance_records = borrow_global_mut(pool_address); @@ -4816,7 +4817,7 @@ module aptos_framework::delegation_pool { validator: &signer, delegator1: &signer, ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting { - // This test case checks the scenario where delegation_pool_partial_governance_voting is disabled. + // This test case begins with the delegation_pool_partial_governance_voting feature disabled. features::change_feature_flags_for_testing( aptos_framework, vector[], @@ -4834,6 +4835,124 @@ module aptos_framework::delegation_pool { vote(delegator1, pool_address, proposal1_id, 10 * ONE_APT, true); } + #[test(aptos_framework = @aptos_framework, validator1 = @0x123, validator2 = @0x234, delegator1 = @0x010, delegator2 = @0x020)] + #[expected_failure(abort_code = 65539, location = aptos_framework::aptos_governance)] + public entry fun test_vote_should_failed_due_to_insufficient_stake_lockup ( + aptos_framework: &signer, + validator1: &signer, + validator2: &signer, + delegator1: &signer, + delegator2: &signer, + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting { + // This test case begins with the delegation_pool_partial_governance_voting feature disabled. + features::change_feature_flags_for_testing( + aptos_framework, + vector[], + vector[features::get_delegation_pool_partial_governance_voting()] + ); + + let _ = setup_vote(aptos_framework, validator1, false); + + let validator1_address = signer::address_of(validator1); + let pool1_address = get_owned_pool_address(validator1_address); + let delegator1_address = signer::address_of(delegator1); + account::create_account_for_test(delegator1_address); + stake::mint(delegator1, 110 * ONE_APT); + add_stake(delegator1, pool1_address, 10 * ONE_APT); + + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS - 500); + end_aptos_epoch(); + + initialize_test_validator(validator2, 100 * ONE_APT, true, true); + let validator2_address = signer::address_of(validator2); + let pool2_address = get_owned_pool_address(validator2_address); + let delegator2_address = signer::address_of(delegator2); + account::create_account_for_test(delegator2_address); + stake::mint(delegator2, 110 * ONE_APT); + add_stake(delegator2, pool2_address, 10 * ONE_APT); + + let proposal_id = aptos_governance::create_proposal_v2_impl( + validator2, + pool2_address, + b"0", + b"", + b"", + true, + ); + + features::change_feature_flags_for_testing( + aptos_framework, + vector[features::get_delegation_pool_partial_governance_voting()], + vector[] + ); + enable_partial_governance_voting(pool1_address); + enable_partial_governance_voting(pool2_address); + + vote(delegator2, pool2_address, proposal_id, 10 * ONE_APT, true); + // Should abort with the error EINSUFFICIENT_STAKE_LOCKUP. + vote(delegator1, pool1_address, proposal_id, 10 * ONE_APT, true); + } + + #[test(aptos_framework = @aptos_framework, validator = @0x123, delegator1 = @0x010)] + #[expected_failure(abort_code = 65551, location = aptos_framework::aptos_governance)] + public entry fun test_vote_should_failed_due_to_proposal_expired( + aptos_framework: &signer, + validator: &signer, + delegator1: &signer, + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting { + // This test case begins with the delegation_pool_partial_governance_voting feature disabled. + features::change_feature_flags_for_testing( + aptos_framework, + vector[], + vector[features::get_delegation_pool_partial_governance_voting()] + ); + // partial voing hasn't been enabled yet. A proposal has been created by the validator. + let proposal1_id = setup_vote(aptos_framework, validator, true); + + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + let delegator1_address = signer::address_of(delegator1); + account::create_account_for_test(delegator1_address); + + stake::mint(delegator1, 110 * ONE_APT); + add_stake(delegator1, pool_address, 10 * ONE_APT); + + timestamp::fast_forward_seconds(2000); + end_aptos_epoch(); + + // Should abort with the error EPROPOSAL_EXPIRED. + vote(delegator1, pool_address, proposal1_id, 10 * ONE_APT, true); + } + + #[test(aptos_framework = @aptos_framework, validator = @0x123, delegator1 = @0x010)] + #[expected_failure(abort_code = 0x10010, location = Self)] + public entry fun test_vote_should_failed_due_to_insufficient_voting_power( + aptos_framework: &signer, + validator: &signer, + delegator1: &signer, + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage, DelegationPoolAllowlisting { + // This test case checks the scenario where delegation_pool_partial_governance_voting is disabled. + features::change_feature_flags_for_testing( + aptos_framework, + vector[], + vector[features::get_delegation_pool_partial_governance_voting()] + ); + // partial voing hasn't been enabled yet. A proposal has been created by the validator. + let proposal1_id = setup_vote(aptos_framework, validator, true); + + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + let delegator1_address = signer::address_of(delegator1); + account::create_account_for_test(delegator1_address); + + stake::mint(delegator1, 110 * ONE_APT); + add_stake(delegator1, pool_address, 10 * ONE_APT); + end_aptos_epoch(); + vote(delegator1, pool_address, proposal1_id, 10 * ONE_APT, true); + // Should abort with the error ENO_VOTING_POWER. + vote(delegator1, pool_address, proposal1_id, 10 * ONE_APT, true); + } + #[test(aptos_framework = @aptos_framework, validator = @0x123, delegator1 = @0x010, voter1 = @0x030)] public entry fun test_delegate_voting_power_should_pass_even_if_no_stake( aptos_framework: &signer, diff --git a/aptos-move/framework/aptos-framework/sources/stake.spec.move b/aptos-move/framework/aptos-framework/sources/stake.spec.move index 975f77bd14b1f..f922821e8ac83 100644 --- a/aptos-move/framework/aptos-framework/sources/stake.spec.move +++ b/aptos-move/framework/aptos-framework/sources/stake.spec.move @@ -586,6 +586,10 @@ spec aptos_framework::stake { } } + spec fun spec_get_lockup_secs(pool_address: address): u64 { + global(pool_address).locked_until_secs + } + spec calculate_rewards_amount { pragma opaque; // TODO: set because of timeout (property proved) diff --git a/aptos-move/framework/aptos-stdlib/doc/big_vector.md b/aptos-move/framework/aptos-stdlib/doc/big_vector.md index 7d785a5b730a1..da978bc07ff9e 100644 --- a/aptos-move/framework/aptos-stdlib/doc/big_vector.md +++ b/aptos-move/framework/aptos-stdlib/doc/big_vector.md @@ -502,7 +502,7 @@ Aborts if i is out of bounds. if (self.end_index == i) { return last_val }; - // because the lack of mem::swap, here we swap remove the requested value from the bucket + // because the lack of mem::swap, here we swap remove the requested value from the bucket // and append the last_val to the bucket then swap the last bucket val back let bucket = table_with_length::borrow_mut(&mut self.buckets, i / self.bucket_size); let bucket_len = vector::length(bucket); diff --git a/aptos-move/framework/move-stdlib/doc/vector.md b/aptos-move/framework/move-stdlib/doc/vector.md index 9e837c9c1cfae..18512424c38e8 100644 --- a/aptos-move/framework/move-stdlib/doc/vector.md +++ b/aptos-move/framework/move-stdlib/doc/vector.md @@ -88,7 +88,8 @@ the return on investment didn't seem worth it for these simple functions. - [Function `rotate_slice`](#@Specification_1_rotate_slice) -
+
use 0x1::mem;
+
@@ -930,14 +931,13 @@ Aborts if i is out of bounds.
public fun replace<Element>(self: &mut vector<Element>, i: u64, val: Element): Element {
     let last_idx = length(self);
     assert!(i < last_idx, EINDEX_OUT_OF_BOUNDS);
-    // TODO: Enable after tests are fixed.
-    // if (USE_MOVE_RANGE) {
-    //     mem::replace(borrow_mut(self, i), val)
-    // } else {
-    push_back(self, val);
-    swap(self, i, last_idx);
-    pop_back(self)
-    // }
+    if (USE_MOVE_RANGE) {
+        mem::replace(borrow_mut(self, i), val)
+    } else {
+        push_back(self, val);
+        swap(self, i, last_idx);
+        pop_back(self)
+    }
 }
 
diff --git a/aptos-move/framework/move-stdlib/sources/mem.move b/aptos-move/framework/move-stdlib/sources/mem.move index cf2eae276f9a5..e96db063db6c0 100644 --- a/aptos-move/framework/move-stdlib/sources/mem.move +++ b/aptos-move/framework/move-stdlib/sources/mem.move @@ -2,7 +2,7 @@ module std::mem { // TODO - functions here are `public(friend)` here for one release, // and to be changed to `public` one release later. - // friend std::vector; + friend std::vector; #[test_only] friend std::mem_tests; diff --git a/aptos-move/framework/move-stdlib/sources/vector.move b/aptos-move/framework/move-stdlib/sources/vector.move index b7add1eb2112a..e4d018e4f0b21 100644 --- a/aptos-move/framework/move-stdlib/sources/vector.move +++ b/aptos-move/framework/move-stdlib/sources/vector.move @@ -9,7 +9,7 @@ /// Move functions here because many have loops, requiring loop invariants to prove, and /// the return on investment didn't seem worth it for these simple functions. module std::vector { - // use std::mem; + use std::mem; /// The index into the vector is out of bounds const EINDEX_OUT_OF_BOUNDS: u64 = 0x20000; @@ -357,14 +357,13 @@ module std::vector { public fun replace(self: &mut vector, i: u64, val: Element): Element { let last_idx = length(self); assert!(i < last_idx, EINDEX_OUT_OF_BOUNDS); - // TODO: Enable after tests are fixed. - // if (USE_MOVE_RANGE) { - // mem::replace(borrow_mut(self, i), val) - // } else { - push_back(self, val); - swap(self, i, last_idx); - pop_back(self) - // } + if (USE_MOVE_RANGE) { + mem::replace(borrow_mut(self, i), val) + } else { + push_back(self, val); + swap(self, i, last_idx); + pop_back(self) + } } /// Apply the function to each element in the vector, consuming it. diff --git a/aptos-move/framework/move-stdlib/src/natives/bcs.rs b/aptos-move/framework/move-stdlib/src/natives/bcs.rs index 1ba7a7f17ca3f..d7cd0e9d258c4 100644 --- a/aptos-move/framework/move-stdlib/src/natives/bcs.rs +++ b/aptos-move/framework/move-stdlib/src/natives/bcs.rs @@ -21,7 +21,7 @@ use move_vm_runtime::native_functions::NativeFunction; use move_vm_types::{ loaded_data::runtime_types::Type, natives::function::{PartialVMError, PartialVMResult}, - value_serde::serialized_size_allowing_delayed_values, + value_serde::ValueSerDeContext, values::{values_impl::Reference, Struct, Value}, }; use smallvec::{smallvec, SmallVec}; @@ -69,7 +69,10 @@ fn native_to_bytes( // implement it in a more efficient way. let val = ref_to_val.read_ref()?; - let serialized_value = match val.simple_serialize(&layout) { + let serialized_value = match ValueSerDeContext::new() + .with_func_args_deserialization(context.function_value_extension()) + .serialize(&val, &layout)? + { Some(serialized_value) => serialized_value, None => { context.charge(BCS_TO_BYTES_FAILURE)?; @@ -131,7 +134,11 @@ fn serialized_size_impl( // implement it in a more efficient way. let value = reference.read_ref()?; let ty_layout = context.type_to_type_layout(ty)?; - serialized_size_allowing_delayed_values(&value, &ty_layout) + + ValueSerDeContext::new() + .with_func_args_deserialization(context.function_value_extension()) + .with_delayed_fields_serde() + .serialized_size(&value, &ty_layout) } fn native_constant_serialized_size( diff --git a/aptos-move/framework/src/natives/event.rs b/aptos-move/framework/src/natives/event.rs index 01de485b3f4fc..f970335350490 100644 --- a/aptos-move/framework/src/natives/event.rs +++ b/aptos-move/framework/src/natives/event.rs @@ -16,12 +16,9 @@ use move_binary_format::errors::PartialVMError; use move_core_types::{language_storage::TypeTag, value::MoveTypeLayout, vm_status::StatusCode}; use move_vm_runtime::native_functions::NativeFunction; #[cfg(feature = "testing")] -use move_vm_types::value_serde::deserialize_and_allow_delayed_values; -#[cfg(feature = "testing")] use move_vm_types::values::{Reference, Struct, StructRef}; use move_vm_types::{ - loaded_data::runtime_types::Type, value_serde::serialize_and_allow_delayed_values, - values::Value, + loaded_data::runtime_types::Type, value_serde::ValueSerDeContext, values::Value, }; use smallvec::{smallvec, SmallVec}; use std::collections::VecDeque; @@ -92,11 +89,16 @@ fn native_write_to_event_store( let ty_tag = context.type_to_type_tag(&ty)?; let (layout, has_aggregator_lifting) = context.type_to_type_layout_with_identifier_mappings(&ty)?; - let blob = serialize_and_allow_delayed_values(&msg, &layout)?.ok_or_else(|| { - SafeNativeError::InvariantViolation(PartialVMError::new( - StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, - )) - })?; + + let blob = ValueSerDeContext::new() + .with_delayed_fields_serde() + .with_func_args_deserialization(context.function_value_extension()) + .serialize(&msg, &layout)? + .ok_or_else(|| { + SafeNativeError::InvariantViolation(PartialVMError::new( + StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, + )) + })?; let key = bcs::from_bytes(guid.as_slice()).map_err(|_| { SafeNativeError::InvariantViolation(PartialVMError::new(StatusCode::EVENT_KEY_MISMATCH)) })?; @@ -147,16 +149,19 @@ fn native_emitted_events_by_handle( let key = EventKey::new(creation_num, addr); let ty_tag = context.type_to_type_tag(&ty)?; let ty_layout = context.type_to_type_layout(&ty)?; - let ctx = context.extensions_mut().get_mut::(); + let ctx = context.extensions().get::(); let events = ctx .emitted_v1_events(&key, &ty_tag) .into_iter() .map(|blob| { - Value::simple_deserialize(blob, &ty_layout).ok_or_else(|| { - SafeNativeError::InvariantViolation(PartialVMError::new( - StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, - )) - }) + ValueSerDeContext::new() + .with_func_args_deserialization(context.function_value_extension()) + .deserialize(blob, &ty_layout) + .ok_or_else(|| { + SafeNativeError::InvariantViolation(PartialVMError::new( + StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, + )) + }) }) .collect::>>()?; Ok(smallvec![Value::vector_for_testing_only(events)]) @@ -175,16 +180,21 @@ fn native_emitted_events( let ty_tag = context.type_to_type_tag(&ty)?; let ty_layout = context.type_to_type_layout(&ty)?; - let ctx = context.extensions_mut().get_mut::(); + let ctx = context.extensions().get::(); + let events = ctx .emitted_v2_events(&ty_tag) .into_iter() .map(|blob| { - deserialize_and_allow_delayed_values(blob, &ty_layout).ok_or_else(|| { - SafeNativeError::InvariantViolation(PartialVMError::new( - StatusCode::VALUE_DESERIALIZATION_ERROR, - )) - }) + ValueSerDeContext::new() + .with_func_args_deserialization(context.function_value_extension()) + .with_delayed_fields_serde() + .deserialize(blob, &ty_layout) + .ok_or_else(|| { + SafeNativeError::InvariantViolation(PartialVMError::new( + StatusCode::VALUE_DESERIALIZATION_ERROR, + )) + }) }) .collect::>>()?; Ok(smallvec![Value::vector_for_testing_only(events)]) @@ -234,12 +244,16 @@ fn native_write_module_event_to_store( } let (layout, has_identifier_mappings) = context.type_to_type_layout_with_identifier_mappings(&ty)?; - let blob = serialize_and_allow_delayed_values(&msg, &layout)?.ok_or_else(|| { - SafeNativeError::InvariantViolation( - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message("Event serialization failure".to_string()), - ) - })?; + let blob = ValueSerDeContext::new() + .with_delayed_fields_serde() + .with_func_args_deserialization(context.function_value_extension()) + .serialize(&msg, &layout)? + .ok_or_else(|| { + SafeNativeError::InvariantViolation( + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message("Event serialization failure".to_string()), + ) + })?; let ctx = context.extensions_mut().get_mut::(); ctx.events.push(( ContractEvent::new_v2(type_tag, blob), diff --git a/aptos-move/framework/src/natives/util.rs b/aptos-move/framework/src/natives/util.rs index da30858dc4edf..f39239491fae1 100644 --- a/aptos-move/framework/src/natives/util.rs +++ b/aptos-move/framework/src/natives/util.rs @@ -8,7 +8,9 @@ use aptos_native_interface::{ }; use move_core_types::gas_algebra::NumBytes; use move_vm_runtime::native_functions::NativeFunction; -use move_vm_types::{loaded_data::runtime_types::Type, values::Value}; +use move_vm_types::{ + loaded_data::runtime_types::Type, value_serde::ValueSerDeContext, values::Value, +}; use smallvec::{smallvec, SmallVec}; use std::collections::VecDeque; @@ -40,7 +42,10 @@ fn native_from_bytes( context.charge( UTIL_FROM_BYTES_BASE + UTIL_FROM_BYTES_PER_BYTE * NumBytes::new(bytes.len() as u64), )?; - let val = match Value::simple_deserialize(&bytes, &layout) { + let val = match ValueSerDeContext::new() + .with_func_args_deserialization(context.function_value_extension()) + .deserialize(&bytes, &layout) + { Some(val) => val, None => { return Err(SafeNativeError::Abort { diff --git a/aptos-move/framework/src/prover.rs b/aptos-move/framework/src/prover.rs index fbe8a7fed97a1..56952fbae1a9b 100644 --- a/aptos-move/framework/src/prover.rs +++ b/aptos-move/framework/src/prover.rs @@ -2,15 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 use crate::build_model; +use anyhow::bail; use codespan_reporting::{ diagnostic::Severity, term::termcolor::{ColorChoice, StandardStream}, }; -use log::LevelFilter; +use log::{info, LevelFilter}; use move_core_types::account_address::AccountAddress; -use move_model::metadata::{CompilerVersion, LanguageVersion}; +use move_model::{ + metadata::{CompilerVersion, LanguageVersion}, + model::{GlobalEnv, VerificationScope}, +}; +use move_prover::cli::Options; use std::{ collections::{BTreeMap, BTreeSet}, + fs, path::Path, time::Instant, }; @@ -27,6 +33,12 @@ pub struct ProverOptions { #[clap(long, short)] pub filter: Option, + /// Scopes verification to the specified function. This can either be a name of the + /// form "mod::func" or simply "func", in the later case every matching function is + /// taken. + #[clap(long, short)] + pub only: Option, + /// Whether to display additional information in error reports. This may help /// debugging but also can make verification slower. #[clap(long, short)] @@ -84,6 +96,24 @@ pub struct ProverOptions { #[clap(long)] pub dump: bool, + /// Whether to benchmark verification. If selected, each verification target in the + /// current package will be verified independently with timing recorded. This attempts + /// to detect timeouts. A benchmark report will be written to `prover_benchmark.fun_data` in the + /// package directory. The command also writes a `prover_benchmark.svg` graphic, which + /// is build from the data in the file above, comparing with any other `*.fun_data` files + /// in the package directory. Thus, you can rename the data file to something like + /// `prover_benchmark_v1.fun_data` and in the next run, compare benchmarks in the `.svg` + /// file from multiple runs. + #[clap(long = "benchmark")] + pub benchmark: bool, + + /// Whether to skip verification of type instantiations of functions. This may miss + /// some verification conditions if different type instantiations can create + /// different behavior via type reflection or storage access, but can speed up + /// verification. + #[clap(long = "skip-instance-check")] + pub skip_instance_check: bool, + #[clap(skip)] pub for_test: bool, } @@ -93,6 +123,7 @@ impl Default for ProverOptions { Self { verbosity: None, filter: None, + only: None, trace: false, cvc5: false, stratification_depth: 6, @@ -106,7 +137,9 @@ impl Default for ProverOptions { loop_unroll: None, stable_test_output: false, dump: false, + benchmark: false, for_test: false, + skip_instance_check: false, } } } @@ -127,6 +160,7 @@ impl ProverOptions { ) -> anyhow::Result<()> { let now = Instant::now(); let for_test = self.for_test; + let benchmark = self.benchmark; let mut model = build_model( dev_mode, package_path, @@ -140,6 +174,15 @@ impl ProverOptions { experiments.to_vec(), )?; let mut options = self.convert_options(); + options.language_version = language_version; + options.model_builder.language_version = language_version.unwrap_or_default(); + if compiler_version.unwrap_or_default() >= CompilerVersion::V2_0 + || language_version + .unwrap_or_default() + .is_at_least(LanguageVersion::V2_0) + { + options.compiler_v2 = true; + } // Need to ensure a distinct output.bpl file for concurrent execution. In non-test // mode, we actually want to use the static output.bpl for debugging purposes let _temp_holder = if for_test { @@ -163,11 +206,21 @@ impl ProverOptions { template_bytes: include_bytes!("aptos-natives.bpl").to_vec(), module_instance_names: move_prover_boogie_backend::options::custom_native_options(), }); - let mut writer = StandardStream::stderr(ColorChoice::Auto); - if compiler_version.unwrap_or_default() == CompilerVersion::V1 { - move_prover::run_move_prover_with_model(&mut model, &mut writer, options, Some(now))?; + if benchmark { + // Special mode of benchmarking + run_prover_benchmark(package_path, &mut model, options)?; } else { - move_prover::run_move_prover_with_model_v2(&mut model, &mut writer, options, now)?; + let mut writer = StandardStream::stderr(ColorChoice::Auto); + if compiler_version.unwrap_or_default() == CompilerVersion::V1 { + move_prover::run_move_prover_with_model( + &mut model, + &mut writer, + options, + Some(now), + )?; + } else { + move_prover::run_move_prover_with_model_v2(&mut model, &mut writer, options, now)?; + } } Ok(()) } @@ -184,6 +237,11 @@ impl ProverOptions { output_path: "".to_string(), verbosity_level, prover: move_prover_bytecode_pipeline::options::ProverOptions { + verify_scope: if let Some(name) = self.only { + VerificationScope::Only(name) + } else { + VerificationScope::All + }, stable_test_output: self.stable_test_output, auto_trace_level: if self.trace { move_prover_bytecode_pipeline::options::AutoTraceLevel::VerifiedFunction @@ -215,6 +273,7 @@ impl ProverOptions { }, custom_natives: None, loop_unroll: self.loop_unroll, + skip_instance_check: self.skip_instance_check, ..Default::default() }, ..Default::default() @@ -234,3 +293,106 @@ impl ProverOptions { } } } + +fn run_prover_benchmark( + package_path: &Path, + env: &mut GlobalEnv, + mut options: Options, +) -> anyhow::Result<()> { + info!("starting prover benchmark"); + // Determine sources and dependencies from the env + let mut sources = BTreeSet::new(); + let mut deps: Vec = vec![]; + for module in env.get_modules() { + let file_name = module.get_source_path().to_string_lossy().to_string(); + if module.is_primary_target() { + sources.insert(module.get_source_path().to_string_lossy().to_string()); + } else if let Some(p) = Path::new(&file_name) + .parent() + .and_then(|p| p.canonicalize().ok()) + { + // The prover doesn't like to have `p` and `p/s` as dep paths, filter those out + let p = p.to_string_lossy().to_string(); + let mut done = false; + for d in &mut deps { + if p.starts_with(&*d) { + // p is subsumed + done = true; + break; + } else if d.starts_with(&p) { + // p is more general or equal to d, swap it out + *d = p.to_string(); + done = true; + break; + } + } + if !done { + deps.push(p) + } + } else { + bail!("invalid file path `{}`", file_name) + } + } + + // Enrich the prover options by the aliases in the env + for (alias, address) in env.get_address_alias_map() { + options.move_named_address_values.push(format!( + "{}={}", + alias.display(env.symbol_pool()), + address.to_hex_literal() + )) + } + + // Create or override a prover_benchmark.toml in the package dir, reflection `options` + let config_file = package_path.join("prover_benchmark.toml"); + let toml = toml::to_string(&options)?; + std::fs::write(&config_file, toml)?; + + // Args for the benchmark API + let mut args = vec![ + // Command name + "bench".to_string(), + // Benchmark by function not module + "--func".to_string(), + // Use as the config the file we derived from `options` + "--config".to_string(), + config_file.to_string_lossy().to_string(), + ]; + + // Add deps and sources to args and run the tool + for dep in deps { + args.push("-d".to_string()); + args.push(dep) + } + args.extend(sources); + move_prover_lab::benchmark::benchmark(&args); + + // The benchmark stores the result in `.fun_data`, now plot it. + // If there are any other `*.fun_data` files, add them to the plot. + let mut args = vec![ + "plot".to_string(), + format!( + "--out={}", + config_file + .as_path() + .with_extension("svg") + .to_string_lossy() + ), + "--sort".to_string(), + ]; + let main_data_file = config_file + .as_path() + .with_extension("fun_data") + .to_string_lossy() + .to_string(); + args.push(main_data_file.clone()); + let paths = fs::read_dir(package_path)?; + for p in paths.flatten() { + let p = p.path().as_path().to_string_lossy().to_string(); + // Only use this if its is not the main data file we already added + if p.ends_with(".fun_data") && !p.ends_with("/prover_benchmark.fun_data") { + args.push(p) + } + } + move_prover_lab::plot::plot_svg(&args) +} diff --git a/aptos-move/framework/table-natives/src/lib.rs b/aptos-move/framework/table-natives/src/lib.rs index d306b7a025890..ab1e0a6dd9190 100644 --- a/aptos-move/framework/table-natives/src/lib.rs +++ b/aptos-move/framework/table-natives/src/lib.rs @@ -28,7 +28,7 @@ pub use move_table_extension::{TableHandle, TableInfo, TableResolver}; use move_vm_runtime::native_functions::NativeFunctionTable; use move_vm_types::{ loaded_data::runtime_types::Type, - value_serde::{deserialize_and_allow_delayed_values, serialize_and_allow_delayed_values}, + value_serde::{FunctionValueExtension, ValueSerDeContext}, values::{GlobalValue, Reference, StructRef, Value}, }; use sha3::{Digest, Sha3_256}; @@ -118,7 +118,10 @@ impl<'a> NativeTableContext<'a> { } /// Computes the change set from a NativeTableContext. - pub fn into_change_set(self) -> PartialVMResult { + pub fn into_change_set( + self, + function_value_extension: &impl FunctionValueExtension, + ) -> PartialVMResult { let NativeTableContext { table_data, .. } = self; let TableData { new_tables, @@ -141,10 +144,24 @@ impl<'a> NativeTableContext<'a> { match op { Op::New(val) => { - entries.insert(key, Op::New(serialize_value(&value_layout_info, &val)?)); + entries.insert( + key, + Op::New(serialize_value( + function_value_extension, + &value_layout_info, + &val, + )?), + ); }, Op::Modify(val) => { - entries.insert(key, Op::Modify(serialize_value(&value_layout_info, &val)?)); + entries.insert( + key, + Op::Modify(serialize_value( + function_value_extension, + &value_layout_info, + &val, + )?), + ); }, Op::Delete => { entries.insert(key, Op::Delete); @@ -204,26 +221,33 @@ impl LayoutInfo { impl Table { fn get_or_create_global_value( &mut self, - context: &NativeTableContext, + function_value_extension: &dyn FunctionValueExtension, + table_context: &NativeTableContext, key: Vec, ) -> PartialVMResult<(&mut GlobalValue, Option>)> { Ok(match self.content.entry(key) { Entry::Vacant(entry) => { // If there is an identifier mapping, we need to pass layout to // ensure it gets recorded. - let data = context.resolver.resolve_table_entry_bytes_with_layout( - &self.handle, - entry.key(), - if self.value_layout_info.has_identifier_mappings { - Some(&self.value_layout_info.layout) - } else { - None - }, - )?; + let data = table_context + .resolver + .resolve_table_entry_bytes_with_layout( + &self.handle, + entry.key(), + if self.value_layout_info.has_identifier_mappings { + Some(&self.value_layout_info.layout) + } else { + None + }, + )?; let (gv, loaded) = match data { Some(val_bytes) => { - let val = deserialize_value(&self.value_layout_info, &val_bytes)?; + let val = deserialize_value( + function_value_extension, + &val_bytes, + &self.value_layout_info, + )?; ( GlobalValue::cached(val)?, Some(NumBytes::new(val_bytes.len() as u64)), @@ -341,6 +365,7 @@ fn native_add_box( context.charge(ADD_BOX_BASE)?; + let function_value_extension = context.function_value_extension(); let table_context = context.extensions().get::(); let mut table_data = table_context.table_data.borrow_mut(); @@ -350,10 +375,11 @@ fn native_add_box( let table = table_data.get_or_create_table(context, handle, &ty_args[0], &ty_args[2])?; - let key_bytes = serialize_key(&table.key_layout, &key)?; + let key_bytes = serialize_key(function_value_extension, &table.key_layout, &key)?; let key_cost = ADD_BOX_PER_BYTE_SERIALIZED * NumBytes::new(key_bytes.len() as u64); - let (gv, loaded) = table.get_or_create_global_value(table_context, key_bytes)?; + let (gv, loaded) = + table.get_or_create_global_value(function_value_extension, table_context, key_bytes)?; let res = match gv.move_to(val) { Ok(_) => Ok(smallvec![]), @@ -381,6 +407,7 @@ fn native_borrow_box( context.charge(BORROW_BOX_BASE)?; + let function_value_extension = context.function_value_extension(); let table_context = context.extensions().get::(); let mut table_data = table_context.table_data.borrow_mut(); @@ -389,10 +416,11 @@ fn native_borrow_box( let table = table_data.get_or_create_table(context, handle, &ty_args[0], &ty_args[2])?; - let key_bytes = serialize_key(&table.key_layout, &key)?; + let key_bytes = serialize_key(function_value_extension, &table.key_layout, &key)?; let key_cost = BORROW_BOX_PER_BYTE_SERIALIZED * NumBytes::new(key_bytes.len() as u64); - let (gv, loaded) = table.get_or_create_global_value(table_context, key_bytes)?; + let (gv, loaded) = + table.get_or_create_global_value(function_value_extension, table_context, key_bytes)?; let res = match gv.borrow_global() { Ok(ref_val) => Ok(smallvec![ref_val]), @@ -420,6 +448,7 @@ fn native_contains_box( context.charge(CONTAINS_BOX_BASE)?; + let function_value_extension = context.function_value_extension(); let table_context = context.extensions().get::(); let mut table_data = table_context.table_data.borrow_mut(); @@ -428,10 +457,11 @@ fn native_contains_box( let table = table_data.get_or_create_table(context, handle, &ty_args[0], &ty_args[2])?; - let key_bytes = serialize_key(&table.key_layout, &key)?; + let key_bytes = serialize_key(function_value_extension, &table.key_layout, &key)?; let key_cost = CONTAINS_BOX_PER_BYTE_SERIALIZED * NumBytes::new(key_bytes.len() as u64); - let (gv, loaded) = table.get_or_create_global_value(table_context, key_bytes)?; + let (gv, loaded) = + table.get_or_create_global_value(function_value_extension, table_context, key_bytes)?; let exists = Value::bool(gv.exists()?); drop(table_data); @@ -453,6 +483,7 @@ fn native_remove_box( context.charge(REMOVE_BOX_BASE)?; + let function_value_extension = context.function_value_extension(); let table_context = context.extensions().get::(); let mut table_data = table_context.table_data.borrow_mut(); @@ -461,10 +492,11 @@ fn native_remove_box( let table = table_data.get_or_create_table(context, handle, &ty_args[0], &ty_args[2])?; - let key_bytes = serialize_key(&table.key_layout, &key)?; + let key_bytes = serialize_key(function_value_extension, &table.key_layout, &key)?; let key_cost = REMOVE_BOX_PER_BYTE_SERIALIZED * NumBytes::new(key_bytes.len() as u64); - let (gv, loaded) = table.get_or_create_global_value(table_context, key_bytes)?; + let (gv, loaded) = + table.get_or_create_global_value(function_value_extension, table_context, key_bytes)?; let res = match gv.move_from() { Ok(val) => Ok(smallvec![val]), Err(_) => Err(SafeNativeError::Abort { @@ -528,34 +560,55 @@ fn get_table_handle(table: &StructRef) -> PartialVMResult { Ok(TableHandle(handle)) } -fn serialize_key(layout: &MoveTypeLayout, key: &Value) -> PartialVMResult> { - key.simple_serialize(layout) +fn serialize_key( + function_value_extension: &dyn FunctionValueExtension, + layout: &MoveTypeLayout, + key: &Value, +) -> PartialVMResult> { + ValueSerDeContext::new() + .with_func_args_deserialization(function_value_extension) + .serialize(key, layout)? .ok_or_else(|| partial_extension_error("cannot serialize table key")) } fn serialize_value( + function_value_extension: &dyn FunctionValueExtension, layout_info: &LayoutInfo, val: &Value, ) -> PartialVMResult<(Bytes, Option>)> { let serialization_result = if layout_info.has_identifier_mappings { // Value contains delayed fields, so we should be able to serialize it. - serialize_and_allow_delayed_values(val, layout_info.layout.as_ref())? + ValueSerDeContext::new() + .with_delayed_fields_serde() + .with_func_args_deserialization(function_value_extension) + .serialize(val, layout_info.layout.as_ref())? .map(|bytes| (bytes.into(), Some(layout_info.layout.clone()))) } else { // No delayed fields, make sure serialization fails if there are any // native values. - val.simple_serialize(layout_info.layout.as_ref()) + ValueSerDeContext::new() + .with_func_args_deserialization(function_value_extension) + .serialize(val, layout_info.layout.as_ref())? .map(|bytes| (bytes.into(), None)) }; serialization_result.ok_or_else(|| partial_extension_error("cannot serialize table value")) } -fn deserialize_value(layout_info: &LayoutInfo, bytes: &[u8]) -> PartialVMResult { +fn deserialize_value( + function_value_extension: &dyn FunctionValueExtension, + bytes: &[u8], + layout_info: &LayoutInfo, +) -> PartialVMResult { let layout = layout_info.layout.as_ref(); let deserialization_result = if layout_info.has_identifier_mappings { - deserialize_and_allow_delayed_values(bytes, layout) + ValueSerDeContext::new() + .with_func_args_deserialization(function_value_extension) + .with_delayed_fields_serde() + .deserialize(bytes, layout) } else { - Value::simple_deserialize(bytes, layout) + ValueSerDeContext::new() + .with_func_args_deserialization(function_value_extension) + .deserialize(bytes, layout) }; deserialization_result.ok_or_else(|| partial_extension_error("cannot deserialize table value")) } diff --git a/crates/aptos/CHANGELOG.md b/crates/aptos/CHANGELOG.md index 591c7bff8abb6..3bff5c8b2bc12 100644 --- a/crates/aptos/CHANGELOG.md +++ b/crates/aptos/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to the Aptos CLI will be captured in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the format set out by [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Unreleased +- Add flag `--benchmark` to `aptos move prove`, which allows to benchmark verification times of individual functions in a package. +- Add flag `--only ` to `aptos move prove`, which allows to scope verification to a function. + +- Fix `aptos init` to show the explorer link for accounts when account is already created on chain instead of prompting to fund the account. ## [5.1.0] - 2024/12/13 - More optimizations are now default for compiler v2. diff --git a/crates/aptos/src/common/init.rs b/crates/aptos/src/common/init.rs index 2dee996cbd185..6053339b51d7b 100644 --- a/crates/aptos/src/common/init.rs +++ b/crates/aptos/src/common/init.rs @@ -372,29 +372,34 @@ impl CliCommand<()> for InitTool { address, profile_name, ); - match network { - Network::Mainnet => { - eprintln!("The account has not been created on chain yet, you will need to create and fund the account by transferring funds from another account"); - }, - Network::Testnet => { - let mint_site_url = get_mint_site_url(Some(address)); - eprintln!("The account has not been created on chain yet. To create the account and get APT on testnet you must visit {}", mint_site_url); - // We don't use `prompt_yes_with_override` here because we only want to - // automatically open the minting site if they're in an interactive setting. - if !self.prompt_options.assume_yes { - eprint!("Press [Enter] to go there now > "); - read_line("Confirmation")?; - open::that(&mint_site_url).map_err(|err| { - CliError::UnexpectedError(format!("Failed to open minting site: {}", err)) - })?; - } - }, - wildcard => { - eprintln!( - "See the account here: {}", - explorer_account_link(address, Some(wildcard)) - ); - }, + if !account_exists { + match network { + Network::Mainnet => { + eprintln!("The account has not been created on chain yet, you will need to create and fund the account by transferring funds from another account"); + }, + Network::Testnet => { + let mint_site_url = get_mint_site_url(Some(address)); + eprintln!("The account has not been created on chain yet. To create the account and get APT on testnet you must visit {}", mint_site_url); + // We don't use `prompt_yes_with_override` here because we only want to + // automatically open the minting site if they're in an interactive setting. + if !self.prompt_options.assume_yes { + eprint!("Press [Enter] to go there now > "); + read_line("Confirmation")?; + open::that(&mint_site_url).map_err(|err| { + CliError::UnexpectedError(format!( + "Failed to open minting site: {}", + err + )) + })?; + } + }, + _ => {}, + } + } else { + eprintln!( + "See the account here: {}", + explorer_account_link(address, Some(network)) + ); } Ok(()) diff --git a/crates/transaction-emitter-lib/src/emitter/submission_worker.rs b/crates/transaction-emitter-lib/src/emitter/submission_worker.rs index be3dafccaeaf2..9563e78648b59 100644 --- a/crates/transaction-emitter-lib/src/emitter/submission_worker.rs +++ b/crates/transaction-emitter-lib/src/emitter/submission_worker.rs @@ -30,7 +30,9 @@ use std::{ sync::{atomic::AtomicU64, Arc}, time::Instant, }; -use tokio::time::sleep; +use tokio::time::{sleep, sleep_until}; + +const ALLOWED_EARLY: Duration = Duration::from_micros(500); pub struct SubmissionWorker { pub(crate) accounts: Vec>, @@ -82,31 +84,18 @@ impl SubmissionWorker { pub(crate) async fn run(mut self, start_instant: Instant) -> Vec { let mut wait_until = start_instant + self.start_sleep_duration; - let now = Instant::now(); - if wait_until > now { - self.sleep_check_done(wait_until - now).await; - } + self.sleep_check_done(wait_until).await; let wait_duration = Duration::from_millis(self.params.wait_millis); while !self.stop.load(Ordering::Relaxed) { - let stats_clone = self.stats.clone(); - let loop_stats = stats_clone.get_cur(); - let loop_start_time = Instant::now(); - if wait_duration.as_secs() > 0 - && loop_start_time.duration_since(wait_until) > Duration::from_secs(5) - { - sample!( - SampleRate::Duration(Duration::from_secs(5)), - error!( - "[{:?}] txn_emitter worker drifted out of sync too much: {}s. Is expiration too short, or 5s buffer on top of it?", - self.client().path_prefix_string(), - loop_start_time.duration_since(wait_until).as_secs() - ) - ); + + if wait_duration.as_secs() > 0 { + self.verify_loop_start_drift(loop_start_time, wait_until); } - // always add expected cycle duration, to not drift from expected pace. - wait_until += wait_duration; + + let stats_clone = self.stats.clone(); + let loop_stats = stats_clone.get_cur(); let requests = self.gen_requests(); if !requests.is_empty() { @@ -175,9 +164,10 @@ impl SubmissionWorker { if self.skip_latency_stats { // we also don't want to be stuck waiting for txn_expiration_time_secs // after stop is called, so we sleep until time or stop is set. - self.sleep_check_done(Duration::from_secs( - self.params.txn_expiration_time_secs + 3, - )) + self.sleep_check_done( + Instant::now() + + Duration::from_secs(self.params.txn_expiration_time_secs + 3), + ) .await } @@ -203,9 +193,11 @@ impl SubmissionWorker { .await; } - let now = Instant::now(); - if wait_until > now { - self.sleep_check_done(wait_until - now).await; + if wait_duration.as_secs() > 0 { + // always add expected cycle duration, to not drift from expected pace, + // irrespectively of how long our iteration lasted. + wait_until += wait_duration; + self.sleep_check_done(wait_until).await; } } @@ -216,16 +208,63 @@ impl SubmissionWorker { } // returns true if it returned early - async fn sleep_check_done(&self, duration: Duration) { - let start_time = Instant::now(); + async fn sleep_check_done(&self, sleep_until_time: Instant) { + // sleep has millisecond granularity - so round the sleep + let sleep_poll_interval = Duration::from_secs(1); loop { - sleep(Duration::from_secs(1)).await; if self.stop.load(Ordering::Relaxed) { return; } - if start_time.elapsed() >= duration { + + let now = Instant::now(); + if now + ALLOWED_EARLY > sleep_until_time { return; } + + if sleep_until_time > now + sleep_poll_interval { + sleep(sleep_poll_interval).await; + } else { + sleep_until(sleep_until_time.into()).await; + } + } + } + + fn verify_loop_start_drift(&self, loop_start_time: Instant, wait_until: Instant) { + if loop_start_time > wait_until { + let delay_s = loop_start_time + .saturating_duration_since(wait_until) + .as_secs_f32(); + if delay_s > 5.0 { + sample!( + SampleRate::Duration(Duration::from_secs(2)), + error!( + "[{:?}] txn_emitter worker drifted out of sync too much: {:.3}s. Is machine underprovisioned? Is expiration too short, or 5s buffer on top of it?", + self.client().path_prefix_string(), + delay_s, + ) + ); + } else if delay_s > 0.3 { + sample!( + SampleRate::Duration(Duration::from_secs(5)), + error!( + "[{:?}] txn_emitter worker called a bit out of sync: {:.3}s. Is machine underprovisioned? Is expiration too short, or 5s buffer on top of it?", + self.client().path_prefix_string(), + delay_s, + ) + ); + } + } else { + let early_s = wait_until.saturating_duration_since(loop_start_time); + if early_s > ALLOWED_EARLY { + sample!( + SampleRate::Duration(Duration::from_secs(5)), + error!( + "[{:?}] txn_emitter worker called too early: {:.3}s. There is some bug in waiting.", + self.client().path_prefix_string(), + early_s.as_secs_f32(), + ) + ); + } } } diff --git a/devtools/aptos-cargo-cli/src/lib.rs b/devtools/aptos-cargo-cli/src/lib.rs index 4dea19a6ec0f9..fc802650b2dc1 100644 --- a/devtools/aptos-cargo-cli/src/lib.rs +++ b/devtools/aptos-cargo-cli/src/lib.rs @@ -258,13 +258,18 @@ impl AptosCargoCommand { self.get_args_and_affected_packages(package_args)?; // Determine if any relevant files or packages were changed - let relevant_changes_detected = detect_relevant_changes( + #[allow(unused_assignments)] + let mut relevant_changes_detected = detect_relevant_changes( RELEVANT_FILE_PATHS_FOR_FRAMEWORK_UPGRADE_TESTS.to_vec(), RELEVANT_PACKAGES_FOR_FRAMEWORK_UPGRADE_TESTS.to_vec(), changed_files, affected_package_paths, ); + // TODO: remove this! This is a temporary fix to disable + // the framework upgrade test while we debug the failures. + relevant_changes_detected = false; + // Output if relevant changes were detected that require the framework upgrade // test. This will be consumed by Github Actions and used to run the test. println!( diff --git a/docker/builder/docker-bake-rust-all.hcl b/docker/builder/docker-bake-rust-all.hcl index 3ad59abc754d2..77a9aafd7e9d5 100644 --- a/docker/builder/docker-bake-rust-all.hcl +++ b/docker/builder/docker-bake-rust-all.hcl @@ -69,7 +69,7 @@ target "debian-base" { dockerfile = "docker/builder/debian-base.Dockerfile" contexts = { # Run `docker buildx imagetools inspect debian:bullseye` to find the latest multi-platform hash - debian = "docker-image://debian:bullseye@sha256:01559430c84e6bc864bed554345d1bfbfa94ac108ab68f39915cae34604b15c3" + debian = "docker-image://debian:bullseye@sha256:e91d1b0684e0f26a29c2353c52d4814f4d153e10b1faddf9fbde473ed71e2fcf" } } diff --git a/protos/scripts/install_deps.sh b/protos/scripts/install_deps.sh index 60f0e422ba607..07e0c0ab23526 100755 --- a/protos/scripts/install_deps.sh +++ b/protos/scripts/install_deps.sh @@ -4,11 +4,14 @@ # The TS plugins are pulled automatically since we depend on them directly from # the buf.build community plugin registry. +set -e +set -x + # For generating Rust code -cargo install --version 0.2.3 protoc-gen-prost -cargo install --version 0.2.3 protoc-gen-prost-serde -cargo install --version 0.3.1 protoc-gen-prost-crate -cargo install --version 0.3.0 protoc-gen-tonic +cargo install --locked --version 0.2.3 protoc-gen-prost +cargo install --locked --version 0.2.3 protoc-gen-prost-serde +cargo install --locked --version 0.3.1 protoc-gen-prost-crate +cargo install --locked --version 0.3.0 protoc-gen-tonic # For generating Python code cd python diff --git a/testsuite/forge-cli/src/suites/land_blocking.rs b/testsuite/forge-cli/src/suites/land_blocking.rs index dbae2019a9a43..5b2a2eafbe456 100644 --- a/testsuite/forge-cli/src/suites/land_blocking.rs +++ b/testsuite/forge-cli/src/suites/land_blocking.rs @@ -29,6 +29,7 @@ pub(crate) fn get_land_blocking_test( pub(crate) fn compat() -> ForgeConfig { ForgeConfig::default() + .with_suite_name("compat".into()) .with_initial_validator_count(NonZeroUsize::new(4).unwrap()) .add_network_test(SimpleValidatorUpgrade) .with_success_criteria(SuccessCriteria::new(5000).add_wait_for_catchup_s(240)) diff --git a/testsuite/forge/src/config.rs b/testsuite/forge/src/config.rs index 599cfad0a411b..61584befb8752 100644 --- a/testsuite/forge/src/config.rs +++ b/testsuite/forge/src/config.rs @@ -170,6 +170,7 @@ impl ForgeConfig { let existing_db_tag = self.existing_db_tag.clone(); let validator_resource_override = self.validator_resource_override; let fullnode_resource_override = self.fullnode_resource_override; + let suite_name = self.get_suite_name(); // Override specific helm values. See reference: terraform/helm/aptos-node/values.yaml Some(Arc::new(move |helm_values: &mut serde_yaml::Value| { @@ -239,10 +240,16 @@ impl ForgeConfig { helm_values["validator"]["config"]["indexer_db_config"]["enable_event"] = true.into(); helm_values["fullnode"]["config"]["indexer_db_config"]["enable_event"] = true.into(); - // enable new pipeline - helm_values["validator"]["config"]["consensus"]["enable_pipeline"] = true.into(); - helm_values["fullnode"]["config"]["consensus_observer"]["enable_pipeline"] = - true.into(); + // This is a temporary hack to disable new pipeline for compat tests. + if !suite_name + .as_ref() + .is_some_and(|name| name.eq_ignore_ascii_case("compat")) + { + // enable new pipeline + helm_values["validator"]["config"]["consensus"]["enable_pipeline"] = true.into(); + helm_values["fullnode"]["config"]["consensus_observer"]["enable_pipeline"] = + true.into(); + } })) } diff --git a/testsuite/fuzzer/fuzz/fuzz_targets/move/value_deserialize.rs b/testsuite/fuzzer/fuzz/fuzz_targets/move/value_deserialize.rs index 3cca926099b3b..fcd5cf6ebb645 100644 --- a/testsuite/fuzzer/fuzz/fuzz_targets/move/value_deserialize.rs +++ b/testsuite/fuzzer/fuzz/fuzz_targets/move/value_deserialize.rs @@ -5,7 +5,7 @@ use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; use move_core_types::value::MoveTypeLayout; -use move_vm_types::values::Value; +use move_vm_types::value_serde::ValueSerDeContext; mod utils; use utils::helpers::is_valid_layout; @@ -19,5 +19,6 @@ fuzz_target!(|fuzz_data: FuzzData| { if fuzz_data.data.is_empty() || !is_valid_layout(&fuzz_data.layout) { return; } - let _ = Value::simple_deserialize(&fuzz_data.data, &fuzz_data.layout); + // TODO: How do we fuzz function resolution? + let _ = ValueSerDeContext::new().deserialize(&fuzz_data.data, &fuzz_data.layout); }); diff --git a/third_party/move/extensions/move-table-extension/src/lib.rs b/third_party/move/extensions/move-table-extension/src/lib.rs index b9235d3e71de9..3c876513723d1 100644 --- a/third_party/move/extensions/move-table-extension/src/lib.rs +++ b/third_party/move/extensions/move-table-extension/src/lib.rs @@ -26,6 +26,7 @@ use move_vm_types::{ loaded_data::runtime_types::Type, natives::function::NativeResult, pop_arg, + value_serde::{FunctionValueExtension, ValueSerDeContext}, values::{GlobalValue, Reference, StructRef, Value}, }; use sha3::{Digest, Sha3_256}; @@ -154,7 +155,10 @@ impl<'a> NativeTableContext<'a> { } /// Computes the change set from a NativeTableContext. - pub fn into_change_set(self) -> PartialVMResult { + pub fn into_change_set( + self, + function_value_extension: &impl FunctionValueExtension, + ) -> PartialVMResult { let NativeTableContext { table_data, .. } = self; let TableData { new_tables, @@ -177,11 +181,11 @@ impl<'a> NativeTableContext<'a> { match op { Op::New(val) => { - let bytes = serialize(&value_layout, &val)?; + let bytes = serialize(function_value_extension, &value_layout, &val)?; entries.insert(key, Op::New(bytes.into())); }, Op::Modify(val) => { - let bytes = serialize(&value_layout, &val)?; + let bytes = serialize(function_value_extension, &value_layout, &val)?; entries.insert(key, Op::Modify(bytes.into())); }, Op::Delete => { @@ -231,18 +235,19 @@ impl TableData { impl Table { fn get_or_create_global_value( &mut self, - context: &NativeTableContext, + function_value_extension: &dyn FunctionValueExtension, + table_context: &NativeTableContext, key: Vec, ) -> PartialVMResult<(&mut GlobalValue, Option>)> { Ok(match self.content.entry(key) { Entry::Vacant(entry) => { - let (gv, loaded) = match context.resolver.resolve_table_entry_bytes_with_layout( - &self.handle, - entry.key(), - None, - )? { + let (gv, loaded) = match table_context + .resolver + .resolve_table_entry_bytes_with_layout(&self.handle, entry.key(), None)? + { Some(val_bytes) => { - let val = deserialize(&self.value_layout, &val_bytes)?; + let val = + deserialize(function_value_extension, &val_bytes, &self.value_layout)?; ( GlobalValue::cached(val)?, Some(NumBytes::new(val_bytes.len() as u64)), @@ -390,6 +395,7 @@ fn native_add_box( assert_eq!(ty_args.len(), 3); assert_eq!(args.len(), 3); + let function_value_extension = context.function_value_extension(); let table_context = context.extensions().get::(); let mut table_data = table_context.table_data.borrow_mut(); @@ -401,10 +407,11 @@ fn native_add_box( let table = table_data.get_or_create_table(context, handle, &ty_args[0], &ty_args[2])?; - let key_bytes = serialize(&table.key_layout, &key)?; + let key_bytes = serialize(function_value_extension, &table.key_layout, &key)?; cost += gas_params.per_byte_serialized * NumBytes::new(key_bytes.len() as u64); - let (gv, loaded) = table.get_or_create_global_value(table_context, key_bytes)?; + let (gv, loaded) = + table.get_or_create_global_value(function_value_extension, table_context, key_bytes)?; cost += common_gas_params.calculate_load_cost(loaded); match gv.move_to(val) { @@ -440,6 +447,7 @@ fn native_borrow_box( assert_eq!(ty_args.len(), 3); assert_eq!(args.len(), 2); + let function_value_extension = context.function_value_extension(); let table_context = context.extensions().get::(); let mut table_data = table_context.table_data.borrow_mut(); @@ -450,10 +458,11 @@ fn native_borrow_box( let mut cost = gas_params.base; - let key_bytes = serialize(&table.key_layout, &key)?; + let key_bytes = serialize(function_value_extension, &table.key_layout, &key)?; cost += gas_params.per_byte_serialized * NumBytes::new(key_bytes.len() as u64); - let (gv, loaded) = table.get_or_create_global_value(table_context, key_bytes)?; + let (gv, loaded) = + table.get_or_create_global_value(function_value_extension, table_context, key_bytes)?; cost += common_gas_params.calculate_load_cost(loaded); match gv.borrow_global() { @@ -489,6 +498,7 @@ fn native_contains_box( assert_eq!(ty_args.len(), 3); assert_eq!(args.len(), 2); + let function_value_extension = context.function_value_extension(); let table_context = context.extensions().get::(); let mut table_data = table_context.table_data.borrow_mut(); @@ -499,10 +509,11 @@ fn native_contains_box( let mut cost = gas_params.base; - let key_bytes = serialize(&table.key_layout, &key)?; + let key_bytes = serialize(function_value_extension, &table.key_layout, &key)?; cost += gas_params.per_byte_serialized * NumBytes::new(key_bytes.len() as u64); - let (gv, loaded) = table.get_or_create_global_value(table_context, key_bytes)?; + let (gv, loaded) = + table.get_or_create_global_value(function_value_extension, table_context, key_bytes)?; cost += common_gas_params.calculate_load_cost(loaded); let exists = Value::bool(gv.exists()?); @@ -537,6 +548,7 @@ fn native_remove_box( assert_eq!(ty_args.len(), 3); assert_eq!(args.len(), 2); + let function_value_extension = context.function_value_extension(); let table_context = context.extensions().get::(); let mut table_data = table_context.table_data.borrow_mut(); @@ -547,10 +559,11 @@ fn native_remove_box( let mut cost = gas_params.base; - let key_bytes = serialize(&table.key_layout, &key)?; + let key_bytes = serialize(function_value_extension, &table.key_layout, &key)?; cost += gas_params.per_byte_serialized * NumBytes::new(key_bytes.len() as u64); - let (gv, loaded) = table.get_or_create_global_value(table_context, key_bytes)?; + let (gv, loaded) = + table.get_or_create_global_value(function_value_extension, table_context, key_bytes)?; cost += common_gas_params.calculate_load_cost(loaded); match gv.move_from() { @@ -685,13 +698,25 @@ fn get_table_handle(table: &StructRef) -> PartialVMResult { Ok(TableHandle(handle)) } -fn serialize(layout: &MoveTypeLayout, val: &Value) -> PartialVMResult> { - val.simple_serialize(layout) +fn serialize( + function_value_extension: &dyn FunctionValueExtension, + layout: &MoveTypeLayout, + val: &Value, +) -> PartialVMResult> { + ValueSerDeContext::new() + .with_func_args_deserialization(function_value_extension) + .serialize(val, layout)? .ok_or_else(|| partial_extension_error("cannot serialize table key or value")) } -fn deserialize(layout: &MoveTypeLayout, bytes: &[u8]) -> PartialVMResult { - Value::simple_deserialize(bytes, layout) +fn deserialize( + function_value_extension: &dyn FunctionValueExtension, + bytes: &[u8], + layout: &MoveTypeLayout, +) -> PartialVMResult { + ValueSerDeContext::new() + .with_func_args_deserialization(function_value_extension) + .deserialize(bytes, layout) .ok_or_else(|| partial_extension_error("cannot deserialize table key or value")) } diff --git a/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.exp b/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.exp new file mode 100644 index 0000000000000..a889af242eb9f --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.exp @@ -0,0 +1,9 @@ + +Diagnostics: +error: unexpected token + ┌─ tests/lambda/inline-parity/subtype_args_ok.move:23:37 + │ +23 │ fun t2(f: |&u64, &mut u64| with copy) { + │ - ^ Expected ')' + │ │ + │ To match this '(' diff --git a/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.lambda.exp b/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.lambda.exp new file mode 100644 index 0000000000000..a889af242eb9f --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.lambda.exp @@ -0,0 +1,9 @@ + +Diagnostics: +error: unexpected token + ┌─ tests/lambda/inline-parity/subtype_args_ok.move:23:37 + │ +23 │ fun t2(f: |&u64, &mut u64| with copy) { + │ - ^ Expected ')' + │ │ + │ To match this '(' diff --git a/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.move b/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.move new file mode 100644 index 0000000000000..98dbdb729a132 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/lambda/inline-parity/subtype_args_ok.move @@ -0,0 +1,27 @@ +module 0x8675309::M { + struct S has drop {} + + fun imm(_x: &T) {} + fun imm_mut(_x: &T, _y: &mut T) {} + fun mut_imm(_x: &mut T, _y: &T) {} + fun imm_imm(_x: &T, _y: &T) {} + + fun t0() { + imm(&mut 0); + imm(&0); + + imm(&mut S{}); + imm(&S{}); + } + + fun t1() { + imm_mut(&mut 0, &mut 0); + mut_imm(&mut 0, &mut 0); + imm_imm(&mut 0, &mut 0); + } + + fun t2(f: |&u64, &mut u64| with copy) { + f(&mut 0, &mut 0); + f(&0, &mut 0); + } +} diff --git a/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.exp b/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.exp new file mode 100644 index 0000000000000..e951e634b479c --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.exp @@ -0,0 +1,37 @@ + +Diagnostics: +error: unsupported language construct + ┌─ tests/lambda/storable/generic_func.move:36:18 + │ +36 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression + +error: unsupported language construct + ┌─ tests/lambda/storable/generic_func.move:36:71 + │ +36 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions + +error: unsupported language construct + ┌─ tests/lambda/storable/generic_func.move:37:18 + │ +37 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression + +error: unsupported language construct + ┌─ tests/lambda/storable/generic_func.move:37:71 + │ +37 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions + +error: unsupported language construct + ┌─ tests/lambda/storable/generic_func.move:53:55 + │ +53 │ assert!(0x42::mod2::item_exists<|address|bool with store+copy>(addr)); + │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types + +error: unsupported language construct + ┌─ tests/lambda/storable/generic_func.move:55:58 + │ +55 │ let found_f = 0x42::mod2::get_item<|address|bool with store+copy>(addr); + │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types diff --git a/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.lambda.exp b/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.lambda.exp new file mode 100644 index 0000000000000..39abd011a6bd2 --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.lambda.exp @@ -0,0 +1,1055 @@ +// -- Model dump before env processor pipeline: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor unused checks: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor type parameter check: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor check recursive struct definition: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor check cyclic type instantiation: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor unused struct params check: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor access and use check before inlining: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor inlining: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor access and use check after inlining: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor acquires check: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor simplifier: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let f2: |address|bool with copy+store = move|addr: address| mod2::item_exists(addr) with copy, drop, store; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor lambda-lifting: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = mod2::item_exists; + { + let f2: |address|bool with copy+store = mod2::item_exists; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor specification checker: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = mod2::item_exists; + { + let f2: |address|bool with copy+store = mod2::item_exists; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + +// -- Model dump after env processor specification rewriter: +module 0x42::mod2 { + struct Registry { + func: F, + } + public fun get_item(addr: address): F + acquires Registry(*) + { + select mod2::Registry.func<&Registry>(BorrowGlobal(Immutable)>(addr)) + } + public fun item_exists(addr: address): bool { + exists>(addr) + } + public fun save_item(owner: &signer,f: F) { + MoveTo>(owner, pack mod2::Registry(f)); + Tuple() + } +} // end 0x42::mod2 +module 0x42::mod3 { + use std::signer; + struct MyStruct1 { + x: u64, + } + struct MyStruct2 { + y: u8, + } + public fun test_item1(owner: signer) { + mod3::test_items(owner, true); + Tuple() + } + public fun test_item2(owner: signer) { + mod3::test_items(owner, false); + Tuple() + } + public fun test_items(owner: signer,use_1: bool) { + { + let struct1: MyStruct1 = pack mod3::MyStruct1(3); + { + let f1: |address|bool with copy+store = mod2::item_exists; + { + let f2: |address|bool with copy+store = mod2::item_exists; + { + let addr: address = signer::address_of(Borrow(Immutable)(owner)); + mod2::save_item(Borrow(Immutable)(owner), struct1); + MoveTo(Borrow(Immutable)(owner), struct1); + if use_1 { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f1); + Tuple() + } else { + mod2::save_item<|address|bool with copy+store>(Borrow(Immutable)(owner), f2); + Tuple() + }; + if mod2::item_exists<|address|bool with copy+store>(addr) { + Tuple() + } else { + Abort(14566554180833181696) + }; + { + let found_f: |address|bool with copy+store = mod2::get_item<|address|bool with copy+store>(addr); + if Eq(use_1, (found_f)(addr)) { + Tuple() + } else { + Abort(14566554180833181696) + }; + Tuple() + } + } + } + } + } + } +} // end 0x42::mod3 + + + +Diagnostics: +error: Function-typed values not yet implemented except as parameters to calls to inline functions + ┌─ tests/lambda/storable/generic_func.move:36:30 + │ +36 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Function-typed values not yet implemented except as parameters to calls to inline functions + ┌─ tests/lambda/storable/generic_func.move:37:30 + │ +37 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Calls to function values other than inline function parameters not yet implemented + ┌─ tests/lambda/storable/generic_func.move:57:26 + │ +57 │ assert!(use_1 == found_f(addr)); + │ ^^^^^^^^^^^^^ diff --git a/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.move b/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.move new file mode 100644 index 0000000000000..b7d3baf9f610a --- /dev/null +++ b/third_party/move/move-compiler-v2/tests/lambda/storable/generic_func.move @@ -0,0 +1,71 @@ +//# publish --print-bytecode +module 0x42::mod2 { + struct Registry has key, store { + func: F + } + + public fun save_item(owner: &signer, f: F) { + move_to>(owner, Registry { func: f }); + } + + public fun item_exists(addr: address): bool { + exists>(addr) + } + + public fun get_item(addr: address): F acquires Registry { + borrow_global>(addr).func + } +} + +//# publish --print-bytecode +module 0x42::mod3 { + use std::signer; + + struct MyStruct1 has key, store, copy { + x: u64 + } + + struct MyStruct2 has key, store, copy { + y: u8 + } + + public fun test_items(owner: signer, use_1: bool) { + let struct1 = MyStruct1 { x: 3 }; + // let struct2 = MyStruct2 { y: 2 }; + + let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + + let addr = signer::address_of(&owner); + 0x42::mod2::save_item(&owner, struct1); + + // Store just MyStruct1 + move_to(&owner, struct1); + + // Store f1 or f2, depending on use_1 + if (use_1) { + 0x42::mod2::save_item(&owner, f1); + } else { + 0x42::mod2::save_item(&owner, f2); + }; + + // In either case, item exists + assert!(0x42::mod2::item_exists<|address|bool with store+copy>(addr)); + + let found_f = 0x42::mod2::get_item<|address|bool with store+copy>(addr); + + assert!(use_1 == found_f(addr)); + } + + public fun test_item1(owner: signer) { + test_items(owner, true); + } + + public fun test_item2(owner: signer) { + test_items(owner, false); + } +} + +//# run --signers 0x42 -- 0x42::mod3::test_item1 + +//# run --signers 0x42 -- 0x42::mod3::test_item2 diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/constants/large_vectors.baseline.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/constants/large_vectors.baseline.exp new file mode 100644 index 0000000000000..12987590c3a8f --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/constants/large_vectors.baseline.exp @@ -0,0 +1,9 @@ +processed 3 tasks + +task 1 'run'. lines 17-17: +return values: 1027 + +task 2 'run'. lines 19-19: +return values: 1025 + +==> Compiler v2 delivered same results! diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/inlining/deep_exp.baseline.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/inlining/deep_exp.baseline.exp new file mode 100644 index 0000000000000..baae5717928b6 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/inlining/deep_exp.baseline.exp @@ -0,0 +1,6 @@ +processed 2 tasks + +task 1 'run'. lines 30-30: +return values: 625 + +==> Compiler v2 delivered same results! diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.baseline.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.baseline.exp new file mode 100644 index 0000000000000..9ced3f98e15e0 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.baseline.exp @@ -0,0 +1,71 @@ +comparison between v1 and v2 failed: += processed 4 tasks += += task 0 'publish'. lines 1-18: += += += += task 1 'publish'. lines 20-67: +- Error: error[E01013]: unsupported language construct ++ Error: compilation errors: ++ error: unsupported language construct += ┌─ TEMPFILE1:32:18 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:32:71 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:33:18 += │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:33:71 += │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:45:55 += │ += 45 │ assert!(0x42::mod2::item_exists<|address|bool with store+copy>(addr)); += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:46:58 += │ += 46 │ let found_f = 0x42::mod2::get_item<|address|bool with store+copy>(addr); += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += += += += task 2 'run'. lines 69-69: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += += task 3 'run'. lines 71-71: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.lambda.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.lambda.exp new file mode 100644 index 0000000000000..14ffdb0cb55b1 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.lambda.exp @@ -0,0 +1,75 @@ +comparison between v1 and v2 failed: += processed 4 tasks += += task 0 'publish'. lines 1-18: += += += += task 1 'publish'. lines 20-67: +- Error: error[E01013]: unsupported language construct +- ┌─ TEMPFILE1:32:18 ++ Error: compilation errors: ++ error: Function-typed values not yet implemented except as parameters to calls to inline functions ++ ┌─ TEMPFILE1:32:30 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; +- │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression ++ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ += +- error[E01013]: unsupported language construct +- ┌─ TEMPFILE1:32:71 ++ error: Function-typed values not yet implemented except as parameters to calls to inline functions ++ ┌─ TEMPFILE1:33:30 += │ +- 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; +- │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions +- +- error[E01013]: unsupported language construct +- ┌─ TEMPFILE1:33:18 +- │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; +- │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression ++ │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ += +- error[E01013]: unsupported language construct +- ┌─ TEMPFILE1:33:71 ++ error: Calls to function values other than inline function parameters not yet implemented ++ ┌─ TEMPFILE1:47:26 += │ +- 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; +- │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions ++ 47 │ assert!(use_1 == found_f(addr)); ++ │ ^^^^^^^^^^^^^ += +- error[E01013]: unsupported language construct +- ┌─ TEMPFILE1:45:55 +- │ +- 45 │ assert!(0x42::mod2::item_exists<|address|bool with store+copy>(addr)); +- │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += +- error[E01013]: unsupported language construct +- ┌─ TEMPFILE1:46:58 +- │ +- 46 │ let found_f = 0x42::mod2::get_item<|address|bool with store+copy>(addr); +- │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += +- +- += task 2 'run'. lines 69-69: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += += task 3 'run'. lines 71-71: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.move b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.move new file mode 100644 index 0000000000000..b7d3baf9f610a --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.move @@ -0,0 +1,71 @@ +//# publish --print-bytecode +module 0x42::mod2 { + struct Registry has key, store { + func: F + } + + public fun save_item(owner: &signer, f: F) { + move_to>(owner, Registry { func: f }); + } + + public fun item_exists(addr: address): bool { + exists>(addr) + } + + public fun get_item(addr: address): F acquires Registry { + borrow_global>(addr).func + } +} + +//# publish --print-bytecode +module 0x42::mod3 { + use std::signer; + + struct MyStruct1 has key, store, copy { + x: u64 + } + + struct MyStruct2 has key, store, copy { + y: u8 + } + + public fun test_items(owner: signer, use_1: bool) { + let struct1 = MyStruct1 { x: 3 }; + // let struct2 = MyStruct2 { y: 2 }; + + let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; + + let addr = signer::address_of(&owner); + 0x42::mod2::save_item(&owner, struct1); + + // Store just MyStruct1 + move_to(&owner, struct1); + + // Store f1 or f2, depending on use_1 + if (use_1) { + 0x42::mod2::save_item(&owner, f1); + } else { + 0x42::mod2::save_item(&owner, f2); + }; + + // In either case, item exists + assert!(0x42::mod2::item_exists<|address|bool with store+copy>(addr)); + + let found_f = 0x42::mod2::get_item<|address|bool with store+copy>(addr); + + assert!(use_1 == found_f(addr)); + } + + public fun test_item1(owner: signer) { + test_items(owner, true); + } + + public fun test_item2(owner: signer) { + test_items(owner, false); + } +} + +//# run --signers 0x42 -- 0x42::mod3::test_item1 + +//# run --signers 0x42 -- 0x42::mod3::test_item2 diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.no-optimize.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.no-optimize.exp new file mode 100644 index 0000000000000..9ced3f98e15e0 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.no-optimize.exp @@ -0,0 +1,71 @@ +comparison between v1 and v2 failed: += processed 4 tasks += += task 0 'publish'. lines 1-18: += += += += task 1 'publish'. lines 20-67: +- Error: error[E01013]: unsupported language construct ++ Error: compilation errors: ++ error: unsupported language construct += ┌─ TEMPFILE1:32:18 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:32:71 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:33:18 += │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:33:71 += │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:45:55 += │ += 45 │ assert!(0x42::mod2::item_exists<|address|bool with store+copy>(addr)); += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:46:58 += │ += 46 │ let found_f = 0x42::mod2::get_item<|address|bool with store+copy>(addr); += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += += += += task 2 'run'. lines 69-69: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += += task 3 'run'. lines 71-71: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.optimize-no-simplify.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.optimize-no-simplify.exp new file mode 100644 index 0000000000000..9ced3f98e15e0 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.optimize-no-simplify.exp @@ -0,0 +1,71 @@ +comparison between v1 and v2 failed: += processed 4 tasks += += task 0 'publish'. lines 1-18: += += += += task 1 'publish'. lines 20-67: +- Error: error[E01013]: unsupported language construct ++ Error: compilation errors: ++ error: unsupported language construct += ┌─ TEMPFILE1:32:18 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:32:71 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:33:18 += │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:33:71 += │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:45:55 += │ += 45 │ assert!(0x42::mod2::item_exists<|address|bool with store+copy>(addr)); += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:46:58 += │ += 46 │ let found_f = 0x42::mod2::get_item<|address|bool with store+copy>(addr); += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += += += += task 2 'run'. lines 69-69: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += += task 3 'run'. lines 71-71: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.optimize.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.optimize.exp new file mode 100644 index 0000000000000..9ced3f98e15e0 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/lambda/generic_func.optimize.exp @@ -0,0 +1,71 @@ +comparison between v1 and v2 failed: += processed 4 tasks += += task 0 'publish'. lines 1-18: += += += += task 1 'publish'. lines 20-67: +- Error: error[E01013]: unsupported language construct ++ Error: compilation errors: ++ error: unsupported language construct += ┌─ TEMPFILE1:32:18 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:32:71 += │ += 32 │ let f1 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:33:18 += │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^ Move 2.2 language construct is not enabled: Modifier on lambda expression += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:33:71 += │ += 33 │ let f2 = move |addr| 0x42::mod2::item_exists(addr) with store+copy; += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Abilities on function expressions += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:45:55 += │ += 45 │ assert!(0x42::mod2::item_exists<|address|bool with store+copy>(addr)); += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += +- error[E01013]: unsupported language construct ++ error: unsupported language construct += ┌─ TEMPFILE1:46:58 += │ += 46 │ let found_f = 0x42::mod2::get_item<|address|bool with store+copy>(addr); += │ ^^^^^^^^^^^^^^^ Move 2.2 language construct is not enabled: Ability constraints on function types += += += += task 2 'run'. lines 69-69: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += += task 3 'run'. lines 71-71: += Error: Function execution failed with VMError: { += major_status: LINKER_ERROR, += sub_status: None, += location: undefined, += indices: redacted, += offsets: redacted, += } += diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/misc/bug_14243_stack_size.baseline.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/misc/bug_14243_stack_size.baseline.exp new file mode 100644 index 0000000000000..5644195ea665d --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/misc/bug_14243_stack_size.baseline.exp @@ -0,0 +1,32 @@ +processed 1 task + +task 0 'print-bytecode'. lines 2-14: +// Move bytecode v7 +module c0ffee.m { + + +id_mut(Arg0: &mut Ty0): &mut Ty0 /* def_idx: 0 */ { +B0: + 0: MoveLoc[0](Arg0: &mut Ty0) + 1: Ret +} +t0() /* def_idx: 1 */ { +L0: loc0: u64 +L1: loc1: &mut u64 +B0: + 0: LdU64(0) + 1: StLoc[0](loc0: u64) + 2: MutBorrowLoc[0](loc0: u64) + 3: StLoc[1](loc1: &mut u64) + 4: CopyLoc[1](loc1: &mut u64) + 5: Call id_mut(&mut u64): &mut u64 + 6: ReadRef + 7: Pop + 8: MoveLoc[1](loc1: &mut u64) + 9: ReadRef + 10: Pop + 11: Ret +} +} + +==> Compiler v2 delivered same results! diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.no-optimize-no-acquires-check.exp similarity index 100% rename from third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.exp rename to third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.no-optimize-no-acquires-check.exp diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.optimize-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.optimize-no-acquires-check.exp new file mode 100644 index 0000000000000..45fc0b41c318d --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.optimize-no-acquires-check.exp @@ -0,0 +1,29 @@ +processed 6 tasks + +task 2 'run'. lines 30-30: +return values: true + +task 3 'run'. lines 32-32: +return values: true + +task 4 'run'. lines 34-34: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x2)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 5 'run'. lines 36-36: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x2)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(2), 3)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.optimize-no-simplify-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.optimize-no-simplify-no-acquires-check.exp new file mode 100644 index 0000000000000..45fc0b41c318d --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/dynamic.optimize-no-simplify-no-acquires-check.exp @@ -0,0 +1,29 @@ +processed 6 tasks + +task 2 'run'. lines 30-30: +return values: true + +task 3 'run'. lines 32-32: +return values: true + +task 4 'run'. lines 34-34: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x2)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 5 'run'. lines 36-36: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x2)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(2), 3)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.no-optimize-no-acquires-check.exp similarity index 100% rename from third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.exp rename to third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.no-optimize-no-acquires-check.exp diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.optimize-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.optimize-no-acquires-check.exp new file mode 100644 index 0000000000000..041671d6c1dfc --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.optimize-no-acquires-check.exp @@ -0,0 +1,18 @@ +processed 5 tasks + +task 2 'run'. lines 25-25: +return values: true + +task 3 'run'. lines 27-27: +return values: true + +task 4 'run'. lines 29-29: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.optimize-no-simplify-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.optimize-no-simplify-no-acquires-check.exp new file mode 100644 index 0000000000000..041671d6c1dfc --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/generic.optimize-no-simplify-no-acquires-check.exp @@ -0,0 +1,18 @@ +processed 5 tasks + +task 2 'run'. lines 25-25: +return values: true + +task 3 'run'. lines 27-27: +return values: true + +task 4 'run'. lines 29-29: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.no-optimize-no-acquires-check.exp similarity index 100% rename from third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.exp rename to third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.no-optimize-no-acquires-check.exp diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.optimize-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.optimize-no-acquires-check.exp new file mode 100644 index 0000000000000..83fdf74d58abe --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.optimize-no-acquires-check.exp @@ -0,0 +1,29 @@ +processed 6 tasks + +task 2 'run'. lines 28-28: +return values: true + +task 3 'run'. lines 30-30: +return values: true + +task 4 'run'. lines 32-32: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 5 'run'. lines 34-34: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(2), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.optimize-no-simplify-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.optimize-no-simplify-no-acquires-check.exp new file mode 100644 index 0000000000000..83fdf74d58abe --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/negation.optimize-no-simplify-no-acquires-check.exp @@ -0,0 +1,29 @@ +processed 6 tasks + +task 2 'run'. lines 28-28: +return values: true + +task 3 'run'. lines 30-30: +return values: true + +task 4 'run'. lines 32-32: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 5 'run'. lines 34-34: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(2), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.no-optimize-no-acquires-check.exp similarity index 100% rename from third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.exp rename to third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.no-optimize-no-acquires-check.exp diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.optimize-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.optimize-no-acquires-check.exp new file mode 100644 index 0000000000000..ab23755203d71 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.optimize-no-acquires-check.exp @@ -0,0 +1,71 @@ +processed 12 tasks + +task 2 'run'. lines 63-63: +return values: true + +task 3 'run'. lines 65-65: +return values: true + +task 4 'run'. lines 67-67: +return values: true + +task 5 'run'. lines 69-69: +return values: true + +task 6 'run'. lines 71-71: +return values: true + +task 7 'run'. lines 73-73: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 8 'run'. lines 75-75: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(2), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 9 'run'. lines 77-77: +Error: Function execution failed with VMError: { + message: not allowed to perform `writes 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(3), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 10 'run'. lines 79-79: +Error: Function execution failed with VMError: { + message: not allowed to perform `writes 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(4), 2)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 11 'run'. lines 81-81: +Error: Function execution failed with VMError: { + message: not allowed to call `0x0000000000000000000000000000000000000000000000000000000000000042::test::fail_no_subsumes`, + major_status: ACCESS_DENIED, + sub_status: None, + location: undefined, + indices: [], + offsets: [], + exec_state: None, +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.optimize-no-simplify-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.optimize-no-simplify-no-acquires-check.exp new file mode 100644 index 0000000000000..ab23755203d71 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/resource.optimize-no-simplify-no-acquires-check.exp @@ -0,0 +1,71 @@ +processed 12 tasks + +task 2 'run'. lines 63-63: +return values: true + +task 3 'run'. lines 65-65: +return values: true + +task 4 'run'. lines 67-67: +return values: true + +task 5 'run'. lines 69-69: +return values: true + +task 6 'run'. lines 71-71: +return values: true + +task 7 'run'. lines 73-73: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 8 'run'. lines 75-75: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(2), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 9 'run'. lines 77-77: +Error: Function execution failed with VMError: { + message: not allowed to perform `writes 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(3), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 10 'run'. lines 79-79: +Error: Function execution failed with VMError: { + message: not allowed to perform `writes 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(4), 2)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 11 'run'. lines 81-81: +Error: Function execution failed with VMError: { + message: not allowed to call `0x0000000000000000000000000000000000000000000000000000000000000042::test::fail_no_subsumes`, + major_status: ACCESS_DENIED, + sub_status: None, + location: undefined, + indices: [], + offsets: [], + exec_state: None, +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.no-optimize-no-acquires-check.exp similarity index 100% rename from third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.exp rename to third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.no-optimize-no-acquires-check.exp diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.optimize-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.optimize-no-acquires-check.exp new file mode 100644 index 0000000000000..aafc7de64f5ef --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.optimize-no-acquires-check.exp @@ -0,0 +1,35 @@ +processed 8 tasks + +task 2 'run'. lines 36-36: +return values: true + +task 3 'run'. lines 38-38: +return values: true + +task 4 'run'. lines 40-40: +return values: true + +task 5 'run'. lines 42-42: +return values: true + +task 6 'run'. lines 44-44: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 7 'run'. lines 46-46: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(2), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.optimize-no-simplify-no-acquires-check.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.optimize-no-simplify-no-acquires-check.exp new file mode 100644 index 0000000000000..aafc7de64f5ef --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/access_control/wildcard.optimize-no-simplify-no-acquires-check.exp @@ -0,0 +1,35 @@ +processed 8 tasks + +task 2 'run'. lines 36-36: +return values: true + +task 3 'run'. lines 38-38: +return values: true + +task 4 'run'. lines 40-40: +return values: true + +task 5 'run'. lines 42-42: +return values: true + +task 6 'run'. lines 44-44: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(1), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} + +task 7 'run'. lines 46-46: +Error: Function execution failed with VMError: { + message: not allowed to perform `reads 0x42::test::R(@0x1)`, + major_status: ACCESS_DENIED, + sub_status: None, + location: 0x42::test, + indices: [], + offsets: [(FunctionDefinitionIndex(2), 1)], + exec_state: Some(ExecutionState { stack_trace: [] }), +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/assert_one.baseline.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/assert_one.baseline.exp new file mode 100644 index 0000000000000..2161a50d2b658 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/assert_one.baseline.exp @@ -0,0 +1,10 @@ +processed 2 tasks + +task 1 'run'. lines 21-34: +Error: Script execution failed with VMError: { + major_status: ABORTED, + sub_status: Some(14566554180833181696), + location: script, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 27)], +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.baseline.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.baseline.exp new file mode 100644 index 0000000000000..bad82805f9bf7 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select.baseline.exp @@ -0,0 +1,19 @@ +processed 5 tasks + +task 1 'run'. lines 37-37: +return values: 43 + +task 2 'run'. lines 39-39: +return values: 43 + +task 3 'run'. lines 41-41: +Error: Function execution failed with VMError: { + major_status: ABORTED, + sub_status: Some(33), + location: 0x42::m, + indices: [], + offsets: [(FunctionDefinitionIndex(0), 10)], +} + +task 4 'run'. lines 43-43: +return values: true diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select_different_offsets.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select_different_offsets.baseline.exp similarity index 100% rename from third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select_different_offsets.exp rename to third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_field_select_different_offsets.baseline.exp diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_scoping.baseline.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_scoping.baseline.exp new file mode 100644 index 0000000000000..4cef654b4343a --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/enum/enum_scoping.baseline.exp @@ -0,0 +1,37 @@ +processed 2 tasks + +task 0 'publish'. lines 1-43: +warning: Unused local variable `x`. Consider removing or prefixing with an underscore: `_x` + ┌─ TEMPFILE:20:17 + │ +20 │ let x = 4; + │ ^ + +warning: Unused local variable `i`. Consider removing or prefixing with an underscore: `_i` + ┌─ TEMPFILE:35:17 + │ +35 │ Two{i, b} => 3, + │ ^ + +warning: Unused local variable `b`. Consider removing or prefixing with an underscore: `_b` + ┌─ TEMPFILE:35:20 + │ +35 │ Two{i, b} => 3, + │ ^ + +warning: Unused assignment to `b`. Consider removing or prefixing with an underscore: `_b` + ┌─ TEMPFILE:35:13 + │ +35 │ Two{i, b} => 3, + │ ^^^^^^^^^ + +warning: Unused assignment to `i`. Consider removing or prefixing with an underscore: `_i` + ┌─ TEMPFILE:35:13 + │ +35 │ Two{i, b} => 3, + │ ^^^^^^^^^ + + + +task 1 'run'. lines 45-45: +return values: 3 diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/index.move b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/index.move index 66093ecfc1e56..310f791fad512 100644 --- a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/index.move +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/index.move @@ -320,7 +320,7 @@ module 0x42::test { assert!(v[0].x == 8, 0); } - fun test_receiver() { + fun test_receiver() acquires S, W { let w = W { x: 3 }; @@ -352,7 +352,7 @@ module 0x42::test { move_to(signer, wrapper); } - fun test_receiver_2() { + fun test_receiver_2() acquires Wrapper { assert!(dispatch(@0x1) == 2, 0); let wrapper = Wrapper { inner: 2 diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/print_bytecode.baseline.exp b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/print_bytecode.baseline.exp new file mode 100644 index 0000000000000..d9856e46d5951 --- /dev/null +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/no-v1-comparison/print_bytecode.baseline.exp @@ -0,0 +1,24 @@ +processed 2 tasks + +task 0 'print-bytecode'. lines 1-4: +// Move bytecode v7 +script { + + +main() /* def_idx: 0 */ { +B0: + 0: Ret +} +} + +task 1 'print-bytecode'. lines 6-11: +// Move bytecode v7 +module 3.N { + + +entry public ex(Arg0: signer, Arg1: u64) /* def_idx: 0 */ { +B0: + 0: LdU64(0) + 1: Abort +} +} diff --git a/third_party/move/move-compiler-v2/transactional-tests/tests/tests.rs b/third_party/move/move-compiler-v2/transactional-tests/tests/tests.rs index 340a2fc584682..0a7436e31bbff 100644 --- a/third_party/move/move-compiler-v2/transactional-tests/tests/tests.rs +++ b/third_party/move/move-compiler-v2/transactional-tests/tests/tests.rs @@ -27,9 +27,8 @@ struct TestConfig { name: &'static str, runner: fn(&Path) -> datatest_stable::Result<()>, experiments: &'static [(&'static str, bool)], - /// Run the tests with language version 1 (if true), - /// or with latest language version (if false). - is_lang_v1: bool, + /// Run the tests with specified language version. + language_version: LanguageVersion, /// Path substrings for tests to include. If empty, all tests are included. include: &'static [&'static str], /// Path substrings for tests to exclude (applied after the include filter). @@ -37,29 +36,39 @@ struct TestConfig { exclude: &'static [&'static str], } +/// Note that any config which has different output for a test directory +/// *must* be added to the `SEPARATE_BASELINE` array below, so that a +/// special output file `test.foo.exp` will be generated for the output +/// of `test.move` for config `foo`. const TEST_CONFIGS: &[TestConfig] = &[ + // Matches all default experiments + TestConfig { + name: "baseline", + runner: |p| run(p, get_config_by_name("baseline")), + experiments: &[], + language_version: LanguageVersion::latest_stable(), + include: &[], + exclude: &["/operator_eval/", "/access_control/"], + }, + // Test optimize/no-optimize/etc., except for `/access_control/` TestConfig { name: "optimize", runner: |p| run(p, get_config_by_name("optimize")), experiments: &[ (Experiment::OPTIMIZE, true), (Experiment::OPTIMIZE_WAITING_FOR_COMPARE_TESTS, true), - (Experiment::ACQUIRES_CHECK, false), ], - is_lang_v1: false, + language_version: LanguageVersion::latest_stable(), include: &[], // all tests except those excluded below - exclude: &["/operator_eval/"], + exclude: &["/operator_eval/", "/access_control/"], }, TestConfig { name: "no-optimize", runner: |p| run(p, get_config_by_name("no-optimize")), - experiments: &[ - (Experiment::OPTIMIZE, false), - (Experiment::ACQUIRES_CHECK, false), - ], - is_lang_v1: false, + experiments: &[(Experiment::OPTIMIZE, false)], + language_version: LanguageVersion::latest_stable(), include: &[], // all tests except those excluded below - exclude: &["/operator_eval/"], + exclude: &["/operator_eval/", "/access_control/"], }, TestConfig { name: "optimize-no-simplify", @@ -68,17 +77,17 @@ const TEST_CONFIGS: &[TestConfig] = &[ (Experiment::OPTIMIZE, true), (Experiment::OPTIMIZE_WAITING_FOR_COMPARE_TESTS, true), (Experiment::AST_SIMPLIFY, false), - (Experiment::ACQUIRES_CHECK, false), ], - is_lang_v1: false, + language_version: LanguageVersion::latest_stable(), include: &[], // all tests except those excluded below - exclude: &["/operator_eval/"], + exclude: &["/operator_eval/", "/access_control/"], }, + // Test `/operator_eval/` with language version 1 and 2 TestConfig { name: "operator-eval-lang-1", runner: |p| run(p, get_config_by_name("operator-eval-lang-1")), experiments: &[(Experiment::OPTIMIZE, true)], - is_lang_v1: true, + language_version: LanguageVersion::V1, include: &["/operator_eval/"], exclude: &[], }, @@ -86,14 +95,78 @@ const TEST_CONFIGS: &[TestConfig] = &[ name: "operator-eval-lang-2", runner: |p| run(p, get_config_by_name("operator-eval-lang-2")), experiments: &[(Experiment::OPTIMIZE, true)], - is_lang_v1: false, + language_version: LanguageVersion::latest_stable(), include: &["/operator_eval/"], exclude: &[], }, + // Test `/lambda/` with lambdas enabled + TestConfig { + name: "lambda", + runner: |p| run(p, get_config_by_name("lambda")), + experiments: &[ + (Experiment::OPTIMIZE, true), + (Experiment::LAMBDA_FIELDS, true), + (Experiment::LAMBDA_IN_PARAMS, true), + (Experiment::LAMBDA_IN_RETURNS, true), + (Experiment::LAMBDA_VALUES, true), + (Experiment::LAMBDA_LIFTING, true), + ], + language_version: LanguageVersion::V2_2, + include: &["/lambda/"], + exclude: &[], + }, + // Test optimize/no-optimize/etc., just for `/access_control/`, which + // needs to disable `ACQUIRES_CHECK`. + TestConfig { + name: "optimize-no-acquires-check", + runner: |p| run(p, get_config_by_name("optimize-no-acquires-check")), + experiments: &[ + (Experiment::OPTIMIZE, true), + (Experiment::OPTIMIZE_WAITING_FOR_COMPARE_TESTS, true), + (Experiment::ACQUIRES_CHECK, false), + ], + language_version: LanguageVersion::latest_stable(), + include: &["/access_control/"], + exclude: &[], + }, + TestConfig { + name: "no-optimize-no-acquires-check", + runner: |p| run(p, get_config_by_name("no-optimize-no-acquires-check")), + experiments: &[ + (Experiment::OPTIMIZE, false), + (Experiment::ACQUIRES_CHECK, false), + ], + language_version: LanguageVersion::latest_stable(), + include: &["/access_control/"], + exclude: &[], + }, + TestConfig { + name: "optimize-no-simplify-no-acquires-check", + runner: |p| { + run( + p, + get_config_by_name("optimize-no-simplify-no-acquires-check"), + ) + }, + experiments: &[ + (Experiment::OPTIMIZE, true), + (Experiment::OPTIMIZE_WAITING_FOR_COMPARE_TESTS, true), + (Experiment::AST_SIMPLIFY, false), + (Experiment::ACQUIRES_CHECK, false), + ], + language_version: LanguageVersion::latest_stable(), + include: &["/access_control/"], + exclude: &[], + }, ]; /// Test files which must use separate baselines because their result /// is different. +/// +/// Note that each config named "foo" above will compare the output of compiling `test.move` with +/// the same baseline file `test.exp` *unless* there is an entry in this array matching the path of +// `test.move`. If there is such an entry, then each config "foo" will have a +/// separate baseline output file `test.foo.exp`. const SEPARATE_BASELINE: &[&str] = &[ // Runs into too-many-locals or stack overflow if not optimized "inlining/deep_exp.move", @@ -109,6 +182,10 @@ const SEPARATE_BASELINE: &[&str] = &[ "no-v1-comparison/assert_one.move", // Flaky redundant unused assignment error "no-v1-comparison/enum/enum_scoping.move", + // Needs LAMBDA features and V2.2+ to function; baseline checks expected errors + "/lambda/", + // Needs ACQUIRES_CHECK disabled to function; baseline checks expected errors + "/access_control/", ]; fn get_config_by_name(name: &str) -> TestConfig { @@ -136,11 +213,7 @@ fn run(path: &Path, config: TestConfig) -> datatest_stable::Result<()> { // Enable access control file format generation for those tests v2_experiments.push((Experiment::GEN_ACCESS_SPECIFIERS.to_string(), true)) } - let language_version = if config.is_lang_v1 { - LanguageVersion::V1 - } else { - LanguageVersion::latest_stable() - }; + let language_version = config.language_version; let vm_test_config = if p.contains(SKIP_V1_COMPARISON_PATH) || move_test_debug() { TestRunConfig::CompilerV2 { language_version, diff --git a/third_party/move/move-model/src/lib.rs b/third_party/move/move-model/src/lib.rs index 737a5a847021d..d16720ecf4642 100644 --- a/third_party/move/move-model/src/lib.rs +++ b/third_party/move/move-model/src/lib.rs @@ -305,13 +305,25 @@ pub fn run_model_builder_with_options_and_compilation_flags< }, }; - // Extract the module/script closure + // Extract the module/script dependency closure let mut visited_modules = BTreeSet::new(); + // Extract the module dependency closure for the vector module + let mut vector_and_its_dependencies = BTreeSet::new(); + let mut seen_vector = false; for (_, mident, mdef) in &expansion_ast.modules { let src_file_hash = mdef.loc.file_hash(); if !dep_files.contains(&src_file_hash) { collect_related_modules_recursive(mident, &expansion_ast.modules, &mut visited_modules); } + if !seen_vector && is_vector(*mident) { + seen_vector = true; + // Collect the vector module and its dependencies. + collect_related_modules_recursive( + mident, + &expansion_ast.modules, + &mut vector_and_its_dependencies, + ); + } } for sdef in expansion_ast.scripts.values() { let src_file_hash = sdef.loc.file_hash(); @@ -330,14 +342,13 @@ pub fn run_model_builder_with_options_and_compilation_flags< let mut expansion_ast = { let E::Program { modules, scripts } = expansion_ast; let modules = modules.filter_map(|mident, mut mdef| { - // We need to always include the `vector` module (only for compiler v2), + // For compiler v2, we need to always include the `vector` module and any of its dependencies, // to handle cases of implicit usage. // E.g., index operation on a vector results in a call to `vector::borrow`. // TODO(#15483): consider refactoring code to avoid this special case. - let is_vector = mident.value.address.into_addr_bytes().into_inner() - == AccountAddress::ONE - && mident.value.module.0.value.as_str() == "vector"; - (is_vector && compile_via_model || visited_modules.contains(&mident.value)).then(|| { + ((compile_via_model && vector_and_its_dependencies.contains(&mident.value)) + || visited_modules.contains(&mident.value)) + .then(|| { mdef.is_source_module = true; mdef }) @@ -416,6 +427,12 @@ pub fn run_model_builder_with_options_and_compilation_flags< } } +/// Is `module_ident` the `vector` module? +fn is_vector(module_ident: ModuleIdent_) -> bool { + module_ident.address.into_addr_bytes().into_inner() == AccountAddress::ONE + && module_ident.module.0.value.as_str() == "vector" +} + fn run_move_checker(env: &mut GlobalEnv, program: E::Program) { let mut builder = ModelBuilder::new(env); for (module_count, (module_id, module_def)) in program diff --git a/third_party/move/move-model/src/metadata.rs b/third_party/move/move-model/src/metadata.rs index a26a67ffef4f1..be8a83e5f84fb 100644 --- a/third_party/move/move-model/src/metadata.rs +++ b/third_party/move/move-model/src/metadata.rs @@ -17,8 +17,10 @@ use std::{ }; const UNSTABLE_MARKER: &str = "-unstable"; -pub const LATEST_STABLE_LANGUAGE_VERSION: &str = "2.1"; -pub const LATEST_STABLE_COMPILER_VERSION: &str = "2.0"; +pub const LATEST_STABLE_LANGUAGE_VERSION_VALUE: LanguageVersion = LanguageVersion::V2_1; +pub const LATEST_STABLE_COMPILER_VERSION_VALUE: CompilerVersion = CompilerVersion::V2_0; +pub const LATEST_STABLE_LANGUAGE_VERSION: &str = LATEST_STABLE_LANGUAGE_VERSION_VALUE.to_str(); +pub const LATEST_STABLE_COMPILER_VERSION: &str = LATEST_STABLE_COMPILER_VERSION_VALUE.to_str(); pub static COMPILATION_METADATA_KEY: &[u8] = "compilation_metadata".as_bytes(); @@ -148,13 +150,13 @@ impl CompilerVersion { } /// The latest compiler version. - pub fn latest() -> Self { + pub const fn latest() -> Self { CompilerVersion::V2_1 } /// The latest stable compiler version. - pub fn latest_stable() -> Self { - CompilerVersion::from_str(LATEST_STABLE_COMPILER_VERSION).expect("valid version") + pub const fn latest_stable() -> Self { + LATEST_STABLE_COMPILER_VERSION_VALUE } /// Check whether the compiler version supports the given language version, @@ -180,6 +182,14 @@ impl CompilerVersion { LanguageVersion::latest_stable() } } + + pub const fn to_str(&self) -> &'static str { + match self { + CompilerVersion::V1 => "1", + CompilerVersion::V2_0 => "2.0", + CompilerVersion::V2_1 => "2.1", + } + } } // ================================================================================' @@ -257,7 +267,7 @@ impl From for CompilerLanguageVersion { impl LanguageVersion { /// Whether the language version is unstable. An unstable version /// should not be allowed on production networks. - pub fn unstable(self) -> bool { + pub const fn unstable(self) -> bool { use LanguageVersion::*; match self { V1 | V2_0 | V2_1 => false, @@ -266,13 +276,13 @@ impl LanguageVersion { } /// The latest language version. - pub fn latest() -> Self { + pub const fn latest() -> Self { LanguageVersion::V2_2 } /// The latest stable language version. - pub fn latest_stable() -> Self { - LanguageVersion::from_str(LATEST_STABLE_LANGUAGE_VERSION).expect("valid version") + pub const fn latest_stable() -> Self { + LATEST_STABLE_LANGUAGE_VERSION_VALUE } /// Whether the language version is equal to greater than `ver` @@ -290,6 +300,15 @@ impl LanguageVersion { LanguageVersion::V2_2 => VERSION_DEFAULT_LANG_V2, // Update once we have v8 bytecode }) } + + pub const fn to_str(&self) -> &'static str { + match self { + LanguageVersion::V1 => "1", + LanguageVersion::V2_0 => "2.0", + LanguageVersion::V2_1 => "2.1", + LanguageVersion::V2_2 => "2.2", + } + } } impl Display for LanguageVersion { diff --git a/third_party/move/move-model/src/model.rs b/third_party/move/move-model/src/model.rs index 446834865c0b3..175923e0dade2 100644 --- a/third_party/move/move-model/src/model.rs +++ b/third_party/move/move-model/src/model.rs @@ -700,6 +700,11 @@ impl GlobalEnv { self.address_alias_map = map } + /// Gets the global address alias map + pub fn get_address_alias_map(&self) -> &BTreeMap { + &self.address_alias_map + } + /// Indicates that all modules in the environment should be treated as /// target modules, i.e. `module.is_target()` returns true. This can be /// used to temporarily override the default which distinguishes diff --git a/third_party/move/move-prover/boogie-backend/src/bytecode_translator.rs b/third_party/move/move-prover/boogie-backend/src/bytecode_translator.rs index 350e912f8f284..5ebb919eff17f 100644 --- a/third_party/move/move-prover/boogie-backend/src/bytecode_translator.rs +++ b/third_party/move/move-prover/boogie-backend/src/bytecode_translator.rs @@ -31,7 +31,10 @@ use move_model::{ ast::{Attribute, TempIndex, TraceKind}, code_writer::CodeWriter, emit, emitln, - model::{FieldEnv, FieldId, GlobalEnv, Loc, NodeId, QualifiedInstId, StructEnv, StructId}, + model::{ + FieldEnv, FieldId, FunctionEnv, GlobalEnv, Loc, NodeId, QualifiedInstId, StructEnv, + StructId, + }, pragmas::{ ADDITION_OVERFLOW_UNCHECKED_PRAGMA, SEED_PRAGMA, TIMEOUT_PRAGMA, VERIFY_DURATION_ESTIMATE_PRAGMA, @@ -278,6 +281,10 @@ impl<'env> BoogieTranslator<'env> { for (variant, ref fun_target) in self.targets.get_targets(fun_env) { if variant.is_verified() && !self.is_not_verified_timeout(fun_target) { verified_functions_count += 1; + debug!( + "will verify primary function `{}`", + env.display(&module_env.get_id().qualified(fun_target.get_id())) + ); // Always produce a verified functions with an empty instantiation such that // there is at least one top-level entry points for a VC. FunctionTranslator { @@ -289,28 +296,45 @@ impl<'env> BoogieTranslator<'env> { // There maybe more verification targets that needs to be produced as we // defer the instantiation of verified functions to this stage - for type_inst in mono_info - .funs - .get(&(fun_target.func_env.get_qualified_id(), variant)) - .unwrap_or(empty) - { - // Skip the none instantiation (i.e., each type parameter is - // instantiated to itself as a concrete type). This has the same - // effect as `type_inst: &[]` and is already captured above. - let is_none_inst = type_inst.iter().enumerate().all( - |(i, t)| matches!(t, Type::TypeParameter(idx) if *idx == i as u16), - ); - if is_none_inst { - continue; - } + if !self.options.skip_instance_check { + for type_inst in mono_info + .funs + .get(&(fun_target.func_env.get_qualified_id(), variant)) + .unwrap_or(empty) + { + // Skip redundant instantiations. Those are any permutation of + // type parameters. We can simple test this by the same number + // of disjoint type parameters equals the count of type parameters. + // Verifying a disjoint set of type parameters is the same + // as the `&[]` verification already done above. + let type_params_in_inst = type_inst + .iter() + .filter(|t| matches!(t, Type::TypeParameter(_))) + .cloned() + .collect::>(); + if type_params_in_inst.len() == type_inst.len() { + continue; + } - verified_functions_count += 1; - FunctionTranslator { - parent: self, - fun_target, - type_inst, + verified_functions_count += 1; + let tctx = &fun_env.get_type_display_ctx(); + debug!( + "will verify function instantiation `{}`<{}>", + env.display( + &module_env.get_id().qualified(fun_target.get_id()) + ), + type_inst + .iter() + .map(|t| t.display(tctx).to_string()) + .join(", ") + ); + FunctionTranslator { + parent: self, + fun_target, + type_inst, + } + .translate(); } - .translate(); } } else { // This variant is inlined, so translate for all type instantiations. @@ -1464,80 +1488,13 @@ impl<'env> FunctionTranslator<'env> { ) .join(","); - // special casing for type reflection - let mut processed = false; - - // TODO(mengxu): change it to a better address name instead of extlib - if env.get_extlib_address() == *module_env.get_name().addr() { - let qualified_name = format!( - "{}::{}", - module_env.get_name().name().display(env.symbol_pool()), - callee_env.get_name().display(env.symbol_pool()), - ); - if qualified_name == TYPE_NAME_MOVE { - assert_eq!(inst.len(), 1); - if dest_str.is_empty() { - emitln!( - writer, - "{}", - boogie_reflection_type_name(env, &inst[0], false) - ); - } else { - emitln!( - writer, - "{} := {};", - dest_str, - boogie_reflection_type_name(env, &inst[0], false) - ); - } - processed = true; - } else if qualified_name == TYPE_INFO_MOVE { - assert_eq!(inst.len(), 1); - let (flag, info) = boogie_reflection_type_info(env, &inst[0]); - emitln!(writer, "if (!{}) {{", flag); - writer.with_indent(|| emitln!(writer, "call $ExecFailureAbort();")); - emitln!(writer, "}"); - if !dest_str.is_empty() { - emitln!(writer, "else {"); - writer.with_indent(|| { - emitln!(writer, "{} := {};", dest_str, info) - }); - emitln!(writer, "}"); - } - processed = true; - } - } - - if env.get_stdlib_address() == *module_env.get_name().addr() { - let qualified_name = format!( - "{}::{}", - module_env.get_name().name().display(env.symbol_pool()), - callee_env.get_name().display(env.symbol_pool()), - ); - if qualified_name == TYPE_NAME_GET_MOVE { - assert_eq!(inst.len(), 1); - if dest_str.is_empty() { - emitln!( - writer, - "{}", - boogie_reflection_type_name(env, &inst[0], true) - ); - } else { - emitln!( - writer, - "{} := {};", - dest_str, - boogie_reflection_type_name(env, &inst[0], true) - ); - } - processed = true; - } - } - - // regular path - if !processed { + if self.try_reflection_call(writer, env, inst, &callee_env, &dest_str) { + // Special case of reflection call, code is generated + } else { + // regular path let targeted = self.fun_target.module_env().is_target(); - // If the callee has been generated from a native interface, return an error + // If the callee has been generated from a native interface, return + // an error if callee_env.is_native() && targeted { for attr in callee_env.get_attributes() { if let Attribute::Apply(_, name, _) = attr { @@ -2601,6 +2558,81 @@ impl<'env> FunctionTranslator<'env> { emitln!(writer); } + fn try_reflection_call( + &self, + writer: &CodeWriter, + env: &GlobalEnv, + inst: &[Type], + callee_env: &FunctionEnv, + dest_str: &String, + ) -> bool { + let module_env = &callee_env.module_env; + let mk_qualified_name = || { + format!( + "{}::{}", + module_env.get_name().name().display(env.symbol_pool()), + callee_env.get_name().display(env.symbol_pool()), + ) + }; + if env.get_extlib_address() == *module_env.get_name().addr() { + let qualified_name = mk_qualified_name(); + if qualified_name == TYPE_NAME_MOVE { + assert_eq!(inst.len(), 1); + if dest_str.is_empty() { + emitln!( + writer, + "{}", + boogie_reflection_type_name(env, &inst[0], false) + ); + } else { + emitln!( + writer, + "{} := {};", + dest_str, + boogie_reflection_type_name(env, &inst[0], false) + ); + } + return true; + } else if qualified_name == TYPE_INFO_MOVE { + assert_eq!(inst.len(), 1); + let (flag, info) = boogie_reflection_type_info(env, &inst[0]); + emitln!(writer, "if (!{}) {{", flag); + writer.with_indent(|| emitln!(writer, "call $ExecFailureAbort();")); + emitln!(writer, "}"); + if !dest_str.is_empty() { + emitln!(writer, "else {"); + writer.with_indent(|| emitln!(writer, "{} := {};", dest_str, info)); + emitln!(writer, "}"); + } + return true; + } + } + + if env.get_stdlib_address() == *module_env.get_name().addr() { + let qualified_name = mk_qualified_name(); + if qualified_name == TYPE_NAME_GET_MOVE { + assert_eq!(inst.len(), 1); + if dest_str.is_empty() { + emitln!( + writer, + "{}", + boogie_reflection_type_name(env, &inst[0], true) + ); + } else { + emitln!( + writer, + "{} := {};", + dest_str, + boogie_reflection_type_name(env, &inst[0], true) + ); + } + return true; + } + } + + false + } + fn translate_write_back(&self, dest: &BorrowNode, edge: &BorrowEdge, src: TempIndex) { use BorrowNode::*; let writer = self.parent.writer; diff --git a/third_party/move/move-prover/boogie-backend/src/options.rs b/third_party/move/move-prover/boogie-backend/src/options.rs index 989095b5659f3..ea88b5c113704 100644 --- a/third_party/move/move-prover/boogie-backend/src/options.rs +++ b/third_party/move/move-prover/boogie-backend/src/options.rs @@ -159,6 +159,11 @@ pub struct BoogieOptions { /// A hard timeout for boogie execution; if the process does not terminate within /// this time frame, it will be killed. Zero for no timeout. pub hard_timeout_secs: u64, + /// Whether to skip verification of type instantiations of functions. This may miss + /// some verification conditions if different type instantiations can create + /// different behavior via type reflection or storage access, but can speed up + /// verification. + pub skip_instance_check: bool, /// What vector theory to use. pub vector_theory: VectorTheory, /// Whether to generate a z3 trace file and where to put it. @@ -207,6 +212,7 @@ impl Default for BoogieOptions { custom_natives: None, loop_unroll: None, borrow_aggregates: vec![], + skip_instance_check: false, } } } diff --git a/third_party/move/move-prover/bytecode-pipeline/Cargo.toml b/third_party/move/move-prover/bytecode-pipeline/Cargo.toml index 366973ed14db2..2d41dd22fa8a4 100644 --- a/third_party/move/move-prover/bytecode-pipeline/Cargo.toml +++ b/third_party/move/move-prover/bytecode-pipeline/Cargo.toml @@ -11,7 +11,6 @@ abstract-domain-derive = { path = "../../move-model/bytecode/abstract_domain_der anyhow = { workspace = true } codespan-reporting = { workspace = true } itertools = { workspace = true } -log = { workspace = true, features = ["serde"] } move-binary-format = { workspace = true } move-core-types = { workspace = true } move-model = { workspace = true } diff --git a/third_party/move/move-prover/bytecode-pipeline/src/global_invariant_instrumentation_v2.rs b/third_party/move/move-prover/bytecode-pipeline/src/global_invariant_instrumentation_v2.rs deleted file mode 100644 index a71b9be51b73a..0000000000000 --- a/third_party/move/move-prover/bytecode-pipeline/src/global_invariant_instrumentation_v2.rs +++ /dev/null @@ -1,794 +0,0 @@ -// Copyright (c) The Diem Core Contributors -// Copyright (c) The Move Contributors -// SPDX-License-Identifier: Apache-2.0 - -// Transformation which injects global invariants into the bytecode. - -use crate::{options::ProverOptions, verification_analysis_v2::InvariantAnalysisData}; -use itertools::Itertools; -#[allow(unused_imports)] -use log::{debug, info, log, warn}; -#[cfg(invariant_trace)] -use move_model::ty::TypeDisplayContext; -use move_model::{ - ast::{ConditionKind, Exp, GlobalInvariant}, - exp_generator::ExpGenerator, - model::{FunId, FunctionEnv, GlobalEnv, GlobalId, Loc, QualifiedId, QualifiedInstId, StructId}, - pragmas::CONDITION_ISOLATED_PROP, - spec_translator::{SpecTranslator, TranslatedSpec}, - ty::{NoUnificationContext, Type, TypeUnificationAdapter, Variance}, -}; -use move_stackless_bytecode::{ - function_data_builder::FunctionDataBuilder, - function_target::{FunctionData, FunctionTarget}, - function_target_pipeline::{ - FunctionTargetProcessor, FunctionTargetsHolder, FunctionVariant, VerificationFlavor, - }, - stackless_bytecode::{BorrowNode, Bytecode, Operation, PropKind}, - usage_analysis, -}; -use std::collections::{BTreeMap, BTreeSet}; - -const GLOBAL_INVARIANT_FAILS_MESSAGE: &str = "global memory invariant does not hold"; - -pub struct GlobalInvariantInstrumentationProcessorV2 {} - -impl GlobalInvariantInstrumentationProcessorV2 { - pub fn new() -> Box { - Box::new(Self {}) - } -} - -impl FunctionTargetProcessor for GlobalInvariantInstrumentationProcessorV2 { - fn process( - &self, - targets: &mut FunctionTargetsHolder, - fun_env: &FunctionEnv, - data: FunctionData, - _scc_opt: Option<&[FunctionEnv]>, - ) -> FunctionData { - if fun_env.is_native() || fun_env.is_intrinsic() { - // Nothing to do. - return data; - } - if !data.variant.is_verified() { - // Only need to instrument if this is a verification variant - return data; - } - - // Analyze invariants - let target = FunctionTarget::new(fun_env, &data); - let Analyzer { plain, func_insts } = Analyzer::analyze(&target); - - // Collect information - let env = target.global_env(); - - // Create variants for possible function instantiations - let mut func_variants = vec![]; - for (i, (ty_args, mut global_ids)) in func_insts.into_iter().enumerate() { - let variant_data = data.fork_with_instantiation( - env, - &ty_args, - FunctionVariant::Verification(VerificationFlavor::Instantiated(i)), - ); - global_ids.extend(plain.clone().into_iter()); - func_variants.push((variant_data, global_ids)); - } - - // Instrument the main variant - let main = Instrumenter::run(fun_env, data, plain); - - // Instrument the variants representing different instantiations - for (variant_data, variant_global_invariants) in func_variants { - let variant = Instrumenter::run(fun_env, variant_data, variant_global_invariants); - targets.insert_target_data( - &fun_env.get_qualified_id(), - variant.variant.clone(), - variant, - ); - } - - // Return the main variant - main - } - - fn name(&self) -> String { - "global_invariant_instrumenter_v2".to_string() - } -} - -struct Analyzer { - plain: BTreeSet, - func_insts: BTreeMap, BTreeSet>, -} - -impl Analyzer { - pub fn analyze(target: &FunctionTarget) -> Self { - let mut analyzer = Self { - plain: BTreeSet::new(), - func_insts: BTreeMap::new(), - }; - analyzer.collect_related_global_invariants(target); - analyzer - } - - /// Collect global invariants that are read and written by this function - fn collect_related_global_invariants(&mut self, target: &FunctionTarget) { - let env = target.global_env(); - - // get memory (list of structs) read or written by the function target, - // then find all invariants in loaded modules that refer to that memory. - let mut invariants_for_used_memory = BTreeSet::new(); - for mem in usage_analysis::get_memory_usage(target).accessed.all.iter() { - invariants_for_used_memory.extend(env.get_global_invariants_for_memory(mem)); - } - - // filter non-applicable global invariants - for invariant_id in invariants_for_used_memory { - self.check_global_invariant_applicability( - target, - env.get_global_invariant(invariant_id).unwrap(), - ); - } - } - - fn check_global_invariant_applicability( - &mut self, - target: &FunctionTarget, - invariant: &GlobalInvariant, - ) { - // marks whether the invariant will be checked in all instantiations of this function - let mut is_generic = false; - - // collect instantiations of this function that are needed to check this global invariant - let mut func_insts = BTreeSet::new(); - - let fun_mems = &usage_analysis::get_memory_usage(target).accessed.all; - let fun_arity = target.get_type_parameter_count(); - for inv_mem in &invariant.mem_usage { - for fun_mem in fun_mems.iter() { - if inv_mem.module_id != fun_mem.module_id || inv_mem.id != fun_mem.id { - continue; - } - let adapter = - TypeUnificationAdapter::new_vec(&fun_mem.inst, &inv_mem.inst, true, true); - let rel = adapter.unify( - &mut NoUnificationContext, - Variance::SpecVariance, - /* shallow_subst */ false, - ); - match rel { - None => continue, - Some((subs_fun, _)) => { - if subs_fun.is_empty() { - is_generic = true; - } else { - func_insts.insert(Type::type_param_map_to_inst(fun_arity, subs_fun)); - } - }, - } - } - } - - // save the instantiation required to evaluate this invariant - for inst in func_insts { - self.func_insts - .entry(inst) - .or_default() - .insert(invariant.id); - } - if is_generic { - self.plain.insert(invariant.id); - } - } -} - -struct Instrumenter<'a> { - options: &'a ProverOptions, - builder: FunctionDataBuilder<'a>, - _function_inst: Vec, - used_memory: BTreeSet>, - // Invariants that unify with the state used in a function instantiation - related_invariants: BTreeSet, - saved_from_before_instr_or_call: Option<(TranslatedSpec, BTreeSet)>, -} - -impl<'a> Instrumenter<'a> { - fn run( - fun_env: &FunctionEnv<'a>, - data: FunctionData, - related_invariants: BTreeSet, - ) -> FunctionData { - if !data.variant.is_verified() { - // Run the instrumentation only if this is a verification variant. - return data; - } - - let global_env = fun_env.module_env.env; - let options = ProverOptions::get(global_env); - let function_inst = data.get_type_instantiation(fun_env); - let builder = FunctionDataBuilder::new(fun_env, data); - let used_memory: BTreeSet<_> = usage_analysis::get_memory_usage(&builder.get_target()) - .accessed - .get_all_inst(&function_inst); - - #[cfg(invariant_trace)] - { - let tctx = TypeDisplayContext::WithEnv { - env: global_env, - type_param_names: None, - }; - println!( - "{}<{}>: {}", - builder.data.variant, - function_inst - .iter() - .map(|t| t.display(&tctx).to_string()) - .join(","), - used_memory - .iter() - .map(|m| global_env.display(m).to_string()) - .join(",") - ); - } - - let mut instrumenter = Instrumenter { - options: options.as_ref(), - builder, - _function_inst: function_inst, - used_memory, - related_invariants, - saved_from_before_instr_or_call: None, - }; - instrumenter.instrument(global_env); - instrumenter.builder.data - } - - fn instrument(&mut self, global_env: &GlobalEnv) { - // Collect information - let fun_env = self.builder.fun_env; - let fun_id = fun_env.get_qualified_id(); - - let inv_ana_data = global_env.get_extension::().unwrap(); - let disabled_inv_fun_set = &inv_ana_data.disabled_inv_fun_set; - let non_inv_fun_set = &inv_ana_data.non_inv_fun_set; - let target_invariants = &inv_ana_data.target_invariants; - let disabled_invs_for_fun = &inv_ana_data.disabled_invs_for_fun; - - // Extract and clear current code - let old_code = std::mem::take(&mut self.builder.data.code); - - // Emit entrypoint assumptions - let mut entrypoint_invariants = self.filter_entrypoint_invariants(&self.related_invariants); - if non_inv_fun_set.contains(&fun_id) { - if let Some(invs_disabled) = disabled_invs_for_fun.get(&fun_id) { - entrypoint_invariants = entrypoint_invariants - .difference(invs_disabled) - .cloned() - .collect(); - } - } - let xlated_spec = self.translate_invariants(&entrypoint_invariants); - self.assert_or_assume_translated_invariants( - &xlated_spec.invariants, - &entrypoint_invariants, - PropKind::Assume, - ); - - // In addition to the entrypoint invariants assumed just above, it is necessary - // to assume more invariants in a special case. When invariants are disabled in - // this function but not in callers, we will later assert those invariants just - // before return instructions. - // We need to assume those invariants at the beginning of the function in order - // to prove them later. They aren't necessarily entrypoint invariants if we are - // verifying a function in a strict dependency, or in a friend module that does not - // have the target module in its dependencies. - // So, the next code finds the set of target invariants (which will be assumed on return) - // and assumes those that are not entrypoint invariants. - if disabled_inv_fun_set.contains(&fun_id) { - // Separate the update invariants, because we never want to assume them. - let (global_target_invs, _update_target_invs) = - self.separate_update_invariants(target_invariants); - let return_invariants: BTreeSet<_> = global_target_invs - .difference(&entrypoint_invariants) - .cloned() - .collect(); - let xlated_spec = self.translate_invariants(&return_invariants); - self.assert_or_assume_translated_invariants( - &xlated_spec.invariants, - &return_invariants, - PropKind::Assume, - ); - } - - // Generate new instrumented code. - for bc in old_code { - self.instrument_bytecode(bc, fun_id, &inv_ana_data, &entrypoint_invariants); - } - } - - /// Returns list of invariant ids to be assumed at the beginning of the current function. - fn filter_entrypoint_invariants( - &self, - related_invariants: &BTreeSet, - ) -> BTreeSet { - // Emit an assume of each invariant over memory touched by this function. - // Such invariants include - // - invariants declared in this module, or - // - invariants declared in transitively dependent modules - // - // Excludes global invariants that - // - are marked by the user explicitly as `[isolated]`, or - // - are not declared in dependent modules of the module defining the - // function (which may not be the target module) and upon which the - // code should therefore not depend, apart from the update itself, or - // - are "update" invariants. - - // Adds back target invariants that are modified (directly or indirectly) in the function. - - let env = self.builder.global_env(); - let module_env = &self.builder.fun_env.module_env; - - related_invariants - .iter() - .filter_map(|id| { - env.get_global_invariant(*id).filter(|inv| { - // excludes "update invariants" - matches!(inv.kind, ConditionKind::GlobalInvariant(..)) - && module_env.is_transitive_dependency(inv.declaring_module) - && !module_env - .env - .is_property_true(&inv.properties, CONDITION_ISOLATED_PROP) - .unwrap_or(false) - }) - }) - .map(|inv| inv.id) - .collect() - } - - fn instrument_bytecode( - &mut self, - bc: Bytecode, - fun_id: QualifiedId, - inv_ana_data: &InvariantAnalysisData, - entrypoint_invariants: &BTreeSet, - ) { - use BorrowNode::*; - use Bytecode::*; - use Operation::*; - let target_invariants = &inv_ana_data.target_invariants; - let disabled_inv_fun_set = &inv_ana_data.disabled_inv_fun_set; - let always_check_invs: BTreeSet; - if let Some(disabled_invs) = &inv_ana_data.disabled_invs_for_fun.get(&fun_id) { - always_check_invs = entrypoint_invariants - .difference(disabled_invs) - .cloned() - .collect(); - } else { - always_check_invs = entrypoint_invariants.clone(); - } - - match &bc { - Call(_, _, WriteBack(GlobalRoot(mem), ..), ..) => { - self.emit_invariants_for_bytecode( - &bc, - &fun_id, - inv_ana_data, - mem, - &always_check_invs, - ); - }, - Call(_, _, MoveTo(mid, sid, inst), ..) | Call(_, _, MoveFrom(mid, sid, inst), ..) => { - let mem = mid.qualified_inst(*sid, inst.to_owned()); - self.emit_invariants_for_bytecode( - &bc, - &fun_id, - inv_ana_data, - &mem, - &always_check_invs, - ); - }, - // Emit assumes before procedure calls. This also deals with saves for update invariants. - Call(_, _, OpaqueCallBegin(module_id, id, _), _, _) => { - self.assume_invariants_for_opaque_begin( - module_id.qualified(*id), - entrypoint_invariants, - inv_ana_data, - ); - // Then emit the call instruction. - self.builder.emit(bc); - }, - // Emit asserts after procedure calls - Call(_, _, OpaqueCallEnd(module_id, id, _), _, _) => { - // First, emit the call instruction. - self.builder.emit(bc.clone()); - self.assert_invariants_for_opaque_end(module_id.qualified(*id), inv_ana_data) - }, - // An inline call needs to be treated similarly (but there is just one instruction. - Call(_, _, Function(module_id, id, _), _, _) => { - self.assume_invariants_for_opaque_begin( - module_id.qualified(*id), - entrypoint_invariants, - inv_ana_data, - ); - // Then emit the call instruction. - self.builder.emit(bc.clone()); - self.assert_invariants_for_opaque_end(module_id.qualified(*id), inv_ana_data) - }, - // When invariants are disabled in the body of this function but not in its - // callers, assert them just before a return instruction (the caller will be - // assuming they hold). - Ret(_, _) => { - if disabled_inv_fun_set.contains(&fun_id) { - // TODO: It is only necessary to assert invariants that were disabled here. - // Asserting more won't hurt, but generates unnecessary work for the prover. - let (global_target_invs, _update_target_invs) = - self.separate_update_invariants(target_invariants); - let xlated_spec = self.translate_invariants(&global_target_invs); - self.assert_or_assume_translated_invariants( - &xlated_spec.invariants, - &global_target_invs, - PropKind::Assert, - ); - } - self.builder.emit(bc); - }, - _ => self.builder.emit(bc), - } - } - - /// Emit invariants and saves for call to OpaqueCallBegin in the - /// special case where the invariants are not checked in the - /// called function. - fn assume_invariants_for_opaque_begin( - &mut self, - called_fun_id: QualifiedId, - entrypoint_invariants: &BTreeSet, - inv_ana_data: &InvariantAnalysisData, - ) { - let target_invariants = &inv_ana_data.target_invariants; - let disabled_inv_fun_set = &inv_ana_data.disabled_inv_fun_set; - let non_inv_fun_set = &inv_ana_data.non_inv_fun_set; - let funs_that_modify_inv = &inv_ana_data.funs_that_modify_inv; - // Normally, invariants would be assumed and asserted in - // a called function, and so there would be no need to assume - // the invariant before the call. - // When invariants are not disabled in the current function - // but the called function doesn't check them, we are going to - // need to assert the invariant when the call returns (at the - // matching OpaqueCallEnd instruction). So, we assume the - // invariant here, before the OpaqueCallBegin, so that we have - // a hope of proving it later. - let fun_id = self.builder.fun_env.get_qualified_id(); - if !disabled_inv_fun_set.contains(&fun_id) - && !non_inv_fun_set.contains(&fun_id) - && non_inv_fun_set.contains(&called_fun_id) - { - // Do not assume update invs - // This prevents ASSERTING the updates because emit_assumes_and_saves - // stores translated invariants for assertion in assume_invariants_for_opaque_end, - // and we don't want updates to be asserted there. - // TODO: This should all be refactored to eliminate hacks like the previous - // sentence. - let (global_invs, _update_invs) = self.separate_update_invariants(target_invariants); - // assume the invariants that are modified by the called function - let modified_invs = - self.get_invs_modified_by_fun(&global_invs, called_fun_id, funs_that_modify_inv); - self.emit_assumes_and_saves_before_bytecode(modified_invs, entrypoint_invariants); - } - } - - /// Called when invariants need to be checked, but an opaque called function - /// doesn't check them. - fn assert_invariants_for_opaque_end( - &mut self, - called_fun_id: QualifiedId, - inv_ana_data: &InvariantAnalysisData, - ) { - let disabled_inv_fun_set = &inv_ana_data.disabled_inv_fun_set; - let non_inv_fun_set = &inv_ana_data.non_inv_fun_set; - // Add invariant assertions after function call when invariant holds in the - // body of the current function, but the called function does not assert - // invariants. - // The asserted invariant ensures the invariant - // holds in the body of the current function, as is required. - let fun_id = self.builder.fun_env.get_qualified_id(); - if !disabled_inv_fun_set.contains(&fun_id) - && !non_inv_fun_set.contains(&fun_id) - && non_inv_fun_set.contains(&called_fun_id) - { - self.emit_asserts_after_bytecode(); - } - } - - /// Emit invariant-related assumptions and assertions around a bytecode. - /// Before instruction, emit assumptions of global invariants, if necessary, - /// and emit saves of old state for update invariants. - fn emit_invariants_for_bytecode( - &mut self, - bc: &Bytecode, - fun_id: &QualifiedId, - inv_ana_data: &InvariantAnalysisData, - mem: &QualifiedInstId, - entrypoint_invariants: &BTreeSet, - ) { - // When invariants are enabled during the body of the current function, add asserts after - // the operation for each invariant that the operation could modify. Such an operation - // includes write-backs to a GlobalRoot or MoveTo/MoveFrom a location in the global storage. - let target_invariants = &inv_ana_data.target_invariants; - - let env = self.builder.global_env(); - // consider only the invariants that are modified by instruction - // TODO (IMPORTANT): target_invariants and disabled_invs were not computed with unification, - // so it may be that some invariants are not going to be emitted that should be. - let mut modified_invariants: BTreeSet = env - .get_global_invariants_for_memory(mem) - .intersection(target_invariants) - .copied() - .collect(); - - if let Some(disabled_invs) = &inv_ana_data.disabled_invs_for_fun.get(fun_id) { - modified_invariants = modified_invariants - .difference(disabled_invs) - .cloned() - .collect(); - } - self.emit_assumes_and_saves_before_bytecode(modified_invariants, entrypoint_invariants); - // put out the modifying instruction byte code. - self.builder.emit(bc.clone()); - self.emit_asserts_after_bytecode(); - } - - // emit assumptions for invariants that were not assumed on entry and saves for types that are embedded - // in "old" in update invariants. - // When processing assumes before an opaque instruction, modified_invs contains no update invariants. - fn emit_assumes_and_saves_before_bytecode( - &mut self, - modified_invs: BTreeSet, - entrypoint_invariants: &BTreeSet, - ) { - // translate all the invariants. Some were already translated at the entrypoint, but - // that's ok because entrypoint invariants are global invariants that don't have "old", - // so redundant state tags are not going to be a problem. - let mut xlated_invs = self.translate_invariants(&modified_invs); - - let (global_invs, _update_invs) = self.separate_update_invariants(&modified_invs); - - // remove entrypoint invariants so we don't assume them again here. - let modified_assumes: BTreeSet = global_invs - .difference(entrypoint_invariants) - .cloned() - .collect(); - // assume the global invariants that weren't assumed at entrypoint - self.assert_or_assume_translated_invariants( - &xlated_invs.invariants, - &modified_assumes, - PropKind::Assume, - ); - // emit the instructions to save state in the state tags assigned in the previous step - self.emit_state_saves_for_update_invs(&mut xlated_invs); - // Save the translated invariants for use in asserts after instruction or opaque call end - if self.saved_from_before_instr_or_call.is_none() { - self.saved_from_before_instr_or_call = Some((xlated_invs, modified_invs)); - } else { - panic!("self.saved_from_pre should be None"); - } - } - - fn emit_asserts_after_bytecode(&mut self) { - // assert the global and update invariants that instruction modifies, regardless of where they - // were assumed - if let Some((xlated_invs, modified_invs)) = - std::mem::take(&mut self.saved_from_before_instr_or_call) - { - self.assert_or_assume_translated_invariants( - &xlated_invs.invariants, - &modified_invs, - PropKind::Assert, - ); - } else { - // This should never happen - panic!("saved_from_pre should be Some"); - } - } - - /// Given a set of invariants, return a pair of sets: global invariants and update invariants - fn separate_update_invariants( - &self, - invariants: &BTreeSet, - ) -> (BTreeSet, BTreeSet) { - let global_env = self.builder.fun_env.module_env.env; - let mut global_invs: BTreeSet = BTreeSet::new(); - let mut update_invs: BTreeSet = BTreeSet::new(); - for inv_id in invariants { - let inv = global_env.get_global_invariant(*inv_id).unwrap(); - if matches!(inv.kind, ConditionKind::GlobalInvariantUpdate(..)) { - update_invs.insert(*inv_id); - } else { - global_invs.insert(*inv_id); - } - } - (global_invs, update_invs) - } - - /// Returns the set of invariants modified by a function - fn get_invs_modified_by_fun( - &self, - inv_set: &BTreeSet, - fun_id: QualifiedId, - funs_that_modify_inv: &BTreeMap>>, - ) -> BTreeSet { - let mut modified_inv_set: BTreeSet = BTreeSet::new(); - for inv_id in inv_set { - if let Some(fun_id_set) = funs_that_modify_inv.get(inv_id) { - if fun_id_set.contains(&fun_id) { - modified_inv_set.insert(*inv_id); - } - } - } - modified_inv_set - } - - /// Update invariants contain "old" expressions, so it is necessary to save any types in the - /// state that appear in the old expressions. "update_invs" argument must contain only update - /// invariants (not checked). - fn emit_state_saves_for_update_invs(&mut self, xlated_spec: &mut TranslatedSpec) { - // Emit all necessary state saves - self.builder - .set_next_debug_comment("state save for global update invariants".to_string()); - for (mem, label) in std::mem::take(&mut xlated_spec.saved_memory) { - self.builder - .emit_with(|id| Bytecode::SaveMem(id, label, mem)); - } - for (var, label) in std::mem::take(&mut xlated_spec.saved_spec_vars) { - self.builder - .emit_with(|id| Bytecode::SaveSpecVar(id, label, var)); - } - self.builder.clear_next_debug_comment(); - } - - /// emit asserts or assumes (depending on prop_kind argument) for the invariants in - /// xlated_invariants that is also in inv_set at the current location, - fn assert_or_assume_translated_invariants( - &mut self, - xlated_invariants: &[(Loc, GlobalId, Exp)], - inv_set: &BTreeSet, - prop_kind: PropKind, - ) { - let global_env = self.builder.global_env(); - for (loc, mid, cond) in xlated_invariants { - if inv_set.contains(mid) { - // Check for hard-to-debug coding error (this is not a user error) - if inv_set.contains(mid) - && matches!(prop_kind, PropKind::Assume) - && matches!( - global_env.get_global_invariant(*mid).unwrap().kind, - ConditionKind::GlobalInvariantUpdate(..) - ) - { - panic!("Not allowed to assume update invariant"); - } - self.emit_invariant(loc, cond, prop_kind); - } - } - } - - /// Emit an assert or assume for one invariant, give location and expression for the property - fn emit_invariant(&mut self, loc: &Loc, cond: &Exp, prop_kind: PropKind) { - self.builder.set_next_debug_comment(format!( - "global invariant {}", - loc.display(self.builder.global_env()) - )); - // No error messages on assumes - if prop_kind == PropKind::Assert { - self.builder - .set_loc_and_vc_info(loc.clone(), GLOBAL_INVARIANT_FAILS_MESSAGE); - } - self.builder - .emit_with(|id| Bytecode::Prop(id, prop_kind, cond.clone())); - } - - /// Translate the given set of invariants. This will care for instantiating the - /// invariants in the function context. - fn translate_invariants(&mut self, invs: &BTreeSet) -> TranslatedSpec { - let inst_invs = self.compute_instances_for_invariants(invs); - SpecTranslator::translate_invariants_by_id( - self.options.auto_trace_level.invariants(), - &mut self.builder, - inst_invs.into_iter(), - ) - } - - /// Compute the instantiations which need to be verified for each invariant in the input. - /// This may filter out certain invariants which do not have a valid instantiation. - fn compute_instances_for_invariants( - &self, - invs: &BTreeSet, - ) -> Vec<(GlobalId, Vec)> { - invs.iter() - .flat_map(|id| { - let inv = self.builder.global_env().get_global_invariant(*id).unwrap(); - self.compute_invariant_instances(inv).into_iter() - }) - .collect() - } - - /// Compute the instantiations for the given invariant, by comparing the memory accessed - /// by the invariant with that of the enclosing function. - fn compute_invariant_instances( - &self, - inv: &GlobalInvariant, - ) -> BTreeSet<(GlobalId, Vec)> { - // Determine the type arity of this invariant. If it is 0, we shortcut with just - // returning a single empty instance. - let arity = match &inv.kind { - ConditionKind::GlobalInvariant(ps) | ConditionKind::GlobalInvariantUpdate(ps) => { - ps.len() as u16 - }, - _ => 0, - }; - if arity == 0 { - return vec![(inv.id, vec![])].into_iter().collect(); - } - - // Holds possible instantiations per type parameter - let mut per_type_param_insts = BTreeMap::new(); - - // Pairwise unify memory used by the invariant against memory in the function. - // Notice that the function memory is already instantiated for the function variant - // we are instrumenting. - for inv_mem in &inv.mem_usage { - for fun_mem in &self.used_memory { - let adapter = TypeUnificationAdapter::new_vec( - &fun_mem.inst, - &inv_mem.inst, - /* treat_lhs_type_param_as_var */ false, - /* treat_rhs_type_local_as_var */ true, - ); - #[cfg(invariant_trace)] - println!( - "{} =?= {} for inv {}", - self.builder.global_env().display(fun_mem), - self.builder.global_env().display(inv_mem), - inv.loc.display(self.builder.global_env()) - ); - let rel = adapter.unify( - &mut NoUnificationContext, - Variance::SpecVariance, - /* shallow_subst */ false, - ); - match rel { - None => continue, - Some((_, subst_rhs)) => { - #[cfg(invariant_trace)] - println!("unifies {:?}", subst_rhs); - for (k, v) in subst_rhs { - per_type_param_insts - .entry(k) - .or_insert_with(BTreeSet::new) - .insert(v); - } - }, - } - } - } - - // Check whether all type parameters have at least one instantiation. If not, this - // invariant is not applicable (this corresponds to an unbound TypeLocal in the older - // translation scheme). - // TODO: we should generate a type check error if an invariant has unused, dead - // type parameters because such an invariant can never pass this test. - let mut all_insts = BTreeSet::new(); - if (0..arity).collect::>() == per_type_param_insts.keys().cloned().collect() { - // Compute the cartesian product of all individual type parameter instantiations. - for inst in per_type_param_insts - .values() - .map(|tys| tys.iter().cloned()) - .multi_cartesian_product() - { - all_insts.insert((inv.id, inst)); - } - } - all_insts - } -} diff --git a/third_party/move/move-prover/bytecode-pipeline/src/lib.rs b/third_party/move/move-prover/bytecode-pipeline/src/lib.rs index f8d8d9ad95e08..d80434244951d 100644 --- a/third_party/move/move-prover/bytecode-pipeline/src/lib.rs +++ b/third_party/move/move-prover/bytecode-pipeline/src/lib.rs @@ -6,7 +6,6 @@ pub mod data_invariant_instrumentation; pub mod eliminate_imm_refs; pub mod global_invariant_analysis; pub mod global_invariant_instrumentation; -pub mod global_invariant_instrumentation_v2; pub mod inconsistency_check; pub mod loop_analysis; pub mod memory_instrumentation; @@ -20,5 +19,4 @@ pub mod packed_types_analysis; pub mod pipeline_factory; pub mod spec_instrumentation; pub mod verification_analysis; -pub mod verification_analysis_v2; pub mod well_formed_instrumentation; diff --git a/third_party/move/move-prover/bytecode-pipeline/src/verification_analysis_v2.rs b/third_party/move/move-prover/bytecode-pipeline/src/verification_analysis_v2.rs deleted file mode 100644 index 5808200509499..0000000000000 --- a/third_party/move/move-prover/bytecode-pipeline/src/verification_analysis_v2.rs +++ /dev/null @@ -1,800 +0,0 @@ -// Copyright (c) The Diem Core Contributors -// Copyright (c) The Move Contributors -// SPDX-License-Identifier: Apache-2.0 - -//! Analysis which computes an annotation for each function whether - -use crate::options::ProverOptions; -use itertools::Itertools; -use log::debug; -use move_model::{ - model::{FunId, FunctionEnv, GlobalEnv, GlobalId, ModuleEnv, QualifiedId, VerificationScope}, - pragmas::{ - CONDITION_SUSPENDABLE_PROP, DELEGATE_INVARIANTS_TO_CALLER_PRAGMA, - DISABLE_INVARIANTS_IN_BODY_PRAGMA, VERIFY_PRAGMA, - }, -}; -use move_stackless_bytecode::{ - dataflow_domains::SetDomain, - function_target::{FunctionData, FunctionTarget}, - function_target_pipeline::{FunctionTargetProcessor, FunctionTargetsHolder, FunctionVariant}, - usage_analysis, COMPILED_MODULE_AVAILABLE, -}; -use std::collections::{BTreeMap, BTreeSet, VecDeque}; - -/// The annotation for information about verification. -#[derive(Clone, Default)] -pub struct VerificationInfoV2 { - /// Whether the function is target of verification. - pub verified: bool, - /// Whether the function needs to have an inlined variant since it is called from a verified - /// function and is not opaque. - pub inlined: bool, -} - -/// Get verification information for this function. -pub fn get_info(target: &FunctionTarget<'_>) -> VerificationInfoV2 { - target - .get_annotations() - .get::() - .cloned() - .unwrap_or_default() -} - -// Analysis info to save for global_invariant_instrumentation phase -pub struct InvariantAnalysisData { - /// The set of all functions in target module. - pub target_fun_ids: BTreeSet>, - /// Functions in dependent modules that are transitively called by functions in target module. - pub dep_fun_ids: BTreeSet>, - /// functions where invariants are disabled by pragma disable_invariants_in_body - pub disabled_inv_fun_set: BTreeSet>, - /// Functions where invariants are disabled in a transitive caller, or by - /// pragma delegate_invariant_to_caller - pub non_inv_fun_set: BTreeSet>, - /// global and update invariants in the target module - pub target_invariants: BTreeSet, - /// Maps invariant ID to set of functions that modify the invariant - /// Does not include update invariants - pub funs_that_modify_inv: BTreeMap>>, - /// Maps function to the set of invariants that it modifies - /// Does not include update invariants - pub invs_modified_by_fun: BTreeMap, BTreeSet>, - /// Functions that modify some invariant in the target - /// Does not include update invariants - pub funs_that_modify_some_inv: BTreeSet>, - /// functions that are in non_inv_fun_set and `M[I]` for some `I`. - /// We have to verify the callers, which may be in friend modules. - pub funs_that_delegate_to_caller: BTreeSet>, - /// Functions that are not in target or deps, but that call a function - /// in non_inv_fun_set that modifies some invariant from target module - /// and eventually calls a function in target mod or a dependency. - pub friend_fun_ids: BTreeSet>, - /// For each function, give the set of invariants that are disabled in that function. - /// This is defined as the least set satisfying set inequalities: (1) in a function where - /// invariants are disabled, it is the set of invariants modified in the function, and - /// (2) in a function in non_inv_fun_set, it is the least set that includes all disabled_invs - /// for calling functions. - pub disabled_invs_for_fun: BTreeMap, BTreeSet>, -} - -/// Get all invariants from target modules -fn get_target_invariants( - global_env: &GlobalEnv, - target_modules: &[ModuleEnv], -) -> BTreeSet { - let target_mod_ids = target_modules - .iter() - .map(|mod_env| mod_env.get_id()) - .flat_map(|target_mod_id| global_env.get_global_invariants_by_module(target_mod_id)) - .collect(); - target_mod_ids -} - -/// Computes and returns the set of disabled invariants for each function in disabled_inv_fun_set -/// Disabled invariants for a function are the invariants modified (directly or indirectly) by the fun -/// that are also declared to be suspendable via "invariant [suspendable] ..." -fn compute_disabled_invs_for_fun( - global_env: &GlobalEnv, - disabled_inv_fun_set: &BTreeSet>, - invs_modified_by_fun: &BTreeMap, BTreeSet>, -) -> BTreeMap, BTreeSet> { - let mut disabled_invs_for_fun: BTreeMap, BTreeSet> = - BTreeMap::new(); - for module_env in global_env.get_modules() { - for fun_env in module_env.get_functions() { - let fun_id = fun_env.get_qualified_id(); - // If function disables invariants, get the set of invariants modified in the function - // and keep only those that are declared to be suspendable - if disabled_inv_fun_set.contains(&fun_id) { - if let Some(modified_invs) = invs_modified_by_fun.get(&fun_id) { - let disabled_invs: BTreeSet = modified_invs - .iter() - .filter(|inv_id| { - global_env - .is_property_true( - &global_env - .get_global_invariant(**inv_id) - .unwrap() - .properties, - CONDITION_SUSPENDABLE_PROP, - ) - .unwrap_or(false) - }) - .cloned() - .collect(); - debug_print_inv_set( - global_env, - &disabled_invs, - "$$$$$$$$$$$$$$$$\ncompute_disabled_invs_for_fun", - ); - disabled_invs_for_fun.insert(fun_id, disabled_invs.clone()); - } - } - } - } - - // Compute a least fixed point of disable_invs_for_fun. Starts with disabled inv functions and - // all invariants that they modify. Then propagate those to called functions. They're not top-sorted - // (which may not be good enough for recursion, in this case, I'm not sure). So fun_ids go back - // into the worklist until the disable_inv_set for each fun converges (worklist will be empty). - let mut worklist: VecDeque> = disabled_inv_fun_set.iter().cloned().collect(); - while let Some(caller_fun_id) = worklist.pop_front() { - // If None, it's ok to skip because there are no disabled_invs to propagate to called funs - if let Some(disabled_invs_for_caller) = disabled_invs_for_fun.remove(&caller_fun_id) { - let called_funs = global_env - .get_function(caller_fun_id) - .get_called_functions() - .cloned() - .expect(COMPILED_MODULE_AVAILABLE); - for called_fun_id in called_funs { - let disabled_invs_for_called = - disabled_invs_for_fun.entry(called_fun_id).or_default(); - // if caller has any disabled_invs that callee does not, add them to called - // and add called to the worklist for further processing - if !disabled_invs_for_caller.is_subset(disabled_invs_for_called) { - // Add missing inv_ids to called set - for inv_id in &disabled_invs_for_caller { - disabled_invs_for_called.insert(*inv_id); - } - worklist.push_back(called_fun_id); - } - } - // put back disabled_invs_for_caller - disabled_invs_for_fun.insert(caller_fun_id, disabled_invs_for_caller); - } - } - disabled_invs_for_fun -} - -/// Check whether function is callable from unknown sites (i.e., it is public or -/// a script fun) and modifies some invariant in the target module. -/// The second condition is an exception for functions that cannot invalidate -/// any invariants. -fn check_legal_disabled_invariants( - fun_env: &FunctionEnv, - disabled_inv_fun_set: &BTreeSet>, - non_inv_fun_set: &BTreeSet>, - funs_that_modify_some_inv: &BTreeSet>, -) { - let global_env = fun_env.module_env.env; - let fun_id = fun_env.get_qualified_id(); - if non_inv_fun_set.contains(&fun_id) && funs_that_modify_some_inv.contains(&fun_id) { - if disabled_inv_fun_set.contains(&fun_id) { - global_env.error( - &fun_env.get_loc(), - "Functions must not have a disable invariant pragma when invariants are \ - disabled in a transitive caller or there is a \ - pragma delegate_invariants_to_caller", - ); - } else if fun_env.has_unknown_callers() { - if is_fun_delegating(fun_env) { - global_env.error( - &fun_env.get_loc(), - "Public or script functions cannot delegate invariants", - ) - } else { - global_env.error_with_notes( - &fun_env.get_loc(), - "Public or script functions cannot be transitively called by \ - functions disabling or delegating invariants", - compute_non_inv_cause_chain(fun_env), - ) - } - } - } -} - -/// Compute the chain of calls which leads to an implicit non-inv function. -fn compute_non_inv_cause_chain(fun_env: &FunctionEnv<'_>) -> Vec { - let global_env = fun_env.module_env.env; - let mut worklist: BTreeSet>> = fun_env - .get_calling_functions() - .expect(COMPILED_MODULE_AVAILABLE) - .into_iter() - .map(|id| vec![id]) - .collect(); - let mut done = BTreeSet::new(); - let mut result = vec![]; - while let Some(caller_list) = worklist.iter().next().cloned() { - worklist.remove(&caller_list); - let caller_id = *caller_list.iter().last().unwrap(); - done.insert(caller_id); - let caller_env = global_env.get_function_qid(caller_id); - let display_chain = || { - vec![fun_env.get_qualified_id()] - .into_iter() - .chain(caller_list.iter().cloned()) - .map(|id| global_env.get_function_qid(id).get_full_name_str()) - .join(" <- ") - }; - if is_fun_disabled(&caller_env) { - result.push(format!("disabled by {}", display_chain())); - } else if is_fun_delegating(&caller_env) { - result.push(format!("delegated by {}", display_chain())); - } else { - worklist.extend( - caller_env - .get_calling_functions() - .expect(COMPILED_MODULE_AVAILABLE) - .into_iter() - .filter_map(|id| { - if done.contains(&id) { - None - } else { - let mut new_caller_list = caller_list.clone(); - new_caller_list.push(id); - Some(new_caller_list) - } - }), - ); - } - } - if result.is_empty() { - result.push("cannot determine disabling reason (bug?)".to_owned()) - } - result -} - -// Using pragmas, find functions called from a context where invariant -// checking is disabled. -// disabled_inv_fun set are disabled by pragma -// non_inv_fun_set are disabled by pragma or because they're called from -// another function in disabled_inv_fun_set or non_inv_fun_set. -fn compute_disabled_and_non_inv_fun_sets( - global_env: &GlobalEnv, -) -> (BTreeSet>, BTreeSet>) { - let mut non_inv_fun_set: BTreeSet> = BTreeSet::new(); - let mut disabled_inv_fun_set: BTreeSet> = BTreeSet::new(); - // invariant: If a function is in non_inv_fun_set and not in worklist, - // then all the functions it calls are also in fun_set - // or in worklist. When worklist is empty, all callees of a function - // in non_inv_fun_set will also be in non_inv_fun_set. - // Each function is added at most once to the worklist. - let mut worklist = vec![]; - for module_env in global_env.get_modules() { - for fun_env in module_env.get_functions() { - if is_fun_disabled(&fun_env) { - let fun_id = fun_env.get_qualified_id(); - disabled_inv_fun_set.insert(fun_id); - worklist.push(fun_id); - } - if is_fun_delegating(&fun_env) { - let fun_id = fun_env.get_qualified_id(); - if non_inv_fun_set.insert(fun_id) { - // Add to work_list only if fun_id is not in non_inv_fun_set (may have inferred - // this from a caller already). - worklist.push(fun_id); - } - } - // Downward closure of non_inv_fun_set - while let Some(called_fun_id) = worklist.pop() { - let called_funs = global_env - .get_function(called_fun_id) - .get_called_functions() - .cloned() - .expect(COMPILED_MODULE_AVAILABLE); - for called_fun_id in called_funs { - if non_inv_fun_set.insert(called_fun_id) { - // Add to work_list only if fun_id is not in fun_set - worklist.push(called_fun_id); - } - } - } - } - } - (disabled_inv_fun_set, non_inv_fun_set) -} - -fn is_fun_disabled(fun_env: &FunctionEnv<'_>) -> bool { - fun_env.is_pragma_true(DISABLE_INVARIANTS_IN_BODY_PRAGMA, || false) -} - -fn is_fun_delegating(fun_env: &FunctionEnv<'_>) -> bool { - fun_env.is_pragma_true(DELEGATE_INVARIANTS_TO_CALLER_PRAGMA, || false) -} - -/// Collect all functions that are defined in the target module, or called transitively -/// from those functions. -/// TODO: This is not very efficient. It would be better to compute the transitive closure. -fn compute_dep_fun_ids( - global_env: &GlobalEnv, - target_modules: &[ModuleEnv], -) -> BTreeSet> { - let mut dep_fun_ids = BTreeSet::new(); - for module_env in global_env.get_modules() { - for target_env in target_modules { - if target_env.is_transitive_dependency(module_env.get_id()) { - for fun_env in module_env.get_functions() { - dep_fun_ids.insert(fun_env.get_qualified_id()); - } - } - } - } - dep_fun_ids -} - -/// Compute a map from each invariant to the set of functions that modify state -/// appearing in the invariant. Return that, and a second value that is the union -/// of functions over all invariants in the first map. -/// This is not applied to update invariants? -fn compute_funs_that_modify_inv( - global_env: &GlobalEnv, - target_invariants: &BTreeSet, - targets: &mut FunctionTargetsHolder, - variant: FunctionVariant, -) -> ( - BTreeMap>>, - BTreeMap, BTreeSet>, - BTreeSet>, -) { - let mut funs_that_modify_inv: BTreeMap>> = - BTreeMap::new(); - let mut funs_that_modify_some_inv: BTreeSet> = BTreeSet::new(); - let mut invs_modified_by_fun: BTreeMap, BTreeSet> = - BTreeMap::new(); - for inv_id in target_invariants { - // Collect the global state used by inv_id (this is computed in usage_analysis.rs) - let inv_mem_use: SetDomain<_> = global_env - .get_global_invariant(*inv_id) - .unwrap() - .mem_usage - .iter() - .cloned() - .collect(); - // set of functions that modify state in inv_id that we are building - let mut fun_id_set: BTreeSet> = BTreeSet::new(); - // Iterate over all functions in the module cluster - for module_env in global_env.get_modules() { - for fun_env in module_env.get_functions() { - // Get all memory modified by this function. - let fun_target = targets.get_target(&fun_env, &variant); - let modified_memory = &usage_analysis::get_memory_usage(&fun_target).modified.all; - // Add functions to set if it modifies mem used in invariant - // TODO: This should be using unification. - if !modified_memory.is_disjoint(&inv_mem_use) { - let fun_id = fun_env.get_qualified_id(); - fun_id_set.insert(fun_id); - funs_that_modify_some_inv.insert(fun_id); - let inv_set = invs_modified_by_fun.entry(fun_id).or_default(); - inv_set.insert(*inv_id); - } - } - } - if !fun_id_set.is_empty() { - funs_that_modify_inv.insert(*inv_id, fun_id_set); - } - } - ( - funs_that_modify_inv, - invs_modified_by_fun, - funs_that_modify_some_inv, - ) -} - -/// Compute the set of functions that are friend modules of target or deps, but not in -/// target or deps, and that call a function in non_inv_fun_set that modifies some target -/// invariant. The Prover needs to verify that these functions preserve the target invariants. -fn compute_friend_fun_ids( - global_env: &GlobalEnv, - target_fun_ids: &BTreeSet>, - dep_fun_ids: &BTreeSet>, - funs_that_delegate_to_caller: &BTreeSet>, -) -> BTreeSet> { - let mut friend_fun_set: BTreeSet> = BTreeSet::new(); - let mut worklist: Vec> = target_fun_ids.iter().cloned().collect(); - worklist.extend(dep_fun_ids.iter().cloned()); - while let Some(fun_id) = worklist.pop() { - // Check for legacy friend pragma - // TODO: Delete when we stop using pragma friend in DiemFramework - let fun_env = global_env.get_function(fun_id); - let friend_env = fun_env.get_transitive_friend(); - let friend_id = friend_env.get_qualified_id(); - // if no transitive friend, it just returns the original fun_env - if friend_id != fun_env.get_qualified_id() && friend_fun_set.insert(friend_id) { - worklist.push(friend_id); - } - if funs_that_delegate_to_caller.contains(&fun_id) { - let callers = fun_env - .get_calling_functions() - .expect(COMPILED_MODULE_AVAILABLE); - for caller_fun_id in callers { - // Exclude callers that are in target or dep modules, because we will verify them, anyway. - // We also don't need to put them in the worklist, because they were in there initially. - // Also, don't need to process if it's already in friend_fun_set - if !target_fun_ids.contains(&caller_fun_id) - && !dep_fun_ids.contains(&caller_fun_id) - && friend_fun_set.insert(caller_fun_id) - { - worklist.push(caller_fun_id); - } - } - } - } - friend_fun_set -} - -#[allow(dead_code)] -/// Debug print: Print global id and body of each invariant, so we can just print the global -/// id's in sets for compactness -fn debug_print_global_ids(global_env: &GlobalEnv, global_ids: &BTreeSet) { - for inv_id in global_ids { - debug_print_inv_full(global_env, inv_id); - } -} - -/// Debugging function to print a set of function id's using their -/// symbolic function names. -#[allow(dead_code)] -fn debug_print_fun_id_set( - global_env: &GlobalEnv, - fun_ids: &BTreeSet>, - set_name: &str, -) { - debug!( - "****************\n{}: {:?}", - set_name, - fun_ids - .iter() - .map(|fun_id| global_env.get_function(*fun_id).get_name_string()) - .collect::>() - ); -} - -/// Debugging code to print sets of invariants -#[allow(dead_code)] -pub fn debug_print_inv_set( - global_env: &GlobalEnv, - global_ids: &BTreeSet, - set_name: &str, -) { - if global_ids.is_empty() { - return; - } - debug!("{}:", set_name); - // for global_id in global_ids { - // debug!("global_id: {:?}", *global_id); - // } - debug!("++++++++++++++++\n{}:", set_name); - for inv_id in global_ids { - debug_print_inv_full(global_env, inv_id); - } -} - -/// Given global id of invariant, prints the global ID and the source code -/// of the invariant -#[allow(dead_code)] -fn debug_print_inv_full(global_env: &GlobalEnv, inv_id: &GlobalId) { - let inv = global_env.get_global_invariant(*inv_id); - let loc = &inv.unwrap().loc; - debug!( - "{:?} {:?}: {}", - *inv_id, - inv.unwrap().kind, - global_env.get_source(loc).unwrap(), - ); -} - -#[allow(dead_code)] -fn debug_print_fun_inv_map( - global_env: &GlobalEnv, - fun_inv_map: &BTreeMap, BTreeSet>, - map_name: &str, -) { - debug!("****************\nMAP NAME {}:", map_name); - for (fun_id, inv_id_set) in fun_inv_map.iter() { - let fname = global_env.get_function(*fun_id).get_name_string(); - debug!("FUNCTION {}:", fname); - for inv_id in inv_id_set { - debug_print_inv_full(global_env, inv_id); - } - // debug_print_inv_set(global_env, inv_id_set, &fname); - } -} - -// global_env.get_function(*fun_id).get_name_string(); - -/// Print sets and maps computed during verification analysis -#[allow(dead_code)] -fn debug_print_invariant_analysis_data( - global_env: &GlobalEnv, - inv_ana_data: &InvariantAnalysisData, -) { - debug_print_fun_id_set(global_env, &inv_ana_data.target_fun_ids, "target_fun_ids"); - debug_print_fun_id_set(global_env, &inv_ana_data.dep_fun_ids, "dep_fun_ids"); - debug_print_fun_id_set( - global_env, - &inv_ana_data.disabled_inv_fun_set, - "disabled_inv_fun_set", - ); - debug_print_fun_id_set(global_env, &inv_ana_data.non_inv_fun_set, "non_inv_fun_set"); - debug_print_inv_set( - global_env, - &inv_ana_data.target_invariants, - "target_invariants", - ); - - // "funs_modified_by_inv" map - - debug_print_fun_inv_map( - global_env, - &inv_ana_data.invs_modified_by_fun, - "invs_modified_by_fun", - ); - - debug_print_fun_id_set( - global_env, - &inv_ana_data.funs_that_modify_some_inv, - "funs_that_modify_some_inv", - ); - debug_print_fun_id_set( - global_env, - &inv_ana_data.funs_that_delegate_to_caller, - "funs_that_delegate_to_caller", - ); - debug_print_fun_id_set(global_env, &inv_ana_data.friend_fun_ids, "friend_fun_ids"); - - debug_print_fun_inv_map( - global_env, - &inv_ana_data.disabled_invs_for_fun, - "disabled_invs_for_fun", - ); -} - -pub struct VerificationAnalysisProcessorV2(); - -impl VerificationAnalysisProcessorV2 { - pub fn new() -> Box { - Box::new(Self()) - } -} - -impl FunctionTargetProcessor for VerificationAnalysisProcessorV2 { - fn process( - &self, - targets: &mut FunctionTargetsHolder, - fun_env: &FunctionEnv, - data: FunctionData, - _scc_opt: Option<&[FunctionEnv]>, - ) -> FunctionData { - let global_env = fun_env.module_env.env; - let fun_id = fun_env.get_qualified_id(); - let variant = data.variant.clone(); - // When this is called, the data of this function is removed from targets so it can - // be mutated, as per pipeline processor design. We put it back temporarily to have - // a unique model of targets. - targets.insert_target_data(&fun_id, variant.clone(), data); - let inv_ana_data = global_env.get_extension::().unwrap(); - let target_fun_ids = &inv_ana_data.target_fun_ids; - let dep_fun_ids = &inv_ana_data.dep_fun_ids; - let friend_fun_ids = &inv_ana_data.friend_fun_ids; - let funs_that_modify_some_inv = &inv_ana_data.funs_that_modify_some_inv; - // Logic to decide whether to verify this function - // Never verify if "pragma verify = false;" - if fun_env.is_pragma_true(VERIFY_PRAGMA, || true) { - let is_in_target_mod = target_fun_ids.contains(&fun_id); - let is_in_deps_and_modifies_inv = - dep_fun_ids.contains(&fun_id) && funs_that_modify_some_inv.contains(&fun_id); - let is_in_friends = friend_fun_ids.contains(&fun_id); - let is_normally_verified = - is_in_target_mod || is_in_deps_and_modifies_inv || is_in_friends; - let options = ProverOptions::get(global_env); - let is_verified = match &options.verify_scope { - VerificationScope::Public => { - (is_in_target_mod && fun_env.is_exposed()) - || is_in_deps_and_modifies_inv - || is_in_friends - }, - VerificationScope::All => is_normally_verified, - VerificationScope::Only(function_name) => { - fun_env.matches_name(function_name) && is_in_target_mod - }, - VerificationScope::OnlyModule(module_name) => { - is_in_target_mod && fun_env.module_env.matches_name(module_name) - }, - VerificationScope::None => false, - }; - if is_verified { - debug!("marking `{}` to be verified", fun_env.get_full_name_str()); - mark_verified(fun_env, variant.clone(), targets); - } - } - - targets.remove_target_data(&fun_id, &variant) - } - - fn name(&self) -> String { - "verification_analysis_v2".to_string() - } - - fn initialize(&self, global_env: &GlobalEnv, targets: &mut FunctionTargetsHolder) { - let options = ProverOptions::get(global_env); - - // If we are verifying only one function or module, check that this indeed exists. - match &options.verify_scope { - VerificationScope::Only(name) | VerificationScope::OnlyModule(name) => { - let for_module = matches!(&options.verify_scope, VerificationScope::OnlyModule(_)); - let mut target_exists = false; - for module in global_env.get_modules() { - if module.is_target() { - if for_module { - target_exists = module.matches_name(name) - } else { - target_exists = module.get_functions().any(|f| f.matches_name(name)); - } - if target_exists { - break; - } - } - } - if !target_exists { - global_env.error( - &global_env.unknown_loc(), - &format!( - "{} target {} does not exist in target modules", - if for_module { "module" } else { "function" }, - name - ), - ) - } - }, - _ => {}, - } - - let target_modules = global_env.get_primary_target_modules(); - let target_fun_ids: BTreeSet> = target_modules - .iter() - .flat_map(|mod_env| mod_env.get_functions()) - .map(|fun| fun.get_qualified_id()) - .collect(); - let dep_fun_ids = compute_dep_fun_ids(global_env, &target_modules); - let (disabled_inv_fun_set, non_inv_fun_set) = - compute_disabled_and_non_inv_fun_sets(global_env); - let target_invariants = get_target_invariants(global_env, &target_modules); - let (funs_that_modify_inv, invs_modified_by_fun, funs_that_modify_some_inv) = - compute_funs_that_modify_inv( - global_env, - &target_invariants, - targets, - FunctionVariant::Baseline, - ); - let funs_that_delegate_to_caller = non_inv_fun_set - .intersection(&funs_that_modify_some_inv) - .cloned() - .collect(); - let friend_fun_ids = compute_friend_fun_ids( - global_env, - &target_fun_ids, - &dep_fun_ids, - &funs_that_delegate_to_caller, - ); - let disabled_invs_for_fun = - compute_disabled_invs_for_fun(global_env, &disabled_inv_fun_set, &invs_modified_by_fun); - - // Check for public or script functions that are in non_inv_fun_set - for module_env in global_env.get_modules() { - for fun_env in module_env.get_functions() { - check_legal_disabled_invariants( - &fun_env, - &disabled_inv_fun_set, - &non_inv_fun_set, - &funs_that_modify_some_inv, - ); - } - } - let inv_ana_data = InvariantAnalysisData { - target_fun_ids, - dep_fun_ids, - disabled_inv_fun_set, - non_inv_fun_set, - target_invariants, - funs_that_modify_inv, - invs_modified_by_fun, - funs_that_modify_some_inv, - funs_that_delegate_to_caller, - friend_fun_ids, - disabled_invs_for_fun, - }; - - // Note: To print verbose debugging info, use - debug_print_invariant_analysis_data(global_env, &inv_ana_data); - - global_env.set_extension(inv_ana_data); - } -} - -/// Mark this function as being verified. If it has a friend and is verified only in the -/// friends context, mark the friend instead. This also marks all functions directly or -/// indirectly called by this function as inlined if they are not opaque. -fn mark_verified( - fun_env: &FunctionEnv<'_>, - variant: FunctionVariant, - targets: &mut FunctionTargetsHolder, -) { - let actual_env = fun_env.get_transitive_friend(); - if actual_env.get_qualified_id() != fun_env.get_qualified_id() { - // Instead of verifying this function directly, we mark the friend as being verified, - // and this function as inlined. - mark_inlined(fun_env, variant.clone(), targets); - } - // The user can override with `pragma verify = false`, so respect this. - let options = ProverOptions::get(fun_env.module_env.env); - if !actual_env.is_explicitly_not_verified(&options.verify_scope) { - let info = targets - .get_data_mut(&actual_env.get_qualified_id(), &variant) - .expect("function data available") - .annotations - .get_or_default_mut::(true); - if !info.verified { - info.verified = true; - mark_callees_inlined(&actual_env, variant, targets); - } - } -} - -/// Mark this function as inlined if it is not opaque, and if it is -/// are called from a verified function via a chain of zero-or-more -/// inline functions. If it is not called from a verified function, -/// it does not need to be inlined. -fn mark_inlined( - fun_env: &FunctionEnv<'_>, - variant: FunctionVariant, - targets: &mut FunctionTargetsHolder, -) { - if fun_env.is_native() || fun_env.is_intrinsic() { - return; - } - debug_assert!( - targets.get_target_variants(fun_env).contains(&variant), - "`{}` has variant `{:?}`", - fun_env.get_name().display(fun_env.symbol_pool()), - variant - ); - let data = targets - .get_data_mut(&fun_env.get_qualified_id(), &variant) - .expect("function data defined"); - let info = data - .annotations - .get_or_default_mut::(true); - if !info.inlined { - info.inlined = true; - mark_callees_inlined(fun_env, variant, targets); - } -} - -/// Continue transitively marking callees as inlined. -fn mark_callees_inlined( - fun_env: &FunctionEnv<'_>, - variant: FunctionVariant, - targets: &mut FunctionTargetsHolder, -) { - for callee in fun_env - .get_called_functions() - .expect(COMPILED_MODULE_AVAILABLE) - { - let callee_env = fun_env.module_env.env.get_function(*callee); - if !callee_env.is_opaque() { - mark_inlined(&callee_env, variant.clone(), targets); - } - } -} diff --git a/third_party/move/move-prover/lab/Cargo.toml b/third_party/move/move-prover/lab/Cargo.toml index 5ebf0e18075a0..dc90304f53b02 100644 --- a/third_party/move/move-prover/lab/Cargo.toml +++ b/third_party/move/move-prover/lab/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "prover-lab" +name = "move-prover-lab" version = "0.1.0" authors = ["Diem Association "] publish = false @@ -12,13 +12,9 @@ chrono = { workspace = true } clap = { workspace = true, features = ["derive"] } codespan-reporting = { workspace = true } itertools = { workspace = true } -log = { workspace = true, features = ["serde"] } -move-compiler = { workspace = true } -move-compiler-v2 = { workspace = true } move-model = { workspace = true } move-prover = { workspace = true } move-prover-boogie-backend = { workspace = true } move-prover-bytecode-pipeline = { workspace = true } plotters = { workspace = true, features = ["evcxr", "line_series", "histogram"] } z3tracer = { workspace = true } - diff --git a/third_party/move/move-prover/lab/src/benchmark.rs b/third_party/move/move-prover/lab/src/benchmark.rs index b05b6515083a4..edc750c34ef2d 100644 --- a/third_party/move/move-prover/lab/src/benchmark.rs +++ b/third_party/move/move-prover/lab/src/benchmark.rs @@ -13,13 +13,7 @@ use clap::{ }; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use itertools::Itertools; -use log::LevelFilter; -use move_compiler::shared::{known_attributes::KnownAttribute, PackagePaths}; -use move_compiler_v2::{env_pipeline::rewrite_target::RewritingScope, Experiment}; -use move_model::{ - model::{FunctionEnv, GlobalEnv, ModuleEnv, VerificationScope}, - parse_addresses_from_options, run_model_builder_with_options, -}; +use move_model::model::{FunctionEnv, GlobalEnv, ModuleEnv, VerificationScope}; use move_prover::{ check_errors, cli::Options, create_and_process_bytecode, create_init_num_operation_state, generate_boogie, verify_boogie, @@ -149,27 +143,9 @@ fn run_benchmark( } else { Options::default() }; - let addrs = parse_addresses_from_options(options.move_named_address_values.clone())?; + options.move_sources.append(&mut modules.to_vec()); options.move_deps.append(&mut dep_dirs.to_vec()); - let skip_attribute_checks = true; - let known_attributes = KnownAttribute::get_all_attribute_names().clone(); - let mut env = run_model_builder_with_options( - vec![PackagePaths { - name: None, - paths: modules.to_vec(), - named_address_map: addrs.clone(), - }], - vec![], - vec![PackagePaths { - name: None, - paths: options.move_deps.clone(), - named_address_map: addrs, - }], - options.model_builder.clone(), - skip_attribute_checks, - &known_attributes, - )?; - let mut error_writer = StandardStream::stderr(ColorChoice::Auto); + options.skip_attribute_checks = true; if use_aptos_natives { options.backend.custom_natives = @@ -184,14 +160,16 @@ fn run_benchmark( // Do not allow any benchmark to run longer than 60s. If this is exceeded it usually // indicates a bug in boogie or the solver, because we already propagate soft timeouts, but // they are ignored. - options.backend.hard_timeout_secs = 400; + options.backend.hard_timeout_secs = 60; options.backend.global_timeout_overwrite = false; - options.backend.vc_timeout = 300; - - options.verbosity_level = LevelFilter::Warn; + options.backend.vc_timeout = 400; + options.set_quiet(); options.backend.proc_cores = 1; options.backend.derive_options(); options.setup_logging(); + + let mut error_writer = StandardStream::stderr(ColorChoice::Auto); + let env = move_prover::create_move_prover_v2_model(&mut error_writer, options.clone())?; check_errors(&env, &options, &mut error_writer, "unexpected build errors")?; let config_descr = if let Some(config) = config_file_opt { @@ -216,17 +194,6 @@ fn run_benchmark( Notice that execution is slow because we enforce single core execution.", config_descr ); - // Need to run the pipeline to - let compiler_options = move_compiler_v2::Options::default() - .set_experiment(Experiment::OPTIMIZE, false) - .set_experiment(Experiment::SPEC_REWRITE, true); - env.set_extension(compiler_options.clone()); - let pipeline = move_compiler_v2::check_and_rewrite_pipeline( - &compiler_options, - true, - RewritingScope::Everything, - ); - pipeline.run(&mut env); runner.bench(&env) } @@ -261,13 +228,13 @@ impl Runner { // Write data record of benchmark result writeln!( self.out, - "{:<40} {:>12} {:>12}", + "{:<50} {:>15} {:>15}", fun.get_full_name_str(), duration.as_millis(), status )?; - println!("\x08\x08{:.3}s {}.", duration.as_secs_f64(), status); + println!(" {:.3}s {}.", duration.as_secs_f64(), status); Ok(()) } diff --git a/third_party/move/move-prover/lab/src/main.rs b/third_party/move/move-prover/lab/src/main.rs index 1db9219c151c9..a9a8c38bae7ae 100644 --- a/third_party/move/move-prover/lab/src/main.rs +++ b/third_party/move/move-prover/lab/src/main.rs @@ -5,7 +5,7 @@ #![forbid(unsafe_code)] use itertools::Itertools; -use prover_lab::{benchmark, plot}; +use move_prover_lab::{benchmark, plot}; fn main() { let args = std::env::args().collect_vec(); diff --git a/third_party/move/move-prover/src/lib.rs b/third_party/move/move-prover/src/lib.rs index 46810fe825d9d..c41f3df89d222 100644 --- a/third_party/move/move-prover/src/lib.rs +++ b/third_party/move/move-prover/src/lib.rs @@ -15,8 +15,8 @@ use move_compiler_v2::{env_pipeline::rewrite_target::RewritingScope, Experiment} use move_docgen::Docgen; use move_errmapgen::ErrmapGen; use move_model::{ - code_writer::CodeWriter, model::GlobalEnv, parse_addresses_from_options, - run_model_builder_with_options, + code_writer::CodeWriter, metadata::LATEST_STABLE_COMPILER_VERSION_VALUE, model::GlobalEnv, + parse_addresses_from_options, run_model_builder_with_options, }; use move_prover_boogie_backend::{ add_prelude, boogie_wrapper::BoogieWrapper, bytecode_translator::BoogieTranslator, @@ -71,19 +71,26 @@ pub fn run_move_prover_v2( options: Options, ) -> anyhow::Result<()> { let now = Instant::now(); - let cloned_options = options.clone(); + let mut env = create_move_prover_v2_model(error_writer, options.clone())?; + run_move_prover_with_model_v2(&mut env, error_writer, options, now) +} + +pub fn create_move_prover_v2_model( + error_writer: &mut W, + options: Options, +) -> anyhow::Result { let compiler_options = move_compiler_v2::Options { - dependencies: cloned_options.move_deps, - named_address_mapping: cloned_options.move_named_address_values, - output_dir: cloned_options.output_path, - language_version: cloned_options.language_version, - compiler_version: None, // TODO: need to pass v2.x here + dependencies: options.move_deps, + named_address_mapping: options.move_named_address_values, + output_dir: options.output_path, + language_version: options.language_version, + compiler_version: Some(LATEST_STABLE_COMPILER_VERSION_VALUE), skip_attribute_checks: true, known_attributes: Default::default(), - testing: cloned_options.backend.stable_test_output, + testing: options.backend.stable_test_output, experiments: vec![], experiment_cache: Default::default(), - sources: cloned_options.move_sources, + sources: options.move_sources, sources_deps: vec![], warn_deprecated: false, warn_of_deprecation_use_in_aptos_libs: false, @@ -94,8 +101,7 @@ pub fn run_move_prover_v2( external_checks: vec![], }; - let mut env = move_compiler_v2::run_move_compiler_for_analysis(error_writer, compiler_options)?; - run_move_prover_with_model_v2(&mut env, error_writer, options, now) + move_compiler_v2::run_move_compiler_for_analysis(error_writer, compiler_options) } /// Create the initial number operation state for each function and struct diff --git a/third_party/move/move-stdlib/src/natives/bcs.rs b/third_party/move/move-stdlib/src/natives/bcs.rs index a9062a4cbbfad..c1722c0165fa1 100644 --- a/third_party/move/move-stdlib/src/natives/bcs.rs +++ b/third_party/move/move-stdlib/src/natives/bcs.rs @@ -13,6 +13,7 @@ use move_vm_types::{ loaded_data::runtime_types::Type, natives::function::NativeResult, pop_arg, + value_serde::ValueSerDeContext, values::{values_impl::Reference, Value}, }; use smallvec::smallvec; @@ -62,7 +63,10 @@ fn native_to_bytes( }; // serialize value let val = ref_to_val.read_ref()?; - let serialized_value = match val.simple_serialize(&layout) { + let serialized_value = match ValueSerDeContext::new() + .with_func_args_deserialization(context.function_value_extension()) + .serialize(&val, &layout)? + { Some(serialized_value) => serialized_value, None => { cost += gas_params.failure; diff --git a/third_party/move/move-vm/integration-tests/src/tests/mod.rs b/third_party/move/move-vm/integration-tests/src/tests/mod.rs index 23ca581ad7e71..18caef5ac3192 100644 --- a/third_party/move/move-vm/integration-tests/src/tests/mod.rs +++ b/third_party/move/move-vm/integration-tests/src/tests/mod.rs @@ -11,6 +11,7 @@ mod instantiation_tests; mod invariant_violation_tests; mod leak_tests; mod loader_tests; +mod module_storage_tests; mod mutated_accounts_tests; mod native_tests; mod nested_loop_tests; diff --git a/third_party/move/move-vm/integration-tests/src/tests/module_storage_tests.rs b/third_party/move/move-vm/integration-tests/src/tests/module_storage_tests.rs new file mode 100644 index 0000000000000..d5a1f80b228fd --- /dev/null +++ b/third_party/move/move-vm/integration-tests/src/tests/module_storage_tests.rs @@ -0,0 +1,137 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::compiler::{as_module, compile_units}; +use bytes::Bytes; +use move_binary_format::file_format::{Ability, AbilitySet}; +use move_core_types::{ + account_address::AccountAddress, + ident_str, + identifier::Identifier, + language_storage::{ModuleId, TypeTag}, +}; +use move_vm_runtime::{ + AsFunctionValueExtension, AsUnsyncModuleStorage, RuntimeEnvironment, WithRuntimeEnvironment, +}; +use move_vm_test_utils::InMemoryStorage; +use move_vm_types::{ + loaded_data::runtime_types::{AbilityInfo, StructIdentifier, StructNameIndex, TypeBuilder}, + value_serde::FunctionValueExtension, +}; +use std::str::FromStr; + +#[cfg(test)] +fn module_bytes(module_code: &str) -> Bytes { + let compiled_module = as_module(compile_units(module_code).unwrap().pop().unwrap()); + let mut bytes = vec![]; + compiled_module.serialize(&mut bytes).unwrap(); + bytes.into() +} + +#[test] +fn test_function_value_extension() { + let mut module_bytes_storage = InMemoryStorage::new(); + + let code = r#" + module 0x1::test { + struct Foo {} + + fun b() { } + fun c(a: u64, _foo: &Foo): u64 { a } + fun d(_a: A, b: u64, _c: C): u64 { b } + } + "#; + let bytes = module_bytes(code); + let test_id = ModuleId::new(AccountAddress::ONE, Identifier::new("test").unwrap()); + module_bytes_storage.add_module_bytes(&AccountAddress::ONE, ident_str!("test"), bytes); + + let code = r#" + module 0x1::other_test { + struct Bar has drop {} + } + "#; + let bytes = module_bytes(code); + let other_test_id = ModuleId::new(AccountAddress::ONE, Identifier::new("other_test").unwrap()); + module_bytes_storage.add_module_bytes(&AccountAddress::ONE, ident_str!("other_test"), bytes); + + let runtime_environment = RuntimeEnvironment::new(vec![]); + let module_storage = module_bytes_storage.into_unsync_module_storage(runtime_environment); + let function_value_extension = module_storage.as_function_value_extension(); + + let result = function_value_extension.get_function_arg_tys( + &ModuleId::new(AccountAddress::ONE, Identifier::new("test").unwrap()), + ident_str!("a"), + vec![], + ); + assert!(result.is_err()); + + let mut types = function_value_extension + .get_function_arg_tys(&test_id, ident_str!("c"), vec![]) + .unwrap(); + assert_eq!(types.len(), 2); + + let ty_builder = TypeBuilder::with_limits(100, 100); + let foo_ty = types.pop().unwrap(); + let name = module_storage + .runtime_environment() + .idx_to_struct_name_for_test(StructNameIndex(0)) + .unwrap(); + assert_eq!(name, StructIdentifier { + module: test_id.clone(), + name: Identifier::new("Foo").unwrap(), + }); + assert_eq!( + foo_ty, + ty_builder + .create_ref_ty( + &ty_builder + .create_struct_ty(StructNameIndex(0), AbilityInfo::struct_(AbilitySet::EMPTY)), + false + ) + .unwrap() + ); + let u64_ty = types.pop().unwrap(); + assert_eq!(u64_ty, ty_builder.create_u64_ty()); + + // Generic function without type parameters should fail. + let result = function_value_extension.get_function_arg_tys( + &ModuleId::new(AccountAddress::ONE, Identifier::new("test").unwrap()), + ident_str!("d"), + vec![], + ); + assert!(result.is_err()); + + let mut types = function_value_extension + .get_function_arg_tys(&test_id, ident_str!("d"), vec![ + TypeTag::from_str("0x1::other_test::Bar").unwrap(), + TypeTag::Vector(Box::new(TypeTag::U8)), + ]) + .unwrap(); + assert_eq!(types.len(), 3); + + let vec_ty = types.pop().unwrap(); + assert_eq!( + vec_ty, + ty_builder + .create_vec_ty(&ty_builder.create_u8_ty()) + .unwrap() + ); + let u64_ty = types.pop().unwrap(); + assert_eq!(u64_ty, ty_builder.create_u64_ty()); + let bar_ty = types.pop().unwrap(); + let name = module_storage + .runtime_environment() + .idx_to_struct_name_for_test(StructNameIndex(1)) + .unwrap(); + assert_eq!(name, StructIdentifier { + module: other_test_id, + name: Identifier::new("Bar").unwrap(), + }); + assert_eq!( + bar_ty, + ty_builder.create_struct_ty( + StructNameIndex(1), + AbilityInfo::struct_(AbilitySet::from_u8(Ability::Drop as u8).unwrap()) + ) + ); +} diff --git a/third_party/move/move-vm/runtime/src/data_cache.rs b/third_party/move/move-vm/runtime/src/data_cache.rs index 0038b783d457b..197516b5d1dcd 100644 --- a/third_party/move/move-vm/runtime/src/data_cache.rs +++ b/third_party/move/move-vm/runtime/src/data_cache.rs @@ -5,6 +5,7 @@ use crate::{ loader::{LegacyModuleStorageAdapter, Loader}, logging::expect_no_verification_errors, + storage::module_storage::FunctionValueExtensionAdapter, ModuleStorage, }; use bytes::Bytes; @@ -26,7 +27,7 @@ use move_core_types::{ use move_vm_types::{ loaded_data::runtime_types::Type, resolver::MoveResolver, - value_serde::deserialize_and_allow_delayed_values, + value_serde::ValueSerDeContext, values::{GlobalValue, Value}, }; use sha3::{Digest, Sha3_256}; @@ -118,8 +119,10 @@ impl<'r> TransactionDataCache<'r> { ) -> PartialVMResult { let resource_converter = |value: Value, layout: MoveTypeLayout, _: bool| -> PartialVMResult { - value - .simple_serialize(&layout) + let function_value_extension = FunctionValueExtensionAdapter { module_storage }; + ValueSerDeContext::new() + .with_func_args_deserialization(&function_value_extension) + .serialize(&value, &layout)? .map(Into::into) .ok_or_else(|| { PartialVMError::new(StatusCode::INTERNAL_TYPE_ERROR) @@ -275,9 +278,14 @@ impl<'r> TransactionDataCache<'r> { }; load_res = Some(NumBytes::new(bytes_loaded as u64)); + let function_value_extension = FunctionValueExtensionAdapter { module_storage }; let gv = match data { Some(blob) => { - let val = match deserialize_and_allow_delayed_values(&blob, &ty_layout) { + let val = match ValueSerDeContext::new() + .with_func_args_deserialization(&function_value_extension) + .with_delayed_fields_serde() + .deserialize(&blob, &ty_layout) + { Some(val) => val, None => { let msg = diff --git a/third_party/move/move-vm/runtime/src/lib.rs b/third_party/move/move-vm/runtime/src/lib.rs index e9dd6f69a1f20..b4e7641d52cfe 100644 --- a/third_party/move/move-vm/runtime/src/lib.rs +++ b/third_party/move/move-vm/runtime/src/lib.rs @@ -34,7 +34,7 @@ mod frame_type_cache; mod runtime_type_checks; mod storage; -pub use loader::{LoadedFunction, Module, Script}; +pub use loader::{Function, LoadedFunction, Module, Script}; #[cfg(any(test, feature = "testing"))] pub use storage::implementations::unreachable_code_storage; pub use storage::{ @@ -46,6 +46,6 @@ pub use storage::{ unsync_code_storage::{AsUnsyncCodeStorage, UnsyncCodeStorage}, unsync_module_storage::{AsUnsyncModuleStorage, BorrowedOrOwned, UnsyncModuleStorage}, }, - module_storage::{ambassador_impl_ModuleStorage, ModuleStorage}, + module_storage::{ambassador_impl_ModuleStorage, AsFunctionValueExtension, ModuleStorage}, publishing::{StagingModuleStorage, VerifiedModuleBundle}, }; diff --git a/third_party/move/move-vm/runtime/src/loader/mod.rs b/third_party/move/move-vm/runtime/src/loader/mod.rs index 1777fd5ac9b9b..0bafef00c1f06 100644 --- a/third_party/move/move-vm/runtime/src/loader/mod.rs +++ b/third_party/move/move-vm/runtime/src/loader/mod.rs @@ -54,11 +54,12 @@ use crate::{ loader::modules::{StructVariantInfo, VariantFieldInfo}, native_functions::NativeFunctions, storage::{ - loader::LoaderV2, struct_name_index_map::StructNameIndexMap, ty_cache::StructInfoCache, + loader::LoaderV2, module_storage::FunctionValueExtensionAdapter, + struct_name_index_map::StructNameIndexMap, ty_cache::StructInfoCache, }, }; -pub use function::LoadedFunction; -pub(crate) use function::{Function, FunctionHandle, FunctionInstantiation, LoadedFunctionOwner}; +pub use function::{Function, LoadedFunction}; +pub(crate) use function::{FunctionHandle, FunctionInstantiation, LoadedFunctionOwner}; pub use modules::Module; pub(crate) use modules::{LegacyModuleCache, LegacyModuleStorage, LegacyModuleStorageAdapter}; use move_binary_format::file_format::{ @@ -66,7 +67,10 @@ use move_binary_format::file_format::{ VariantFieldInstantiationIndex, VariantIndex, }; use move_vm_metrics::{Timer, VM_TIMER}; -use move_vm_types::loaded_data::runtime_types::{StructLayout, TypeBuilder}; +use move_vm_types::{ + loaded_data::runtime_types::{StructLayout, TypeBuilder}, + value_serde::FunctionValueExtension, +}; pub use script::Script; pub(crate) use script::ScriptCache; use type_loader::intern_type; @@ -308,8 +312,7 @@ impl Loader { .resolve_module_and_function_by_name(module_id, function_name) .map_err(|err| err.finish(Location::Undefined)) }, - Loader::V2(loader) => loader.load_function_without_ty_args( - module_storage, + Loader::V2(_) => module_storage.fetch_function_definition( module_id.address(), module_id.name(), function_name, @@ -330,8 +333,8 @@ impl Loader { ) -> VMResult { match self { Self::V1(loader) => loader.load_type(ty_tag, data_store, module_store), - Self::V2(loader) => loader - .load_ty(module_storage, ty_tag) + Self::V2(_) => module_storage + .fetch_ty(ty_tag) .map_err(|e| e.finish(Location::Undefined)), } } @@ -356,8 +359,7 @@ impl Loader { Loader::V1(_) => { module_store.get_struct_type_by_identifier(&struct_name.name, &struct_name.module) }, - Loader::V2(loader) => loader.load_struct_ty( - module_storage, + Loader::V2(_) => module_storage.fetch_struct_ty( struct_name.module.address(), struct_name.module.name(), struct_name.name.as_ident_str(), @@ -1295,13 +1297,9 @@ impl<'a> Resolver<'a> { Loader::V1(_) => self .module_store .resolve_module_and_function_by_name(module_id, function_name)?, - Loader::V2(loader) => loader - .load_function_without_ty_args( - self.module_storage, - module_id.address(), - module_id.name(), - function_name, - ) + Loader::V2(_) => self + .module_storage + .fetch_function_definition(module_id.address(), module_id.name(), function_name) .map_err(|_| { // Note: legacy loader implementation used this error, so we need to remap. PartialVMError::new(StatusCode::FUNCTION_RESOLUTION_FAILURE).with_message( @@ -1652,6 +1650,20 @@ impl<'a> Resolver<'a> { } } +impl<'a> FunctionValueExtension for Resolver<'a> { + fn get_function_arg_tys( + &self, + module_id: &ModuleId, + function_name: &IdentStr, + ty_arg_tags: Vec, + ) -> PartialVMResult> { + let function_value_extension = FunctionValueExtensionAdapter { + module_storage: self.module_storage, + }; + function_value_extension.get_function_arg_tys(module_id, function_name, ty_arg_tags) + } +} + /// Maximal depth of a value in terms of type depth. pub const VALUE_DEPTH_MAX: u64 = 128; diff --git a/third_party/move/move-vm/runtime/src/native_functions.rs b/third_party/move/move-vm/runtime/src/native_functions.rs index 8cce249a6ebdd..214115c30b4e5 100644 --- a/third_party/move/move-vm/runtime/src/native_functions.rs +++ b/third_party/move/move-vm/runtime/src/native_functions.rs @@ -21,7 +21,8 @@ use move_core_types::{ vm_status::StatusCode, }; use move_vm_types::{ - loaded_data::runtime_types::Type, natives::function::NativeResult, values::Value, + loaded_data::runtime_types::Type, natives::function::NativeResult, + value_serde::FunctionValueExtension, values::Value, }; use std::{ collections::{HashMap, VecDeque}, @@ -197,6 +198,10 @@ impl<'a, 'b, 'c> NativeContext<'a, 'b, 'c> { self.traversal_context } + pub fn function_value_extension(&self) -> &dyn FunctionValueExtension { + self.resolver + } + pub fn load_function( &mut self, module_id: &ModuleId, @@ -220,13 +225,10 @@ impl<'a, 'b, 'c> NativeContext<'a, 'b, 'c> { .module_store() .resolve_module_and_function_by_name(module_id, function_name)? }, - Loader::V2(loader) => loader - .load_function_without_ty_args( - self.resolver.module_storage(), - module_id.address(), - module_id.name(), - function_name, - ) + Loader::V2(_) => self + .resolver + .module_storage() + .fetch_function_definition(module_id.address(), module_id.name(), function_name) // TODO(loader_v2): // Keeping this consistent with loader V1 implementation which returned that // error. Check if we can avoid remapping by replaying transactions. diff --git a/third_party/move/move-vm/runtime/src/runtime.rs b/third_party/move/move-vm/runtime/src/runtime.rs index 0ad885bca6c11..d33a49edfd645 100644 --- a/third_party/move/move-vm/runtime/src/runtime.rs +++ b/third_party/move/move-vm/runtime/src/runtime.rs @@ -12,7 +12,7 @@ use crate::{ native_extensions::NativeContextExtensions, session::SerializedReturnValues, storage::{code_storage::CodeStorage, module_storage::ModuleStorage}, - RuntimeEnvironment, + AsFunctionValueExtension, RuntimeEnvironment, }; use move_binary_format::{ access::ModuleAccess, @@ -29,6 +29,7 @@ use move_vm_metrics::{Timer, VM_TIMER}; use move_vm_types::{ gas::GasMeter, loaded_data::runtime_types::Type, + value_serde::ValueSerDeContext, values::{Locals, Reference, VMValueCast, Value}, }; use std::{borrow::Borrow, collections::BTreeSet, sync::Arc}; @@ -270,7 +271,11 @@ impl VMRuntime { return Err(deserialization_error()); } - match Value::simple_deserialize(arg.borrow(), &layout) { + let function_value_extension = module_storage.as_function_value_extension(); + match ValueSerDeContext::new() + .with_func_args_deserialization(&function_value_extension) + .deserialize(arg.borrow(), &layout) + { Some(val) => Ok(val), None => Err(deserialization_error()), } @@ -354,8 +359,10 @@ impl VMRuntime { return Err(serialization_error()); } - let bytes = value - .simple_serialize(&layout) + let function_value_extension = module_storage.as_function_value_extension(); + let bytes = ValueSerDeContext::new() + .with_func_args_deserialization(&function_value_extension) + .serialize(&value, &layout)? .ok_or_else(serialization_error)?; Ok((bytes, layout)) } diff --git a/third_party/move/move-vm/runtime/src/storage/code_storage.rs b/third_party/move/move-vm/runtime/src/storage/code_storage.rs index 5274a52543186..8afcef7777d32 100644 --- a/third_party/move/move-vm/runtime/src/storage/code_storage.rs +++ b/third_party/move/move-vm/runtime/src/storage/code_storage.rs @@ -1,12 +1,12 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::{loader::Script, logging::expect_no_verification_errors, ModuleStorage}; +use crate::{loader::Script, ModuleStorage}; use ambassador::delegatable_trait; use move_binary_format::{errors::VMResult, file_format::CompiledScript}; use move_vm_types::{ code::{Code, ScriptCache}, - module_linker_error, sha3_256, + sha3_256, }; use std::sync::Arc; @@ -72,9 +72,7 @@ where .immediate_dependencies_iter() .map(|(addr, name)| { // Since module is stored on-chain, we should not see any verification errors here. - self.fetch_verified_module(addr, name) - .map_err(expect_no_verification_errors)? - .ok_or_else(|| module_linker_error!(addr, name)) + self.fetch_existing_verified_module(addr, name) }) .collect::>>()?; let verified_script = self diff --git a/third_party/move/move-vm/runtime/src/storage/environment.rs b/third_party/move/move-vm/runtime/src/storage/environment.rs index 7d5ae85f0640e..9a813576dbdd4 100644 --- a/third_party/move/move-vm/runtime/src/storage/environment.rs +++ b/third_party/move/move-vm/runtime/src/storage/environment.rs @@ -295,6 +295,15 @@ impl RuntimeEnvironment { ) -> PartialVMResult { self.struct_name_index_map.struct_name_to_idx(&struct_name) } + + /// Test-only function to be able to check cached struct names. + #[cfg(any(test, feature = "testing"))] + pub fn idx_to_struct_name_for_test( + &self, + idx: StructNameIndex, + ) -> PartialVMResult { + self.struct_name_index_map.idx_to_struct_name(idx) + } } impl Clone for RuntimeEnvironment { diff --git a/third_party/move/move-vm/runtime/src/storage/implementations/unsync_code_storage.rs b/third_party/move/move-vm/runtime/src/storage/implementations/unsync_code_storage.rs index 19f6f1ae55fee..b559a6c6ab12f 100644 --- a/third_party/move/move-vm/runtime/src/storage/implementations/unsync_code_storage.rs +++ b/third_party/move/move-vm/runtime/src/storage/implementations/unsync_code_storage.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - loader::{Module, Script}, + loader::{Function, Module, Script}, storage::{ code_storage::{ambassador_impl_CodeStorage, CodeStorage}, environment::{ @@ -14,10 +14,18 @@ use crate::{ }; use ambassador::Delegate; use bytes::Bytes; -use move_binary_format::{errors::VMResult, file_format::CompiledScript, CompiledModule}; -use move_core_types::{account_address::AccountAddress, identifier::IdentStr, metadata::Metadata}; -use move_vm_types::code::{ - ambassador_impl_ScriptCache, Code, ModuleBytesStorage, ScriptCache, UnsyncScriptCache, +use move_binary_format::{ + errors::{PartialVMResult, VMResult}, + file_format::CompiledScript, + CompiledModule, +}; +use move_core_types::{ + account_address::AccountAddress, identifier::IdentStr, language_storage::TypeTag, + metadata::Metadata, +}; +use move_vm_types::{ + code::{ambassador_impl_ScriptCache, Code, ModuleBytesStorage, ScriptCache, UnsyncScriptCache}, + loaded_data::runtime_types::{StructType, Type}, }; use std::sync::Arc; diff --git a/third_party/move/move-vm/runtime/src/storage/implementations/unsync_module_storage.rs b/third_party/move/move-vm/runtime/src/storage/implementations/unsync_module_storage.rs index fba7581d9d341..b5543e0bb4527 100644 --- a/third_party/move/move-vm/runtime/src/storage/implementations/unsync_module_storage.rs +++ b/third_party/move/move-vm/runtime/src/storage/implementations/unsync_module_storage.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - loader::Module, + loader::{Function, Module}, storage::{ environment::{ ambassador_impl_WithRuntimeEnvironment, RuntimeEnvironment, WithRuntimeEnvironment, @@ -12,9 +12,14 @@ use crate::{ }; use ambassador::Delegate; use bytes::Bytes; -use move_binary_format::{errors::VMResult, CompiledModule}; +use move_binary_format::{ + errors::{PartialVMResult, VMResult}, + CompiledModule, +}; use move_core_types::{ - account_address::AccountAddress, identifier::IdentStr, language_storage::ModuleId, + account_address::AccountAddress, + identifier::IdentStr, + language_storage::{ModuleId, TypeTag}, metadata::Metadata, }; use move_vm_types::{ @@ -22,6 +27,7 @@ use move_vm_types::{ ambassador_impl_ModuleCache, ModuleBytesStorage, ModuleCache, ModuleCode, ModuleCodeBuilder, UnsyncModuleCache, WithBytes, WithHash, }, + loaded_data::runtime_types::{StructType, Type}, sha3_256, }; use std::{borrow::Borrow, ops::Deref, sync::Arc}; diff --git a/third_party/move/move-vm/runtime/src/storage/loader.rs b/third_party/move/move-vm/runtime/src/storage/loader.rs index 539a439c0c4f5..3e4d3cfbcd9ca 100644 --- a/third_party/move/move-vm/runtime/src/storage/loader.rs +++ b/third_party/move/move-vm/runtime/src/storage/loader.rs @@ -2,16 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - config::VMConfig, - loader::{Function, LoadedFunctionOwner, Module}, - logging::expect_no_verification_errors, - module_traversal::TraversalContext, - storage::module_storage::ModuleStorage, - CodeStorage, LoadedFunction, + config::VMConfig, loader::LoadedFunctionOwner, module_traversal::TraversalContext, + storage::module_storage::ModuleStorage, CodeStorage, LoadedFunction, }; use move_binary_format::{ access::{ModuleAccess, ScriptAccess}, - errors::{Location, PartialVMError, PartialVMResult, VMResult}, + errors::{Location, PartialVMResult, VMResult}, CompiledModule, }; use move_core_types::{ @@ -19,11 +15,10 @@ use move_core_types::{ gas_algebra::NumBytes, identifier::IdentStr, language_storage::{ModuleId, TypeTag}, - vm_status::StatusCode, }; use move_vm_types::{ gas::GasMeter, - loaded_data::runtime_types::{StructType, Type, TypeBuilder}, + loaded_data::runtime_types::{Type, TypeBuilder}, module_linker_error, }; use std::{collections::BTreeMap, sync::Arc}; @@ -137,7 +132,7 @@ impl LoaderV2 { // arguments for scripts are verified on the client side. let ty_args = ty_tag_args .iter() - .map(|ty_tag| self.load_ty(code_storage, ty_tag)) + .map(|ty_tag| code_storage.fetch_ty(ty_tag)) .collect::>>() .map_err(|err| err.finish(Location::Script))?; @@ -151,95 +146,6 @@ impl LoaderV2 { function: main, }) } - - /// Returns a loaded & verified module corresponding to the specified name. - pub(crate) fn load_module( - &self, - module_storage: &dyn ModuleStorage, - address: &AccountAddress, - module_name: &IdentStr, - ) -> VMResult> { - module_storage - .fetch_verified_module(address, module_name) - .map_err(expect_no_verification_errors)? - .ok_or_else(|| module_linker_error!(address, module_name)) - } - - /// Returns the function definition corresponding to the specified name, as well as the module - /// where this function is defined. - pub(crate) fn load_function_without_ty_args( - &self, - module_storage: &dyn ModuleStorage, - address: &AccountAddress, - module_name: &IdentStr, - function_name: &IdentStr, - ) -> VMResult<(Arc, Arc)> { - let module = self.load_module(module_storage, address, module_name)?; - let function = module - .function_map - .get(function_name) - .and_then(|idx| module.function_defs.get(*idx)) - .ok_or_else(|| { - PartialVMError::new(StatusCode::FUNCTION_RESOLUTION_FAILURE) - .with_message(format!( - "Function {}::{}::{} does not exist", - address, module_name, function_name - )) - .finish(Location::Undefined) - })? - .clone(); - Ok((module, function)) - } - - /// Returns a struct type corresponding to the specified name. The module - /// containing the struct is loaded. - pub(crate) fn load_struct_ty( - &self, - module_storage: &dyn ModuleStorage, - address: &AccountAddress, - module_name: &IdentStr, - struct_name: &IdentStr, - ) -> PartialVMResult> { - let module = self - .load_module(module_storage, address, module_name) - .map_err(|err| err.to_partial())?; - Ok(module - .struct_map - .get(struct_name) - .and_then(|idx| module.structs.get(*idx)) - .ok_or_else(|| { - PartialVMError::new(StatusCode::TYPE_RESOLUTION_FAILURE).with_message(format!( - "Struct {}::{}::{} does not exist", - address, module_name, struct_name - )) - })? - .definition_struct_type - .clone()) - } - - /// Returns a runtime type corresponding to the specified type tag (file format type - /// representation). In case struct types are transitively loaded, the module containing - /// the struct definition is also loaded. - pub(crate) fn load_ty( - &self, - module_storage: &impl ModuleStorage, - ty_tag: &TypeTag, - ) -> PartialVMResult { - // TODO(loader_v2): Loader V1 uses VMResults everywhere, but partial VM errors - // seem better fit. Here we map error to VMError to reuse existing - // type builder implementation, and then strip the location info. - self.ty_builder() - .create_ty(ty_tag, |st| { - self.load_struct_ty( - module_storage, - &st.address, - st.module.as_ident_str(), - st.name.as_ident_str(), - ) - .map_err(|err| err.finish(Location::Undefined)) - }) - .map_err(|err| err.to_partial()) - } } impl Clone for LoaderV2 { diff --git a/third_party/move/move-vm/runtime/src/storage/module_storage.rs b/third_party/move/move-vm/runtime/src/storage/module_storage.rs index da64fc6dbd001..c96571b046cd1 100644 --- a/third_party/move/move-vm/runtime/src/storage/module_storage.rs +++ b/third_party/move/move-vm/runtime/src/storage/module_storage.rs @@ -1,19 +1,31 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::{loader::Module, WithRuntimeEnvironment}; +use crate::{ + loader::{Function, Module}, + logging::expect_no_verification_errors, + WithRuntimeEnvironment, +}; use ambassador::delegatable_trait; use bytes::Bytes; use hashbrown::HashSet; -use move_binary_format::{errors::VMResult, CompiledModule}; +use move_binary_format::{ + errors::{Location, PartialVMError, PartialVMResult, VMResult}, + CompiledModule, +}; use move_core_types::{ - account_address::AccountAddress, identifier::IdentStr, language_storage::ModuleId, + account_address::AccountAddress, + identifier::IdentStr, + language_storage::{ModuleId, TypeTag}, metadata::Metadata, + vm_status::StatusCode, }; use move_vm_metrics::{Timer, VM_TIMER}; use move_vm_types::{ code::{ModuleCache, ModuleCode, ModuleCodeBuilder, WithBytes, WithHash, WithSize}, + loaded_data::runtime_types::{StructType, Type}, module_cyclic_dependency_error, module_linker_error, + value_serde::FunctionValueExtension, }; use std::sync::Arc; @@ -101,6 +113,91 @@ pub trait ModuleStorage: WithRuntimeEnvironment { address: &AccountAddress, module_name: &IdentStr, ) -> VMResult>>; + + /// Returns the verified module. If it does not exist, a linker error is returned. All other + /// errors are mapped using [expect_no_verification_errors] - since on-chain code should not + /// fail bytecode verification. + fn fetch_existing_verified_module( + &self, + address: &AccountAddress, + module_name: &IdentStr, + ) -> VMResult> { + self.fetch_verified_module(address, module_name) + .map_err(expect_no_verification_errors)? + .ok_or_else(|| module_linker_error!(address, module_name)) + } + + /// Returns a struct type corresponding to the specified name. The module containing the struct + /// will be fetched and cached beforehand. + fn fetch_struct_ty( + &self, + address: &AccountAddress, + module_name: &IdentStr, + struct_name: &IdentStr, + ) -> PartialVMResult> { + let module = self + .fetch_existing_verified_module(address, module_name) + .map_err(|err| err.to_partial())?; + Ok(module + .struct_map + .get(struct_name) + .and_then(|idx| module.structs.get(*idx)) + .ok_or_else(|| { + PartialVMError::new(StatusCode::TYPE_RESOLUTION_FAILURE).with_message(format!( + "Struct {}::{}::{} does not exist", + address, module_name, struct_name + )) + })? + .definition_struct_type + .clone()) + } + + /// Returns a runtime type corresponding to the specified type tag (file format type + /// representation). If a struct type is constructed, the module containing the struct + /// definition is fetched and cached. + fn fetch_ty(&self, ty_tag: &TypeTag) -> PartialVMResult { + // TODO(loader_v2): Loader V1 uses VMResults everywhere, but partial VM errors + // seem better fit. Here we map error to VMError to reuse existing + // type builder implementation, and then strip the location info. + self.runtime_environment() + .vm_config() + .ty_builder + .create_ty(ty_tag, |st| { + self.fetch_struct_ty( + &st.address, + st.module.as_ident_str(), + st.name.as_ident_str(), + ) + .map_err(|err| err.finish(Location::Undefined)) + }) + .map_err(|err| err.to_partial()) + } + + /// Returns the function definition corresponding to the specified name, as well as the module + /// where this function is defined. The returned function can contain uninstantiated generic + /// types and its signature. The returned module is verified. + fn fetch_function_definition( + &self, + address: &AccountAddress, + module_name: &IdentStr, + function_name: &IdentStr, + ) -> VMResult<(Arc, Arc)> { + let module = self.fetch_existing_verified_module(address, module_name)?; + let function = module + .function_map + .get(function_name) + .and_then(|idx| module.function_defs.get(*idx)) + .ok_or_else(|| { + PartialVMError::new(StatusCode::FUNCTION_RESOLUTION_FAILURE) + .with_message(format!( + "Function {}::{}::{} does not exist", + address, module_name, function_name + )) + .finish(Location::Undefined) + })? + .clone(); + Ok((module, function)) + } } impl ModuleStorage for T @@ -302,3 +399,53 @@ where )?; Ok(module.code().verified().clone()) } + +/// Avoids the orphan rule to implement external [FunctionValueExtension] for any generic type that +/// implements [ModuleStorage]. +pub struct FunctionValueExtensionAdapter<'a> { + pub(crate) module_storage: &'a dyn ModuleStorage, +} + +pub trait AsFunctionValueExtension { + fn as_function_value_extension(&self) -> FunctionValueExtensionAdapter; +} + +impl AsFunctionValueExtension for T { + fn as_function_value_extension(&self) -> FunctionValueExtensionAdapter { + FunctionValueExtensionAdapter { + module_storage: self, + } + } +} + +impl<'a> FunctionValueExtension for FunctionValueExtensionAdapter<'a> { + fn get_function_arg_tys( + &self, + module_id: &ModuleId, + function_name: &IdentStr, + substitution_ty_arg_tags: Vec, + ) -> PartialVMResult> { + let substitution_ty_args = substitution_ty_arg_tags + .into_iter() + .map(|tag| self.module_storage.fetch_ty(&tag)) + .collect::>>()?; + + let (_, function) = self + .module_storage + .fetch_function_definition(module_id.address(), module_id.name(), function_name) + .map_err(|err| err.to_partial())?; + + let ty_builder = &self + .module_storage + .runtime_environment() + .vm_config() + .ty_builder; + function + .param_tys() + .iter() + .map(|ty_to_substitute| { + ty_builder.create_ty_with_subst(ty_to_substitute, &substitution_ty_args) + }) + .collect::>>() + } +} diff --git a/third_party/move/move-vm/runtime/src/storage/publishing.rs b/third_party/move/move-vm/runtime/src/storage/publishing.rs index d6d3320e9c54a..b59001ba45897 100644 --- a/third_party/move/move-vm/runtime/src/storage/publishing.rs +++ b/third_party/move/move-vm/runtime/src/storage/publishing.rs @@ -2,25 +2,30 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - ambassador_impl_ModuleStorage, ambassador_impl_WithRuntimeEnvironment, AsUnsyncModuleStorage, - Module, ModuleStorage, RuntimeEnvironment, UnsyncModuleStorage, WithRuntimeEnvironment, + ambassador_impl_ModuleStorage, ambassador_impl_WithRuntimeEnvironment, loader::Function, + AsUnsyncModuleStorage, Module, ModuleStorage, RuntimeEnvironment, UnsyncModuleStorage, + WithRuntimeEnvironment, }; use ambassador::Delegate; use bytes::Bytes; use move_binary_format::{ access::ModuleAccess, compatibility::Compatibility, - errors::{verification_error, Location, PartialVMError, VMResult}, + errors::{verification_error, Location, PartialVMError, PartialVMResult, VMResult}, normalized, CompiledModule, IndexKind, }; use move_core_types::{ account_address::AccountAddress, identifier::{IdentStr, Identifier}, - language_storage::ModuleId, + language_storage::{ModuleId, TypeTag}, metadata::Metadata, vm_status::StatusCode, }; -use move_vm_types::{code::ModuleBytesStorage, module_linker_error}; +use move_vm_types::{ + code::ModuleBytesStorage, + loaded_data::runtime_types::{StructType, Type}, + module_linker_error, +}; use std::{ collections::{btree_map, BTreeMap}, sync::Arc, diff --git a/third_party/move/move-vm/types/src/value_serde.rs b/third_party/move/move-vm/types/src/value_serde.rs index 5f17bfe9b3848..c8f390096f9ac 100644 --- a/third_party/move/move-vm/types/src/value_serde.rs +++ b/third_party/move/move-vm/types/src/value_serde.rs @@ -2,182 +2,183 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - delayed_values::delayed_field_id::{ - DelayedFieldID, ExtractUniqueIndex, ExtractWidth, TryFromMoveValue, TryIntoMoveValue, - }, + delayed_values::delayed_field_id::DelayedFieldID, + loaded_data::runtime_types::Type, values::{DeserializationSeed, SerializationReadyValue, Value}, }; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ + identifier::IdentStr, + language_storage::{ModuleId, TypeTag}, value::{IdentifierMappingKind, MoveTypeLayout}, vm_status::StatusCode, }; -use serde::{ - de::{DeserializeSeed, Error as DeError}, - ser::Error as SerError, - Deserializer, Serialize, Serializer, -}; use std::cell::RefCell; -pub trait CustomDeserializer { - fn custom_deserialize<'d, D: Deserializer<'d>>( - &self, - deserializer: D, - kind: &IdentifierMappingKind, - layout: &MoveTypeLayout, - ) -> Result; -} - -pub trait CustomSerializer { - fn custom_serialize( +/// An extension to (de)serializer to lookup information about function values. +pub trait FunctionValueExtension { + /// Given the module's id and the function name, returns the parameter types of the + /// corresponding function, instantiated with the provided set of type tags. + fn get_function_arg_tys( &self, - serializer: S, - kind: &IdentifierMappingKind, - layout: &MoveTypeLayout, - id: DelayedFieldID, - ) -> Result; + module_id: &ModuleId, + function_name: &IdentStr, + ty_arg_tags: Vec, + ) -> PartialVMResult>; +} + +/// An extension to (de)serializer to lookup information about delayed fields. +pub(crate) struct DelayedFieldsExtension<'a> { + /// Number of delayed fields (de)serialized, capped. + pub(crate) delayed_fields_count: RefCell, + /// Optional mapping to ids/values. The mapping is used to replace ids with values at + /// serialization time and values with ids at deserialization time. If [None], ids and values + /// are serialized as is. + pub(crate) mapping: Option<&'a dyn ValueToIdentifierMapping>, +} + +impl<'a> DelayedFieldsExtension<'a> { + // Temporarily limit the number of delayed fields per resource, until proper charges are + // implemented. + // TODO[agg_v2](clean): + // Propagate up, so this value is controlled by the gas schedule version. + const MAX_DELAYED_FIELDS_PER_RESOURCE: usize = 10; + + /// Increments the delayed fields count, and checks if there are too many of them. If so, an + /// error is returned. + pub(crate) fn inc_and_check_delayed_fields_count(&self) -> PartialVMResult<()> { + *self.delayed_fields_count.borrow_mut() += 1; + if *self.delayed_fields_count.borrow() > Self::MAX_DELAYED_FIELDS_PER_RESOURCE { + return Err(PartialVMError::new(StatusCode::TOO_MANY_DELAYED_FIELDS) + .with_message("Too many Delayed fields in a single resource.".to_string())); + } + Ok(()) + } } -/// Custom (de)serializer which allows delayed values to be (de)serialized as -/// is. This means that when a delayed value is serialized, the deserialization -/// must construct the delayed value back. -pub struct RelaxedCustomSerDe { - delayed_fields_count: RefCell, +/// A (de)serializer context for a single Move [Value], containing optional extensions. If +/// extension is not provided, but required at (de)serialization time, (de)serialization fails. +pub struct ValueSerDeContext<'a> { + #[allow(dead_code)] + pub(crate) function_extension: Option<&'a dyn FunctionValueExtension>, + pub(crate) delayed_fields_extension: Option>, } -impl RelaxedCustomSerDe { +impl<'a> ValueSerDeContext<'a> { + /// Default (de)serializer that disallows delayed fields. #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { - delayed_fields_count: RefCell::new(0), + function_extension: None, + delayed_fields_extension: None, } } -} -// TODO[agg_v2](clean): propagate up, so this value is controlled by the gas schedule version. -// Temporarily limit the number of delayed fields per resource, -// until proper charges are implemented. -pub const MAX_DELAYED_FIELDS_PER_RESOURCE: usize = 10; - -impl CustomDeserializer for RelaxedCustomSerDe { - fn custom_deserialize<'d, D: Deserializer<'d>>( - &self, - deserializer: D, - kind: &IdentifierMappingKind, - layout: &MoveTypeLayout, - ) -> Result { - *self.delayed_fields_count.borrow_mut() += 1; + /// Custom (de)serializer such that supports lookup of the argument types of a function during + /// function value deserialization. + pub fn with_func_args_deserialization( + mut self, + function_extension: &'a dyn FunctionValueExtension, + ) -> Self { + self.function_extension = Some(function_extension); + self + } - let value = DeserializationSeed { - custom_deserializer: None::<&RelaxedCustomSerDe>, - layout, + /// Returns the same extension but without allowing the delayed fields. + pub(crate) fn clone_without_delayed_fields(&self) -> Self { + Self { + function_extension: self.function_extension, + delayed_fields_extension: None, } - .deserialize(deserializer)?; - let (id, _width) = - DelayedFieldID::try_from_move_value(layout, value, &()).map_err(|_| { - D::Error::custom(format!( - "Custom deserialization failed for {:?} with layout {}", - kind, layout - )) - })?; - Ok(Value::delayed_value(id)) } -} -impl CustomSerializer for RelaxedCustomSerDe { - fn custom_serialize( - &self, - serializer: S, - kind: &IdentifierMappingKind, - layout: &MoveTypeLayout, - id: DelayedFieldID, - ) -> Result { - *self.delayed_fields_count.borrow_mut() += 1; + /// Custom (de)serializer such that: + /// 1. when serializing, the delayed value is replaced with a concrete value instance and + /// serialized instead; + /// 2. when deserializing, the concrete value instance is replaced with a delayed id. + pub fn with_delayed_fields_replacement( + mut self, + mapping: &'a dyn ValueToIdentifierMapping, + ) -> Self { + self.delayed_fields_extension = Some(DelayedFieldsExtension { + delayed_fields_count: RefCell::new(0), + mapping: Some(mapping), + }); + self + } - let value = id.try_into_move_value(layout).map_err(|_| { - S::Error::custom(format!( - "Custom serialization failed for {:?} with layout {}", - kind, layout - )) - })?; - SerializationReadyValue { - custom_serializer: None::<&RelaxedCustomSerDe>, + /// Custom (de)serializer that allows delayed values to be (de)serialized as is. This means + /// that when a delayed value is serialized, the deserialization must construct the delayed + /// value back. + pub fn with_delayed_fields_serde(mut self) -> Self { + self.delayed_fields_extension = Some(DelayedFieldsExtension { + delayed_fields_count: RefCell::new(0), + mapping: None, + }); + self + } + + /// Serializes a [Value] based on the provided layout. For legacy reasons, all serialization + /// errors are mapped to [None]. If [DelayedFieldsExtension] is set, and there are too many + /// delayed fields, an error may be returned. + pub fn serialize( + self, + value: &Value, + layout: &MoveTypeLayout, + ) -> PartialVMResult>> { + let value = SerializationReadyValue { + ctx: &self, layout, value: &value.0, + }; + + match bcs::to_bytes(&value).ok() { + Some(bytes) => Ok(Some(bytes)), + None => { + // Check if the error is due to too many delayed fields. If so, to be compatible + // with the older implementation return an error. + if let Some(delayed_fields_extension) = self.delayed_fields_extension { + if delayed_fields_extension.delayed_fields_count.into_inner() + > DelayedFieldsExtension::MAX_DELAYED_FIELDS_PER_RESOURCE + { + return Err(PartialVMError::new(StatusCode::TOO_MANY_DELAYED_FIELDS) + .with_message( + "Too many Delayed fields in a single resource.".to_string(), + )); + } + } + Ok(None) + }, } - .serialize(serializer) } -} -pub fn deserialize_and_allow_delayed_values( - bytes: &[u8], - layout: &MoveTypeLayout, -) -> Option { - let native_deserializer = RelaxedCustomSerDe::new(); - let seed = DeserializationSeed { - custom_deserializer: Some(&native_deserializer), - layout, - }; - bcs::from_bytes_seed(seed, bytes).ok().filter(|_| { - // Should never happen, it should always fail first in serialize_and_allow_delayed_values - // so we can treat it as regular deserialization error. - native_deserializer.delayed_fields_count.into_inner() <= MAX_DELAYED_FIELDS_PER_RESOURCE - }) -} - -pub fn serialize_and_allow_delayed_values( - value: &Value, - layout: &MoveTypeLayout, -) -> PartialVMResult>> { - let native_serializer = RelaxedCustomSerDe::new(); - let value = SerializationReadyValue { - custom_serializer: Some(&native_serializer), - layout, - value: &value.0, - }; - bcs::to_bytes(&value) - .ok() - .map(|v| { - if native_serializer.delayed_fields_count.into_inner() - <= MAX_DELAYED_FIELDS_PER_RESOURCE - { - Ok(v) - } else { - Err(PartialVMError::new(StatusCode::TOO_MANY_DELAYED_FIELDS) - .with_message("Too many Delayed fields in a single resource.".to_string())) - } + /// Returns the serialized size of a [Value] with the associated layout. All errors are mapped + /// to [StatusCode::VALUE_SERIALIZATION_ERROR]. + pub fn serialized_size(self, value: &Value, layout: &MoveTypeLayout) -> PartialVMResult { + let value = SerializationReadyValue { + ctx: &self, + layout, + value: &value.0, + }; + bcs::serialized_size(&value).map_err(|e| { + PartialVMError::new(StatusCode::VALUE_SERIALIZATION_ERROR).with_message(format!( + "failed to compute serialized size of a value: {:?}", + e + )) }) - .transpose() -} + } -/// Returns the serialized size in bytes of a Move value, with compatible layout. -/// Note that the layout should match, as otherwise serialization fails. This -/// method explicitly allows having delayed values inside the passed in Move value -/// because their size is fixed and cannot change. -pub fn serialized_size_allowing_delayed_values( - value: &Value, - layout: &MoveTypeLayout, -) -> PartialVMResult { - let native_serializer = RelaxedCustomSerDe::new(); - let value = SerializationReadyValue { - custom_serializer: Some(&native_serializer), - layout, - value: &value.0, - }; - bcs::serialized_size(&value).map_err(|e| { - PartialVMError::new(StatusCode::VALUE_SERIALIZATION_ERROR).with_message(format!( - "failed to compute serialized size of a value: {:?}", - e - )) - }) + /// Deserializes the bytes using the provided layout into a Move [Value]. + pub fn deserialize(self, bytes: &[u8], layout: &MoveTypeLayout) -> Option { + let seed = DeserializationSeed { ctx: &self, layout }; + bcs::from_bytes_seed(seed, bytes).ok() + } } /// Allow conversion between values and identifiers (delayed values). For example, /// this trait can be implemented to fetch a concrete Move value from the global /// state based on the identifier stored inside a delayed value. pub trait ValueToIdentifierMapping { - type Identifier; - fn value_to_identifier( &self, // We need kind to distinguish between aggregators and snapshots @@ -185,116 +186,11 @@ pub trait ValueToIdentifierMapping { kind: &IdentifierMappingKind, layout: &MoveTypeLayout, value: Value, - ) -> PartialVMResult; + ) -> PartialVMResult; fn identifier_to_value( &self, layout: &MoveTypeLayout, - identifier: Self::Identifier, + identifier: DelayedFieldID, ) -> PartialVMResult; } - -/// Custom (de)serializer such that: -/// 1. when encountering a delayed value, ir uses its id to replace it with a concrete -/// value instance and serialize it instead; -/// 2. when deserializing, the concrete value instance is replaced with a delayed value. -pub struct CustomSerDeWithExchange<'a, I: From + ExtractWidth + ExtractUniqueIndex> { - mapping: &'a dyn ValueToIdentifierMapping, - delayed_fields_count: RefCell, -} - -impl<'a, I: From + ExtractWidth + ExtractUniqueIndex> CustomSerDeWithExchange<'a, I> { - pub fn new(mapping: &'a dyn ValueToIdentifierMapping) -> Self { - Self { - mapping, - delayed_fields_count: RefCell::new(0), - } - } -} - -impl<'a, I: From + ExtractWidth + ExtractUniqueIndex> CustomSerializer - for CustomSerDeWithExchange<'a, I> -{ - fn custom_serialize( - &self, - serializer: S, - _kind: &IdentifierMappingKind, - layout: &MoveTypeLayout, - sized_id: DelayedFieldID, - ) -> Result { - *self.delayed_fields_count.borrow_mut() += 1; - - let value = self - .mapping - .identifier_to_value(layout, sized_id.as_u64().into()) - .map_err(|e| S::Error::custom(format!("{}", e)))?; - SerializationReadyValue { - custom_serializer: None::<&RelaxedCustomSerDe>, - layout, - value: &value.0, - } - .serialize(serializer) - } -} - -impl<'a, I: From + ExtractWidth + ExtractUniqueIndex> CustomDeserializer - for CustomSerDeWithExchange<'a, I> -{ - fn custom_deserialize<'d, D: Deserializer<'d>>( - &self, - deserializer: D, - kind: &IdentifierMappingKind, - layout: &MoveTypeLayout, - ) -> Result { - *self.delayed_fields_count.borrow_mut() += 1; - - let value = DeserializationSeed { - custom_deserializer: None::<&RelaxedCustomSerDe>, - layout, - } - .deserialize(deserializer)?; - let id = self - .mapping - .value_to_identifier(kind, layout, value) - .map_err(|e| D::Error::custom(format!("{}", e)))?; - Ok(Value::delayed_value(DelayedFieldID::new_with_width( - id.extract_unique_index(), - id.extract_width(), - ))) - } -} - -pub fn deserialize_and_replace_values_with_ids + ExtractWidth + ExtractUniqueIndex>( - bytes: &[u8], - layout: &MoveTypeLayout, - mapping: &impl ValueToIdentifierMapping, -) -> Option { - let custom_deserializer = CustomSerDeWithExchange::new(mapping); - let seed = DeserializationSeed { - custom_deserializer: Some(&custom_deserializer), - layout, - }; - bcs::from_bytes_seed(seed, bytes).ok().filter(|_| { - // Should never happen, it should always fail first in serialize_and_allow_delayed_values - // so we can treat it as regular deserialization error. - custom_deserializer.delayed_fields_count.into_inner() <= MAX_DELAYED_FIELDS_PER_RESOURCE - }) -} - -pub fn serialize_and_replace_ids_with_values + ExtractWidth + ExtractUniqueIndex>( - value: &Value, - layout: &MoveTypeLayout, - mapping: &impl ValueToIdentifierMapping, -) -> Option> { - let custom_serializer = CustomSerDeWithExchange::new(mapping); - let value = SerializationReadyValue { - custom_serializer: Some(&custom_serializer), - layout, - value: &value.0, - }; - bcs::to_bytes(&value).ok().filter(|_| { - // Should never happen, it should always fail first in serialize_and_allow_delayed_values - // so we can treat it as regular deserialization error. - custom_serializer.delayed_fields_count.into_inner() <= MAX_DELAYED_FIELDS_PER_RESOURCE - }) -} diff --git a/third_party/move/move-vm/types/src/values/serialization_tests.rs b/third_party/move/move-vm/types/src/values/serialization_tests.rs index 6085a50183e2c..e1c48c082fcbe 100644 --- a/third_party/move/move-vm/types/src/values/serialization_tests.rs +++ b/third_party/move/move-vm/types/src/values/serialization_tests.rs @@ -5,7 +5,7 @@ use crate::{ delayed_values::delayed_field_id::DelayedFieldID, - value_serde::{serialize_and_allow_delayed_values, serialized_size_allowing_delayed_values}, + value_serde::ValueSerDeContext, values::{values_impl, Struct, Value}, }; use claims::{assert_err, assert_ok, assert_some}; @@ -85,10 +85,13 @@ fn enum_round_trip_vm_value() { )), ]; for value in good_values { - let blob = value - .simple_serialize(&layout) + let blob = ValueSerDeContext::new() + .serialize(&value, &layout) + .unwrap() .expect("serialization succeeds"); - let de_value = Value::simple_deserialize(&blob, &layout).expect("deserialization succeeds"); + let de_value = ValueSerDeContext::new() + .deserialize(&blob, &layout) + .expect("deserialization succeeds"); assert!( value.equals(&de_value).unwrap(), "roundtrip serialization succeeds" @@ -96,12 +99,18 @@ fn enum_round_trip_vm_value() { } let bad_tag_value = Value::struct_(Struct::pack_variant(3, [Value::u64(42)])); assert!( - bad_tag_value.simple_serialize(&layout).is_none(), + ValueSerDeContext::new() + .serialize(&bad_tag_value, &layout) + .unwrap() + .is_none(), "serialization fails" ); let bad_struct_value = Value::struct_(Struct::pack([Value::u64(42)])); assert!( - bad_struct_value.simple_serialize(&layout).is_none(), + ValueSerDeContext::new() + .serialize(&bad_struct_value, &layout) + .unwrap() + .is_none(), "serialization fails" ); } @@ -160,14 +169,17 @@ fn enum_rust_round_trip_vm_value() { RustEnum::BoolNumber(true, 13), ]; for (move_value, rust_value) in move_values.into_iter().zip(rust_values) { - let from_move = move_value - .simple_serialize(&layout) + let from_move = ValueSerDeContext::new() + .serialize(&move_value, &layout) + .unwrap() .expect("from move succeeds"); let to_rust = bcs::from_bytes::(&from_move).expect("to rust successful"); assert_eq!(to_rust, rust_value); let from_rust = bcs::to_bytes(&rust_value).expect("from rust succeeds"); - let to_move = Value::simple_deserialize(&from_rust, &layout).expect("to move succeeds"); + let to_move = ValueSerDeContext::new() + .deserialize(&from_rust, &layout) + .expect("to move succeeds"); assert!( to_move.equals(&move_value).unwrap(), "from rust to move failed" @@ -224,10 +236,13 @@ fn test_serialized_size() { ), ]; for (value, layout) in good_values_layouts_sizes { - let bytes = assert_some!(assert_ok!(serialize_and_allow_delayed_values( - &value, &layout - ))); - let size = assert_ok!(serialized_size_allowing_delayed_values(&value, &layout)); + let bytes = assert_some!(assert_ok!(ValueSerDeContext::new() + .with_delayed_fields_serde() + .serialize(&value, &layout))); + + let size = assert_ok!(ValueSerDeContext::new() + .with_delayed_fields_serde() + .serialized_size(&value, &layout)); assert_eq!(size, bytes.len()); } @@ -241,6 +256,8 @@ fn test_serialized_size() { (Value::u64(12), Native(Aggregator, Box::new(U64))), ]; for (value, layout) in bad_values_layouts_sizes { - assert_err!(serialized_size_allowing_delayed_values(&value, &layout)); + assert_err!(ValueSerDeContext::new() + .with_delayed_fields_serde() + .serialized_size(&value, &layout)); } } diff --git a/third_party/move/move-vm/types/src/values/value_prop_tests.rs b/third_party/move/move-vm/types/src/values/value_prop_tests.rs index cd612bac743df..24e2ed8b3422d 100644 --- a/third_party/move/move-vm/types/src/values/value_prop_tests.rs +++ b/third_party/move/move-vm/types/src/values/value_prop_tests.rs @@ -2,15 +2,15 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::values::{prop::layout_and_value_strategy, Value}; +use crate::{value_serde::ValueSerDeContext, values::prop::layout_and_value_strategy}; use move_core_types::value::MoveValue; use proptest::prelude::*; proptest! { #[test] fn serializer_round_trip((layout, value) in layout_and_value_strategy()) { - let blob = value.simple_serialize(&layout).expect("must serialize"); - let value_deserialized = Value::simple_deserialize(&blob, &layout).expect("must deserialize"); + let blob = ValueSerDeContext::new().serialize(&value, &layout).unwrap().expect("must serialize"); + let value_deserialized = ValueSerDeContext::new().deserialize(&blob, &layout).expect("must deserialize"); assert!(value.equals(&value_deserialized).unwrap()); let move_value = value.as_move_value(&layout); diff --git a/third_party/move/move-vm/types/src/values/values_impl.rs b/third_party/move/move-vm/types/src/values/values_impl.rs index 42fbe5d9278c3..fae84d1694a85 100644 --- a/third_party/move/move-vm/types/src/values/values_impl.rs +++ b/third_party/move/move-vm/types/src/values/values_impl.rs @@ -5,22 +5,29 @@ #![allow(clippy::arc_with_non_send_sync)] use crate::{ + delayed_values::delayed_field_id::{DelayedFieldID, TryFromMoveValue, TryIntoMoveValue}, loaded_data::runtime_types::Type, + value_serde::ValueSerDeContext, views::{ValueView, ValueVisitor}, }; use itertools::Itertools; use move_binary_format::{ errors::*, - file_format::{Constant, SignatureToken}, + file_format::{Constant, SignatureToken, VariantIndex}, }; use move_core_types::{ account_address::AccountAddress, effects::Op, gas_algebra::AbstractMemorySize, u256, value, - value::{MoveStructLayout, MoveTypeLayout}, + value::{MoveStruct, MoveStructLayout, MoveTypeLayout, MoveValue}, vm_status::{sub_status::NFE_VECTOR_ERROR_BASE, StatusCode}, }; +use serde::{ + de::{EnumAccess, Error as DeError, Unexpected, VariantAccess}, + ser::{Error as SerError, SerializeSeq, SerializeTuple, SerializeTupleVariant}, + Deserialize, +}; use std::{ cell::RefCell, cmp::Ordering, @@ -3625,57 +3632,12 @@ pub mod debug { * is to involve an explicit representation of the type layout. * **************************************************************************************/ -use crate::value_serde::{CustomDeserializer, CustomSerializer, RelaxedCustomSerDe}; -use move_binary_format::file_format::VariantIndex; -use serde::{ - de::{EnumAccess, Error as DeError, Unexpected, VariantAccess}, - ser::{Error as SerError, SerializeSeq, SerializeTuple, SerializeTupleVariant}, - Deserialize, -}; - -impl Value { - pub fn simple_deserialize(blob: &[u8], layout: &MoveTypeLayout) -> Option { - let seed = DeserializationSeed { - custom_deserializer: None::<&RelaxedCustomSerDe>, - layout, - }; - bcs::from_bytes_seed(seed, blob).ok() - } - - pub fn simple_serialize(&self, layout: &MoveTypeLayout) -> Option> { - bcs::to_bytes(&SerializationReadyValue { - custom_serializer: None::<&RelaxedCustomSerDe>, - layout, - value: &self.0, - }) - .ok() - } -} - -impl Struct { - pub fn simple_deserialize(blob: &[u8], layout: &MoveStructLayout) -> Option { - let seed = DeserializationSeed { - custom_deserializer: None::<&RelaxedCustomSerDe>, - layout, - }; - bcs::from_bytes_seed(seed, blob).ok() - } - - pub fn simple_serialize(&self, layout: &MoveStructLayout) -> Option> { - bcs::to_bytes(&SerializationReadyValue { - custom_serializer: None::<&RelaxedCustomSerDe>, - layout, - value: &self.fields, - }) - .ok() - } -} // Wrapper around value with additional information which can be used by the // serializer. -pub(crate) struct SerializationReadyValue<'c, 'l, 'v, L, V, C> { - // Allows to perform a custom serialization for delayed values. - pub(crate) custom_serializer: Option<&'c C>, +pub(crate) struct SerializationReadyValue<'c, 'l, 'v, L, V> { + // Contains the current (possibly custom) serialization context. + pub(crate) ctx: &'c ValueSerDeContext<'c>, // Layout for guiding serialization. pub(crate) layout: &'l L, // Value to serialize. @@ -3688,8 +3650,8 @@ fn invariant_violation(message: String) -> S::Error { ) } -impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize - for SerializationReadyValue<'c, 'l, 'v, MoveTypeLayout, ValueImpl, C> +impl<'c, 'l, 'v> serde::Serialize + for SerializationReadyValue<'c, 'l, 'v, MoveTypeLayout, ValueImpl> { fn serialize(&self, serializer: S) -> Result { use MoveTypeLayout as L; @@ -3708,7 +3670,7 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize // Structs. (L::Struct(struct_layout), ValueImpl::Container(Container::Struct(r))) => { (SerializationReadyValue { - custom_serializer: self.custom_serializer, + ctx: self.ctx, layout: struct_layout, value: &*r.borrow(), }) @@ -3732,7 +3694,7 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize let mut t = serializer.serialize_seq(Some(v.len()))?; for value in v.iter() { t.serialize_element(&SerializationReadyValue { - custom_serializer: self.custom_serializer, + ctx: self.ctx, layout, value, })?; @@ -3756,7 +3718,7 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize ))); } (SerializationReadyValue { - custom_serializer: self.custom_serializer, + ctx: self.ctx, layout: &L::Address, value: &v[0], }) @@ -3766,14 +3728,37 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize // Delayed values. For their serialization, we must have custom // serialization available, otherwise an error is returned. (L::Native(kind, layout), ValueImpl::DelayedFieldID { id }) => { - match self.custom_serializer { - Some(custom_serializer) => { - custom_serializer.custom_serialize(serializer, kind, layout, *id) + match &self.ctx.delayed_fields_extension { + Some(delayed_fields_extension) => { + delayed_fields_extension + .inc_and_check_delayed_fields_count() + .map_err(S::Error::custom)?; + + let value = match delayed_fields_extension.mapping { + Some(mapping) => mapping + .identifier_to_value(layout, *id) + .map_err(|e| S::Error::custom(format!("{}", e)))?, + None => id.try_into_move_value(layout).map_err(|_| { + S::Error::custom(format!( + "Custom serialization failed for {:?} with layout {}", + kind, layout + )) + })?, + }; + + // The resulting value should not contain any delayed fields, we disallow + // this by using a context without the delayed field extension. + let ctx = self.ctx.clone_without_delayed_fields(); + let value = SerializationReadyValue { + ctx: &ctx, + layout: layout.as_ref(), + value: &value.0, + }; + value.serialize(serializer) }, None => { - // If no custom serializer, it is not known how the - // delayed value should be serialized. So, just return - // an error. + // If no delayed field extension, it is not known how the delayed value + // should be serialized. So, just return an error. Err(invariant_violation::(format!( "no custom serializer for delayed value ({:?}) with layout {}", kind, layout @@ -3791,8 +3776,8 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize } } -impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize - for SerializationReadyValue<'c, 'l, 'v, MoveStructLayout, Vec, C> +impl<'c, 'l, 'v> serde::Serialize + for SerializationReadyValue<'c, 'l, 'v, MoveStructLayout, Vec> { fn serialize(&self, serializer: S) -> Result { let mut values = self.value.as_slice(); @@ -3818,7 +3803,7 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize variant_tag, variant_name, &SerializationReadyValue { - custom_serializer: self.custom_serializer, + ctx: self.ctx, layout: &variant_layouts[0], value: &values[0], }, @@ -3832,7 +3817,7 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize )?; for (layout, value) in variant_layouts.iter().zip(values) { t.serialize_field(&SerializationReadyValue { - custom_serializer: self.custom_serializer, + ctx: self.ctx, layout, value, })? @@ -3851,7 +3836,7 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize } for (field_layout, value) in field_layouts.iter().zip(values.iter()) { t.serialize_element(&SerializationReadyValue { - custom_serializer: self.custom_serializer, + ctx: self.ctx, layout: field_layout, value, })?; @@ -3863,17 +3848,14 @@ impl<'c, 'l, 'v, C: CustomSerializer> serde::Serialize // Seed used by deserializer to ensure there is information about the value // being deserialized. -pub(crate) struct DeserializationSeed<'c, L, C> { - // Allows to deserialize delayed values in the custom format using external - // deserializer. - pub(crate) custom_deserializer: Option<&'c C>, +pub(crate) struct DeserializationSeed<'c, L> { + // Holds extensions external to the deserializer. + pub(crate) ctx: &'c ValueSerDeContext<'c>, // Layout to guide deserialization. pub(crate) layout: L, } -impl<'d, 'c, C: CustomDeserializer> serde::de::DeserializeSeed<'d> - for DeserializationSeed<'c, &MoveTypeLayout, C> -{ +impl<'d, 'c> serde::de::DeserializeSeed<'d> for DeserializationSeed<'c, &MoveTypeLayout> { type Value = Value; fn deserialize>( @@ -3897,7 +3879,7 @@ impl<'d, 'c, C: CustomDeserializer> serde::de::DeserializeSeed<'d> // Structs. L::Struct(struct_layout) => { let seed = DeserializationSeed { - custom_deserializer: self.custom_deserializer, + ctx: self.ctx, layout: struct_layout, }; Ok(Value::struct_(seed.deserialize(deserializer)?)) @@ -3915,7 +3897,7 @@ impl<'d, 'c, C: CustomDeserializer> serde::de::DeserializeSeed<'d> L::Address => Value::vector_address(Vec::deserialize(deserializer)?), layout => { let seed = DeserializationSeed { - custom_deserializer: self.custom_deserializer, + ctx: self.ctx, layout, }; let vector = deserializer.deserialize_seq(VectorElementVisitor(seed))?; @@ -3927,9 +3909,34 @@ impl<'d, 'c, C: CustomDeserializer> serde::de::DeserializeSeed<'d> // Delayed values should always use custom deserialization. L::Native(kind, layout) => { - match self.custom_deserializer { - Some(native_deserializer) => { - native_deserializer.custom_deserialize(deserializer, kind, layout) + match &self.ctx.delayed_fields_extension { + Some(delayed_fields_extension) => { + delayed_fields_extension + .inc_and_check_delayed_fields_count() + .map_err(D::Error::custom)?; + + let value = DeserializationSeed { + ctx: &self.ctx.clone_without_delayed_fields(), + layout: layout.as_ref(), + } + .deserialize(deserializer)?; + let id = match delayed_fields_extension.mapping { + Some(mapping) => mapping + .value_to_identifier(kind, layout, value) + .map_err(|e| D::Error::custom(format!("{}", e)))?, + None => { + let (id, _) = + DelayedFieldID::try_from_move_value(layout, value, &()) + .map_err(|_| { + D::Error::custom(format!( + "Custom deserialization failed for {:?} with layout {}", + kind, layout + )) + })?; + id + }, + }; + Ok(Value::delayed_value(id)) }, None => { // If no custom deserializer, it is not known how the @@ -3949,9 +3956,7 @@ impl<'d, 'c, C: CustomDeserializer> serde::de::DeserializeSeed<'d> } } -impl<'d, C: CustomDeserializer> serde::de::DeserializeSeed<'d> - for DeserializationSeed<'_, &MoveStructLayout, C> -{ +impl<'d> serde::de::DeserializeSeed<'d> for DeserializationSeed<'_, &MoveStructLayout> { type Value = Struct; fn deserialize>( @@ -3962,7 +3967,7 @@ impl<'d, C: CustomDeserializer> serde::de::DeserializeSeed<'d> MoveStructLayout::Runtime(field_layouts) => { let fields = deserializer.deserialize_tuple( field_layouts.len(), - StructFieldVisitor(self.custom_deserializer, field_layouts), + StructFieldVisitor(self.ctx, field_layouts), )?; Ok(Struct::pack(fields)) }, @@ -3974,7 +3979,7 @@ impl<'d, C: CustomDeserializer> serde::de::DeserializeSeed<'d> let fields = deserializer.deserialize_enum( value::MOVE_ENUM_NAME, variant_names, - StructVariantVisitor(self.custom_deserializer, variants), + StructVariantVisitor(self.ctx, variants), )?; Ok(Struct::pack(fields)) }, @@ -3987,9 +3992,9 @@ impl<'d, C: CustomDeserializer> serde::de::DeserializeSeed<'d> } } -struct VectorElementVisitor<'c, 'l, C>(DeserializationSeed<'c, &'l MoveTypeLayout, C>); +struct VectorElementVisitor<'c, 'l>(DeserializationSeed<'c, &'l MoveTypeLayout>); -impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for VectorElementVisitor<'c, 'l, C> { +impl<'d, 'c, 'l> serde::de::Visitor<'d> for VectorElementVisitor<'c, 'l> { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -4002,7 +4007,7 @@ impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for VectorElement { let mut vals = Vec::new(); while let Some(elem) = seq.next_element_seed(DeserializationSeed { - custom_deserializer: self.0.custom_deserializer, + ctx: self.0.ctx, layout: self.0.layout, })? { vals.push(elem.0) @@ -4011,9 +4016,9 @@ impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for VectorElement } } -struct StructFieldVisitor<'c, 'l, C>(Option<&'c C>, &'l [MoveTypeLayout]); +struct StructFieldVisitor<'c, 'l>(&'c ValueSerDeContext<'c>, &'l [MoveTypeLayout]); -impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for StructFieldVisitor<'c, 'l, C> { +impl<'d, 'c, 'l> serde::de::Visitor<'d> for StructFieldVisitor<'c, 'l> { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -4027,7 +4032,7 @@ impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for StructFieldVi let mut val = Vec::new(); for (i, field_layout) in self.1.iter().enumerate() { if let Some(elem) = seq.next_element_seed(DeserializationSeed { - custom_deserializer: self.0, + ctx: self.0, layout: field_layout, })? { val.push(elem) @@ -4039,9 +4044,9 @@ impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for StructFieldVi } } -struct StructVariantVisitor<'c, 'l, C>(Option<&'c C>, &'l [Vec]); +struct StructVariantVisitor<'c, 'l>(&'c ValueSerDeContext<'c>, &'l [Vec]); -impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for StructVariantVisitor<'c, 'l, C> { +impl<'d, 'c, 'l> serde::de::Visitor<'d> for StructVariantVisitor<'c, 'l> { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -4065,7 +4070,7 @@ impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for StructVariant }, 1 => { values.push(rest.newtype_variant_seed(DeserializationSeed { - custom_deserializer: self.0, + ctx: self.0, layout: &fields[0], })?); Ok(values) @@ -4091,7 +4096,7 @@ impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for StructVariant // Note this is actually directly serialized as u16, but this is equivalent // to MoveTypeLayout::U16, so we can reuse the custom deserializer seed. let variant_tag = match seq.next_element_seed(DeserializationSeed { - custom_deserializer: self.0, + ctx: self.0, layout: &MoveTypeLayout::U16, })? { Some(elem) => { @@ -4117,7 +4122,7 @@ impl<'d, 'c, 'l, C: CustomDeserializer> serde::de::Visitor<'d> for StructVariant // Based on the validated variant tag, we know the field types for (i, field_layout) in self.1[variant_tag].iter().enumerate() { if let Some(elem) = seq.next_element_seed(DeserializationSeed { - custom_deserializer: self.0, + ctx: self.0, layout: field_layout, })? { val.push(elem) @@ -4162,7 +4167,7 @@ impl Value { pub fn deserialize_constant(constant: &Constant) -> Option { let layout = Self::constant_sig_token_to_layout(&constant.type_)?; - Value::simple_deserialize(&constant.data, &layout) + ValueSerDeContext::new().deserialize(&constant.data, &layout) } } @@ -4583,9 +4588,6 @@ pub mod prop { } } -use crate::delayed_values::delayed_field_id::DelayedFieldID; -use move_core_types::value::{MoveStruct, MoveValue}; - impl ValueImpl { pub fn as_move_value(&self, layout: &MoveTypeLayout) -> MoveValue { use MoveTypeLayout as L; diff --git a/third_party/move/testing-infra/transactional-test-runner/src/framework.rs b/third_party/move/testing-infra/transactional-test-runner/src/framework.rs index 5ca8a4b0763e8..2158f95b6a2b7 100644 --- a/third_party/move/testing-infra/transactional-test-runner/src/framework.rs +++ b/third_party/move/testing-infra/transactional-test-runner/src/framework.rs @@ -50,6 +50,7 @@ use move_model::{ }; use move_symbol_pool::Symbol; use move_vm_runtime::session::SerializedReturnValues; +use move_vm_types::value_serde::ValueSerDeContext; use once_cell::sync::Lazy; use regex::Regex; use std::{ @@ -621,8 +622,7 @@ fn display_return_values(return_values: SerializedReturnValues) -> Option>(); @@ -641,7 +641,8 @@ fn display_return_values(return_values: SerializedReturnValues) -> Option>(); let printed = values diff --git a/third_party/move/tools/move-unit-test/src/extensions.rs b/third_party/move/tools/move-unit-test/src/extensions.rs index f9fd6d78203c4..05b1058540ef4 100644 --- a/third_party/move/tools/move-unit-test/src/extensions.rs +++ b/third_party/move/tools/move-unit-test/src/extensions.rs @@ -13,6 +13,7 @@ use move_table_extension::NativeTableContext; use move_vm_runtime::native_extensions::NativeContextExtensions; #[cfg(feature = "table-extension")] use move_vm_test_utils::BlankStorage; +use move_vm_types::value_serde::FunctionValueExtension; use once_cell::sync::Lazy; use std::{fmt::Write, sync::Mutex}; @@ -50,22 +51,32 @@ pub(crate) fn new_extensions<'a>() -> NativeContextExtensions<'a> { /// Print the change sets for available native context extensions. #[allow(unused)] -pub(crate) fn print_change_sets(_w: &mut W, extensions: &mut NativeContextExtensions) { +pub(crate) fn print_change_sets( + w: &mut W, + native_extensions: &mut NativeContextExtensions, + function_value_extension: &impl FunctionValueExtension, +) { #[cfg(feature = "table-extension")] - print_table_extension(_w, extensions); + print_table_extension(w, native_extensions, function_value_extension); } // ============================================================================================= // Table Extensions #[cfg(feature = "table-extension")] -fn create_table_extension(extensions: &mut NativeContextExtensions) { - extensions.add(NativeTableContext::new([0u8; 32], &*DUMMY_RESOLVER)); +fn create_table_extension(native_extensions: &mut NativeContextExtensions) { + native_extensions.add(NativeTableContext::new([0u8; 32], &*DUMMY_RESOLVER)); } #[cfg(feature = "table-extension")] -fn print_table_extension(w: &mut W, extensions: &mut NativeContextExtensions) { - let cs = extensions.remove::().into_change_set(); +fn print_table_extension( + w: &mut W, + native_extensions: &mut NativeContextExtensions, + function_value_extension: &impl FunctionValueExtension, +) { + let cs = native_extensions + .remove::() + .into_change_set(function_value_extension); if let Ok(cs) = cs { if !cs.new_tables.is_empty() { writeln!( diff --git a/third_party/move/tools/move-unit-test/src/test_runner.rs b/third_party/move/tools/move-unit-test/src/test_runner.rs index 6c5e839411fc6..03ea88601103e 100644 --- a/third_party/move/tools/move-unit-test/src/test_runner.rs +++ b/third_party/move/tools/move-unit-test/src/test_runner.rs @@ -27,7 +27,7 @@ use move_vm_runtime::{ move_vm::MoveVM, native_extensions::NativeContextExtensions, native_functions::NativeFunctionTable, - AsUnsyncModuleStorage, RuntimeEnvironment, + AsFunctionValueExtension, AsUnsyncModuleStorage, RuntimeEnvironment, }; use move_vm_test_utils::InMemoryStorage; use rayon::prelude::*; @@ -86,6 +86,7 @@ fn print_resources_and_extensions( cs: &ChangeSet, extensions: &mut NativeContextExtensions, storage: &InMemoryStorage, + natives: &NativeFunctionTable, ) -> Result { use std::fmt::Write; let mut buf = String::new(); @@ -104,7 +105,10 @@ fn print_resources_and_extensions( } } - extensions::print_change_sets(&mut buf, extensions); + let runtime_environment = RuntimeEnvironment::new(natives.clone()); + let module_storage = storage.as_unsync_module_storage(runtime_environment); + let function_value_extension = module_storage.as_function_value_extension(); + extensions::print_change_sets(&mut buf, extensions, &function_value_extension); Ok(buf) } @@ -339,6 +343,7 @@ impl SharedTestingConfig { &changeset, &mut extensions, &self.starting_storage_state, + &self.native_function_table, ) .ok() }) diff --git a/types/src/transaction/mod.rs b/types/src/transaction/mod.rs index 138dd8e81496c..5c2560926d9bc 100644 --- a/types/src/transaction/mod.rs +++ b/types/src/transaction/mod.rs @@ -68,9 +68,6 @@ pub use change_set::ChangeSet; pub use module::{Module, ModuleBundle}; pub use move_core_types::transaction_argument::TransactionArgument; use move_core_types::vm_status::AbortLocation; -use move_vm_types::delayed_values::delayed_field_id::{ - ExtractUniqueIndex, ExtractWidth, TryFromMoveValue, TryIntoMoveValue, -}; pub use multisig::{ExecutionError, Multisig, MultisigTransactionPayload}; use once_cell::sync::OnceCell; pub use script::{ @@ -2063,22 +2060,6 @@ pub trait BlockExecutableTransaction: Sync + Send + Clone + 'static { + Debug + DeserializeOwned + Serialize; - /// Delayed field identifier type. - type Identifier: PartialOrd - + Ord - + Send - + Sync - + Clone - + Hash - + Eq - + Debug - + Copy - + From - + From<(u32, u32)> - + ExtractUniqueIndex - + ExtractWidth - + TryIntoMoveValue - + TryFromMoveValue; type Value: Send + Sync + Debug + Clone + TransactionWrite; type Event: Send + Sync + Debug + Clone + TransactionEvent; diff --git a/types/src/transaction/signature_verified_transaction.rs b/types/src/transaction/signature_verified_transaction.rs index cd35b573b42c1..6d405aa54d8fb 100644 --- a/types/src/transaction/signature_verified_transaction.rs +++ b/types/src/transaction/signature_verified_transaction.rs @@ -9,7 +9,6 @@ use crate::{ }; use aptos_crypto::{hash::CryptoHash, HashValue}; use move_core_types::{account_address::AccountAddress, language_storage::StructTag}; -use move_vm_types::delayed_values::delayed_field_id::DelayedFieldID; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -61,7 +60,6 @@ impl SignatureVerifiedTransaction { impl BlockExecutableTransaction for SignatureVerifiedTransaction { type Event = ContractEvent; - type Identifier = DelayedFieldID; type Key = StateKey; type Tag = StructTag; type Value = WriteOp;