diff --git a/fastpay_core/Cargo.toml b/fastpay_core/Cargo.toml index 31d7811d2adac..d2142442a3660 100644 --- a/fastpay_core/Cargo.toml +++ b/fastpay_core/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] anyhow = "1.0" +bcs = "0.1.2" failure = "0.1.8" futures = "0.3.5" rand = "0.7.3" @@ -19,4 +20,6 @@ fastx-types = { path = "../fastx_types" } move-binary-format = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } move-core-types = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } +move-vm-runtime = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } + diff --git a/fastpay_core/src/authority.rs b/fastpay_core/src/authority.rs index 8de962085fb42..999bde8341554 100644 --- a/fastpay_core/src/authority.rs +++ b/fastpay_core/src/authority.rs @@ -16,7 +16,13 @@ use move_core_types::{ language_storage::{ModuleId, StructTag}, resolver::{ModuleResolver, ResourceResolver}, }; -use std::{collections::BTreeMap, convert::TryInto, sync::Arc, sync::Mutex}; +use move_vm_runtime::native_functions::NativeFunctionTable; +use std::{ + collections::{BTreeMap, HashSet}, + convert::TryInto, + sync::Arc, + sync::Mutex, +}; #[cfg(test)] #[path = "unit_tests/authority_tests.rs"] @@ -68,6 +74,9 @@ pub struct AuthorityState { /// An index mapping object IDs to the Transaction Digest that created them. /// This is used by synchronization logic to sync authorities. parent_sync: BTreeMap<(ObjectID, SequenceNumber), TransactionDigest>, + + /// Move native functions that are available to invoke + native_functions: NativeFunctionTable, } /// Interface provided by each (shard of an) authority. @@ -107,6 +116,11 @@ impl Authority for AuthorityState { !input_objects.is_empty(), FastPayError::InsufficientObjectNumber ); + // Ensure that there are no duplicate inputs + let mut used = HashSet::new(); + if !input_objects.iter().all(|o| used.insert(o)) { + return Err(FastPayError::DuplicateObjectRefInput); + } for object_ref in input_objects { let (object_id, sequence_number) = object_ref; @@ -234,19 +248,38 @@ impl Authority for AuthorityState { temporary_store.write_object(output_object); } OrderKind::Call(c) => { - let sender = c.sender.to_address_hack(); + // unwraps here are safe because we built `inputs` // TODO(https://github.com/MystenLabs/fastnft/issues/45): charge for gas - adapter::execute( + let mut gas_object = inputs.pop().unwrap(); + let module = inputs.pop().unwrap(); + // Fake the gas payment + gas_object.next_sequence_number = gas_object.next_sequence_number.increment()?; + temporary_store.write_object(gas_object); + match adapter::execute( &mut temporary_store, - &c.module, + self.native_functions.clone(), + module, &c.function, - sender, - c.object_arguments.clone(), - c.pure_arguments.clone(), - c.type_arguments.clone(), + c.type_arguments, + inputs, + c.pure_arguments, Some(c.gas_budget), - ) - .map_err(|_| FastPayError::MoveExecutionFailure)?; + tx_ctx, + ) { + Ok(()) => { + // TODO(https://github.com/MystenLabs/fastnft/issues/63): AccountInfoResponse should return all object ID outputs. + // but for now it only returns one, so use this hack + object_id = if temporary_store.written.len() > 1 { + temporary_store.written[1].0 + } else { + c.gas_payment.0 + } + } + Err(_e) => { + // TODO(https://github.com/MystenLabs/fastnft/issues/63): return this error to the client + object_id = c.gas_payment.0; + } + } } OrderKind::Publish(m) => { // Fake the gas payment @@ -259,13 +292,12 @@ impl Authority for AuthorityState { let sender = m.sender.to_address_hack(); match adapter::publish(&mut temporary_store, m.modules, &sender, &mut tx_ctx) { Ok(outputs) => { - // TODO: AccountInfoResponse should return all object ID outputs. + // TODO(https://github.com/MystenLabs/fastnft/issues/63): AccountInfoResponse should return all object ID outputs. // but for now it only returns one, so use this hack object_id = outputs[0].0; } Err(_e) => { - println!("failure during publishing: {:?}", _e); - // TODO: return this error to the client + // TODO(https://github.com/MystenLabs/fastnft/issues/63): return this error to the client object_id = m.gas_payment.0; } } @@ -340,6 +372,7 @@ impl AuthorityState { number_of_shards: 1, certificates: BTreeMap::new(), parent_sync: BTreeMap::new(), + native_functions: NativeFunctionTable::new(), } } @@ -360,6 +393,7 @@ impl AuthorityState { number_of_shards, certificates: BTreeMap::new(), parent_sync: BTreeMap::new(), + native_functions: NativeFunctionTable::new(), } } @@ -377,7 +411,7 @@ impl AuthorityState { Self::get_shard(self.number_of_shards, object_id) } - fn object_state(&self, object_id: &ObjectID) -> Result { + pub fn object_state(&self, object_id: &ObjectID) -> Result { self.objects .lock() .unwrap() diff --git a/fastpay_core/src/genesis.rs b/fastpay_core/src/genesis.rs deleted file mode 100644 index 8ad4d3552094a..0000000000000 --- a/fastpay_core/src/genesis.rs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) Mysten Labs -// SPDX-License-Identifier: Apache-2.0 - -use anyhow::Result; -use fastx_adapter::adapter; -use fastx_framework::{self}; -use fastx_types::{ - base_types::{PublicKeyBytes, SequenceNumber, TransactionDigest, TxContext}, - object::Object, - FASTX_FRAMEWORK_ADDRESS, -}; - -/// Create and return objects wrapping the genesis modules for fastX -pub fn create_genesis_module_objects() -> Result> { - let mut tx_context = TxContext::new(TransactionDigest::genesis()); - let mut modules = fastx_framework::get_framework_modules()?; - adapter::generate_module_ids(&mut modules, &mut tx_context)?; - let module_objects = modules - .into_iter() - .map(|m| { - Object::new_module( - m, - PublicKeyBytes::from_move_address_hack(&FASTX_FRAMEWORK_ADDRESS), - SequenceNumber::new(), - ) - }) - .collect(); - Ok(module_objects) -} diff --git a/fastpay_core/src/lib.rs b/fastpay_core/src/lib.rs index 1f06e85389cae..7b60c42eb6598 100644 --- a/fastpay_core/src/lib.rs +++ b/fastpay_core/src/lib.rs @@ -12,4 +12,3 @@ pub mod authority; pub mod client; pub mod downloader; pub mod fastpay_smart_contract; -pub mod genesis; diff --git a/fastpay_core/src/unit_tests/authority_tests.rs b/fastpay_core/src/unit_tests/authority_tests.rs index 9ce87de0a2990..2a16cc47d9391 100644 --- a/fastpay_core/src/unit_tests/authority_tests.rs +++ b/fastpay_core/src/unit_tests/authority_tests.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use super::*; -use crate::genesis; +use bcs; +use fastx_adapter::genesis; #[cfg(test)] use fastx_types::base_types::dbg_addr; use move_binary_format::{ @@ -10,8 +11,6 @@ use move_binary_format::{ CompiledModule, }; use move_core_types::ident_str; -#[cfg(test)] -use move_core_types::language_storage::ModuleId; #[test] fn test_handle_transfer_order_bad_signature() { @@ -188,7 +187,8 @@ fn test_publish_dependent_module_ok() { let gas_payment_object = Object::with_id_owner_for_testing(gas_payment_object_id, sender); let gas_payment_object_ref = gas_payment_object.to_object_reference(); // create a genesis state that contains the gas object and genesis modules - let mut genesis_module_objects = genesis::create_genesis_module_objects().unwrap(); + let genesis = genesis::GENESIS.lock().unwrap(); + let mut genesis_module_objects = genesis.objects.clone(); let genesis_module = match &genesis_module_objects[0].data { Data::Module(m) => CompiledModule::deserialize(m).unwrap(), _ => unreachable!(), @@ -241,39 +241,58 @@ fn test_publish_module_no_dependencies_ok() { } #[test] -fn test_handle_move_order_bad() { +fn test_handle_move_order() { let (sender, sender_key) = get_key_pair(); // create a dummy gas payment object. ok for now because we don't check gas let gas_payment_object_id = ObjectID::random(); let gas_payment_object = Object::with_id_owner_for_testing(gas_payment_object_id, sender); let gas_payment_object_ref = gas_payment_object.to_object_reference(); - // create a dummy module. execution will fail when we try to read it - let dummy_module_object_id = ObjectID::random(); - let dummy_module_object = Object::with_id_owner_for_testing(dummy_module_object_id, sender); + // find the function Object::create and call it to create a new object + let genesis = genesis::GENESIS.lock().unwrap(); + let mut genesis_module_objects = genesis.objects.clone(); + let module_object_ref = genesis_module_objects + .iter() + .find_map(|o| match o.data.as_module() { + Some(m) => { + if m.self_id().name() == ident_str!("ObjectBasics") { + Some((*m.self_id().address(), SequenceNumber::new())) + } else { + None + } + } + None => None, + }) + .unwrap(); - let mut authority_state = - init_state_with_objects(vec![gas_payment_object, dummy_module_object]); + genesis_module_objects.push(gas_payment_object); + let mut authority_state = init_state_with_objects(genesis_module_objects); + authority_state.native_functions = genesis.native_functions.clone(); - let module_id = ModuleId::new(dummy_module_object_id, ident_str!("Module").to_owned()); - let function = ident_str!("function_name").to_owned(); + let function = ident_str!("create").to_owned(); let order = Order::new_move_call( sender, - module_id, + module_object_ref, function, Vec::new(), gas_payment_object_ref, Vec::new(), - Vec::new(), + vec![ + 16u64.to_le_bytes().to_vec(), + bcs::to_bytes(&sender.to_address_hack().to_vec()).unwrap(), + ], 1000, &sender_key, ); - - // Submit the confirmation. *Now* execution actually happens, and it should fail when we try to look up our dummy module. - // we unfortunately don't get a very descriptive error message, but we can at least see that something went wrong inside the VM - match send_and_confirm_order(&mut authority_state, order) { - Err(FastPayError::MoveExecutionFailure) => (), - r => panic!("Unexpected result {:?}", r), - } + let res = send_and_confirm_order(&mut authority_state, order).unwrap(); + let created_object_id = res.object_id; + // check that order actually created an object with the expected ID, owner, sequence number + let created_obj = authority_state.object_state(&created_object_id).unwrap(); + assert_eq!( + created_obj.owner.to_address_hack(), + sender.to_address_hack() + ); + assert_eq!(created_obj.id(), created_object_id); + assert_eq!(created_obj.next_sequence_number, SequenceNumber::new()); } #[test] diff --git a/fastx_programmability/adapter/Cargo.toml b/fastx_programmability/adapter/Cargo.toml index 9313481e00de0..c06fde354dae1 100644 --- a/fastx_programmability/adapter/Cargo.toml +++ b/fastx_programmability/adapter/Cargo.toml @@ -10,6 +10,7 @@ edition = "2018" [dependencies] anyhow = "1.0.38" bcs = "0.1.2" +once_cell = "1.9.0" structopt = "0.3.21" move-binary-format = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } @@ -25,6 +26,7 @@ fastx-types = { path = "../../fastx_types" } [dev-dependencies] datatest-stable = "0.1.1" +move-package = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } [[bin]] name = "fastx" diff --git a/fastx_programmability/adapter/src/adapter.rs b/fastx_programmability/adapter/src/adapter.rs index a3bf3cfec1ea3..686bb70b00cb7 100644 --- a/fastx_programmability/adapter/src/adapter.rs +++ b/fastx_programmability/adapter/src/adapter.rs @@ -1,32 +1,39 @@ // Copyright (c) Mysten Labs // SPDX-License-Identifier: Apache-2.0 -use anyhow::{bail, Result}; -use fastx_framework::natives; +use anyhow::Result; + +use crate::{ + bytecode_rewriter::ModuleHandleRewriter, + genesis::{TX_CONTEXT_ADDRESS, TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME}, +}; use fastx_types::{ - base_types::{FastPayAddress, ObjectRef, SequenceNumber, TxContext}, + base_types::{FastPayAddress, ObjectID, ObjectRef, SequenceNumber, TxContext}, error::{FastPayError, FastPayResult}, - object::Object, + object::{Data, MoveObject, Object}, storage::Storage, - FASTX_FRAMEWORK_ADDRESS, MOVE_STDLIB_ADDRESS, }; use fastx_verifier::verifier; -use move_binary_format::{errors::VMError, file_format::CompiledModule}; +use move_binary_format::{ + file_format::CompiledModule, + normalized::{Function, Type}, +}; use move_cli::sandbox::utils::get_gas_status; use move_core_types::{ account_address::AccountAddress, - effects::ChangeSet, - gas_schedule::GasAlgebra, - identifier::{IdentStr, Identifier}, - language_storage::{ModuleId, TypeTag}, - resolver::{ModuleResolver, MoveResolver, ResourceResolver}, - transaction_argument::{convert_txn_args, TransactionArgument}, + identifier::Identifier, + language_storage::{ModuleId, StructTag, TypeTag}, + resolver::{ModuleResolver, ResourceResolver}, +}; +use move_vm_runtime::{ + move_vm::MoveVM, native_functions::NativeFunctionTable, session::ExecutionResult, }; -use move_vm_runtime::move_vm::MoveVM; use std::{collections::BTreeMap, fmt::Debug}; -use crate::bytecode_rewriter::ModuleHandleRewriter; +#[cfg(test)] +#[path = "unit_tests/adapter_tests.rs"] +mod adapter_tests; /// Execute `module::function(object_args ++ pure_args)` as a call from `sender` with the given `gas_budget`. /// Execution will read from/write to the store in `state_view`. @@ -34,97 +41,67 @@ use crate::bytecode_rewriter::ModuleHandleRewriter; #[allow(clippy::too_many_arguments)] pub fn execute + ModuleResolver + Storage>( state_view: &mut S, - module: &ModuleId, + natives: NativeFunctionTable, + module_object: Object, function: &Identifier, - sender: AccountAddress, - object_args: Vec, - mut pure_args: Vec>, type_args: Vec, + object_args: Vec, + pure_args: Vec>, gas_budget: Option, -) -> Result<()> { - let obj_ids: Vec = object_args - .iter() - .map(|o| TransactionArgument::Address(o.0)) - .collect(); - let mut args = convert_txn_args(&obj_ids); - args.append(&mut pure_args); + ctx: TxContext, +) -> Result<(), FastPayError> { + let TypeCheckSuccess { + module_id, + args, + mutable_ref_objects, + by_value_objects, + } = resolve_and_type_check( + module_object, + function, + &type_args, + object_args, + pure_args, + ctx, + )?; - if let Err(error) = verify_module(module, state_view) { - // TODO: execute should return Result<(), FastPayError> - bail!("Verification error: {:?}", error) - } - match execute_function( - module, + let vm = MoveVM::new(natives) + .expect("VM creation only fails if natives are invalid, and we created the natives"); + let mut gas_status = + get_gas_status(gas_budget).map_err(|e| FastPayError::GasBudgetTooHigh { + error: e.to_string(), + })?; + let session = vm.new_session(state_view); + match session.execute_function_for_effects( + &module_id, function, type_args, - vec![sender], - pure_args, - gas_budget, - state_view, - )? { + args, + &mut gas_status, + ) { ExecutionResult::Success { change_set, events, - gas_used: _, + return_values, + mutable_ref_values, + gas_used: _, // TODO(https://github.com/MystenLabs/fastnft/issues/45): charge } => { - // process change set. important to do this before processing events because it's where deletions happen - for (addr, addr_changes) in change_set.into_inner() { - for (struct_tag, bytes_opt) in addr_changes.into_resources() { - match bytes_opt { - Some(bytes) => { - // object mutated during execution - let sequence_number = state_view - .read_object(&addr) - .ok_or(FastPayError::ObjectNotFound)? - .next_sequence_number - .increment() - .map_err(|_| FastPayError::InvalidSequenceNumber)?; - - let owner = FastPayAddress::from_move_address_hack(&sender); - let object = - Object::new_move(struct_tag, bytes, owner, sequence_number); - state_view.write_object(object); - } - None => state_view.delete_object(&addr), - } - } - } - // process events - for e in events { - if is_transfer_event(&e) { - let (guid, _seq_num, type_, event_bytes) = e; - match type_ { - TypeTag::Struct(s_type) => { - // special transfer event. process by saving object under given authenticator - let transferred_obj = event_bytes; - let recipient = AccountAddress::from_bytes(guid)?; - let sequence_number = SequenceNumber::new(); - let owner = FastPayAddress::from_move_address_hack(&recipient); - let mut object = - Object::new_move(s_type, transferred_obj, owner, sequence_number); - - // If object exists, find new sequence number - if let Some(old_object) = state_view.read_object(&object.id()) { - let sequence_number = - old_object.next_sequence_number.increment()?; - object.next_sequence_number = sequence_number; - } - - state_view.write_object(object); - } - _ => unreachable!("Only structs can be transferred"), - } - } else { - // the fastX framework doesn't support user-generated events yet, so shouldn't hit this - unimplemented!("Processing user events") - } - } - } - ExecutionResult::Fail { error, gas_used: _ } => { - bail!("Fail: {}", error) + // we already checked that the function had no return types in resolve_and_type_check--it should + // also not return any values at runtime + debug_assert!(return_values.is_empty()); + // FastX Move programs should never touch global state, so ChangeSet should be empty + debug_assert!(change_set.accounts().is_empty()); + // Input ref parameters we put in should be the same number we get out + debug_assert!(mutable_ref_objects.len() == mutable_ref_values.len()); + let mutable_refs = mutable_ref_objects + .into_iter() + .zip(mutable_ref_values.into_iter()); + process_successful_execution(state_view, by_value_objects, mutable_refs, events)?; + Ok(()) } + ExecutionResult::Fail { error, gas_used: _ } => Err(FastPayError::AbortedExecution { + error: error.to_string(), + }), } - Ok(()) } pub fn publish + ModuleResolver + Storage>( @@ -185,7 +162,7 @@ pub fn publish + ModuleResolver, ctx: &mut TxContext, -) -> Result<(), FastPayError> { +) -> Result, FastPayError> { let mut sub_map = BTreeMap::new(); for module in modules.iter() { // derive a fresh ID's for each module and mutate its self address to the ID. @@ -205,7 +182,7 @@ pub fn generate_module_ids( // rewrite module handles to reflect freshly generated ID's rewriter.sub_module_ids(module); } - Ok(()) + Ok(rewriter.into_inner()) } /// Check if this is a special event type emitted when there is a transfer between fastX addresses @@ -214,113 +191,292 @@ pub fn is_transfer_event(e: &Event) -> bool { !e.0.is_empty() } -// TODO: Code below here probably wants to move into the VM or elsewhere in -// the Diem codebase--seems generically useful + nothing similar exists - type Event = (Vec, u64, TypeTag, Vec); -/// Result of executing a script or script function in the VM -pub enum ExecutionResult { - /// Execution completed successfully. Changes to global state are - /// captured in `change_set`, and `events` are recorded in the order - /// they were emitted. `gas_used` records the amount of gas expended - /// by execution. Note that this will be 0 in unmetered mode. - Success { - change_set: ChangeSet, - events: Vec, - gas_used: u64, - }, - /// Execution failed for the reason described in `error`. - /// `gas_used` records the amount of gas expended by execution. Note - /// that this will be 0 in unmetered mode. - Fail { error: VMError, gas_used: u64 }, +/// Update `state_view` with the effects of successfully executing a transaction: +/// - Look for each input in `by_value_objects` to determine whether the object was transferred, frozen, or deleted +/// - Update objects passed via a mutable reference in `mutable_refs` to their new values +/// - Process creation of new objects and user-emittd events in `events` +fn process_successful_execution< + E: Debug, + S: ResourceResolver + ModuleResolver + Storage, +>( + state_view: &mut S, + mut by_value_objects: BTreeMap, + mutable_refs: impl Iterator)>, + events: Vec, +) -> Result<(), FastPayError> { + for (mut obj, new_contents) in mutable_refs { + match &mut obj.data { + Data::Move(m) => m.contents = new_contents, + _ => unreachable!("We previously checked that mutable ref inputs are Move objects"), + }; + let sequence_number = obj.next_sequence_number.increment()?; + obj.next_sequence_number = sequence_number; + state_view.write_object(obj); + } + // process events to identify transfers, freezes + // TODO(https://github.com/MystenLabs/fastnft/issues/96): implement freeze and immutable objects + for e in events { + if is_transfer_event(&e) { + let (guid, _seq_num, type_, event_bytes) = e; + match type_ { + TypeTag::Struct(s_type) => { + // special transfer event. process by saving object under given authenticator + let contents = event_bytes; + let recipient = FastPayAddress::from_move_address_hack( + &AccountAddress::from_bytes(guid) + .expect("Unwrap safe due to enforcement in native function"), + ); + let move_obj = MoveObject::new(s_type, contents); + let id = move_obj.id(); + // If object exists, find new sequence number + let mut new_object = if let Some(mut old_object) = by_value_objects.remove(&id) + { + match &mut old_object.data { + Data::Move(o) => { + debug_assert!(o.type_ == move_obj.type_); + o.contents = move_obj.contents; + } + Data::Module(..) => + // Impossible because the object store is well-typed + { + unreachable!() + } + }; + let sequence_number = old_object.next_sequence_number.increment()?; + old_object.next_sequence_number = sequence_number; + old_object + } else { + Object::new_move(move_obj, recipient, SequenceNumber::new()) + }; + new_object.owner = recipient; + state_view.write_object(new_object); + } + _ => unreachable!("Only structs can be transferred"), + } + } else { + // the fastX framework doesn't support user-generated events yet, so shouldn't hit this + unimplemented!("Processing user events") + } + } + // any object left in `by_value_objects` is an input passed by value that was not transferred or frozen. + // this means that either the object was (1) deleted from the FastX system altogether, or + // (2) wrapped inside another object that is in the FastX object pool + // in either case, we want to delete it + for id in by_value_objects.keys() { + state_view.delete_object(id); + } + Ok(()) } -fn create_vm() -> MoveVM { - let natives = natives::all_natives(MOVE_STDLIB_ADDRESS, FASTX_FRAMEWORK_ADDRESS); - MoveVM::new(natives).expect("VM creation only fails if natives are invalid") +struct TypeCheckSuccess { + module_id: ModuleId, + args: Vec>, + by_value_objects: BTreeMap, + mutable_ref_objects: Vec, } -/// Execute the function named `script_function` in `module` with the given -/// `type_args`, `signer_addresses`, and `args` as input. -/// Execute the function according to the given `gas_budget`. If this budget -/// is `Some(t)`, use `t` use `t`; if None, run the VM in unmetered mode -/// Read published modules and global state from `resolver` and native functions -/// from `natives`. -#[allow(clippy::too_many_arguments)] -pub fn execute_function( - module: &ModuleId, - script_function: &IdentStr, - type_args: Vec, - signer_addresses: Vec, - mut args: Vec>, - gas_budget: Option, - resolver: &Resolver, -) -> Result { - let vm = create_vm(); - let mut gas_status = get_gas_status(gas_budget)?; - let mut session = vm.new_session(resolver); - // prepend signers to args - let mut signer_args: Vec> = signer_addresses - .iter() - .map(|s| bcs::to_bytes(s).unwrap()) - .collect(); - signer_args.append(&mut args); - - let res = { - session - .execute_function( - module, - script_function, - type_args, - signer_args, - &mut gas_status, +/// - Check that `module_object` and `function` are valid +/// - Check that the the signature of `function` is well-typed w.r.t `type_args`, `object_args`, and `pure_args` +/// - Return the ID of the resolved module, a vector of BCS encoded arguments to pass to the VM, and a partitioning +/// of the input objects into objects passed by value vs by mutable reference +fn resolve_and_type_check( + module_object: Object, + function: &Identifier, + type_args: &[TypeTag], + object_args: Vec, + mut pure_args: Vec>, + ctx: TxContext, +) -> Result { + // resolve the function we are calling + let (function_signature, module_id) = match module_object.data { + Data::Module(bytes) => { + let m = CompiledModule::deserialize(&bytes).expect( + "Unwrap safe because FastX serializes/verifies modules before publishing them", + ); + ( + Function::new_from_name(&m, function).ok_or(FastPayError::FunctionNotFound { + error: format!( + "Could not resolve function {} in module {}", + function, + m.self_id() + ), + })?, + m.self_id(), ) - .map(|_| ()) - }; - let gas_used = match gas_budget { - Some(budget) => budget - gas_status.remaining_gas().get(), - None => 0, - }; - if let Err(error) = res { - Ok(ExecutionResult::Fail { error, gas_used }) - } else { - let (change_set, events) = session.finish().map_err(|e| e.into_vm_status())?; - Ok(ExecutionResult::Success { - change_set, - events, - gas_used, - }) - } -} - -// Load, deserialize, and check the module with the fastx bytecode verifier, without linking -fn verify_module(id: &ModuleId, resolver: &Resolver) -> FastPayResult { - let module_bytes = match resolver.get_module(id) { - Ok(Some(bytes)) => bytes, - Ok(None) => { + } + Data::Move(_) => { return Err(FastPayError::ModuleLoadFailure { - error: format!("Resolver returned None for module {}", &id), + error: "Expected a module object, but found a Move object".to_string(), }) } - Err(err) => { - return Err(FastPayError::ModuleLoadFailure { - error: format!("Resolver failed to load module {}: {:?}", &id, err), + }; + // check validity conditions on the invoked function + if !function_signature.return_.is_empty() { + return Err(FastPayError::InvalidFunctionSignature { + error: "Invoked function must not return a value".to_string(), + }); + } + if !function_signature + .parameters + .iter() + .all(|ty| ty.is_closed()) + { + return Err(FastPayError::InvalidFunctionSignature { + error: "Invoked function must not have an unbound type parameter".to_string(), + }); + } + // check arity of type and value arguments + if function_signature.type_parameters.len() != type_args.len() { + return Err(FastPayError::InvalidFunctionSignature { + error: format!( + "Expected {:?} type arguments, but found {:?}", + function_signature.type_parameters.len(), + type_args.len() + ), + }); + } + // total number of args is |objects| + |pure_args| + 1 for the the `TxContext` object + let num_args = object_args.len() + pure_args.len() + 1; + if function_signature.parameters.len() != num_args { + return Err(FastPayError::InvalidFunctionSignature { + error: format!( + "Expected {:?} arguments, but found {:?}", + function_signature.parameters.len(), + num_args + ), + }); + } + // check that the last arg is a TxContext object + match &function_signature.parameters[function_signature.parameters.len() - 1] { + Type::Struct { + address, + module, + name, + type_arguments, + } if address == &TX_CONTEXT_ADDRESS + && module.as_ident_str() == TX_CONTEXT_MODULE_NAME + && name.as_ident_str() == TX_CONTEXT_STRUCT_NAME + && type_arguments.is_empty() => {} + t => { + return Err(FastPayError::InvalidFunctionSignature { + error: format!( + "Expected last parameter of function signature to be {}::{}::{}, but found {}", + TX_CONTEXT_ADDRESS, TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME, t + ), }) } }; - // for bytes obtained from the data store, they should always deserialize and verify. - // It is an invariant violation if they don't. - let module = CompiledModule::deserialize(&module_bytes).map_err(|err| { - FastPayError::ModuleLoadFailure { - error: err.to_string(), + // type check object arguments passed in by value and by reference + let mut args = Vec::new(); + let mut mutable_ref_objects = Vec::new(); + let mut by_value_objects = BTreeMap::new(); + #[cfg(debug_assertions)] + let mut num_immutable_objects = 0; + let num_objects = object_args.len(); + + let ty_args: Vec = type_args.iter().map(|t| Type::from(t.clone())).collect(); + for (idx, object) in object_args.into_iter().enumerate() { + let mut param_type = function_signature.parameters[idx].clone(); + if !param_type.is_closed() { + param_type = param_type.subst(&ty_args); + } + match &object.data { + Data::Move(m) => { + args.push(m.contents.clone()); + // check that m.type_ matches the parameter types of the function + match ¶m_type { + Type::MutableReference(inner_t) => { + // (https://github.com/MystenLabs/fastnft/issues/96): check m.mutability + type_check_struct(&m.type_, inner_t)?; + mutable_ref_objects.push(object); + } + Type::Reference(inner_t) => { + type_check_struct(&m.type_, inner_t)?; + #[cfg(debug_assertions)] + { + num_immutable_objects += 1 + } + } + Type::Struct { .. } => { + // TODO(https://github.com/MystenLabs/fastnft/issues/96): check m.mutability + type_check_struct(&m.type_, ¶m_type)?; + let res = by_value_objects.insert(object.id(), object); + // should always pass due to earlier "no duplicate ID's" check + debug_assert!(res.is_none()) + } + t => { + return Err(FastPayError::TypeError { + error: format!( + "Found object argument {}, but function expects {}", + m.type_, t + ), + }) + } + } + } + Data::Module(_) => { + return Err(FastPayError::TypeError { + error: format!("Found module argument, but function expects {}", param_type), + }) + } + } + } + debug_assert!( + by_value_objects.len() + mutable_ref_objects.len() + num_immutable_objects == num_objects + ); + // check that the non-object parameters are primitive types + for param_type in + &function_signature.parameters[args.len()..function_signature.parameters.len() - 1] + { + if !is_primitive(param_type) { + return Err(FastPayError::TypeError { + error: format!("Expected primitive type, but found {}", param_type), + }); + } + } + args.append(&mut pure_args); + + args.push(ctx.to_bcs_bytes_hack()); + + Ok(TypeCheckSuccess { + module_id, + args, + by_value_objects, + mutable_ref_objects, + }) +} + +fn type_check_struct(arg_type: &StructTag, param_type: &Type) -> Result<(), FastPayError> { + if let Some(param_struct_type) = param_type.clone().into_struct_tag() { + if arg_type != ¶m_struct_type { + Err(FastPayError::TypeError { + error: format!( + "Expected argument of type {}, but found type {}", + param_struct_type, arg_type + ), + }) + } else { + Ok(()) } - })?; + } else { + Err(FastPayError::TypeError { + error: format!( + "Expected argument of type {}, but found struct type {}", + param_type, arg_type + ), + }) + } +} - // bytecode verifier checks that can be performed with the module itself - verifier::verify_module(&module).map_err(|err| FastPayError::ModuleVerificationFailure { - error: err.to_string(), - })?; - Ok(()) +// TODO: upstream Type::is_primitive in diem +fn is_primitive(t: &Type) -> bool { + use Type::*; + match t { + Bool | U8 | U64 | U128 | Address => true, + Vector(inner_t) => is_primitive(inner_t), + Signer | Struct { .. } | TypeParameter(_) | Reference(_) | MutableReference(_) => false, + } } diff --git a/fastx_programmability/adapter/src/genesis.rs b/fastx_programmability/adapter/src/genesis.rs new file mode 100644 index 0000000000000..b48061eaa230c --- /dev/null +++ b/fastx_programmability/adapter/src/genesis.rs @@ -0,0 +1,79 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +use crate::adapter; +use anyhow::Result; +use fastx_framework::{self}; +use fastx_types::{ + base_types::{PublicKeyBytes, SequenceNumber, TransactionDigest, TxContext}, + object::Object, + FASTX_FRAMEWORK_ADDRESS, MOVE_STDLIB_ADDRESS, +}; +use move_binary_format::access::ModuleAccess; +use move_core_types::{ + account_address::AccountAddress, ident_str, identifier::IdentStr, language_storage::ModuleId, +}; +use move_vm_runtime::native_functions::NativeFunctionTable; +use once_cell::sync::Lazy; +use std::sync::Mutex; + +/// 0x873707f730d18d3867cb77ec7c838c0b +pub const TX_CONTEXT_ADDRESS: AccountAddress = AccountAddress::new([ + 0x87, 0x37, 0x07, 0xf7, 0x30, 0xd1, 0x8d, 0x38, 0x67, 0xcb, 0x77, 0xec, 0x7c, 0x83, 0x8c, 0x0b, +]); +pub const TX_CONTEXT_MODULE_NAME: &IdentStr = ident_str!("TxContext"); +pub const TX_CONTEXT_STRUCT_NAME: &IdentStr = TX_CONTEXT_MODULE_NAME; + +pub static GENESIS: Lazy> = + Lazy::new(|| Mutex::new(create_genesis_module_objects().unwrap())); + +pub struct Genesis { + pub objects: Vec, + pub native_functions: NativeFunctionTable, +} + +/// Create and return objects wrapping the genesis modules for fastX +fn create_genesis_module_objects() -> Result { + let mut tx_context = TxContext::new(TransactionDigest::genesis()); + let mut modules = fastx_framework::get_framework_modules()?; + let sub_map = adapter::generate_module_ids(&mut modules, &mut tx_context)?; + let mut native_functions = + fastx_framework::natives::all_natives(MOVE_STDLIB_ADDRESS, FASTX_FRAMEWORK_ADDRESS); + // Rewrite native function table to reflect address substitutions. Otherwise, natives will fail to link + for native in native_functions.iter_mut() { + let old_id = ModuleId::new(native.0, native.1.to_owned()); + if let Some(new_id) = sub_map.get(&old_id) { + native.0 = *new_id.address(); + native.1 = new_id.name().to_owned(); + } + } + + let objects = modules + .into_iter() + .map(|m| { + let self_id = m.self_id(); + // check that modules the runtime needs to know about have the expected names and addresses + // if these assertions fail, it's likely because approrpiate constants need to be updated + if self_id.name() == TX_CONTEXT_MODULE_NAME { + assert!( + self_id.address() == &TX_CONTEXT_ADDRESS, + "Found new address for TxContext: {}", + self_id.address() + ); + assert!( + m.identifier_at(m.struct_handle_at(m.struct_defs[0].struct_handle).name) + == TX_CONTEXT_STRUCT_NAME + ); + } + Object::new_module( + m, + PublicKeyBytes::from_move_address_hack(&FASTX_FRAMEWORK_ADDRESS), + SequenceNumber::new(), + ) + }) + .collect(); + Ok(Genesis { + objects, + native_functions, + }) +} diff --git a/fastx_programmability/adapter/src/lib.rs b/fastx_programmability/adapter/src/lib.rs index 6a22dafc0821b..16fc27d789e2b 100644 --- a/fastx_programmability/adapter/src/lib.rs +++ b/fastx_programmability/adapter/src/lib.rs @@ -3,6 +3,7 @@ pub mod adapter; pub mod bytecode_rewriter; +pub mod genesis; pub mod state_view; use move_core_types::account_address::AccountAddress; diff --git a/fastx_programmability/adapter/src/unit_tests/adapter_tests.rs b/fastx_programmability/adapter/src/unit_tests/adapter_tests.rs new file mode 100644 index 0000000000000..500fb11500765 --- /dev/null +++ b/fastx_programmability/adapter/src/unit_tests/adapter_tests.rs @@ -0,0 +1,262 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +use crate::{adapter, genesis}; +use fastx_types::storage::Storage; +use std::mem; + +use super::*; + +// temporary store where writes buffer before they get committed +#[derive(Default, Debug)] +struct ScratchPad { + updated: BTreeMap, + created: BTreeMap, + deleted: Vec, +} +#[derive(Default, Debug)] +struct InMemoryStorage { + persistent: BTreeMap, + temporary: ScratchPad, +} + +impl InMemoryStorage { + pub fn new(objects: Vec) -> Self { + let mut persistent = BTreeMap::new(); + for o in objects { + persistent.insert(o.id(), o); + } + Self { + persistent, + temporary: ScratchPad::default(), + } + } + + /// Return the object wrapping the module `name` (if any) + pub fn find_module(&self, name: &str) -> Option { + let id = Identifier::new(name).unwrap(); + for o in self.persistent.values() { + match o.data.as_module() { + Some(m) if m.self_id().name() == id.as_ident_str() => return Some(o.clone()), + _ => (), + } + } + None + } + + /// Flush writes in scratchpad to persistent storage + pub fn flush(&mut self) { + let to_flush = mem::take(&mut self.temporary); + for (id, o) in to_flush.created { + assert!(self.persistent.insert(id, o).is_none()) + } + for (id, o) in to_flush.updated { + assert!(self.persistent.insert(id, o).is_some()) + } + for id in to_flush.deleted { + self.persistent.remove(&id); + } + } + + pub fn created(&self) -> &BTreeMap { + &self.temporary.created + } + + pub fn updated(&self) -> &BTreeMap { + &self.temporary.updated + } + + pub fn deleted(&self) -> &[ObjectID] { + &self.temporary.deleted + } +} + +impl Storage for InMemoryStorage { + fn read_object(&self, id: &ObjectID) -> Option { + self.persistent.get(id).cloned() + } + + // buffer write to appropriate place in temporary storage + fn write_object(&mut self, object: Object) { + let id = object.id(); + if self.persistent.contains_key(&id) { + self.temporary.updated.insert(id, object); + } else { + self.temporary.created.insert(id, object); + } + } + + // buffer delete + fn delete_object(&mut self, id: &ObjectID) { + self.temporary.deleted.push(*id) + } +} + +impl ModuleResolver for InMemoryStorage { + type Error = (); + + fn get_module(&self, module_id: &ModuleId) -> Result>, Self::Error> { + Ok(self + .read_object(module_id.address()) + .map(|o| match &o.data { + Data::Module(m) => m.clone(), + Data::Move(_) => panic!("Type error"), + })) + } +} + +impl ResourceResolver for InMemoryStorage { + type Error = (); + + fn get_resource( + &self, + _address: &AccountAddress, + _struct_tag: &StructTag, + ) -> Result>, Self::Error> { + unreachable!("Should never be called in FastX") + } +} + +/// Exercise test functions that create, transfer, read, update, and delete objects +#[test] +fn test_object_basics() { + let addr1 = AccountAddress::from_hex_literal("0x1").unwrap(); + let addr2 = AccountAddress::from_hex_literal("0x2").unwrap(); + + let genesis = genesis::GENESIS.lock().unwrap(); + let native_functions = genesis.native_functions.clone(); + let mut storage = InMemoryStorage::new(genesis.objects.clone()); + + fn call( + storage: &mut InMemoryStorage, + native_functions: &NativeFunctionTable, + name: &str, + object_args: Vec, + pure_args: Vec>, + ) { + let gas_budget = None; + let module = storage.find_module("ObjectBasics").unwrap(); + + adapter::execute( + storage, + native_functions.clone(), + module, + &Identifier::new(name).unwrap(), + Vec::new(), + object_args, + pure_args, + gas_budget, + TxContext::random(), + ) + .unwrap(); + } + + // 1. Create obj1 owned by addr1 + // ObjectBasics::create expects integer value and recipient address + let pure_args = vec![ + 10u64.to_le_bytes().to_vec(), + bcs::to_bytes(&addr1.to_vec()).unwrap(), + ]; + call( + &mut storage, + &native_functions, + "create", + Vec::new(), + pure_args, + ); + + let created = storage.created(); + assert_eq!(created.len(), 1); + assert!(storage.updated().is_empty()); + assert!(storage.deleted().is_empty()); + let id1 = created + .keys() + .cloned() + .collect::>() + .pop() + .unwrap(); + storage.flush(); + let mut obj1 = storage.read_object(&id1).unwrap(); + let mut obj1_seq = SequenceNumber::new(); + assert_eq!(obj1.owner.to_address_hack(), addr1); + assert_eq!(obj1.next_sequence_number, obj1_seq); + + // 2. Transfer obj1 to addr2 + let pure_args = vec![bcs::to_bytes(&addr2.to_vec()).unwrap()]; + call( + &mut storage, + &native_functions, + "transfer", + vec![obj1.clone()], + pure_args, + ); + + let updated = storage.updated(); + assert_eq!(updated.len(), 1); + assert!(storage.created().is_empty()); + assert!(storage.deleted().is_empty()); + storage.flush(); + let transferred_obj = storage.read_object(&id1).unwrap(); + assert_eq!(transferred_obj.owner.to_address_hack(), addr2); + obj1_seq = obj1_seq.increment().unwrap(); + assert_eq!(transferred_obj.next_sequence_number, obj1_seq); + assert_eq!(obj1.data, transferred_obj.data); + obj1 = transferred_obj; + + // 3. Create another object obj2 owned by addr2, use it to update addr1 + let pure_args = vec![ + 20u64.to_le_bytes().to_vec(), + bcs::to_bytes(&addr2.to_vec()).unwrap(), + ]; + call( + &mut storage, + &native_functions, + "create", + Vec::new(), + pure_args, + ); + let obj2 = storage + .created() + .values() + .cloned() + .collect::>() + .pop() + .unwrap(); + storage.flush(); + + call( + &mut storage, + &native_functions, + "update", + vec![obj1.clone(), obj2], + Vec::new(), + ); + let updated = storage.updated(); + assert_eq!(updated.len(), 1); + assert!(storage.created().is_empty()); + assert!(storage.deleted().is_empty()); + storage.flush(); + let updated_obj = storage.read_object(&id1).unwrap(); + assert_eq!(updated_obj.owner.to_address_hack(), addr2); + obj1_seq = obj1_seq.increment().unwrap(); + assert_eq!(updated_obj.next_sequence_number, obj1_seq); + assert_ne!(obj1.data, updated_obj.data); + obj1 = updated_obj; + + // 4. Delete obj1 + call( + &mut storage, + &native_functions, + "delete", + vec![obj1], + Vec::new(), + ); + let deleted = storage.deleted(); + assert_eq!(deleted.len(), 1); + assert!(storage.created().is_empty()); + assert!(storage.updated().is_empty()); + storage.flush(); + assert!(storage.read_object(&id1).is_none()) +} + +// TODO(https://github.com/MystenLabs/fastnft/issues/92): tests that exercise all the error codes of the adapter diff --git a/fastx_programmability/framework/Cargo.toml b/fastx_programmability/framework/Cargo.toml index 41fc7a65324e9..d9fad74297c8f 100644 --- a/fastx_programmability/framework/Cargo.toml +++ b/fastx_programmability/framework/Cargo.toml @@ -20,4 +20,4 @@ move-core-types = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4 move-package = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } move-stdlib = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } move-vm-runtime = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } -move-vm-types = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } \ No newline at end of file +move-vm-types = { git = "https://github.com/diem/diem", rev="346301f33b3489bb4e486ae6c0aa5e030223b492" } diff --git a/fastx_programmability/framework/Move.toml b/fastx_programmability/framework/Move.toml index f3c83208c85dd..aa287f9058e9b 100644 --- a/fastx_programmability/framework/Move.toml +++ b/fastx_programmability/framework/Move.toml @@ -3,7 +3,7 @@ name = "FastX" version = "0.0.1" [dependencies] -MoveStdlib = { git = "https://github.com/diem/diem.git", subdir="language/move-stdlib", rev = "5572eae5e35407f10316b473f95cb155a5e8d40b" } +MoveStdlib = { git = "https://github.com/diem/diem.git", subdir="language/move-stdlib", rev = "346301f33b3489bb4e486ae6c0aa5e030223b492" } [addresses] Std = "0x1" diff --git a/fastx_programmability/framework/sources/Object.move b/fastx_programmability/framework/sources/Object.move deleted file mode 100644 index 11afa97171f0d..0000000000000 --- a/fastx_programmability/framework/sources/Object.move +++ /dev/null @@ -1,21 +0,0 @@ -/// Utility functions for dynamic lookup of objects -// TODO: We may not want to expose some or all of thes. -// For now, we're using dynamic lookups as a temporary -// crutch to work around the inability to pass structured -// objects (i.e., FastX objects) into the Move VM from the -// outside -module FastX::Object { - //use FastX::ID::ID; - //use FastX::TxContext::TxContext; - - /*/// Remove and return the object of type `T` with id `ID`. - /// Aborts if the global object pool does not have an object - /// named `id`. - /// Aborts if `T.owner != ctx.sender`. - // TODO: enforce private type restriction on `T` - public native fun remove(id: &ID, ctx: &TxContext): T; - */ - - // TODO: can't support borrow and borrow_mut because of dangling ref - // risks -} diff --git a/fastx_programmability/framework/sources/ObjectBasics.move b/fastx_programmability/framework/sources/ObjectBasics.move new file mode 100644 index 0000000000000..36c79ad2edb1a --- /dev/null +++ b/fastx_programmability/framework/sources/ObjectBasics.move @@ -0,0 +1,30 @@ +/// Test CTURD object basics (create, transfer, update, read, delete) +module FastX::ObjectBasics { + use FastX::Authenticator; + use FastX::ID::ID; + use FastX::TxContext::{Self, TxContext}; + use FastX::Transfer; + + struct Object has key { + id: ID, + value: u64, + } + + public fun create(value: u64, recipient: vector, ctx: TxContext) { + Transfer::transfer(Object { id: TxContext::new_id(&mut ctx), value }, Authenticator::new(recipient)) + } + + public fun transfer(o: Object, recipient: vector, _ctx: TxContext) { + Transfer::transfer(o, Authenticator::new(recipient)) + } + + // test that reading o2 and updating o1 works + public fun update(o1: &mut Object, o2: &Object, _ctx: TxContext) { + o1.value = o2.value + } + + public fun delete(o: Object, _ctx: TxContext) { + let Object { id: _, value: _ } = o; + } + +} diff --git a/fastx_programmability/framework/src/natives/authenticator.rs b/fastx_programmability/framework/src/natives/authenticator.rs index d40478ed804cd..225f74944be1a 100644 --- a/fastx_programmability/framework/src/natives/authenticator.rs +++ b/fastx_programmability/framework/src/natives/authenticator.rs @@ -23,7 +23,7 @@ pub fn bytes_to_address( debug_assert!(args.len() == 1); let addr_bytes = pop_arg!(args, Vec); - assert!(addr_bytes.len() == 32); + assert!(addr_bytes.len() >= 16); // truncate the ID to 16 bytes // TODO: truncation not secure. we'll either need to support longer account addresses in Move or do this a different way // TODO: fix unwrap diff --git a/fastx_types/src/base_types.rs b/fastx_types/src/base_types.rs index 61f3b63cff682..75202a1fa1e00 100644 --- a/fastx_types/src/base_types.rs +++ b/fastx_types/src/base_types.rs @@ -103,6 +103,30 @@ impl TxContext { id } + + // TODO(https://github.com/MystenLabs/fastnft/issues/89): temporary hack for Move compatibility + pub fn to_bcs_bytes_hack(&self) -> Vec { + let sender = self.digest.0 .0; + let inputs_hash = self.digest.0 .0.to_vec(); + let obj = TxContextForMove { + sender, + inputs_hash, + ids_created: self.ids_created, + }; + bcs::to_bytes(&obj).unwrap() + } + + // for testing + pub fn random() -> Self { + Self::new(TransactionDigest::random()) + } +} + +#[derive(Serialize)] +struct TxContextForMove { + sender: AccountAddress, + inputs_hash: Vec, + ids_created: u64, } impl TransactionDigest { @@ -118,6 +142,11 @@ impl TransactionDigest { SequenceNumber::new(), ) } + + // for testing + pub fn random() -> Self { + Self::new(ObjectID::random(), SequenceNumber::new()) + } } pub fn get_key_pair() -> (FastPayAddress, KeyPair) { diff --git a/fastx_types/src/error.rs b/fastx_types/src/error.rs index 25f255d98e2ed..41b92323d3faa 100644 --- a/fastx_types/src/error.rs +++ b/fastx_types/src/error.rs @@ -96,10 +96,12 @@ pub enum FastPayError { InvalidDecoding, #[error("Unexpected message.")] UnexpectedMessage, + #[error("The transaction inputs contain duplicates ObjectRef's")] + DuplicateObjectRefInput, #[error("Network error while querying service: {:?}.", error)] ClientIoError { error: String }, - // Move related errors + // Move module publishing related errors #[error("Failed to load the Move module, reason: {error:?}.")] ModuleLoadFailure { error: String }, #[error("Failed to verify the Move module, reason: {error:?}.")] @@ -109,6 +111,18 @@ pub enum FastPayError { #[error("Failed to publish the Move module(s), reason: {error:?}.")] ModulePublishFailure { error: String }, + // Move call related errors + #[error("Gas budget set higher than max: {error:?}.")] + GasBudgetTooHigh { error: String }, + #[error("Function resolution failure: {error:?}.")] + FunctionNotFound { error: String }, + #[error("Function signature is invalid: {error:?}.")] + InvalidFunctionSignature { error: String }, + #[error("Type error while binding function arguments: {error:?}.")] + TypeError { error: String }, + #[error("Execution aborted: {error:?}.")] + AbortedExecution { error: String }, + // Internal state errors #[error("Attempt to re-initialize an order lock.")] OrderLockExists, diff --git a/fastx_types/src/messages.rs b/fastx_types/src/messages.rs index 311a61e65f7e0..89f550e34f341 100644 --- a/fastx_types/src/messages.rs +++ b/fastx_types/src/messages.rs @@ -7,10 +7,7 @@ use super::{base_types::*, committee::Committee, error::*}; #[path = "unit_tests/messages_tests.rs"] mod messages_tests; -use move_core_types::{ - identifier::Identifier, - language_storage::{ModuleId, TypeTag}, -}; +use move_core_types::{identifier::Identifier, language_storage::TypeTag}; use serde::{Deserialize, Serialize}; use std::{ collections::HashSet, @@ -42,7 +39,7 @@ pub struct Transfer { #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub struct MoveCall { pub sender: FastPayAddress, - pub module: ModuleId, // TODO: Could also be ObjectId? + pub module: ObjectRef, pub function: Identifier, pub type_arguments: Vec, pub gas_payment: ObjectRef, @@ -181,7 +178,7 @@ impl Order { #[allow(clippy::too_many_arguments)] pub fn new_move_call( sender: FastPayAddress, - module: ModuleId, // TODO: Could also be ObjectId? + module: ObjectRef, function: Identifier, type_arguments: Vec, gas_payment: ObjectRef, @@ -250,9 +247,9 @@ impl Order { } OrderKind::Call(c) => { let mut call_inputs = Vec::with_capacity(2 + c.object_arguments.len()); - call_inputs.push(c.gas_payment); - call_inputs.push((*c.module.address(), 0.into())); call_inputs.extend(c.object_arguments.clone()); + call_inputs.push(c.module); + call_inputs.push(c.gas_payment); call_inputs } OrderKind::Publish(m) => { diff --git a/fastx_types/src/object.rs b/fastx_types/src/object.rs index 31b82999e9c0c..4e0177b4e779f 100644 --- a/fastx_types/src/object.rs +++ b/fastx_types/src/object.rs @@ -14,6 +14,16 @@ pub struct MoveObject { pub contents: Vec, } +impl MoveObject { + pub fn new(type_: StructTag, contents: Vec) -> Self { + Self { type_, contents } + } + + pub fn id(&self) -> ObjectID { + AccountAddress::try_from(&self.contents[0..16]).unwrap() + } +} + #[derive(Eq, PartialEq, Debug, Clone)] #[allow(clippy::large_enum_variant)] pub enum Data { @@ -32,6 +42,14 @@ impl Data { Module { .. } => true, } } + + pub fn as_module(&self) -> Option { + use Data::*; + match self { + Move(_) => None, + Module(bytes) => CompiledModule::deserialize(bytes).ok(), + } + } } #[derive(Eq, PartialEq, Debug, Clone)] @@ -46,13 +64,12 @@ pub struct Object { impl Object { /// Create a new Move object pub fn new_move( - type_: StructTag, - contents: Vec, + o: MoveObject, owner: FastPayAddress, next_sequence_number: SequenceNumber, ) -> Self { Object { - data: Data::Move(MoveObject { type_, contents }), + data: Data::Move(o), owner, next_sequence_number, } @@ -84,7 +101,7 @@ impl Object { use Data::*; match &self.data { - Move(v) => AccountAddress::try_from(&v.contents[0..16]).unwrap(), //unimplemented!("parse ID from bytes"), // TODO: parse from v + Move(v) => v.id(), Module(m) => { // TODO: extract ID by peeking into the bytes instead of deserializing *CompiledModule::deserialize(m).unwrap().self_id().address()