Skip to content

Commit

Permalink
feat(poc): e2e test with extension system (#28)
Browse files Browse the repository at this point in the history
* e2e runtime test with extension fungibles
  • Loading branch information
indirection42 committed Jun 18, 2024
1 parent f1b7052 commit 94b1c13
Show file tree
Hide file tree
Showing 20 changed files with 1,523 additions and 547 deletions.
1,458 changes: 1,053 additions & 405 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ run: chainspec
poc-host-%: poc-guest-%
RUST_LOG=trace cargo run -p poc-host-$*

poc-guests: poc-guest-pass-custom-type poc-guest-query-balance
poc-guests: poc-guest-pass-custom-type poc-guest-query-balance poc-guest-query-balance-fungibles poc-guest-transparent-call

dummy-poc-guests: dummy-poc-guest-pass-custom-type dummy-poc-guest-query-balance
dummy-poc-guests: dummy-poc-guest-pass-custom-type dummy-poc-guest-query-balance dummy-poc-guest-query-balance-fungibles dummy-poc-guest-transparent-call

poc-guest-%:
cd poc/guests; RUSTFLAGS=$(GUEST_RUST_FLAGS) cargo build -q --release --bin poc-guest-$* -p poc-guest-$*
Expand All @@ -25,7 +25,7 @@ polkatool:
cargo install --path vendor/polkavm/tools/polkatool

chain-spec-builder:
cargo install --git https://github.com/paritytech/polkadot-sdk --tag polkadot-v1.9.0 staging-chain-spec-builder
cargo install --git https://github.com/paritytech/polkadot-sdk --tag polkadot-v1.12.0 staging-chain-spec-builder

fmt:
cargo fmt --all
Expand Down
3 changes: 2 additions & 1 deletion poc/extensions/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "poc-extension"
name = "poc-extensions"
version = "0.1.0"
edition = "2021"

Expand All @@ -8,6 +8,7 @@ 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 }
impl-trait-for-tuples = "0.2.2"
log = "0.4.21"

[features]
default = ["std"]
Expand Down
2 changes: 2 additions & 0 deletions poc/extensions/src/dispatchable.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::Vec;
pub trait Dispatchable {
fn dispatch(self) -> Result<Vec<u8>, DispatchError>;
}

