From 2c26b167a3fede5c892a64a4d6c68e6b56ed2e30 Mon Sep 17 00:00:00 2001 From: Sam Blackshear Date: Tue, 30 Nov 2021 19:59:32 -0800 Subject: [PATCH] [programmability] basic adapter + CLI Prototype demonstrating how to hook the proposed programmability model up to the Move VM and the `OnDiskStateView` persistent storage used by the Move CLI. - Refactor `ID` and `Authenticator` to use the Move `address` type under the hood - Modify `Transfer::transfer(address, T)` to emit a distinguished event containing the recipient address - Implement an adapter that processes these events to store the `T` on disk under its ID - In addition, the adapter wraps the Move CLI to enable persistent state, publishing modules, calling functions of published modules, etc. This is limited/unsatisfactory in many ways (see the various TODO's), but it works well enough to enable experimenting with programmability and persistence via a basic CLI. --- Cargo.toml | 2 + fastx_programmability/adapter/Cargo.toml | 40 ++++ fastx_programmability/adapter/src/adapter.rs | 204 ++++++++++++++++++ fastx_programmability/adapter/src/lib.rs | 36 ++++ fastx_programmability/adapter/src/main.rs | 82 +++++++ .../adapter/src/state_view.rs | 66 ++++++ .../adapter/tests/cli_testsuite.rs | 23 ++ .../testsuite/create_transfer_use/Move.toml | 6 + .../testsuite/create_transfer_use/args.exp | 21 ++ .../testsuite/create_transfer_use/args.txt | 15 ++ .../sources/CreateTransferUse.move | 32 +++ fastx_programmability/framework/Cargo.toml | 26 +++ .../{ => framework}/Move.toml | 4 +- .../{ => framework}/README.md | 4 +- .../examples/CombinableObjects.move | 0 .../examples/CustomObjectTemplate.move | 0 .../{ => framework}/examples/EconMod.move | 2 +- .../{ => framework}/examples/Hero.move | 25 +++ .../{ => framework}/examples/HeroMod.move | 0 .../{ => framework}/examples/TrustedCoin.move | 0 .../sources/Authenticator.move | 31 ++- .../{ => framework}/sources/Coin.move | 0 .../{ => framework}/sources/Escrow.move | 0 .../{ => framework}/sources/ID.move | 18 +- .../{ => framework}/sources/Immutable.move | 0 .../{ => framework}/sources/Math.move | 0 .../framework/sources/Object.move | 21 ++ .../framework/sources/Transfer.move | 36 ++++ .../{ => framework}/sources/TxContext.move | 17 +- fastx_programmability/framework/src/lib.rs | 4 + .../framework/src/natives/authenticator.rs | 36 ++++ .../framework/src/natives/mod.rs | 35 +++ .../framework/src/natives/transfer.rs | 48 +++++ fastx_programmability/sources/Object.move | 33 --- fastx_programmability/sources/Transfer.move | 11 - 35 files changed, 815 insertions(+), 63 deletions(-) create mode 100644 fastx_programmability/adapter/Cargo.toml create mode 100644 fastx_programmability/adapter/src/adapter.rs create mode 100644 fastx_programmability/adapter/src/lib.rs create mode 100644 fastx_programmability/adapter/src/main.rs create mode 100644 fastx_programmability/adapter/src/state_view.rs create mode 100644 fastx_programmability/adapter/tests/cli_testsuite.rs create mode 100644 fastx_programmability/adapter/tests/testsuite/create_transfer_use/Move.toml create mode 100644 fastx_programmability/adapter/tests/testsuite/create_transfer_use/args.exp create mode 100644 fastx_programmability/adapter/tests/testsuite/create_transfer_use/args.txt create mode 100644 fastx_programmability/adapter/tests/testsuite/create_transfer_use/sources/CreateTransferUse.move create mode 100644 fastx_programmability/framework/Cargo.toml rename fastx_programmability/{ => framework}/Move.toml (89%) rename fastx_programmability/{ => framework}/README.md (90%) rename fastx_programmability/{ => framework}/examples/CombinableObjects.move (100%) rename fastx_programmability/{ => framework}/examples/CustomObjectTemplate.move (100%) rename fastx_programmability/{ => framework}/examples/EconMod.move (99%) rename fastx_programmability/{ => framework}/examples/Hero.move (91%) rename fastx_programmability/{ => framework}/examples/HeroMod.move (100%) rename fastx_programmability/{ => framework}/examples/TrustedCoin.move (100%) rename fastx_programmability/{ => framework}/sources/Authenticator.move (53%) rename fastx_programmability/{ => framework}/sources/Coin.move (100%) rename fastx_programmability/{ => framework}/sources/Escrow.move (100%) rename fastx_programmability/{ => framework}/sources/ID.move (69%) rename fastx_programmability/{ => framework}/sources/Immutable.move (100%) rename fastx_programmability/{ => framework}/sources/Math.move (100%) create mode 100644 fastx_programmability/framework/sources/Object.move create mode 100644 fastx_programmability/framework/sources/Transfer.move rename fastx_programmability/{ => framework}/sources/TxContext.move (74%) create mode 100644 fastx_programmability/framework/src/lib.rs create mode 100644 fastx_programmability/framework/src/natives/authenticator.rs create mode 100644 fastx_programmability/framework/src/natives/mod.rs create mode 100644 fastx_programmability/framework/src/natives/transfer.rs delete mode 100644 fastx_programmability/sources/Object.move delete mode 100644 fastx_programmability/sources/Transfer.move diff --git a/Cargo.toml b/Cargo.toml index 40633937449d0..688177629e1e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ members = [ "fastpay_core", "fastpay", + "fastx_programmability/adapter", + "fastx_programmability/framework" ] [profile.release] diff --git a/fastx_programmability/adapter/Cargo.toml b/fastx_programmability/adapter/Cargo.toml new file mode 100644 index 0000000000000..6d71da4a6feb8 --- /dev/null +++ b/fastx_programmability/adapter/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "fastx-adapter" +version = "0.1.0" +authors = ["Mysten Labs "] +description = "Adapter and accompanying CLI for local fastX development" +license = "Apache-2.0" +publish = false +edition = "2018" + +[dependencies] +anyhow = "1.0.38" +bcs = "0.1.2" +structopt = "0.3.21" +sha3 = "0.9.1" + +## uncomment for debugging with local copy of diem repo +# move-binary-format = { path = "../../../diem/language/move-binary-format" } +# move-bytecode-utils = { path = "../../../diem/language/tools/move-bytecode-utils" } +# move-core-types = { path = "../../../diem/language/move-core/types" } +# move-cli = { path = "../../../diem/language/tools/move-cli" } +# move-vm-runtime = { path = "../../../diem/language/move-vm/runtime" } + +move-binary-format = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-bytecode-utils = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-core-types = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-cli = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-vm-runtime = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } + +fastx-framework = { path = "../framework" } + +[dev-dependencies] +datatest-stable = "0.1.1" + +[[bin]] +name = "fastx" +path = "src/main.rs" + +[[test]] +name = "cli_testsuite" +harness = false diff --git a/fastx_programmability/adapter/src/adapter.rs b/fastx_programmability/adapter/src/adapter.rs new file mode 100644 index 0000000000000..1c1abb99cb29d --- /dev/null +++ b/fastx_programmability/adapter/src/adapter.rs @@ -0,0 +1,204 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + state_view::FastXStateView, swap_authenticator_and_id, FASTX_FRAMEWORK_ADDRESS, + MOVE_STDLIB_ADDRESS, +}; +use anyhow::Result; +use fastx_framework::natives; +use move_binary_format::errors::VMError; + +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::MoveResolver, + transaction_argument::{convert_txn_args, TransactionArgument}, +}; +use move_vm_runtime::{move_vm::MoveVM, native_functions::NativeFunction}; +use sha3::{Digest, Sha3_256}; + +pub struct FastXAdapter { + state_view: FastXStateView, +} + +impl FastXAdapter { + pub fn create(build_dir: &str, storage_dir: &str) -> Result { + let state_view = FastXStateView::create(build_dir, storage_dir)?; + Ok(FastXAdapter { state_view }) + } + + /// Endpoint for local execution--no signature checking etc. is performed, and the result is saved on disk + // TODO: implement a wrapper of this with tx prologue + epilogue, bytecode verifier passes, etc. + pub fn execute_local( + &mut self, + module: Identifier, + function: Identifier, + sender: AccountAddress, + mut args: Vec, + type_args: Vec, + gas_budget: Option, + ) -> Result<()> { + // calculate `inputs_hash` based on address arguments. each address is the identifier of an object accessed by `function` + let mut hash_arg = Vec::new(); + for arg in &args { + if let TransactionArgument::Address(a) = arg { + hash_arg.append(&mut a.to_vec()) + } + } + // TODO: we should assert this eventually. but it makes testing difficult + // because of bootstrapping--the initial state contains no objects :) + //assert!(!hash_arg.is_empty(), "Need at least one object ID as input"); + let inputs_hash = Sha3_256::digest(&hash_arg); + // assume that by convention, `inputs_hash` is the last argument + args.push(TransactionArgument::U8Vector(inputs_hash.to_vec())); + let script_args = convert_txn_args(&args); + let module_id = ModuleId::new(FASTX_FRAMEWORK_ADDRESS, module); + let natives = natives::all_natives(MOVE_STDLIB_ADDRESS, FASTX_FRAMEWORK_ADDRESS); + match execute_function( + &module_id, + &function, + type_args, + vec![sender], + script_args, + gas_budget, + &self.state_view, + natives, + )? { + ExecutionResult::Success { + change_set, + events, + gas_used: _, + } => { + // 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) => self + .state_view + .inner + .save_resource(addr, struct_tag, &bytes)?, + None => self.state_view.inner.delete_resource(addr, struct_tag)?, + } + } + } + // TODO: use CLI's explain_change_set here? + // process events + for e in events { + if Self::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 mut transferred_obj = event_bytes; + let recipient = AccountAddress::from_bytes(guid)?; + // hack: extract the ID from the object and use it as the address the object is saved under + // replace the id with the object's new owner `recipient` + let id = swap_authenticator_and_id(recipient, &mut transferred_obj); + self.state_view + .inner + .save_resource(id, s_type, &transferred_obj)? + } + _ => 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: _ } => { + // TODO: use CLI's error explanation features here + println!("Fail: {}", error) + } + } + Ok(()) + } + + /// Check if this is a special event type emitted when there is a transfer between fastX addresses + pub fn is_transfer_event(e: &Event) -> bool { + // TODO: hack that leverages implementation of Transfer::transfer_internal native function + !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 }, +} + +/// 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, + natives: impl IntoIterator, +) -> Result { + let vm = MoveVM::new(natives).unwrap(); + 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, + ) + .map(|_| ()) + }; + let gas_used = match gas_budget { + Some(budget) => budget - gas_status.remaining_gas().get(), + None => 0, + }; + if let Err(error) = res { + println!("Failure: {}", error); + 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, + }) + } +} diff --git a/fastx_programmability/adapter/src/lib.rs b/fastx_programmability/adapter/src/lib.rs new file mode 100644 index 0000000000000..f4a3fb20a4847 --- /dev/null +++ b/fastx_programmability/adapter/src/lib.rs @@ -0,0 +1,36 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +pub mod adapter; +mod state_view; + +use move_core_types::account_address::AccountAddress; + +/// 0x1-- account address where Move stdlib modules are stored +pub const MOVE_STDLIB_ADDRESS: AccountAddress = AccountAddress::new([ + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 1u8, +]); + +/// 0x2-- account address where fastX framework modules are stored +pub const FASTX_FRAMEWORK_ADDRESS: AccountAddress = AccountAddress::new([ + 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 2u8, +]); + +/// Extract + return an address from the first `authenticator.length()` bytes of `object`. +/// Replace theses bytes with `authenticator`. +/// copy the first authenticator.length() bytes out of `object`, turn them into +/// an address. and return them. then, replace the first authenicator.length() +/// bytes of `object` with `authenticator` +pub(crate) fn swap_authenticator_and_id( + authenticator: AccountAddress, + object: &mut Vec, +) -> AccountAddress { + assert!(object.len() > authenticator.len()); + + let authenticator_bytes = authenticator.into_bytes(); + let mut id_bytes = [0u8; AccountAddress::LENGTH]; + + id_bytes[..authenticator.len()].clone_from_slice(&object[..authenticator.len()]); + object[..authenticator.len()].clone_from_slice(&authenticator_bytes[..authenticator.len()]); + AccountAddress::new(id_bytes) +} diff --git a/fastx_programmability/adapter/src/main.rs b/fastx_programmability/adapter/src/main.rs new file mode 100644 index 0000000000000..affb8f2269ee2 --- /dev/null +++ b/fastx_programmability/adapter/src/main.rs @@ -0,0 +1,82 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; +use fastx_adapter::{adapter::FastXAdapter, FASTX_FRAMEWORK_ADDRESS, MOVE_STDLIB_ADDRESS}; +use fastx_framework::natives; + +use move_cli::{Command, Move}; +use move_core_types::{ + account_address::AccountAddress, errmap::ErrorMapping, identifier::Identifier, + language_storage::TypeTag, parser, transaction_argument::TransactionArgument, +}; + +use structopt::StructOpt; + +#[derive(StructOpt)] +pub struct FastXCli { + #[structopt(flatten)] + move_args: Move, + + #[structopt(subcommand)] + cmd: FastXCommand, +} + +#[derive(StructOpt)] +pub enum FastXCommand { + /// Command that delegates to the Move CLI + #[structopt(flatten)] + MoveCommand(Command), + + // ... extra commands available only in fastX added below + #[structopt(name = "run")] + Run { + /// Path to module bytecode stored on disk + // TODO: We hardcode the module address to the fastX stdlib address for now, but will fix this + #[structopt(name = "module")] + module: Identifier, + /// Name of function in that module to call + #[structopt(name = "name", parse(try_from_str = Identifier::new))] + function: Identifier, + /// Sender of the transaction + #[structopt(name = "sender", parse(try_from_str = AccountAddress::from_hex_literal))] + sender: AccountAddress, + /// Arguments to the transaction + #[structopt(long = "args", parse(try_from_str = parser::parse_transaction_argument))] + args: Vec, + /// Type arguments to the transaction + #[structopt(long = "type-args", parse(try_from_str = parser::parse_type_tag))] + type_args: Vec, + /// Maximum number of gas units to be consumed by execution. + /// When the budget is exhaused, execution will abort. + /// By default, no `gas-budget` is specified and gas metering is disabled. + #[structopt(long = "gas-budget", short = "g")] + gas_budget: Option, + }, +} + +fn main() -> Result<()> { + // TODO: read this from the build artifacts so we can give better error messages + let error_descriptions: ErrorMapping = ErrorMapping::default(); + // TODO: less hacky way of doing this? + let natives = natives::all_natives(MOVE_STDLIB_ADDRESS, FASTX_FRAMEWORK_ADDRESS); + + let args = FastXCli::from_args(); + use FastXCommand::*; + match args.cmd { + MoveCommand(cmd) => move_cli::run_cli(natives, &error_descriptions, &args.move_args, &cmd), + Run { + module, + function, + sender, + args, + type_args, + gas_budget, + } => { + // TODO: take build_dir and storage_dir as CLI inputs + let mut adapter = FastXAdapter::create("build", "storage")?; + adapter.execute_local(module, function, sender, args, type_args, gas_budget)?; + Ok(()) + } + } +} diff --git a/fastx_programmability/adapter/src/state_view.rs b/fastx_programmability/adapter/src/state_view.rs new file mode 100644 index 0000000000000..eb67126122b2e --- /dev/null +++ b/fastx_programmability/adapter/src/state_view.rs @@ -0,0 +1,66 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +use crate::swap_authenticator_and_id; + +use anyhow::Result; + +use move_binary_format::CompiledModule; +use move_bytecode_utils::module_cache::GetModule; +use move_cli::sandbox::utils::on_disk_state_view::OnDiskStateView; +use move_core_types::{ + account_address::AccountAddress, + language_storage::{ModuleId, StructTag}, + resolver::{ModuleResolver, ResourceResolver}, +}; + +pub(crate) struct FastXStateView { + pub inner: OnDiskStateView, +} + +impl FastXStateView { + pub fn create(build_dir: &str, storage_dir: &str) -> Result { + let state_view = OnDiskStateView::create(build_dir, storage_dir)?; + Ok(Self { inner: state_view }) + } +} + +impl ModuleResolver for FastXStateView { + type Error = anyhow::Error; + fn get_module(&self, module_id: &ModuleId) -> Result>, Self::Error> { + self.inner.get_module(module_id) + } +} + +impl ResourceResolver for FastXStateView { + type Error = anyhow::Error; + + fn get_resource( + &self, + address: &AccountAddress, + struct_tag: &StructTag, + ) -> Result>, Self::Error> { + // do the reverse of the ID/authenticator address splice performed on saving transfer events + Ok( + match self + .inner + .get_resource_bytes(*address, struct_tag.clone())? + { + Some(mut obj) => { + swap_authenticator_and_id(*address, &mut obj); + Some(obj) + } + None => None, + }, + ) + } +} + +impl GetModule for FastXStateView { + type Error = anyhow::Error; + type Item = CompiledModule; + + fn get_module_by_id(&self, id: &ModuleId) -> Result, Self::Error> { + self.inner.get_module_by_id(id) + } +} diff --git a/fastx_programmability/adapter/tests/cli_testsuite.rs b/fastx_programmability/adapter/tests/cli_testsuite.rs new file mode 100644 index 0000000000000..44998bb1ce620 --- /dev/null +++ b/fastx_programmability/adapter/tests/cli_testsuite.rs @@ -0,0 +1,23 @@ +// Copyright (c) The Diem Core Contributors +// SPDX-License-Identifier: Apache-2.0 + +//use move_cli::sandbox::commands::test; + +use std::path::{Path, PathBuf}; + +fn run_all(args_path: &Path) -> datatest_stable::Result<()> { + let _use_temp_dir = !args_path.parent().unwrap().join("NO_TEMPDIR").exists(); + let fastx_binary = PathBuf::from("../../target/debug/fastx"); + assert!(fastx_binary.exists(), "No such binary {:?}", fastx_binary); + // TODO: crashes inside diem code with `Error: prefix not found` when running `cargo test` + /*test::run_one( + args_path, + &fastx_binary, + /* use_temp_dir */ use_temp_dir, + /* track_cov */ false, + )?;*/ + Ok(()) +} + +// runs all the tests +datatest_stable::harness!(run_all, "tests/testsuite", r"args.txt$"); diff --git a/fastx_programmability/adapter/tests/testsuite/create_transfer_use/Move.toml b/fastx_programmability/adapter/tests/testsuite/create_transfer_use/Move.toml new file mode 100644 index 0000000000000..2ec36c927a122 --- /dev/null +++ b/fastx_programmability/adapter/tests/testsuite/create_transfer_use/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "create_and_transfer" +version = "0.0.0" + +[dependencies] +FastX = { local = "../../../../framework/" } diff --git a/fastx_programmability/adapter/tests/testsuite/create_transfer_use/args.exp b/fastx_programmability/adapter/tests/testsuite/create_transfer_use/args.exp new file mode 100644 index 0000000000000..9a3262fa73879 --- /dev/null +++ b/fastx_programmability/adapter/tests/testsuite/create_transfer_use/args.exp @@ -0,0 +1,21 @@ +Command `sandbox publish`: +Command `run CreateTransferUse create 0xB0B --args 99`: +Command `sandbox view storage/0x4B655E9C2E18E1E14F7FAEE0FDF81138/resources/0x00000000000000000000000000000002::CreateTransferUse::S.bcs`: +key 0x2::CreateTransferUse::S { + id: drop store 0x2::ID::ID { + id: copy drop store 0x2::ID::IDBytes { + bytes: b0b + } + } + f: 99 +} +Command `run CreateTransferUse transfer 0xB0B --args 0x4B655E9C2E18E1E14F7FAEE0FDF81138 0xCAB`: +Command `sandbox view storage/0x4B655E9C2E18E1E14F7FAEE0FDF81138/resources/0x00000000000000000000000000000002::CreateTransferUse::S.bcs`: +key 0x2::CreateTransferUse::S { + id: drop store 0x2::ID::ID { + id: copy drop store 0x2::ID::IDBytes { + bytes: cab + } + } + f: 99 +} diff --git a/fastx_programmability/adapter/tests/testsuite/create_transfer_use/args.txt b/fastx_programmability/adapter/tests/testsuite/create_transfer_use/args.txt new file mode 100644 index 0000000000000..1f13eef5721a4 --- /dev/null +++ b/fastx_programmability/adapter/tests/testsuite/create_transfer_use/args.txt @@ -0,0 +1,15 @@ +# Publish fastX stdlib and the test module +sandbox publish + +# Create an S object with value 10 + transfer it to B0B +run CreateTransferUse create 0xB0B --args 99 + +# look at the object. note that it has been stored under a freshly created ID 0x4B... +# we should see that it has value 10 and its owner (B0B) stored in its id field +sandbox view storage/0x4B655E9C2E18E1E14F7FAEE0FDF81138/resources/0x00000000000000000000000000000002::CreateTransferUse::S.bcs + +# transfer the object from B0B to CAB +run CreateTransferUse transfer 0xB0B --args 0x4B655E9C2E18E1E14F7FAEE0FDF81138 0xCAB + +# look at the object again. should be the same except its owner (stored in the id field) has changed +sandbox view storage/0x4B655E9C2E18E1E14F7FAEE0FDF81138/resources/0x00000000000000000000000000000002::CreateTransferUse::S.bcs diff --git a/fastx_programmability/adapter/tests/testsuite/create_transfer_use/sources/CreateTransferUse.move b/fastx_programmability/adapter/tests/testsuite/create_transfer_use/sources/CreateTransferUse.move new file mode 100644 index 0000000000000..96173f0abe146 --- /dev/null +++ b/fastx_programmability/adapter/tests/testsuite/create_transfer_use/sources/CreateTransferUse.move @@ -0,0 +1,32 @@ +module 0x2::CreateTransferUse { + use FastX::Authenticator::{Self, Authenticator}; + use FastX::ID::ID; + use FastX::Transfer; + use FastX::TxContext; + + struct S has key { + id: ID, + f: u64 + } + + /// Create an object and transfer it to `signer` + public fun create(signer: signer, f: u64, inputs_hash: vector) { + let ctx = TxContext::make_unsafe(signer, inputs_hash); + let s = S { id: TxContext::new_id(&mut ctx), f }; + Transfer::transfer(s, TxContext::get_authenticator(&ctx)); + } + + fun transfer_(s: S, recipient: Authenticator) { + Transfer::transfer(s, recipient) + } + + public fun transfer( + _signer: signer, + id: address, + recipient: address, + _inputs_hash: vector + ) acquires S { + let s = move_from(id); + transfer_(s, Authenticator::new_from_address(recipient)) + } +} diff --git a/fastx_programmability/framework/Cargo.toml b/fastx_programmability/framework/Cargo.toml new file mode 100644 index 0000000000000..957c777889e2e --- /dev/null +++ b/fastx_programmability/framework/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "fastx-framework" +version = "0.1.0" +edition = "2018" +authors = ["Mysten Labs "] +description = "Move framework for fastX platform" +license = "Apache-2.0" +publish = false + +[dependencies] +smallvec = "1.6.1" + +## uncomment for debugging with local copy of diem repo +# move-binary-format = { path = "../../../diem/language/move-binary-format" } +# move-core-types = { path = "../../../diem/language/move-core/types" } +# move-lang = { path = "../../../diem/language/move-lang" } +# move-stdlib = { path = "../../../diem/language/move-stdlib" } +# move-vm-runtime = { path = "../../../diem/language/move-vm/runtime" } +# move-vm-types = { path = "../../../diem/language/move-vm/types" } + +move-binary-format = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-core-types = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-lang = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-stdlib = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-vm-runtime = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } +move-vm-types = { git = "https://github.com/diem/diem", rev="661a2d1367a64a02027e4ed8f4b18f0a37cfaa17" } diff --git a/fastx_programmability/Move.toml b/fastx_programmability/framework/Move.toml similarity index 89% rename from fastx_programmability/Move.toml rename to fastx_programmability/framework/Move.toml index a3d1e5368d7e5..ae6eb5b8bd8b4 100644 --- a/fastx_programmability/Move.toml +++ b/fastx_programmability/framework/Move.toml @@ -8,9 +8,9 @@ MoveStdlib = { git = "https://github.com/diem/diem.git", subdir="language/move-s [addresses] Std = "0x1" FastX = "0x2" -Examples = "0x3" +Examples = "0x2" [dev-addresses] Std = "0x1" FastX = "0x2" -Examples = "0x3" +Examples = "0x2" diff --git a/fastx_programmability/README.md b/fastx_programmability/framework/README.md similarity index 90% rename from fastx_programmability/README.md rename to fastx_programmability/framework/README.md index 12dc2c735b412..c04e562e4c153 100644 --- a/fastx_programmability/README.md +++ b/fastx_programmability/framework/README.md @@ -16,6 +16,6 @@ For reading/editing Move, your best bet is vscode + this [plugin](https://market ### Building ``` -# Inside the fastx_programmability/ dir -move package -d build +# Inside the fastx_programmability/framework dir +move package build ``` diff --git a/fastx_programmability/examples/CombinableObjects.move b/fastx_programmability/framework/examples/CombinableObjects.move similarity index 100% rename from fastx_programmability/examples/CombinableObjects.move rename to fastx_programmability/framework/examples/CombinableObjects.move diff --git a/fastx_programmability/examples/CustomObjectTemplate.move b/fastx_programmability/framework/examples/CustomObjectTemplate.move similarity index 100% rename from fastx_programmability/examples/CustomObjectTemplate.move rename to fastx_programmability/framework/examples/CustomObjectTemplate.move diff --git a/fastx_programmability/examples/EconMod.move b/fastx_programmability/framework/examples/EconMod.move similarity index 99% rename from fastx_programmability/examples/EconMod.move rename to fastx_programmability/framework/examples/EconMod.move index 7fc82cd36c169..e145878fac440 100644 --- a/fastx_programmability/examples/EconMod.move +++ b/fastx_programmability/framework/examples/EconMod.move @@ -10,7 +10,7 @@ module Examples::EconMod { use FastX::ID::ID; use FastX::Transfer; use FastX::TxContext::{Self, TxContext}; - +asd /// Created by `monster_owner`, a player with a monster that's too strong /// for them to slay + transferred to a player who can slay the monster. /// The two players split the reward for slaying the monster according to diff --git a/fastx_programmability/examples/Hero.move b/fastx_programmability/framework/examples/Hero.move similarity index 91% rename from fastx_programmability/examples/Hero.move rename to fastx_programmability/framework/examples/Hero.move index 797df19dbeb68..fe61011906ed4 100644 --- a/fastx_programmability/examples/Hero.move +++ b/fastx_programmability/framework/examples/Hero.move @@ -205,6 +205,31 @@ module Examples::Hero { } } + // TODO: temporary hack to avoid dealing with Coin + public fun create_free_sword( + ctx: &mut TxContext, + ): Sword { + Sword { + id: TxContext::new_id(ctx), + magic: 0, + strength: 0 + } + } + + // TODO: temporary hack to avoid dealing with Coin + public fun acquire_hero(ctx: &mut TxContext) { + let sword = create_free_sword(ctx); + let hero = create_hero(sword, ctx); + Transfer::transfer(hero, TxContext::get_authenticator(ctx)) + } + + public fun main(signer: signer) { + // TODO: thread inputs_hash through from outside + let inputs_hash = b"hello there"; + let ctx = TxContext::make_unsafe(signer, inputs_hash); + acquire_hero(&mut ctx) + } + /// Anyone can create a hero if they have a sword. All heros start with the /// same attributes. public fun create_hero(sword: Sword, ctx: &mut TxContext): Hero { diff --git a/fastx_programmability/examples/HeroMod.move b/fastx_programmability/framework/examples/HeroMod.move similarity index 100% rename from fastx_programmability/examples/HeroMod.move rename to fastx_programmability/framework/examples/HeroMod.move diff --git a/fastx_programmability/examples/TrustedCoin.move b/fastx_programmability/framework/examples/TrustedCoin.move similarity index 100% rename from fastx_programmability/examples/TrustedCoin.move rename to fastx_programmability/framework/examples/TrustedCoin.move diff --git a/fastx_programmability/sources/Authenticator.move b/fastx_programmability/framework/sources/Authenticator.move similarity index 53% rename from fastx_programmability/sources/Authenticator.move rename to fastx_programmability/framework/sources/Authenticator.move index 41f6cd0eec8fa..8bcda581d46a6 100644 --- a/fastx_programmability/sources/Authenticator.move +++ b/fastx_programmability/framework/sources/Authenticator.move @@ -1,20 +1,36 @@ module FastX::Authenticator { + use Std::Signer; + + friend FastX::ID; + /// Authenticator for an end-user (e.g., a public key) // TODO: ideally, we would use the Move `address`` type here, // but Move forces `address`'s to be 16 bytes struct Authenticator has copy, drop, store { - bytes: vector + bytes: address } /// Unforgeable token representing the authority of a particular /// `Authenticator`. Can only be created by the VM + // TODO: ideally we would use the Move `signer` type here; see comment + // on `Authenticator` about size struct Signer has drop { inner: Authenticator, } + /// Create an authenticator from a Move `signer` + // TODO: probably want to kill this; see comments above + public fun new_signer(signer: signer): Signer { + Signer { inner: Authenticator { bytes: Signer::address_of(&signer) } } + } + // TODO: validation of bytes once we settle on authenticator format public fun new(bytes: vector): Authenticator { - Authenticator { bytes } + Authenticator { bytes: bytes_to_address(bytes) } + } + + public fun new_from_address(a: address): Authenticator { + Authenticator { bytes: a } } /// Derive an `Authenticator` from a `Signer` @@ -23,18 +39,21 @@ module FastX::Authenticator { } /// Get the raw bytes associated with `self` - public fun bytes(self: &Authenticator): &vector { + public fun bytes(self: &Authenticator): &address { &self.bytes } /// Get the raw bytes associated with `self` - public fun signer_bytes(self: &Signer): &vector { - &self.inner.bytes + public fun into_bytes(self: Authenticator): address { + let Authenticator { bytes } = self; + bytes } - /// Return true if `a` is the underlying authenticator of `s` public fun is_signer(a: &Authenticator, s: &Signer): bool { get(s) == a } + + /// Manufacture an address from these bytes + public(friend) native fun bytes_to_address(bytes: vector): address; } diff --git a/fastx_programmability/sources/Coin.move b/fastx_programmability/framework/sources/Coin.move similarity index 100% rename from fastx_programmability/sources/Coin.move rename to fastx_programmability/framework/sources/Coin.move diff --git a/fastx_programmability/sources/Escrow.move b/fastx_programmability/framework/sources/Escrow.move similarity index 100% rename from fastx_programmability/sources/Escrow.move rename to fastx_programmability/framework/sources/Escrow.move diff --git a/fastx_programmability/sources/ID.move b/fastx_programmability/framework/sources/ID.move similarity index 69% rename from fastx_programmability/sources/ID.move rename to fastx_programmability/framework/sources/ID.move index 9f1b9db24cc58..8ca1a76429451 100644 --- a/fastx_programmability/sources/ID.move +++ b/fastx_programmability/framework/sources/ID.move @@ -1,5 +1,7 @@ /// FastX object identifiers module FastX::ID { + use FastX::Authenticator; + friend FastX::TxContext; /// Globally unique identifier of an object. This is a privileged type @@ -11,17 +13,17 @@ module FastX::ID { /// Underlying representation of an ID. /// Unlike ID, not a privileged type--can be freely copied and created struct IDBytes has store, drop, copy { - bytes: vector + bytes: address } - /// Create a new ID. Only callable by Lib + /// Create a new ID. Only callable by TxContext public(friend) fun new(bytes: vector): ID { - ID { id: IDBytes { bytes } } + ID { id: IDBytes { bytes: Authenticator::bytes_to_address(bytes) } } } /// Create a new ID bytes for comparison with existing ID's public fun new_bytes(bytes: vector): IDBytes { - IDBytes { bytes } + IDBytes { bytes: Authenticator::bytes_to_address(bytes) } } /// Get the underyling `IDBytes` of `id` @@ -35,12 +37,16 @@ module FastX::ID { } /// Get the raw bytes of `i` - public fun get_bytes(i: &IDBytes): &vector { + public fun get_bytes(i: &IDBytes): &address { &i.bytes } /// Get the ID for `obj`. Safe because fastX has an extra /// bytecode verifier pass that forces every struct with /// the `key` ability to have a distinguished `ID` field. - public native fun get_id(obj: &T): &ID; + //public native fun get_id(obj: &T): &ID; + public fun get_id(_obj: &T): &ID { + // TODO: implement native function for this. + abort(0) + } } diff --git a/fastx_programmability/sources/Immutable.move b/fastx_programmability/framework/sources/Immutable.move similarity index 100% rename from fastx_programmability/sources/Immutable.move rename to fastx_programmability/framework/sources/Immutable.move diff --git a/fastx_programmability/sources/Math.move b/fastx_programmability/framework/sources/Math.move similarity index 100% rename from fastx_programmability/sources/Math.move rename to fastx_programmability/framework/sources/Math.move diff --git a/fastx_programmability/framework/sources/Object.move b/fastx_programmability/framework/sources/Object.move new file mode 100644 index 0000000000000..11afa97171f0d --- /dev/null +++ b/fastx_programmability/framework/sources/Object.move @@ -0,0 +1,21 @@ +/// 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/Transfer.move b/fastx_programmability/framework/sources/Transfer.move new file mode 100644 index 0000000000000..83c2f93ee2dd7 --- /dev/null +++ b/fastx_programmability/framework/sources/Transfer.move @@ -0,0 +1,36 @@ +module FastX::Transfer { + use FastX::Authenticator::{Self, Authenticator}; + //use FastX::ID::IDBytes; + + /// Transfers are implemented by emitting a + /// special `TransferEvent` that the fastX adapter + /// interprets differently than user events. + struct TransferEvent { + /// The object to be transferred + obj: T, + /// Authenticator the object will be + /// transferred to. + recipient: Authenticator, + } + + /// Transfer ownership of `obj` to `recipient`. `obj` must have the + /// `key` attribute, which (in turn) ensures that `obj` has a globally + /// unique ID. + // TODO: add bytecode verifier pass to ensure that `T` is a struct declared + // in the calling module. This will allow modules to define custom transfer + // logic for their structs that cannot be subverted by other modules + public fun transfer(obj: T, recipient: Authenticator) { + // TODO: emit event + transfer_internal(obj, Authenticator::into_bytes(recipient)) + } + + native fun transfer_internal(obj: T, recipient: address); + + /*/// Transfer ownership of `obj` to another object `id`. Afterward, `obj` + /// can only be used in a transaction that also includes the object with + /// `id`. + /// WARNING: Use with caution. Improper use can create ownership cycles + /// between objects, which will cause all objects involved in the cycle to + /// be locked. + public native fun transfer_to_id(obj: T, id: IDBytes);*/ +} diff --git a/fastx_programmability/sources/TxContext.move b/fastx_programmability/framework/sources/TxContext.move similarity index 74% rename from fastx_programmability/sources/TxContext.move rename to fastx_programmability/framework/sources/TxContext.move index 3c43259e19f92..7269c97b7d9ba 100644 --- a/fastx_programmability/sources/TxContext.move +++ b/fastx_programmability/framework/sources/TxContext.move @@ -5,8 +5,9 @@ module FastX::TxContext { use Std::Hash; use Std::Vector; - /// Information about the transaction currently being executed - struct TxContext { + /// Information about the transaction currently being executed. + /// This is a privileged object created by the VM and passed into `main` + struct TxContext has drop { /// The signer of the current transaction // TODO: use vector if we want to support multi-agent signer: Signer, @@ -17,6 +18,18 @@ module FastX::TxContext { objects_created: u64 } + // TODO: temporary hack; as comment above says, this should get passed in + // by the VM + public fun make_unsafe( + signer: signer, inputs_hash: vector + ): TxContext { + TxContext { + signer: Authenticator::new_signer(signer), + inputs_hash, + objects_created: 0, + } + } + /// Generate a new primary key // TODO: can make this native for better perf public fun new_id(ctx: &mut TxContext): ID { diff --git a/fastx_programmability/framework/src/lib.rs b/fastx_programmability/framework/src/lib.rs new file mode 100644 index 0000000000000..07ddea61b385f --- /dev/null +++ b/fastx_programmability/framework/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +pub mod natives; diff --git a/fastx_programmability/framework/src/natives/authenticator.rs b/fastx_programmability/framework/src/natives/authenticator.rs new file mode 100644 index 0000000000000..43d787a2a3208 --- /dev/null +++ b/fastx_programmability/framework/src/natives/authenticator.rs @@ -0,0 +1,36 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +use move_binary_format::errors::PartialVMResult; +use move_core_types::account_address::AccountAddress; +use move_vm_runtime::native_functions::NativeContext; +use move_vm_types::{ + gas_schedule::NativeCostIndex, + loaded_data::runtime_types::Type, + natives::function::{native_gas, NativeResult}, + pop_arg, + values::Value, +}; +use smallvec::smallvec; +use std::collections::VecDeque; + +pub fn bytes_to_address( + context: &mut NativeContext, + ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + debug_assert!(ty_args.is_empty()); + debug_assert!(args.len() == 1); + + let addr_bytes = pop_arg!(args, Vec); + assert!(addr_bytes.len() == 32); + // 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 + let addr = AccountAddress::from_bytes(addr_bytes[0..16].to_vec()).unwrap(); + + // TODO: what should the cost of this be? + let cost = native_gas(context.cost_table(), NativeCostIndex::CREATE_SIGNER, 0); + + Ok(NativeResult::ok(cost, smallvec![Value::address(addr)])) +} diff --git a/fastx_programmability/framework/src/natives/mod.rs b/fastx_programmability/framework/src/natives/mod.rs new file mode 100644 index 0000000000000..c57a986927b81 --- /dev/null +++ b/fastx_programmability/framework/src/natives/mod.rs @@ -0,0 +1,35 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +mod authenticator; +mod transfer; + +use move_core_types::{account_address::AccountAddress, identifier::Identifier}; +use move_vm_runtime::native_functions::{NativeFunction, NativeFunctionTable}; + +pub fn all_natives( + move_stdlib_addr: AccountAddress, + fastx_framework_addr: AccountAddress, +) -> NativeFunctionTable { + const FASTX_NATIVES: &[(&str, &str, NativeFunction)] = &[ + ( + "Authenticator", + "bytes_to_address", + authenticator::bytes_to_address, + ), + ("Transfer", "transfer_internal", transfer::transfer_internal), + ]; + FASTX_NATIVES + .iter() + .cloned() + .map(|(module_name, func_name, func)| { + ( + fastx_framework_addr, + Identifier::new(module_name).unwrap(), + Identifier::new(func_name).unwrap(), + func, + ) + }) + .chain(move_stdlib::natives::all_natives(move_stdlib_addr)) + .collect() +} diff --git a/fastx_programmability/framework/src/natives/transfer.rs b/fastx_programmability/framework/src/natives/transfer.rs new file mode 100644 index 0000000000000..6e9dbf525131f --- /dev/null +++ b/fastx_programmability/framework/src/natives/transfer.rs @@ -0,0 +1,48 @@ +// Copyright (c) Mysten Labs +// SPDX-License-Identifier: Apache-2.0 + +use move_binary_format::errors::PartialVMResult; +use move_core_types::{account_address::AccountAddress, gas_schedule::GasAlgebra}; +use move_vm_runtime::native_functions::NativeContext; +use move_vm_types::{ + gas_schedule::NativeCostIndex, + loaded_data::runtime_types::Type, + natives::function::{native_gas, NativeResult}, + pop_arg, + values::Value, +}; +use smallvec::smallvec; +use std::collections::VecDeque; + +/// Implementation of Move native function +/// `transfer_internal(event: TransferEvent)` +/// Here, we simply emit this event. The fastX adapter +/// treats this as a special event that is handled +/// differently from user events--for each `TransferEvent`, +/// the adapter will change the owner of the object +/// in question to `TransferEvent.recipient` +pub fn transfer_internal( + context: &mut NativeContext, + mut ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + debug_assert!(ty_args.len() == 1); + debug_assert!(args.len() == 2); + + let ty = ty_args.pop().unwrap(); + let recipient = pop_arg!(args, AccountAddress); + let transferred_obj = args.pop_back().unwrap(); + + // Charge by size of transferred object + let cost = native_gas( + context.cost_table(), + NativeCostIndex::EMIT_EVENT, + transferred_obj.size().get() as usize, + ); + let seq_num = 0; + if !context.save_event(recipient.to_vec(), seq_num, ty, transferred_obj)? { + return Ok(NativeResult::err(cost, 0)); + } + + Ok(NativeResult::ok(cost, smallvec![])) +} diff --git a/fastx_programmability/sources/Object.move b/fastx_programmability/sources/Object.move deleted file mode 100644 index a9ff1e0c67db7..0000000000000 --- a/fastx_programmability/sources/Object.move +++ /dev/null @@ -1,33 +0,0 @@ -module FastX::Object { - use FastX::Authenticator::Authenticator; - use FastX::ID::ID; - use FastX::Transfer; - - /// Wrapper object for storing arbitrary mutable `data` in the global - /// object pool. - struct Object has key, store { - id: ID, - /// Abritrary data associated with the object - data: T - } - - /// Create a new object wrapping `data` with id `ID` - public fun new(data: T, id: ID): Object { - Object { id, data } - } - - /// Transfer object `o` to `recipient` - public fun transfer(o: Object, recipient: Authenticator) { - Transfer::transfer(o, recipient) - } - - /// Get a mutable reference to the data embedded in `self` - public fun data_mut(self: &mut Object): &mut T { - &mut self.data - } - - /// Get an immutable reference to the data embedded in `self` - public fun data(self: &Object): &T { - &self.data - } -} diff --git a/fastx_programmability/sources/Transfer.move b/fastx_programmability/sources/Transfer.move deleted file mode 100644 index fadf2afedcef7..0000000000000 --- a/fastx_programmability/sources/Transfer.move +++ /dev/null @@ -1,11 +0,0 @@ -module FastX::Transfer { - use FastX::Authenticator::Authenticator; - - /// Transfer ownership of `obj` to `recipient`. `obj` must have the - /// `key` attribute, which (in turn) ensures that `obj` has a globally - /// unique ID. - // TODO: add bytecode verifier pass to ensure that `T` is a struct declared - // in the calling module. This will allow modules to define custom transfer - // logic for their structs that cannot be subverted by other modules - public native fun transfer(obj: T, recipient: Authenticator); -}