diff --git a/Cargo.lock b/Cargo.lock index 25542975564b22..dac1d5feb15abd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1361,10 +1361,11 @@ dependencies = [ "move-binary-format", "move-bytecode-verifier", "move-core-types", - "reqwest 0.11.23", + "reqwest 0.12.5", "serde", "serde_bytes", "serde_json", + "tsify-next", "wasm-bindgen", "wasm-bindgen-futures", ] @@ -8680,6 +8681,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "goldenfile" version = "1.6.0" @@ -9443,6 +9457,22 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.6" @@ -14179,7 +14209,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.28", "hyper-rustls 0.24.2", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -14218,19 +14248,24 @@ dependencies = [ "async-compression", "base64 0.22.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", "futures-util", + "h2 0.4.5", "http 1.1.0", "http-body 1.0.0", "http-body-util", "hyper 1.4.1", "hyper-rustls 0.27.2", + "hyper-tls 0.6.0", "hyper-util", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -14243,7 +14278,9 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", + "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.0", "tokio-util 0.7.10", "tower-service", @@ -15064,6 +15101,17 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "serde_json" version = "1.0.114" @@ -16964,6 +17012,31 @@ dependencies = [ "termcolor", ] +[[package]] +name = "tsify-next" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4a645dca4ee0800f5ab60ce166deba2db6a0315de795a2691e138a3d55d756" +dependencies = [ + "gloo-utils", + "serde", + "serde_json", + "tsify-next-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-next-macros" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d5c06f8a51d759bb58129e30b2631739e7e1e4579fad1f30ac09a6c88e488a6" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.48", +] + [[package]] name = "tui" version = "0.19.0" diff --git a/aptos-move/dynamic-transaction-composer/Cargo.toml b/aptos-move/dynamic-transaction-composer/Cargo.toml index 69efe46c324bf0..6f24ba215ef556 100644 --- a/aptos-move/dynamic-transaction-composer/Cargo.toml +++ b/aptos-move/dynamic-transaction-composer/Cargo.toml @@ -27,6 +27,7 @@ reqwest = { version = "*", features = ["blocking"] } serde = { workspace = true } serde_bytes = { workspace = true } serde_json = { workspace = true } +tsify-next = "0.5.4" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4.42" diff --git a/aptos-move/dynamic-transaction-composer/src/builder.rs b/aptos-move/dynamic-transaction-composer/src/builder.rs index 89fdfa183657ae..33a5d6e537d89d 100644 --- a/aptos-move/dynamic-transaction-composer/src/builder.rs +++ b/aptos-move/dynamic-transaction-composer/src/builder.rs @@ -1,103 +1,138 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::{ArgumentOperation, BatchArgument, BatchedFunctionCall, PreviousResult}; +//! Code to generate compiled Move script from a series of individual Move calls. +//! +//! Defined a struct called `TransactionComposer`. With `TransactionComposer`, you will be able to add new Move calls +//! to it via the `add_batched_call` api. The code will perform a light weight type check to make sure the ability and +//! type safety of the Move calls will be held. After adding the calls, you will be able to consume the `TransactionComposer` +//! and get the corresponding `CompiledScript` via the `generate_batched_calls` api. + +use crate::{ + helpers::{import_signature, import_type_tag, Script}, + ArgumentOperation, APTOS_SCRIPT_BUILDER_KEY, +}; +#[cfg(test)] +use crate::{BatchArgument, BatchedFunctionCall}; use anyhow::{anyhow, bail, Result}; use move_binary_format::{ - access::ModuleAccess, + access::ScriptAccess, binary_views::BinaryIndexedView, - file_format::{Ability, AbilitySet, FunctionDefinition, FunctionHandle, SignatureToken}, + builders::CompiledScriptBuilder, + errors::PartialVMResult, + file_format::{ + empty_script, Bytecode, FunctionHandleIndex, FunctionInstantiation, + FunctionInstantiationIndex, Signature, SignatureToken, TableIndex, + }, CompiledModule, }; use move_core_types::{ - identifier::{IdentStr, Identifier}, - language_storage::{ModuleId, StructTag, TypeTag}, + identifier::Identifier, + language_storage::{ModuleId, TypeTag}, + metadata::Metadata, + transaction_argument::TransactionArgument, }; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{collections::BTreeMap, str::FromStr}; +use tsify_next::Tsify; use wasm_bindgen::prelude::*; #[wasm_bindgen] -/// Arguments for each function. -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -pub enum BatchArgumentType { - Raw, - Signer, - PreviousResult, +#[derive(Tsify, Clone, Serialize, Deserialize, PartialEq, Eq, Debug)] +pub struct AllocatedLocal { + op_type: ArgumentOperation, + is_parameter: bool, + local_idx: u16, +} + +#[derive(Tsify, Clone, Debug, Serialize, Deserialize)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub enum Argument { + Raw(Vec), + Signer(u16), + Local(AllocatedLocal), } -#[wasm_bindgen(js_name = BatchArgument)] -/// Arguments for each function. Wasm bindgen only support C-style enum so use option to work around. -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -pub struct BatchArgumentWASM { - ty: BatchArgumentType, - raw: Option>, - signer: Option, - previous_result: Option, +#[derive(Clone, Debug)] +#[wasm_bindgen] +struct BuilderCall { + type_args: Vec, + call_idx: FunctionHandleIndex, + arguments: Vec, + // Assigned index of returned values in the local pool. + returns: Vec, + + #[cfg(test)] + type_tags: Vec, } +#[derive(Clone, Debug)] #[wasm_bindgen] -pub struct BatchedFunctionCallBuilder { - calls: Vec, - num_signers: u16, +pub struct TransactionComposer { modules: BTreeMap, - // (nth move call, position in the returned call results) -> (Type of the value, Ability of the value). - type_of_returns: BTreeMap<(u16, u16), (TypeTag, AbilitySet)>, + builder: CompiledScriptBuilder, + calls: Vec, + parameters: Vec>, + locals_availability: Vec, + + locals_ty: Vec, + parameters_ty: Vec, + + #[cfg(test)] + signer_count: u16, } #[wasm_bindgen] -impl BatchedFunctionCallBuilder { +impl TransactionComposer { // Create a builder with one signer needed for script. This should be the default configuration. pub fn single_signer() -> Self { + let mut script = empty_script(); + script.code.code = vec![]; + + let builder = CompiledScriptBuilder::new(script); Self { - calls: vec![], - num_signers: 1, modules: BTreeMap::new(), - type_of_returns: BTreeMap::new(), + builder, + calls: vec![], + parameters: vec![], + locals_availability: vec![], + locals_ty: vec![], + parameters_ty: vec![SignatureToken::Reference(Box::new(SignatureToken::Signer))], + + #[cfg(test)] + signer_count: 1, } } // Create a builder with one signer needed for script. This would be needed for multi-agent transaction where multiple signers are present. pub fn multi_signer(signer_count: u16) -> Self { + let mut script = empty_script(); + script.code.code = vec![]; + + let builder = CompiledScriptBuilder::new(script); Self { - calls: vec![], - num_signers: signer_count, modules: BTreeMap::new(), - type_of_returns: BTreeMap::new(), + builder, + calls: vec![], + parameters: vec![], + locals_availability: vec![], + locals_ty: vec![], + parameters_ty: std::iter::repeat(SignatureToken::Reference(Box::new( + SignatureToken::Signer, + ))) + .take(signer_count.into()) + .collect(), + + #[cfg(test)] + signer_count, } } - #[wasm_bindgen(js_name = add_batched_call)] - // Add a call to the script builder. Script builder will check the type compatibility of the call and returns error. - pub fn add_batched_call_wasm( - &mut self, - module: String, - function: String, - ty_args: Vec, - args: Vec, - ) -> Result, String> { - Ok(self - .add_batched_call( - module, - function, - ty_args, - args.into_iter().map(BatchArgument::from).collect(), - ) - .map_err(|err| format!("{:?}", err))? - .into_iter() - .map(|arg| arg.into()) - .collect()) - } - // Consume the builder and generate a serialized script with calls in the builder. - pub fn generate_batched_calls(self) -> Result, String> { - crate::codegen::generate_script_from_batched_calls( - &self.calls, - self.num_signers, - &self.modules, - ) - .map_err(|err| format!("{:?}", err)) + pub fn generate_batched_calls(self, with_metadata: bool) -> Result, String> { + self.generate_batched_calls_impl(with_metadata) + .map_err(|e| e.to_string()) } // Load up a module from a remote endpoint. Will need to invoke this function prior to the call. @@ -106,225 +141,82 @@ impl BatchedFunctionCallBuilder { api_url: String, module_name: String, ) -> Result<(), JsValue> { - self.load_module_impl(api_url, module_name) + let module_id = ModuleId::from_str(&module_name) + .map_err(|err| JsValue::from(format!("Invalid module name: {:?}", err)))?; + self.load_module_impl(api_url.as_str(), module_id) .await .map_err(|err| JsValue::from(format!("{:?}", err))) } -} -// Given a module, return the handle and definition of a provided function name -fn find_function<'a>( - map: &'a BTreeMap, - module_id: &ModuleId, - func_name: &IdentStr, -) -> anyhow::Result<( - &'a CompiledModule, - &'a FunctionHandle, - &'a FunctionDefinition, -)> { - if let Some(module) = map.get(module_id) { - for def in module.function_defs() { - let handle = module.function_handle_at(def.function); - if module.identifier_at(handle.name) == func_name { - return Ok((module, handle, def)); - } - } + pub async fn load_type_tag( + &mut self, + api_url: String, + type_tag: String, + ) -> Result<(), JsValue> { + let type_tag = TypeTag::from_str(&type_tag) + .map_err(|err| JsValue::from(format!("Invalid type name: {:?}", err)))?; + self.load_type_tag_impl(api_url.as_str(), &type_tag) + .await + .map_err(|err| JsValue::from(format!("{:?}", err))) } - anyhow::bail!("{}::{} doesn't exist on chain", module_id, func_name) -} -impl BatchedFunctionCallBuilder { - // Generate the return values for a function call. Those returned values could be passed - // as an arugment to a future function call. - fn return_values( + #[wasm_bindgen(js_name = add_batched_call)] + pub fn add_batched_call_wasm( &mut self, - module_id: &ModuleId, - func_name: &IdentStr, - ty_args: &[TypeTag], - ) -> anyhow::Result> { - if self.calls.is_empty() { - bail!("No calls exists in the builder yet"); - } - let (module, handle, _) = find_function(&self.modules, module_id, func_name)?; - let mut returns = vec![]; - for (idx, sig) in module.signature_at(handle.return_).0.iter().enumerate() { - let call_idx = (self.calls.len() - 1) as u16; - let return_idx = idx as u16; - let ty = Self::type_tag_from_signature(module, sig, ty_args)?; - let ability = BinaryIndexedView::Module(module) - .abilities(sig, &handle.type_parameters) - .unwrap(); - - self.type_of_returns - .insert((call_idx, return_idx), (ty, ability)); - - returns.push(BatchArgument::PreviousResult(PreviousResult { - call_idx, - return_idx, - operation_type: ArgumentOperation::Move, - })); - } - Ok(returns) + module: String, + function: String, + ty_args: Vec, + args: Vec, + ) -> Result, JsValue> { + self.add_batched_call(module, function, ty_args, args) + .map_err(|err| JsValue::from(format!("{:?}", err))) } +} - // Check if the arguments could be used to invoke a function. - fn check_parameters( - &self, - module_id: &ModuleId, - func_name: &IdentStr, - params: &[BatchArgument], - ty_args: &[TypeTag], - ) -> anyhow::Result<()> { - let (module, handle, def) = find_function(&self.modules, module_id, func_name)?; +impl TransactionComposer { + pub fn insert_module(&mut self, module: CompiledModule) { + self.modules.insert(module.self_id(), module); + } - if !def.visibility.is_public() { - bail!( - "{}::{} function is not public and thus not invokable in a transaction", - module_id, - func_name - ); - } + fn check_argument_compatibility( + &mut self, + argument: &AllocatedLocal, + expected_ty: &SignatureToken, + ) -> anyhow::Result<()> { + let local_ty = if argument.is_parameter { + self.parameters_ty[argument.local_idx as usize].clone() + } else { + self.locals_ty[argument.local_idx as usize].clone() + }; - let param_tys = module.signature_at(handle.parameters); - if param_tys.0.len() != params.len() { - bail!( - "{}::{} function argument length mismatch, expected: {:?}, got: {:?}", - module_id, - func_name, - param_tys.len(), - params.len() - ); - } - for (tok, param) in param_tys.0.iter().zip(params.iter()) { - if let BatchArgument::PreviousResult(PreviousResult { - call_idx, - return_idx, - operation_type, - }) = param - { - let (ty, ability) = self - .type_of_returns - .get(&(*call_idx, *return_idx)) - .ok_or_else(|| anyhow!("Type of return not found"))?; - match operation_type { - ArgumentOperation::Copy => { - if !ability.has_ability(Ability::Copy) { - bail!("Copying value of non-copyable type: {}", ty); - } - let expected_ty = Self::type_tag_from_signature(module, tok, ty_args)?; - if &expected_ty != ty { - bail!( - "Type mismatch in arguments. Expected: {}, got: {}", - expected_ty, - ty - ); - } - }, - ArgumentOperation::Move => { - let expected_ty = Self::type_tag_from_signature(module, tok, ty_args)?; - if &expected_ty != ty { - bail!( - "Type mismatch in arguments. Expected: {}, got: {}", - expected_ty, - ty - ); - } - }, - ArgumentOperation::Borrow => { - let expected_ty = match tok { - SignatureToken::Reference(inner_ty) => { - Self::type_tag_from_signature(module, inner_ty, ty_args)? - }, - _ => bail!("Type mismatch in arguments. Got reference of {}", ty), - }; - - if &expected_ty != ty { - bail!( - "Type mismatch in arguments. Expected: {}, got: {}", - expected_ty, - ty - ); - } - }, - ArgumentOperation::BorrowMut => { - let expected_ty = match tok { - SignatureToken::MutableReference(inner_ty) => { - Self::type_tag_from_signature(module, inner_ty, ty_args)? - }, - _ => bail!( - "Type mismatch in arguments. Got mutable reference of {}", - ty - ), - }; - if &expected_ty != ty { - bail!( - "Type mismatch in arguments. Expected: {}, got: {}", - expected_ty, - ty - ); - } - }, + let ty = match argument.op_type { + ArgumentOperation::Borrow => SignatureToken::Reference(Box::new(local_ty)), + ArgumentOperation::BorrowMut => SignatureToken::MutableReference(Box::new(local_ty)), + ArgumentOperation::Copy => { + let ability = BinaryIndexedView::Script(self.builder.as_script()) + .abilities(&local_ty, &[]) + .map_err(|_| anyhow!("Failed to calculate ability for type"))?; + if !ability.has_copy() { + bail!("Trying to copy move values that does NOT have copy ability"); } - } - } - Ok(()) - } - - // Normalize signature token into type tag so they can be compared in different modules. - fn type_tag_from_signature( - module: &CompiledModule, - tok: &SignatureToken, - ty_args: &[TypeTag], - ) -> anyhow::Result { - Ok(match tok { - SignatureToken::Address => TypeTag::Address, - SignatureToken::Bool => TypeTag::Bool, - SignatureToken::U8 => TypeTag::U8, - SignatureToken::U16 => TypeTag::U16, - SignatureToken::U32 => TypeTag::U32, - SignatureToken::U64 => TypeTag::U64, - SignatureToken::U128 => TypeTag::U128, - SignatureToken::U256 => TypeTag::U256, - SignatureToken::Signer => TypeTag::Signer, - SignatureToken::MutableReference(_) | SignatureToken::Reference(_) => { - // TODO: This forbids returning references from a call, which is not necessary. Remove the constraint later. - bail!("Unexpected reference type in the return value") - }, - SignatureToken::Struct(idx) => { - let st_handle = module.struct_handle_at(*idx); - let module_handle = module.module_handle_at(st_handle.module); - TypeTag::Struct(Box::new(StructTag { - address: *module.address_identifier_at(module_handle.address), - module: module.identifier_at(module_handle.name).to_owned(), - name: module.identifier_at(st_handle.name).to_owned(), - type_args: vec![], - })) + local_ty }, - SignatureToken::Vector(tok) => TypeTag::Vector(Box::new( - Self::type_tag_from_signature(module, tok, ty_args)?, - )), - SignatureToken::StructInstantiation(idx, toks) => { - let st_handle = module.struct_handle_at(*idx); - let module_handle = module.module_handle_at(st_handle.module); - TypeTag::Struct(Box::new(StructTag { - address: *module.address_identifier_at(module_handle.address), - module: module.identifier_at(module_handle.name).to_owned(), - name: module.identifier_at(st_handle.name).to_owned(), - type_args: toks - .iter() - .map(|tok| Self::type_tag_from_signature(module, tok, ty_args)) - .collect::>()?, - })) + ArgumentOperation::Move => { + if !argument.is_parameter { + if self.locals_availability[argument.local_idx as usize] { + self.locals_availability[argument.local_idx as usize] = false; + } else { + bail!("Trying to use a Move value that has already been moved"); + } + } + local_ty }, - SignatureToken::TypeParameter(idx) => ty_args - .get(*idx as usize) - .ok_or_else(|| anyhow!("Type parameter access out of bound"))? - .clone(), - }) - } + }; - pub fn insert_module(&mut self, module: CompiledModule) { - self.modules.insert(module.self_id(), module); + if &ty != expected_ty { + bail!("Type mismatch when passing arugments around"); + } + Ok(()) } pub fn add_batched_call( @@ -332,30 +224,199 @@ impl BatchedFunctionCallBuilder { module: String, function: String, ty_args: Vec, - args: Vec, - ) -> anyhow::Result> { + args: Vec, + ) -> anyhow::Result> { let ty_args = ty_args .iter() .map(|s| TypeTag::from_str(s)) .collect::>>()?; let module = ModuleId::from_str(&module)?; let function = Identifier::new(function)?; - self.check_parameters(&module, &function, &args, &ty_args)?; - self.calls.push(BatchedFunctionCall { - module: module.clone(), - function: function.clone(), - ty_args: ty_args.clone(), - args, + + let target_module = match self.modules.get(&module) { + Some(module) => module, + None => { + bail!("Module {} is not yet loaded", module); + }, + }; + + let call_idx = self + .builder + .import_call_by_name(function.as_ident_str(), target_module)?; + let call_type_params = ty_args + .iter() + .map(|ty| import_type_tag(&mut self.builder, ty, &self.modules)) + .collect::>>()?; + + let subst_mapping = call_type_params + .iter() + .enumerate() + .map(|(idx, ty)| (idx as u16, ty.clone())) + .collect::>(); + + let mut arguments = vec![]; + let expected_args_ty = { + let script = self.builder.as_script(); + let func = script.function_handle_at(call_idx); + if script.signature_at(func.parameters).0.len() != args.len() { + bail!( + "Function {}::{} argument call size mismatch", + module, + function + ); + } + script + .signature_at(func.parameters) + .0 + .iter() + .map(|ty| ty.instantiate(&subst_mapping)) + .collect::>() + }; + + for (arg, ty) in args.into_iter().zip(expected_args_ty) { + match arg { + Argument::Local(i) => { + self.check_argument_compatibility(&i, &ty)?; + arguments.push(i) + }, + Argument::Signer(i) => arguments.push(AllocatedLocal { + op_type: ArgumentOperation::Copy, + is_parameter: true, + local_idx: i, + }), + Argument::Raw(bytes) => { + let new_local_idx = self.parameters_ty.len() as u16; + self.parameters_ty.push(ty); + self.parameters.push(bytes); + arguments.push(AllocatedLocal { + op_type: ArgumentOperation::Move, + is_parameter: true, + local_idx: new_local_idx, + }) + }, + } + } + + let mut returns = vec![]; + + let expected_returns_ty = { + let func = self.builder.as_script().function_handle_at(call_idx); + self.builder + .as_script() + .signature_at(func.return_) + .0 + .iter() + .map(|ty| ty.instantiate(&subst_mapping)) + .collect::>() + }; + + for return_ty in expected_returns_ty { + let local_idx = self.locals_ty.len() as u16; + self.locals_ty.push(return_ty); + self.locals_availability.push(true); + returns.push(local_idx); + } + + self.calls.push(BuilderCall { + type_args: call_type_params, + call_idx, + arguments, + returns: returns.clone(), + #[cfg(test)] + type_tags: ty_args, }); - self.return_values(&module, &function, &ty_args) + Ok(returns + .into_iter() + .map(|idx| { + Argument::Local(AllocatedLocal { + op_type: ArgumentOperation::Move, + is_parameter: false, + local_idx: idx, + }) + }) + .collect()) } - async fn load_module_impl( - &mut self, - api_url: String, - module_name: String, - ) -> anyhow::Result<()> { - let module_id = ModuleId::from_str(&module_name).unwrap(); + fn check_drop_at_end(&self) -> anyhow::Result<()> { + let view = BinaryIndexedView::Script(self.builder.as_script()); + for (available, ty) in self.locals_availability.iter().zip(self.locals_ty.iter()) { + if *available { + let ability = view + .abilities(ty, &[]) + .map_err(|_| anyhow!("Failed to calculate ability for type"))?; + if !ability.has_drop() { + bail!("Unused non-droppable Move value"); + } + } + } + Ok(()) + } + + fn generate_batched_calls_impl(self, with_metadata: bool) -> anyhow::Result> { + self.check_drop_at_end()?; + let parameters_count = self.parameters_ty.len() as u16; + let mut script = self.builder.into_script(); + for call in self.calls { + for arg in call.arguments { + script.code.code.push(arg.to_instruction(parameters_count)?); + } + + // Calls + if call.type_args.is_empty() { + script.code.code.push(Bytecode::Call(call.call_idx)); + } else { + if script.function_instantiations.len() >= TableIndex::MAX as usize { + bail!("Too many function instantiations"); + } + let fi_idx = + FunctionInstantiationIndex(script.function_instantiations.len() as u16); + + let type_parameters = import_signature(&mut script, Signature(call.type_args))?; + + script.function_instantiations.push(FunctionInstantiation { + handle: call.call_idx, + type_parameters, + }); + script.code.code.push(Bytecode::CallGeneric(fi_idx)); + } + + // Storing return values + for arg in call.returns { + script + .code + .code + .push(Bytecode::StLoc((arg + parameters_count) as u8)); + } + } + script.code.code.push(Bytecode::Ret); + script.parameters = import_signature(&mut script, Signature(self.parameters_ty))?; + script.code.locals = import_signature(&mut script, Signature(self.locals_ty))?; + + move_bytecode_verifier::verify_script(&script).map_err(|err| anyhow!("{:?}", err))?; + if with_metadata { + script.metadata.push(Metadata { + key: APTOS_SCRIPT_BUILDER_KEY.to_owned(), + value: vec![], + }); + } + let mut bytes = vec![]; + script + .serialize(&mut bytes) + .map_err(|err| anyhow!("Failed to serialize script: {:?}", err))?; + + Ok(bcs::to_bytes(&Script { + code: bytes, + ty_args: vec![], + args: self + .parameters + .into_iter() + .map(TransactionArgument::Serialized) + .collect(), + }) + .unwrap()) + } + + async fn load_module_impl(&mut self, api_url: &str, module_id: ModuleId) -> anyhow::Result<()> { let url = format!( "{}/{}/{}/{}/{}", api_url, "accounts", &module_id.address, "module", &module_id.name @@ -392,66 +453,137 @@ impl BatchedFunctionCallBuilder { } } + async fn load_type_tag_impl( + &mut self, + api_url: &str, + type_tag: &TypeTag, + ) -> anyhow::Result<()> { + match type_tag { + TypeTag::Struct(s) => { + self.load_module_impl(api_url, s.module_id()).await?; + for ty in s.type_args.iter() { + Box::pin(self.load_type_tag_impl(api_url, ty)).await?; + } + Ok(()) + }, + TypeTag::Vector(v) => Box::pin(self.load_type_tag_impl(api_url, &v)).await, + _ => Ok(()), + } + } + #[cfg(test)] - pub(crate) fn calls(&self) -> &[BatchedFunctionCall] { - &self.calls + pub(crate) fn assert_decompilation_eq(&self, calls: &[BatchedFunctionCall]) { + let script = self.builder.as_script(); + + assert_eq!(self.calls.len(), calls.len()); + for (lhs_call, rhs_call) in self.calls.iter().zip(calls.iter()) { + let fh = script.function_handle_at(lhs_call.call_idx); + assert_eq!( + script.identifier_at(fh.name), + rhs_call.function.as_ident_str() + ); + assert_eq!( + script.module_id_for_handle(script.module_handle_at(fh.module)), + rhs_call.module + ); + assert_eq!(lhs_call.type_tags, rhs_call.ty_args); + assert_eq!(lhs_call.arguments.len(), rhs_call.args.len()); + for (lhs_arg, rhs_arg) in lhs_call.arguments.iter().zip(rhs_call.args.iter()) { + self.check_argument_eq(lhs_arg, rhs_arg); + } + } + } + + #[cfg(test)] + fn check_argument_eq(&self, lhs: &AllocatedLocal, rhs: &BatchArgument) { + use crate::PreviousResult; + + match rhs { + BatchArgument::PreviousResult(PreviousResult { + call_idx, + return_idx, + operation_type, + }) => { + let local_idx = self.calls[*call_idx as usize].returns[*return_idx as usize]; + assert_eq!(lhs, &AllocatedLocal { + local_idx, + op_type: operation_type.clone(), + is_parameter: false, + }); + }, + BatchArgument::Raw(input) => { + assert!(lhs.is_parameter); + assert_eq!(lhs.op_type, ArgumentOperation::Move); + assert_eq!( + &self.parameters[(lhs.local_idx - self.signer_count) as usize], + input + ); + }, + BatchArgument::Signer(idx) => { + assert_eq!(lhs, &AllocatedLocal { + op_type: ArgumentOperation::Copy, + is_parameter: true, + local_idx: *idx + }) + }, + } } } -#[wasm_bindgen(js_class = BatchArgument)] -impl BatchArgumentWASM { +impl AllocatedLocal { + fn to_instruction(&self, parameter_size: u16) -> anyhow::Result { + let local_idx = if self.is_parameter { + self.local_idx + } else { + parameter_size + .checked_add(self.local_idx) + .ok_or_else(|| anyhow!("Too many locals"))? + }; + if local_idx >= u8::MAX as u16 { + bail!("Too many locals"); + }; + Ok(match self.op_type { + ArgumentOperation::Borrow => Bytecode::ImmBorrowLoc(local_idx as u8), + ArgumentOperation::BorrowMut => Bytecode::MutBorrowLoc(local_idx as u8), + ArgumentOperation::Move => Bytecode::MoveLoc(local_idx as u8), + ArgumentOperation::Copy => Bytecode::CopyLoc(local_idx as u8), + }) + } +} + +#[wasm_bindgen] +impl Argument { pub fn new_bytes(bytes: Vec) -> Self { - Self { - ty: BatchArgumentType::Raw, - raw: Some(bytes), - signer: None, - previous_result: None, - } + Argument::Raw(bytes) } pub fn new_signer(signer_idx: u16) -> Self { - Self { - ty: BatchArgumentType::Signer, - raw: None, - signer: Some(signer_idx), - previous_result: None, - } + Argument::Signer(signer_idx) } - pub fn borrow(&self) -> Result { + pub fn borrow(&self) -> Result { self.change_op_type(ArgumentOperation::Borrow) } - pub fn borrow_mut(&self) -> Result { + pub fn borrow_mut(&self) -> Result { self.change_op_type(ArgumentOperation::BorrowMut) } - pub fn copy(&self) -> Result { + pub fn copy(&self) -> Result { self.change_op_type(ArgumentOperation::Copy) } - fn change_op_type( - &self, - operation_type: ArgumentOperation, - ) -> Result { - match (&self.ty, &self.previous_result) { - ( - BatchArgumentType::PreviousResult, - Some(PreviousResult { - call_idx, - return_idx, - operation_type: _, - }), - ) => Ok(BatchArgumentWASM { - ty: BatchArgumentType::PreviousResult, - raw: None, - signer: None, - previous_result: Some(PreviousResult { - call_idx: *call_idx, - return_idx: *return_idx, - operation_type, - }), - }), + fn change_op_type(&self, operation_type: ArgumentOperation) -> Result { + match &self { + Argument::Local(AllocatedLocal { + op_type: _, + is_parameter, + local_idx, + }) => Ok(Argument::Local(AllocatedLocal { + op_type: operation_type, + is_parameter: *is_parameter, + local_idx: *local_idx, + })), _ => Err( "Unexpected argument type, can only borrow from previous function results" .to_string(), @@ -459,40 +591,3 @@ impl BatchArgumentWASM { } } } - -impl From for BatchArgument { - fn from(value: BatchArgumentWASM) -> Self { - match value.ty { - BatchArgumentType::PreviousResult => { - BatchArgument::PreviousResult(value.previous_result.unwrap()) - }, - BatchArgumentType::Raw => BatchArgument::Raw(value.raw.unwrap()), - BatchArgumentType::Signer => BatchArgument::Signer(value.signer.unwrap()), - } - } -} - -impl From for BatchArgumentWASM { - fn from(value: BatchArgument) -> BatchArgumentWASM { - match value { - BatchArgument::PreviousResult(r) => BatchArgumentWASM { - ty: BatchArgumentType::PreviousResult, - raw: None, - signer: None, - previous_result: Some(r), - }, - BatchArgument::Raw(r) => BatchArgumentWASM { - ty: BatchArgumentType::Raw, - raw: Some(r), - signer: None, - previous_result: None, - }, - BatchArgument::Signer(r) => BatchArgumentWASM { - ty: BatchArgumentType::Signer, - raw: None, - signer: Some(r), - previous_result: None, - }, - } - } -} diff --git a/aptos-move/dynamic-transaction-composer/src/codegen.rs b/aptos-move/dynamic-transaction-composer/src/codegen.rs deleted file mode 100644 index e040c29f56d50b..00000000000000 --- a/aptos-move/dynamic-transaction-composer/src/codegen.rs +++ /dev/null @@ -1,380 +0,0 @@ -// Copyright © Aptos Foundation -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - ArgumentOperation, BatchArgument, BatchedFunctionCall, PreviousResult, APTOS_SCRIPT_BUILDER_KEY, -}; -use move_binary_format::{ - access::{ModuleAccess, ScriptAccess}, - builders::CompiledScriptBuilder, - errors::{PartialVMError, PartialVMResult}, - file_format::*, -}; -use move_core_types::{ - identifier::IdentStr, - language_storage::{ModuleId, TypeTag}, - metadata::Metadata, - transaction_argument::TransactionArgument, - vm_status::StatusCode, -}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -#[derive(Default)] -struct Context { - script: CompiledScript, - // Arguments to the compiled script. - args: Vec>, - // (nth move call) -> (type arguments for the call) - ty_args: Vec>, - // (nth move call) -> (starting position of its return values in the local pool) - returned_val_to_local: Vec, - // (nth move call) -> (starting position of its arguments in the argument pool) - args_to_local: Vec, - // (nth move call) -> how many return values it got - return_counts: Vec, - // Number of signers at the beginning of arugment pool - signer_counts: u16, - // Local signature pool of the script. We will use this pool to replace the pool in CompiledScript. - locals: Vec, - // Parameter signature pool of the script. We will use this pool to replace the pool in CompiledScript. - parameters: Vec, -} - -fn instantiate( - token: &SignatureToken, - subst_mapping: &BTreeMap, -) -> SignatureToken { - use SignatureToken::*; - - match token { - Bool => Bool, - U8 => U8, - U16 => U16, - U32 => U32, - U64 => U64, - U128 => U128, - U256 => U256, - Address => Address, - Signer => Signer, - Vector(ty) => Vector(Box::new(instantiate(ty, subst_mapping))), - Struct(idx) => Struct(*idx), - StructInstantiation(idx, struct_type_args) => StructInstantiation( - *idx, - struct_type_args - .iter() - .map(|ty| instantiate(ty, subst_mapping)) - .collect(), - ), - Reference(ty) => Reference(Box::new(instantiate(ty, subst_mapping))), - MutableReference(ty) => MutableReference(Box::new(instantiate(ty, subst_mapping))), - TypeParameter(idx) => subst_mapping.get(idx).unwrap().clone(), - } -} - -impl Context { - // Add signers to the beginning of the script's parameter pool - fn add_signers(&mut self, signer_counts: u16) -> PartialVMResult<()> { - for _ in 0..signer_counts { - self.parameters - .push(SignatureToken::Reference(Box::new(SignatureToken::Signer))); - } - self.signer_counts = signer_counts; - Ok(()) - } - - // Iterate through the batched calls and convert all arguments in the BatchedFunctionCall into - // arguments to the script. - fn allocate_parameters(&mut self, calls: &[BatchedFunctionCall]) -> PartialVMResult<()> { - let mut total_args_count = self.parameters.len() as u16; - for call in calls { - self.args_to_local.push(total_args_count); - for arg in call.args.iter() { - // Only the serialized arguments need to be allocated as input to the script. - // Return values from previous calls will be stored in the locals. - if let BatchArgument::Raw(bytes) = arg { - if total_args_count == TableIndex::MAX { - return Err(PartialVMError::new(StatusCode::INDEX_OUT_OF_BOUNDS)); - } - self.args.push(bytes.clone()); - total_args_count += 1; - } - } - } - Ok(()) - } - - // Generate instructions for invoking one batched call. - fn compile_batched_call( - &mut self, - current_call_idx: usize, - call: &BatchedFunctionCall, - func_id: FunctionHandleIndex, - ) -> PartialVMResult<()> { - let func_handle = self.script.function_handle_at(func_id).clone(); - - // Allocate generic type parameters to the script - // - // Creates mapping from local type argument index to type argument index in the script - let mut subst_mapping = BTreeMap::new(); - for (idx, ty_param) in self.ty_args[current_call_idx].iter().enumerate() { - subst_mapping.insert(idx as u16, ty_param.clone()); - } - - // Instructions for loading parameters - for (idx, arg) in call.args.iter().enumerate() { - match arg { - BatchArgument::PreviousResult(PreviousResult { - call_idx, - return_idx, - operation_type, - }) => { - if let Some(idx) = self.returned_val_to_local.get(*call_idx as usize) { - if *return_idx >= self.return_counts[*call_idx as usize] { - return Err(PartialVMError::new(StatusCode::INDEX_OUT_OF_BOUNDS)); - } - // Locals will need to be offset by: - // 1. The parameters to the script (signer + arguments) - // 2. All the previously returned values prior to the call. - let local_idx = - (*idx + *return_idx + self.args.len() as u16 + self.signer_counts) - as u8; - self.script.code.code.push(match operation_type { - ArgumentOperation::Borrow => Bytecode::ImmBorrowLoc(local_idx), - ArgumentOperation::BorrowMut => Bytecode::MutBorrowLoc(local_idx), - ArgumentOperation::Move => Bytecode::MoveLoc(local_idx), - ArgumentOperation::Copy => Bytecode::CopyLoc(local_idx), - }); - } - }, - BatchArgument::Signer(i) => { - // Signer will always appear at the beginning of locals. No offsets needed. - self.script.code.code.push(Bytecode::CopyLoc(*i as u8)); - }, - BatchArgument::Raw(_) => { - // A new serialized argument to the script. Push the type to the parameter pool of the script. - let type_ = &self.script.signature_at(func_handle.parameters).0[idx]; - let inst_ty = if call.ty_args.is_empty() { - type_.clone() - } else { - instantiate(type_, &subst_mapping) - }; - let param_idx = self.parameters.len() as u8; - self.parameters.push(inst_ty); - self.script.code.code.push(Bytecode::MoveLoc(param_idx)); - }, - } - } - - // Make function call - if call.ty_args.is_empty() { - self.script.code.code.push(Bytecode::Call(func_id)); - } else { - if self.script.function_instantiations.len() >= TableIndex::MAX as usize { - return Err(PartialVMError::new(StatusCode::INDEX_OUT_OF_BOUNDS)); - } - let fi_idx = - FunctionInstantiationIndex(self.script.function_instantiations.len() as u16); - - // Generic call type arguments will always be instantiated with script's type parameters. - let inst_sig = self.ty_args[current_call_idx].clone(); - - let type_parameters = self.import_signature(Signature(inst_sig))?; - self.script - .function_instantiations - .push(FunctionInstantiation { - handle: func_id, - type_parameters, - }); - self.script.code.code.push(Bytecode::CallGeneric(fi_idx)); - } - - self.returned_val_to_local.push(self.locals.len() as u16); - self.return_counts - .push(self.script.signature_at(func_handle.return_).0.len() as u16); - - // Instruction for storing return values - let ret_locals = self - .script - .signature_at(func_handle.return_) - .0 - .iter() - .map(|ret| { - if call.ty_args.is_empty() { - ret.clone() - } else { - instantiate(ret, &subst_mapping) - } - }) - .collect::>(); - - for ret_ty in ret_locals { - let local_idx = self.locals.len() + self.args.len() + self.signer_counts as usize; - if local_idx >= u8::MAX as usize { - return Err(PartialVMError::new(StatusCode::INDEX_OUT_OF_BOUNDS)); - } - self.locals.push(ret_ty); - self.script.code.code.push(Bytecode::StLoc(local_idx as u8)); - } - - Ok(()) - } - - fn import_signature(&mut self, sig: Signature) -> PartialVMResult { - Ok(SignatureIndex(match self - .script - .signatures() - .iter() - .position(|item| item == &sig) - { - Some(idx) => idx, - None => { - let idx = self.script.signatures().len(); - if idx >= TableIndex::MAX as usize { - return Err(PartialVMError::new(StatusCode::INDEX_OUT_OF_BOUNDS)); - } - self.script.signatures.push(sig); - idx - }, - } as u16)) - } -} - -#[derive(Serialize, Deserialize)] -pub(crate) struct Script { - #[serde(with = "serde_bytes")] - pub code: Vec, - pub ty_args: Vec, - pub args: Vec, -} - -// Given a module, return the handle idx of the named struct -fn find_struct<'a>( - map: &'a BTreeMap, - module_id: &ModuleId, - struct_name: &IdentStr, -) -> PartialVMResult<(&'a CompiledModule, StructHandleIndex)> { - if let Some(module) = map.get(module_id) { - for (idx, handle) in module.struct_handles().iter().enumerate() { - if module.identifier_at(handle.name) == struct_name { - return Ok((module, StructHandleIndex::new(idx as TableIndex))); - } - } - return Err( - PartialVMError::new(StatusCode::LOOKUP_FAILED).with_message(format!( - "Struct {}::{} doesn't yet exist in the cache", - module_id, struct_name - )), - ); - } - Err( - PartialVMError::new(StatusCode::LOOKUP_FAILED).with_message(format!( - "Module {} doesn't yet exist in the cache", - module_id - )), - ) -} - -fn import_type_tag( - script_builder: &mut CompiledScriptBuilder, - type_tag: &TypeTag, - module_resolver: &BTreeMap, -) -> PartialVMResult { - Ok(match type_tag { - TypeTag::Address => SignatureToken::Address, - TypeTag::U8 => SignatureToken::U8, - TypeTag::U16 => SignatureToken::U16, - TypeTag::U32 => SignatureToken::U32, - TypeTag::U64 => SignatureToken::U64, - TypeTag::U128 => SignatureToken::U128, - TypeTag::U256 => SignatureToken::U256, - TypeTag::Bool => SignatureToken::Bool, - TypeTag::Signer => SignatureToken::Signer, - TypeTag::Vector(t) => SignatureToken::Vector(Box::new(import_type_tag( - script_builder, - t, - module_resolver, - )?)), - TypeTag::Struct(s) => { - let (module, handle_idx) = - find_struct(module_resolver, &s.module_id(), s.name.as_ident_str())?; - let struct_idx = script_builder.import_struct(module, handle_idx)?; - if s.type_args.is_empty() { - SignatureToken::Struct(struct_idx) - } else { - SignatureToken::StructInstantiation( - struct_idx, - s.type_args - .iter() - .map(|ty| import_type_tag(script_builder, ty, module_resolver)) - .collect::>>()?, - ) - } - }, - }) -} - -#[allow(clippy::field_reassign_with_default)] -pub fn generate_script_from_batched_calls( - calls: &[BatchedFunctionCall], - signer_count: u16, - module_resolver: &BTreeMap, -) -> PartialVMResult> { - let mut context = Context::default(); - let mut script_builder = CompiledScriptBuilder::new(CompiledScript::default()); - let call_idxs = calls - .iter() - .map(|call| { - let target_module = match module_resolver.get(&call.module) { - Some(module) => module, - None => { - return Err(PartialVMError::new(StatusCode::LOOKUP_FAILED) - .with_message(format!("Module {} is not yet loaded", call.module))) - }, - }; - script_builder.import_call_by_name(&call.function, target_module) - }) - .collect::>>()?; - let call_type_params = calls - .iter() - .map(|call| { - call.ty_args - .iter() - .map(|ty| import_type_tag(&mut script_builder, ty, module_resolver)) - .collect::>>() - }) - .collect::>>()?; - context.script = script_builder.into_script(); - context.ty_args = call_type_params; - context.add_signers(signer_count)?; - context.allocate_parameters(calls)?; - for (call_count, (call, fh_idx)) in calls.iter().zip(call_idxs.into_iter()).enumerate() { - context.compile_batched_call(call_count, call, fh_idx)?; - } - context.script.code.code.push(Bytecode::Ret); - context.script.parameters = context.import_signature(Signature(context.parameters.clone()))?; - context.script.code.locals = context.import_signature(Signature(context.locals.clone()))?; - move_bytecode_verifier::verify_script(&context.script).map_err(|err| { - err.to_partial() - .append_message_with_separator(',', format!("{:?}", context.script)) - })?; - context.script.metadata.push(Metadata { - key: APTOS_SCRIPT_BUILDER_KEY.to_owned(), - value: vec![], - }); - let mut bytes = vec![]; - context - .script - .serialize(&mut bytes) - .map_err(|_| PartialVMError::new(StatusCode::CODE_DESERIALIZATION_ERROR))?; - Ok(bcs::to_bytes(&Script { - code: bytes, - ty_args: vec![], - args: context - .args - .into_iter() - .map(TransactionArgument::Serialized) - .collect(), - }) - .unwrap()) -} diff --git a/aptos-move/dynamic-transaction-composer/src/decompiler.rs b/aptos-move/dynamic-transaction-composer/src/decompiler.rs index 5c044e7bf56d29..1efc68a8df94b6 100644 --- a/aptos-move/dynamic-transaction-composer/src/decompiler.rs +++ b/aptos-move/dynamic-transaction-composer/src/decompiler.rs @@ -1,6 +1,11 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 +//! Code to decode the series of individual Move calls from a compiled script. +//! +//! Core api is `generate_batched_call_payload` which takes in a compiled Move script and render it as a +//! series of `BatchedFunctionCall`. + use crate::{ ArgumentOperation, BatchArgument, BatchedFunctionCall, PreviousResult, APTOS_SCRIPT_BUILDER_KEY, }; @@ -260,7 +265,7 @@ pub fn generate_batched_call_payload( pub fn generate_batched_call_payload_serialized( script: &[u8], ) -> anyhow::Result> { - let script = bcs::from_bytes::(script)?; + let script = bcs::from_bytes::(script)?; generate_batched_call_payload(&script.code, &script.args) } diff --git a/aptos-move/dynamic-transaction-composer/src/helpers.rs b/aptos-move/dynamic-transaction-composer/src/helpers.rs new file mode 100644 index 00000000000000..cbc4696adf05c4 --- /dev/null +++ b/aptos-move/dynamic-transaction-composer/src/helpers.rs @@ -0,0 +1,110 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use move_binary_format::{ + access::{ModuleAccess, ScriptAccess}, + builders::CompiledScriptBuilder, + errors::{PartialVMError, PartialVMResult}, + file_format::*, +}; +use move_core_types::{ + identifier::IdentStr, + language_storage::{ModuleId, TypeTag}, + transaction_argument::TransactionArgument, + vm_status::StatusCode, +}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Serialize, Deserialize)] +pub(crate) struct Script { + #[serde(with = "serde_bytes")] + pub code: Vec, + pub ty_args: Vec, + pub args: Vec, +} + +pub(crate) fn import_type_tag( + script_builder: &mut CompiledScriptBuilder, + type_tag: &TypeTag, + module_resolver: &BTreeMap, +) -> PartialVMResult { + Ok(match type_tag { + TypeTag::Address => SignatureToken::Address, + TypeTag::U8 => SignatureToken::U8, + TypeTag::U16 => SignatureToken::U16, + TypeTag::U32 => SignatureToken::U32, + TypeTag::U64 => SignatureToken::U64, + TypeTag::U128 => SignatureToken::U128, + TypeTag::U256 => SignatureToken::U256, + TypeTag::Bool => SignatureToken::Bool, + TypeTag::Signer => SignatureToken::Signer, + TypeTag::Vector(t) => SignatureToken::Vector(Box::new(import_type_tag( + script_builder, + t, + module_resolver, + )?)), + TypeTag::Struct(s) => { + let (module, handle_idx) = + find_struct(module_resolver, &s.module_id(), s.name.as_ident_str())?; + let struct_idx = script_builder.import_struct(module, handle_idx)?; + if s.type_args.is_empty() { + SignatureToken::Struct(struct_idx) + } else { + SignatureToken::StructInstantiation( + struct_idx, + s.type_args + .iter() + .map(|ty| import_type_tag(script_builder, ty, module_resolver)) + .collect::>>()?, + ) + } + }, + }) +} + +// Given a module, return the handle idx of the named struct +pub(crate) fn find_struct<'a>( + map: &'a BTreeMap, + module_id: &ModuleId, + struct_name: &IdentStr, +) -> PartialVMResult<(&'a CompiledModule, StructHandleIndex)> { + if let Some(module) = map.get(module_id) { + for (idx, handle) in module.struct_handles().iter().enumerate() { + if module.identifier_at(handle.name) == struct_name { + return Ok((module, StructHandleIndex::new(idx as TableIndex))); + } + } + return Err( + PartialVMError::new(StatusCode::LOOKUP_FAILED).with_message(format!( + "Struct {}::{} doesn't yet exist in the cache", + module_id, struct_name + )), + ); + } + Err( + PartialVMError::new(StatusCode::LOOKUP_FAILED).with_message(format!( + "Module {} doesn't yet exist in the cache", + module_id + )), + ) +} + +pub(crate) fn import_signature( + script: &mut CompiledScript, + sig: Signature, +) -> PartialVMResult { + Ok(SignatureIndex( + match script.signatures().iter().position(|item| item == &sig) { + Some(idx) => idx, + None => { + let idx = script.signatures().len(); + if idx >= TableIndex::MAX as usize { + return Err(PartialVMError::new(StatusCode::INDEX_OUT_OF_BOUNDS)); + } + script.signatures.push(sig); + idx + }, + } as u16, + )) +} diff --git a/aptos-move/dynamic-transaction-composer/src/lib.rs b/aptos-move/dynamic-transaction-composer/src/lib.rs index 461293040daf51..d327bac28f6e6d 100644 --- a/aptos-move/dynamic-transaction-composer/src/lib.rs +++ b/aptos-move/dynamic-transaction-composer/src/lib.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 pub use crate::{ - builder::{BatchArgumentWASM, BatchedFunctionCallBuilder}, + builder::TransactionComposer, decompiler::{generate_batched_call_payload, generate_batched_call_payload_wasm}, }; use move_core_types::{ @@ -11,11 +11,13 @@ use move_core_types::{ language_storage::{ModuleId, TypeTag}, }; use serde::{Deserialize, Serialize}; +use tsify_next::Tsify; use wasm_bindgen::prelude::wasm_bindgen; mod builder; -mod codegen; mod decompiler; +mod helpers; + #[cfg(test)] pub mod tests; @@ -29,7 +31,8 @@ pub struct PreviousResult { operation_type: ArgumentOperation, } -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] pub enum BatchArgument { Raw(Vec), Signer(u16), diff --git a/aptos-move/dynamic-transaction-composer/src/tests/mod.rs b/aptos-move/dynamic-transaction-composer/src/tests/mod.rs index 687df46b535682..01be0fd8702a33 100644 --- a/aptos-move/dynamic-transaction-composer/src/tests/mod.rs +++ b/aptos-move/dynamic-transaction-composer/src/tests/mod.rs @@ -1,7 +1,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -use crate::{BatchArgumentWASM, BatchedFunctionCallBuilder}; +use crate::{builder::Argument, TransactionComposer}; use aptos_types::{ state_store::state_key::StateKey, transaction::{ExecutionStatus, TransactionStatus}, @@ -13,7 +13,7 @@ use move_core_types::{ }; use std::{path::PathBuf, str::FromStr}; -fn load_module(builder: &mut BatchedFunctionCallBuilder, harness: &MoveHarness, module_name: &str) { +fn load_module(builder: &mut TransactionComposer, harness: &MoveHarness, module_name: &str) { let module = ModuleId::from_str(module_name).unwrap(); let bytes = harness .read_state_value_bytes(&StateKey::module_id(&module)) @@ -27,27 +27,25 @@ fn simple_builder() { let alice = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap()); let bob = h.new_account_at(AccountAddress::from_hex_literal("0xface").unwrap()); - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::aptos_account"); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::aptos_account".to_string(), "transfer".to_string(), vec![], vec![ - BatchArgumentWASM::new_signer(0), - BatchArgumentWASM::new_bytes( + Argument::new_signer(0), + Argument::new_bytes( MoveValue::Address(*bob.address()) .simple_serialize() .unwrap(), ), - BatchArgumentWASM::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), ], ) .unwrap(); - - let expected_calls = builder.calls().to_vec(); - let script = builder.generate_batched_calls().unwrap(); + let script = builder.clone().generate_batched_calls(true).unwrap(); let txn = alice .transaction() @@ -61,9 +59,8 @@ fn simple_builder() { assert_eq!(h.read_aptos_balance(bob.address()), 1_000_000_000_000_010); - assert_eq!( - crate::decompiler::generate_batched_call_payload_serialized(&script).unwrap(), - expected_calls + builder.assert_decompilation_eq( + &crate::decompiler::generate_batched_call_payload_serialized(&script).unwrap(), ); } @@ -73,23 +70,23 @@ fn chained_deposit() { let alice = h.new_account_at(AccountAddress::from_hex_literal("0xcafe").unwrap()); let bob = h.new_account_at(AccountAddress::from_hex_literal("0xface").unwrap()); - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::coin"); load_module(&mut builder, &h, "0x1::aptos_coin"); load_module(&mut builder, &h, "0x1::primary_fungible_store"); let mut returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::coin".to_string(), "withdraw".to_string(), vec!["0x1::aptos_coin::AptosCoin".to_string()], vec![ - BatchArgumentWASM::new_signer(0), - BatchArgumentWASM::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), + Argument::new_signer(0), + Argument::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), ], ) .unwrap(); let mut returns_2 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::coin".to_string(), "coin_to_fungible_asset".to_string(), vec!["0x1::aptos_coin::AptosCoin".to_string()], @@ -97,12 +94,12 @@ fn chained_deposit() { ) .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::primary_fungible_store".to_string(), "deposit".to_string(), vec![], vec![ - BatchArgumentWASM::new_bytes( + Argument::new_bytes( MoveValue::Address(*bob.address()) .simple_serialize() .unwrap(), @@ -112,8 +109,7 @@ fn chained_deposit() { ) .unwrap(); - let expected_calls = builder.calls().to_vec(); - let script = builder.generate_batched_calls().unwrap(); + let script = builder.clone().generate_batched_calls(true).unwrap(); let txn = alice .transaction() @@ -127,9 +123,8 @@ fn chained_deposit() { ); assert_eq!(h.read_aptos_balance(bob.address()), 1_000_000_000_000_010); - assert_eq!( - crate::decompiler::generate_batched_call_payload_serialized(&script).unwrap(), - expected_calls + builder.assert_decompilation_eq( + &crate::decompiler::generate_batched_call_payload_serialized(&script).unwrap(), ); } @@ -138,28 +133,28 @@ fn chained_deposit_mismatch() { let mut h = MoveHarness::new(); let bob = h.new_account_at(AccountAddress::from_hex_literal("0xface").unwrap()); - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::coin"); load_module(&mut builder, &h, "0x1::aptos_coin"); load_module(&mut builder, &h, "0x1::primary_fungible_store"); let mut returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::coin".to_string(), "withdraw".to_string(), vec!["0x1::aptos_coin::AptosCoin".to_string()], vec![ - BatchArgumentWASM::new_signer(0), - BatchArgumentWASM::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), + Argument::new_signer(0), + Argument::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), ], ) .unwrap(); assert!(builder - .add_batched_call_wasm( + .add_batched_call( "0x1::primary_fungible_store".to_string(), "deposit".to_string(), vec![], vec![ - BatchArgumentWASM::new_bytes( + Argument::new_bytes( MoveValue::Address(*bob.address()) .simple_serialize() .unwrap(), @@ -176,23 +171,23 @@ fn chained_deposit_invalid_copy() { let mut h = MoveHarness::new(); let bob = h.new_account_at(AccountAddress::from_hex_literal("0xface").unwrap()); - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::coin"); load_module(&mut builder, &h, "0x1::aptos_coin"); load_module(&mut builder, &h, "0x1::primary_fungible_store"); let mut returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::coin".to_string(), "withdraw".to_string(), vec!["0x1::aptos_coin::AptosCoin".to_string()], vec![ - BatchArgumentWASM::new_signer(0), - BatchArgumentWASM::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), + Argument::new_signer(0), + Argument::new_bytes(MoveValue::U64(10).simple_serialize().unwrap()), ], ) .unwrap(); let mut returns_2 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::coin".to_string(), "coin_to_fungible_asset".to_string(), vec!["0x1::aptos_coin::AptosCoin".to_string()], @@ -201,12 +196,12 @@ fn chained_deposit_invalid_copy() { .unwrap(); let return_val = returns_2.pop().unwrap(); assert!(builder - .add_batched_call_wasm( + .add_batched_call( "0x1::primary_fungible_store".to_string(), "deposit".to_string(), vec![], vec![ - BatchArgumentWASM::new_bytes( + Argument::new_bytes( MoveValue::Address(*bob.address()) .simple_serialize() .unwrap(), @@ -218,12 +213,12 @@ fn chained_deposit_invalid_copy() { .is_err()); assert!(builder - .add_batched_call_wasm( + .add_batched_call( "0x1::primary_fungible_store".to_string(), "deposit".to_string(), vec![], vec![ - BatchArgumentWASM::new_bytes( + Argument::new_bytes( MoveValue::Address(*bob.address()) .simple_serialize() .unwrap(), @@ -248,8 +243,8 @@ fn test_module() { .join("test_modules"); h.publish_package_cache_building(&account, &module_path); - let mut run_txn = |batch_builder: BatchedFunctionCallBuilder, h: &mut MoveHarness| { - let script = batch_builder.generate_batched_calls().unwrap(); + let mut run_txn = |batch_builder: TransactionComposer, h: &mut MoveHarness| { + let script = batch_builder.generate_batched_calls(true).unwrap(); let txn = alice .transaction() .script(bcs::from_bytes(&script).unwrap()) @@ -265,14 +260,14 @@ fn test_module() { }; // Create a copyable value and copy it twice - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_copyable_value".to_string(), vec![], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -281,40 +276,80 @@ fn test_module() { .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_copyable_value".to_string(), vec![], vec![ returns_1.copy().unwrap(), - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_copyable_value".to_string(), vec![], vec![ returns_1, - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .unwrap(); run_txn(builder, &mut h); + // Create a copyable value and move it twice + let mut builder = TransactionComposer::single_signer(); + load_module(&mut builder, &h, "0x1::batched_execution"); + let returns_1 = builder + .add_batched_call( + "0x1::batched_execution".to_string(), + "create_copyable_value".to_string(), + vec![], + vec![Argument::new_bytes( + MoveValue::U8(10).simple_serialize().unwrap(), + )], + ) + .unwrap() + .pop() + .unwrap(); + + builder + .add_batched_call( + "0x1::batched_execution".to_string(), + "consume_copyable_value".to_string(), + vec![], + vec![ + returns_1.clone(), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + ], + ) + .unwrap(); + + assert!(builder + .add_batched_call( + "0x1::batched_execution".to_string(), + "consume_copyable_value".to_string(), + vec![], + vec![ + returns_1, + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + ], + ) + .is_err()); + // Copying a non-copyable value should return error on call. - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_non_droppable_value".to_string(), vec![], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -323,26 +358,26 @@ fn test_module() { .unwrap(); assert!(builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_non_droppable_value".to_string(), vec![], vec![ returns_1.copy().unwrap(), - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .is_err()); // Create a value and pass it to the wrong type - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_non_droppable_value".to_string(), vec![], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -351,26 +386,26 @@ fn test_module() { .unwrap(); assert!(builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_droppable_value".to_string(), vec![], vec![ returns_1, - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .is_err()); // Create a non droppable value and never use it. - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let _returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_non_droppable_value".to_string(), vec![], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -378,17 +413,17 @@ fn test_module() { .pop() .unwrap(); - assert!(builder.generate_batched_calls().is_err()); + assert!(builder.generate_batched_calls(true).is_err()); // Create a value and pass by reference - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_copyable_value".to_string(), vec![], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -397,25 +432,25 @@ fn test_module() { .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "check_copyable_value".to_string(), vec![], vec![ returns_1.borrow().unwrap(), - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_copyable_value".to_string(), vec![], vec![ returns_1, - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .unwrap(); @@ -423,14 +458,14 @@ fn test_module() { run_txn(builder, &mut h); // Create a value and pass by mutable reference and then mutate. - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_non_droppable_value".to_string(), vec![], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -439,25 +474,25 @@ fn test_module() { .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "mutate_non_droppable_value".to_string(), vec![], vec![ returns_1.borrow_mut().unwrap(), - BatchArgumentWASM::new_bytes(MoveValue::U8(42).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(42).simple_serialize().unwrap()), ], ) .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_non_droppable_value".to_string(), vec![], vec![ returns_1, - BatchArgumentWASM::new_bytes(MoveValue::U8(42).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(42).simple_serialize().unwrap()), ], ) .unwrap(); @@ -465,14 +500,14 @@ fn test_module() { run_txn(builder, &mut h); // Create a value and pass it to a generic function - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_non_droppable_value".to_string(), vec![], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -481,7 +516,7 @@ fn test_module() { .unwrap(); let returns_2 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "id".to_string(), vec!["0x1::batched_execution::NonDroppableValue".to_string()], @@ -492,28 +527,51 @@ fn test_module() { .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_non_droppable_value".to_string(), vec![], vec![ returns_2, - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .unwrap(); - run_txn(builder, &mut h); + // Create a value and pass it to a generic function with invalid type. + let mut builder = TransactionComposer::single_signer(); + load_module(&mut builder, &h, "0x1::batched_execution"); + let returns_1 = builder + .add_batched_call( + "0x1::batched_execution".to_string(), + "create_non_droppable_value".to_string(), + vec![], + vec![Argument::new_bytes( + MoveValue::U8(10).simple_serialize().unwrap(), + )], + ) + .unwrap() + .pop() + .unwrap(); + + assert!(builder + .add_batched_call( + "0x1::batched_execution".to_string(), + "id".to_string(), + vec!["0x1::batched_execution::DroppableValue".to_string()], + vec![returns_1], + ) + .is_err()); // Create a droppable value with generics and don't use it - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_generic_droppable_value".to_string(), vec!["0x1::batched_execution::Foo".to_string()], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -521,14 +579,14 @@ fn test_module() { run_txn(builder, &mut h); // Create a generic value and consume it - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_generic_non_droppable_value".to_string(), vec!["0x1::batched_execution::Foo".to_string()], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -537,13 +595,13 @@ fn test_module() { .unwrap(); builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_generic_non_droppable_value".to_string(), vec!["0x1::batched_execution::Foo".to_string()], vec![ returns_1, - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .unwrap(); @@ -551,14 +609,14 @@ fn test_module() { run_txn(builder, &mut h); // Create a generic value and destruct it with wrong type parameter. - let mut builder = BatchedFunctionCallBuilder::single_signer(); + let mut builder = TransactionComposer::single_signer(); load_module(&mut builder, &h, "0x1::batched_execution"); let returns_1 = builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "create_generic_non_droppable_value".to_string(), vec!["0x1::batched_execution::Foo".to_string()], - vec![BatchArgumentWASM::new_bytes( + vec![Argument::new_bytes( MoveValue::U8(10).simple_serialize().unwrap(), )], ) @@ -567,13 +625,13 @@ fn test_module() { .unwrap(); assert!(builder - .add_batched_call_wasm( + .add_batched_call( "0x1::batched_execution".to_string(), "consume_generic_non_droppable_value".to_string(), vec!["0x1::batched_execution::Bar".to_string()], vec![ returns_1, - BatchArgumentWASM::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), + Argument::new_bytes(MoveValue::U8(10).simple_serialize().unwrap()), ], ) .is_err()); diff --git a/third_party/move/move-binary-format/src/builders.rs b/third_party/move/move-binary-format/src/builders.rs index f415d481982df6..0a4c6027d4bc52 100644 --- a/third_party/move/move-binary-format/src/builders.rs +++ b/third_party/move/move-binary-format/src/builders.rs @@ -16,6 +16,7 @@ use std::collections::{btree_map::Entry, BTreeMap}; /// Structure to support incremental additions to an existing `CompiledScript`. /// For incremental construction, start with `CompiledScript::new()`. +#[derive(Clone, Debug)] pub struct CompiledScriptBuilder { script: CompiledScript, address_pool: BTreeMap, @@ -313,4 +314,8 @@ impl CompiledScriptBuilder { pub fn into_script(self) -> CompiledScript { self.script } + + pub fn as_script(&self) -> &CompiledScript { + &self.script + } } diff --git a/third_party/move/move-binary-format/src/file_format.rs b/third_party/move/move-binary-format/src/file_format.rs index 6feb954f8191ef..58263b69c05ace 100644 --- a/third_party/move/move-binary-format/src/file_format.rs +++ b/third_party/move/move-binary-format/src/file_format.rs @@ -48,7 +48,7 @@ use move_core_types::{ use proptest::{collection::vec, prelude::*, strategy::BoxedStrategy}; use ref_cast::RefCast; use serde::{Deserialize, Serialize}; -use std::{fmt, fmt::Formatter, ops::BitOr}; +use std::{collections::BTreeMap, fmt::{self, Formatter}, ops::BitOr}; use variant_count::VariantCount; /// Generic index into one of the tables in the binary format. @@ -1426,6 +1426,33 @@ impl SignatureToken { pub fn num_nodes(&self) -> usize { self.preorder_traversal().count() } + + pub fn instantiate(&self, subst_mapping: &BTreeMap) -> SignatureToken { + use SignatureToken::*; + match self { + Bool => Bool, + U8 => U8, + U16 => U16, + U32 => U32, + U64 => U64, + U128 => U128, + U256 => U256, + Address => Address, + Signer => Signer, + Vector(ty) => Vector(Box::new(ty.instantiate(subst_mapping))), + Struct(idx) => Struct(*idx), + StructInstantiation(idx, struct_type_args) => StructInstantiation( + *idx, + struct_type_args + .iter() + .map(|ty| ty.instantiate(subst_mapping)) + .collect(), + ), + Reference(ty) => Reference(Box::new(ty.instantiate(subst_mapping))), + MutableReference(ty) => MutableReference(Box::new(ty.instantiate(subst_mapping))), + TypeParameter(idx) => subst_mapping.get(idx).unwrap().clone(), + } + } } /// A `Constant` is a serialized value along with its type. That type will be deserialized by the