#[derive(Debug)]
pub enum DispatchError {
PhantomData,
}
1 change: 1 addition & 0 deletions poc/extensions/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// TODO: contain source error
use crate::DispatchError;
use parity_scale_codec::Error as CodeCError;
#[derive(Debug)]
pub enum ExtensionError {
PermissionError,
PolkavmError,
Expand Down
22 changes: 12 additions & 10 deletions poc/extensions/src/extension_core.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
use crate::Vec;
use crate::{DispatchError, Dispatchable};
use crate::{ExtensionId, ExtensionIdTy};
use parity_scale_codec::{Decode, Encode};

pub trait ExtensionCore {
type Config: Config;
fn some_host_function(
args: <Self::Config as Config>::ArgsOfSomeHostFunction,
) -> <Self::Config as Config>::ResultOfSomeHostFunction;
fn has_extension(id: <Self::Config as Config>::ExtensionId) -> bool;
// crypto functions
// fn blake2_64(data: Vec<u8>) -> [u8; 8];
// fn blake2_128(data: Vec<u8>) -> [u8; 16];
// fn blake2_256(data: Vec<u8>) -> [u8; 32];
// fn twox_64(data: Vec<u8>) -> [u8; 8];
// fn read_storage(key: Vec<u8>) -> Option<Vec<u8>>;
}

pub trait Config {
type ArgsOfSomeHostFunction: Decode;
type ResultOfSomeHostFunction: Encode;
type ExtensionId: Decode;
}

// #[extension(ExtensionCore)]
Expand All @@ -20,17 +23,16 @@ pub trait Config {
mod generated_by_extension_decl {
use super::*;

type ExtensionIdOf<T> = <<T as ExtensionCore>::Config as Config>::ExtensionId;
#[derive(Decode)]
pub enum ExtensionCoreCall<Impl: ExtensionCore> {
SomeHostFunction {
args: <Impl::Config as Config>::ArgsOfSomeHostFunction,
},
HasExtension { id: ExtensionIdOf<Impl> },
}

impl<Impl: ExtensionCore> Dispatchable for ExtensionCoreCall<Impl> {
fn dispatch(self) -> Result<Vec<u8>, DispatchError> {
match self {
Self::SomeHostFunction { args } => Ok(Impl::some_host_function(args).encode()),
Self::HasExtension { id } => Ok(Impl::has_extension(id).encode()),
}
}
}
Expand Down
40 changes: 31 additions & 9 deletions poc/extensions/src/extension_fungibles.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
use crate::Vec;
use crate::{DispatchError, Dispatchable};
use crate::{ExtensionId, ExtensionIdTy};
use core::marker::PhantomData;
use parity_scale_codec::{Decode, Encode};

pub type AccountIdFor<T> = <<T as ExtensionFungibles>::Config as Config>::AccountId;
pub type BalanceFor<T> = <<T as ExtensionFungibles>::Config as Config>::Balance;
pub type AssetIdFor<T> = <<T as ExtensionFungibles>::Config as Config>::AssetId;

pub trait ExtensionFungibles {
fn free_balance_of(who: [u8; 32]) -> u32;
fn reserved_balance_of(who: [u8; 32]) -> u32;
type Config: Config;
// fungibles::Inspect (not extensive)
// fn total_inssuance(asset: AssetIdFor<Self>) -> BalanceFor<Self>;
// fn minimum_balance(asset: AssetIdFor<Self>) -> BalanceFor<Self>;
fn total_supply(asset: AssetIdFor<Self>) -> BalanceFor<Self>;
fn balance(asset: AssetIdFor<Self>, who: AccountIdFor<Self>) -> BalanceFor<Self>;
// fungibles::InspectEnumerable
// fn asset_ids() -> Vec<AccountIdFor<Self>>;
// fn account_balances(who: AccountIdFor<Self>) -> Vec<(AssetIdFor<Self>, BalanceFor<Self>)>;
}

pub trait Config {
type AccountId: Decode;
type AssetId: Decode;
type Balance: Encode;
}

// #[extension(ExtensionFungibles)]
Expand All @@ -14,19 +31,24 @@ pub trait ExtensionFungibles {
mod generated_by_extension_decl {

use super::*;

#[derive(Decode)]
pub enum ExtensionFungiblesCall<Impl: ExtensionFungibles> {
FreeBalanceOf { who: [u8; 32] },
ReservedBalanceOf { who: [u8; 32] },
_Marker(PhantomData<Impl>),
// TODO: not extensive
Balance {
asset: AssetIdFor<Impl>,
who: AccountIdFor<Impl>,
},
TotalSupply {
asset: AssetIdFor<Impl>,
},
}

impl<Impl: ExtensionFungibles> Dispatchable for ExtensionFungiblesCall<Impl> {
fn dispatch(self) -> Result<Vec<u8>, DispatchError> {
match self {
Self::FreeBalanceOf { who } => Ok(Impl::free_balance_of(who).encode()),
Self::ReservedBalanceOf { who } => Ok(Impl::reserved_balance_of(who).encode()),
Self::_Marker(_) => Err(DispatchError::PhantomData),
Self::Balance { asset, who } => Ok(Impl::balance(asset, who).encode()),
Self::TotalSupply { asset } => Ok(Impl::total_supply(asset).encode()),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions poc/extensions/src/guest.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use scale_info::prelude::string::String;
pub trait Guest {
fn program(&self) -> &[u8];
}
Expand Down
143 changes: 97 additions & 46 deletions poc/extensions/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
use core::marker::PhantomData;
extern crate alloc;
pub use alloc::vec::Vec;

use parity_scale_codec::Decode;
use poc_executor::{XcqExecutor, XcqExecutorContext};
Expand All @@ -17,8 +19,8 @@ mod error;
pub use error::ExtensionError;
mod macros;

mod extension_core;
mod extension_fungibles;
pub mod extension_core;
pub mod extension_fungibles;

mod perm_controller;
pub use perm_controller::{InvokeSource, PermController};
Expand All @@ -30,7 +32,7 @@ pub use guest::{Guest, Input, Method};
trait Extension: Dispatchable + ExtensionId + Decode {}
impl<T> Extension for T where T: Dispatchable + ExtensionId + Decode {}

trait ExtensionTuple {
pub trait ExtensionTuple {
fn dispatch(extension_id: ExtensionIdTy, data: &[u8]) -> Result<Vec<u8>, ExtensionError>;
}

Expand All @@ -53,35 +55,43 @@ impl<E: ExtensionTuple, P: PermController> XcqExecutorContext for Context<E, P>
let invoke_source = self.invoke_source;
linker
.func_wrap(
"_",
move |mut caller: poc_executor::Caller<_>,
extension_id: u64,
call_ptr: u32,
call_len: u32,
res_ptr: u32|
-> u32 {
"call",
move |mut caller: poc_executor::Caller<_>, extension_id: u64, call_ptr: u32, call_len: u32| -> u64 {
// useful closure to handle early return
let mut func_with_result = || -> Result<u32, ExtensionError> {
let mut func_with_result = || -> Result<u64, ExtensionError> {
let call_bytes = caller
.read_memory_into_vec(call_ptr, call_len)
.map_err(|_| ExtensionError::PolkavmError)?;
if P::is_allowed(extension_id, &call_bytes, invoke_source) {
#[cfg(feature = "std")]
log::trace!(
"(host call): extension_id: {}, call_bytes: {:?}",
extension_id,
call_bytes
);
if !P::is_allowed(extension_id, &call_bytes, invoke_source) {
return Err(ExtensionError::PermissionError);
}
let res_bytes = E::dispatch(extension_id, &call_bytes)?;
#[cfg(feature = "std")]
log::trace!("(host call): res_bytes: {:?}", res_bytes);
let res_bytes_len = res_bytes.len();
let res_ptr = caller.sbrk(res_bytes_len as u32).ok_or(ExtensionError::PolkavmError)?;
caller
.write_memory(res_ptr, &res_bytes[..])
.write_memory(res_ptr, &res_bytes)
.map_err(|_| ExtensionError::PolkavmError)?;
Ok(res_bytes.len() as u32)
Ok(((res_ptr as u64) << 32) | (res_bytes_len as u64))
};
func_with_result().unwrap_or(0)
let result = func_with_result();
#[cfg(feature = "std")]
log::trace!("(host call): result: {:?}", result);
result.unwrap_or(0)
},
)
.unwrap();
}
}

struct ExtensionsExecutor<E: ExtensionTuple, P: PermController> {
pub struct ExtensionsExecutor<E: ExtensionTuple, P: PermController> {
executor: XcqExecutor<Context<E, P>>,
}
impl<E: ExtensionTuple, P: PermController> ExtensionsExecutor<E, P> {
Expand All @@ -94,7 +104,7 @@ impl<E: ExtensionTuple, P: PermController> ExtensionsExecutor<E, P> {
// In PoC, guest and input are opaque to the runtime
// In SDK, we can make them has type
#[allow(dead_code)]
fn execute_method<G: Guest, I: Input>(&mut self, guest: G, input: I) -> XcqResult {
pub fn execute_method<G: Guest, I: Input>(&mut self, guest: G, input: I) -> XcqResult {
self.executor
.execute(guest.program(), input.method(), input.args())
.map_err(|e| format!("{:?}", e))
Expand All @@ -111,38 +121,53 @@ mod tests {
// extension_core impls
pub struct ExtensionCoreImpl;

#[derive(Encode, Decode)]
pub struct ArgsImpl {
pub a: u32,
pub b: u32,
}

pub struct ConfigImpl;
impl extension_core::Config for ConfigImpl {
// this associated type is generated by the macro
type ArgsOfSomeHostFunction = ArgsImpl;
type ResultOfSomeHostFunction = u32;
pub struct ExtensionCoreConfigImpl;
impl extension_core::Config for ExtensionCoreConfigImpl {
type ExtensionId = u64;
}

impl ExtensionCore for ExtensionCoreImpl {
type Config = ConfigImpl;
fn some_host_function(
args: <Self::Config as extension_core::Config>::ArgsOfSomeHostFunction,
) -> <Self::Config as extension_core::Config>::ResultOfSomeHostFunction {
args.a + args.b
type Config = ExtensionCoreConfigImpl;
fn has_extension(id: <Self::Config as extension_core::Config>::ExtensionId) -> bool {
matches!(id, 0 | 1)
}
}

// extension_fungibles impls
pub struct ExtensionFungiblesImpl;

pub struct ExtensionFungiblesConfigImpl;

impl extension_fungibles::Config for ExtensionFungiblesConfigImpl {
type AccountId = [u8; 32];
type Balance = u32;
type AssetId = u64;
}

use crate::extension_fungibles::AccountIdFor;
use crate::extension_fungibles::AssetIdFor;
use crate::extension_fungibles::BalanceFor;

impl ExtensionFungibles for ExtensionFungiblesImpl {
fn free_balance_of(_who: [u8; 32]) -> u32 {
100
type Config = ExtensionFungiblesConfigImpl;
// fn total_inssuance(_asset: AssetIdFor<Self>) -> BalanceFor<Self> {
// 100
// }
// fn minimum_balance(_asset: AssetIdFor<Self>) -> BalanceFor<Self> {
// 0
// }
fn balance(_asset: AssetIdFor<Self>, _who: AccountIdFor<Self>) -> BalanceFor<Self> {
0
}
fn reserved_balance_of(_who: [u8; 32]) -> u32 {
42
fn total_supply(_asset: AssetIdFor<Self>) -> BalanceFor<Self> {
100
}
// fn asset_ids() -> Vec<AccountIdFor<Self>> {
// vec![]
// }
// fn account_balances(_who: AccountIdFor<Self>) -> Vec<(AssetIdFor<Self>, BalanceFor<Self>)> {
// vec![]
// }
}

type Extensions = (
Expand Down Expand Up @@ -175,20 +200,46 @@ mod tests {
}
}

// TODO: refine the test
#[derive(Encode, Decode)]
enum CoreMethod {
HasExtension { id: u64 },
}

#[derive(Encode, Decode)]
enum FungiblesMethod {
Balance { asset: u64, who: [u8; 32] },
TotalSupply { asset: u64 },
}

#[test]
fn extensions_executor_fails() {
fn call_core_works() {
let blob = include_bytes!("../../../output/poc-guest-transparent-call.polkavm");
let mut executor = ExtensionsExecutor::<Extensions, ()>::new(InvokeSource::RuntimeAPI);
let guest = GuestImpl {
program: vec![0, 1, 2, 3],
};
let guest = GuestImpl { program: blob.to_vec() };
let method = CoreMethod::HasExtension { id: 0 };
let mut input_data = 0u64.encode();
input_data.extend_from_slice(&method.encode());
let input = InputImpl {
method: "main".to_string(),
args: vec![0, 1, 2, 3],
args: input_data,
};
let res = executor.execute_method(guest, input);
assert!(res.is_err())
let res = executor.execute_method(guest, input).unwrap();
assert_eq!(res, vec![1]);
}

// TODO: add success test
#[test]
fn call_fungibles_works() {
let blob = include_bytes!("../../../output/poc-guest-transparent-call.polkavm");
let mut executor = ExtensionsExecutor::<Extensions, ()>::new(InvokeSource::RuntimeAPI);
let guest = GuestImpl { program: blob.to_vec() };
let method = FungiblesMethod::TotalSupply { asset: 1u64 };
let mut input_data = 1u64.encode();
input_data.extend_from_slice(&method.encode());
let input = InputImpl {
method: "main".to_string(),
args: input_data,
};
let res = executor.execute_method(guest, input).unwrap();
assert_eq!(res, vec![100u8, 0u8, 0u8, 0u8]);
}
}
Loading

0 comments on commit 94b1c13

Please sign in to comment.