From 6fa5421eed2dd171f8a6118a192a813cf28cef63 Mon Sep 17 00:00:00 2001 From: indirection42 Date: Fri, 31 May 2024 15:14:46 +0800 Subject: [PATCH] feat: modify dispatch --- Cargo.lock | 1 + poc/extensions/Cargo.toml | 3 +- poc/extensions/src/dispatchable.rs | 8 ++ poc/extensions/src/extension_core.rs | 78 +++++++++++--- poc/extensions/src/extension_fungibles.rs | 102 +++++++++++++++--- poc/extensions/src/guest.rs | 12 +-- poc/extensions/src/lib.rs | 126 ++++++++++++++-------- 7 files changed, 248 insertions(+), 82 deletions(-) create mode 100644 poc/extensions/src/dispatchable.rs diff --git a/Cargo.lock b/Cargo.lock index f5f01ac..27bed2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2525,6 +2525,7 @@ dependencies = [ name = "poc-extension" version = "0.1.0" dependencies = [ + "parity-scale-codec", "poc-executor", "scale-info", ] diff --git a/poc/extensions/Cargo.toml b/poc/extensions/Cargo.toml index 0a9da4c..0c4886f 100644 --- a/poc/extensions/Cargo.toml +++ b/poc/extensions/Cargo.toml @@ -4,9 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] +parity-scale-codec = { version = "3.6.12", default-features = false } scale-info = { version = "2.6.0", default-features = false } poc-executor = { path = "../executor", default-features = false } [features] default = ["std"] -std = ["scale-info/std", "poc-executor/std"] +std = ["parity-scale-codec/std", "scale-info/std", "poc-executor/std"] diff --git a/poc/extensions/src/dispatchable.rs b/poc/extensions/src/dispatchable.rs new file mode 100644 index 0000000..f1b92cf --- /dev/null +++ b/poc/extensions/src/dispatchable.rs @@ -0,0 +1,8 @@ +pub trait Dispatchable { + type MethodName; + type MethodIndex; + fn query_method(method_name: Self::MethodName) -> Self::MethodIndex; + fn dispatch(self) -> Vec; +} + +pub type ExtensionTypeId = u64; diff --git a/poc/extensions/src/extension_core.rs b/poc/extensions/src/extension_core.rs index 566bd49..402ebeb 100644 --- a/poc/extensions/src/extension_core.rs +++ b/poc/extensions/src/extension_core.rs @@ -1,28 +1,74 @@ -use super::{Extension, ExtensionTypeId}; -pub trait ExtensionCore: Extension { +use super::Dispatchable; +use super::ExtensionError; +use super::ExtensionTypeId; +use parity_scale_codec::{Decode, Encode}; +// SDK codes +pub trait ExtensionCore: Dispatchable + TryFrom<(u32, Vec)> { // ExtensionId should be generated by the macro // It should normalize the order of methods and parameter names const TYPE_ID: ExtensionTypeId = 0u64; - // TODO: Actual args and return values are complex types - // and we adapt them to polkavm ABI in `impl XcqExecutorContext for HostFunctions` - fn some_host_function() -> u32; - fn another_host_function() -> u32; + type ArgsOfSomeHostFunction; + type ResultOfSomeHostFunction; + fn some_host_function(args: Self::ArgsOfSomeHostFunction) -> Self::ResultOfSomeHostFunction; } -#[derive(Clone)] -struct ExtensionCoreImpl; +// User impls: handwritten -impl Extension for ExtensionCoreImpl { - fn methods(&self) -> Vec { - vec!["some_host_function".to_string(), "another_host_function".to_string()] - } +#[derive(Encode, Decode)] +pub struct ArgsImpl { + pub a: u32, + pub b: u32, } impl ExtensionCore for ExtensionCoreImpl { - fn some_host_function() -> u32 { - 100 + type ArgsOfSomeHostFunction = ArgsImpl; + type ResultOfSomeHostFunction = u32; + fn some_host_function(args: Self::ArgsOfSomeHostFunction) -> Self::ResultOfSomeHostFunction { + args.a + args.b + } +} + +// User impls: generated +impl Dispatchable for ExtensionCoreImpl { + type MethodName = Vec; + type MethodIndex = u32; + fn query_method(method_name: Self::MethodName) -> Self::MethodIndex { + match method_name.as_slice() { + b"query_method" => 0, + b"some_host_function" => 1, + _ => Self::MethodIndex::MAX, + } } - fn another_host_function() -> u32 { - 42 + fn dispatch(self) -> Vec { + match self { + Self::QueryMethod { method_name } => Self::query_method(method_name).encode(), + Self::SomeHostFunction { args } => Self::some_host_function(args).encode(), + } + } +} +pub enum ExtensionCoreImpl { + // args can be decoded later or here + QueryMethod { + method_name: ::MethodName, + }, + SomeHostFunction { + args: ArgsImpl, + }, +} + +impl TryFrom<(u32, Vec)> for ExtensionCoreImpl { + type Error = ExtensionError; + fn try_from((method_idx, args): (u32, Vec)) -> Result { + match method_idx { + 0 => Ok(Self::QueryMethod { + method_name: ::MethodName::decode(&mut &args[..]) + .map_err(Self::Error::DecodeError)?, + }), + 1 => { + let args = ArgsImpl::decode(&mut &args[..]).map_err(Self::Error::DecodeError)?; + Ok(Self::SomeHostFunction { args }) + } + _ => Err(Self::Error::UnsupportedMethod), + } } } diff --git a/poc/extensions/src/extension_fungibles.rs b/poc/extensions/src/extension_fungibles.rs index 8a65a92..7cc6bc0 100644 --- a/poc/extensions/src/extension_fungibles.rs +++ b/poc/extensions/src/extension_fungibles.rs @@ -1,28 +1,96 @@ -use super::{Extension, ExtensionTypeId}; -pub trait ExtensionFungibles: Extension { +use super::Dispatchable; +use super::ExtensionError; +use super::ExtensionTypeId; +use parity_scale_codec::{Decode, Encode}; +pub trait ExtensionFungibles: Dispatchable + TryFrom<(u32, Vec)> { // ExtensionId should be generated by the macro // It should normalize the order of methods and parameter names const TYPE_ID: ExtensionTypeId = 1u64; - // TODO: Actual args and return values are complex types - // and we adapt them to polkavm ABI in `impl XcqExecutorContext for HostFunctions` - fn transfer(from: u32, to: u32, amount: u64) -> u64; - fn balance(account: u32) -> u64; -} - -#[derive(Clone)] -struct ExtensionFungiblesImpl; - -impl Extension for ExtensionFungiblesImpl { - fn methods(&self) -> Vec { - vec!["transfer".to_string(), "balance".to_string()] - } + // TODO: In practice, these type are defined together in Config + type AccountId; + type Balance; + fn transfer(from: Self::AccountId, to: Self::AccountId, amount: Self::Balance) -> Self::Balance; + fn balance(account: Self::AccountId) -> Self::Balance; } impl ExtensionFungibles for ExtensionFungiblesImpl { - fn transfer(_from: u32, _to: u32, _amount: u64) -> u64 { + type AccountId = u32; + type Balance = u64; + fn transfer(_from: Self::AccountId, _to: Self::AccountId, _amount: Self::Balance) -> Self::Balance { unimplemented!() } - fn balance(_account: u32) -> u64 { + fn balance(_account: u32) -> Self::Balance { unimplemented!() } } + +// User impls: generated +impl Dispatchable for ExtensionFungiblesImpl { + type MethodName = Vec; + type MethodIndex = u32; + fn query_method(method_name: Self::MethodName) -> Self::MethodIndex { + match method_name.as_slice() { + b"query_method" => 0, + b"transfer" => 1, + b"balance" => 2, + _ => Self::MethodIndex::MAX, + } + } + fn dispatch(self) -> Vec { + match self { + Self::QueryMethod { method_name } => Self::query_method(method_name).encode(), + Self::Transfer { from, to, amount } => Self::transfer(from, to, amount).encode(), + Self::Balance_ { account } => Self::balance(account).encode(), + } + } +} +pub enum ExtensionFungiblesImpl { + QueryMethod { + method_name: ::MethodName, + }, + Transfer { + from: u32, + to: u32, + amount: u64, + }, + // Differentiate with associated type Balance + Balance_ { + account: u32, + }, +} + +impl TryFrom<(u32, Vec)> for ExtensionFungiblesImpl { + type Error = ExtensionError; + fn try_from((method_idx, args): (u32, Vec)) -> Result { + match method_idx { + 0 => Ok(Self::QueryMethod { + method_name: ::MethodName::decode(&mut &args[..]) + .map_err(Self::Error::DecodeError)?, + }), + 1 => { + // This struct encoding must be compatible with the guest side + #[derive(Encode, Decode)] + struct TransferArgs { + from: u32, + to: u32, + amount: u64, + } + let args = TransferArgs::decode(&mut &args[..]).map_err(Self::Error::DecodeError)?; + Ok(Self::Transfer { + from: args.from, + to: args.to, + amount: args.amount, + }) + } + 2 => { + #[derive(Encode, Decode)] + struct BalanceArgs { + account: u32, + } + let args = BalanceArgs::decode(&mut &args[..]).map_err(Self::Error::DecodeError)?; + Ok(Self::Balance_ { account: args.account }) + } + _ => Err(Self::Error::UnsupportedMethod), + } + } +} diff --git a/poc/extensions/src/guest.rs b/poc/extensions/src/guest.rs index 9025b30..54dbbef 100644 --- a/poc/extensions/src/guest.rs +++ b/poc/extensions/src/guest.rs @@ -4,9 +4,9 @@ pub trait Guest { fn program(&self) -> &[u8]; } -struct GuestImpl { - extension_type: ExtensionTypeId, - program: Vec, +pub struct GuestImpl { + pub extension_type: ExtensionTypeId, + pub program: Vec, } impl Guest for GuestImpl { @@ -25,9 +25,9 @@ pub trait Input { fn args(&self) -> &[u8]; } -struct InputImpl { - method: Method, - args: Vec, +pub struct InputImpl { + pub method: Method, + pub args: Vec, } impl Input for InputImpl { diff --git a/poc/extensions/src/lib.rs b/poc/extensions/src/lib.rs index 2b69de7..93c69f7 100644 --- a/poc/extensions/src/lib.rs +++ b/poc/extensions/src/lib.rs @@ -1,4 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] +use core::marker::PhantomData; +use parity_scale_codec::Error as CodeCError; use poc_executor::{XcqExecutor, XcqExecutorContext}; #[cfg(not(feature = "std"))] use scale_info::prelude::{format, string::String}; @@ -12,56 +14,74 @@ mod extension_fungibles; pub use extension_fungibles::ExtensionFungibles; mod guest; pub use guest::{Guest, Input}; +mod dispatchable; +pub use dispatchable::{Dispatchable, ExtensionTypeId}; mod macros; -type Error = String; - -type ExtensionTypeId = u64; -// General trait for all extension interfaces -pub trait Extension { - fn methods(&self) -> Vec; +pub enum ExtensionError { + DecodeError(CodeCError), + UnsupportedMethod, } - // Aggregated struct for all extension functions that runtime supports // Generated by some macros pub struct HostFunctions { - phantom: core::marker::PhantomData<(E1, E2)>, + phantom: PhantomData<(E1, E2)>, } // Generated by some macros impl XcqExecutorContext for HostFunctions { fn register_host_functions(&mut self, linker: &mut poc_executor::Linker) { - // As aforementioned, we may use macros to directly copy the functions impls to here - linker - .func_wrap("some_host_function", move || -> u32 { - ::some_host_function() - }) - .unwrap(); - linker - .func_wrap("another_host_function", || -> u32 { - ::another_host_function() - }) - .unwrap(); - linker - .func_wrap("transfer", move |from: u32, to: u32, amount: u64| -> u64 { - ::transfer(from, to, amount) - }) - .unwrap(); linker - .func_wrap("balance", move |account: u32| -> u64 { - ::balance(account) - }) + .func_wrap( + "_", + |mut caller: poc_executor::Caller<_>, + extension_id: u64, + method_idx: u32, + args_ptr: u32, + args_len: u32, + res_ptr: u32| + -> u32 { + let args_bytes = caller + .read_memory_into_vec(args_ptr, args_len) + .expect("read_memory_into_vec failed"); + // TODO: If we want to use match, we need construct a ExtensionId enum + // since plain number cannot be checked exhaustively + if extension_id == E1::TYPE_ID { + let method = match E1::try_from((method_idx, args_bytes)) { + Ok(method) => method, + Err(_) => { + return 0; + } + }; + let res_bytes = method.dispatch(); + caller.write_memory(res_ptr, &res_bytes[..]).unwrap(); + res_bytes.len() as u32 + } else if extension_id == E2::TYPE_ID { + let method = match E2::try_from((method_idx, args_bytes)) { + Ok(method) => method, + Err(_) => { + return 0; + } + }; + let res_bytes = method.dispatch(); + caller.write_memory(res_ptr, &res_bytes[..]).unwrap(); + res_bytes.len() as u32 + } else { + 0 + } + }, + ) .unwrap(); } } -// pub trait ExtensionsExecutor { -// type ExecutorType; -// fn from_extensions()-> -// fn execute_method(&self, guest: Guest, input: Input) -> XcqResult; +// Original From +// #[some_macro] +// struct ExtensionsExecutor { +// E1: ExtensionCore +// E2: ExtensionFungibles // } - // Expanded // In practice, generics are generated by macros struct ExtensionsExecutor { @@ -69,7 +89,8 @@ struct ExtensionsExecutor { } impl ExtensionsExecutor { // Generated by macro - pub fn from_extensions(_extension_core: E1, _extension_fungibles: E2) -> Self { + #[allow(dead_code)] + pub fn new() -> Self { let host_functions = HostFunctions:: { phantom: core::marker::PhantomData, }; @@ -78,18 +99,39 @@ impl ExtensionsExecutor { } // In PoC, guest and input are opague to the runtime // In SDK, we can make them has type - fn execute_method(&self, guest: G, input: I) -> XcqResult { + #[allow(dead_code)] + fn execute_method(&mut self, guest: G, input: I) -> XcqResult { let extension_id = guest.type_id(); - // In practice, these match statements should be generated by some macros - // Check if the runtime supports the extension - match extension_id { - // TODO: E1, E2 information are discarded after `HostFunctions` is generated - // How to fix it? - E1::TYPE_ID | E2::TYPE_ID => {} - _ => return Err(format!("Unsupported extension: {}", extension_id)), - } + // TODO: Check if the runtime supports the extension + if extension_id != E1::TYPE_ID || extension_id != E2::TYPE_ID { + return Err(format!("Unsupported extension: {}", extension_id)); + }; self.executor .execute(guest.program(), input.method(), input.args()) .map_err(|e| format!("{:?}", e)) } } + +#[cfg(test)] +mod tests { + use super::extension_core::ExtensionCoreImpl; + use super::extension_fungibles::ExtensionFungiblesImpl; + use super::guest::{GuestImpl, InputImpl}; + use super::*; + + // TODO: refine the test + #[test] + fn extensions_executor_fails() { + let mut executor = ExtensionsExecutor::::new(); + let guest = GuestImpl { + extension_type: 0u64, + program: vec![0, 1, 2, 3], + }; + let input = InputImpl { + method: "main".to_string(), + args: vec![0, 1, 2, 3], + }; + let res = executor.execute_method(guest, input); + assert!(res.is_err()) + } +}