From 89c918595f0d7dbaf0646712fc4d360d875568c2 Mon Sep 17 00:00:00 2001 From: indirection42 Date: Mon, 17 Mar 2025 16:04:01 +0800 Subject: [PATCH 1/4] refactor(pvq-program): refactor except some test issues --- Cargo.lock | 72 ++-- Cargo.toml | 6 +- Makefile | 24 +- poc/guests/.cargo/config.toml | 5 - poc/guests/Cargo.toml | 13 - poc/guests/sum-balance-percent/src/main.rs | 21 -- poc/guests/sum-balance/src/main.rs | 19 - poc/guests/total-supply/src/main.rs | 14 - pvq-api/Cargo.toml | 15 - pvq-api/procedural/src/lib.rs | 26 -- pvq-api/procedural/src/program/expand/mod.rs | 337 ------------------ pvq-api/procedural/src/program/parse/call.rs | 47 --- .../src/program/parse/entrypoint.rs | 73 ---- pvq-api/procedural/src/program/parse/mod.rs | 103 ------ pvq-executor/Cargo.toml | 3 +- pvq-executor/src/lib.rs | 5 +- pvq-extension/procedural/Cargo.toml | 2 +- pvq-extension/src/context.rs | 12 +- pvq-program/.cargo/config.toml | 5 + pvq-program/Cargo.toml | 21 ++ .../examples}/Cargo.lock | 138 +++---- pvq-program/examples/Cargo.toml | 17 + .../sum-balance-hand-written}/Cargo.toml | 4 +- .../sum-balance-hand-written/src/main.rs | 83 +++++ .../examples/sum-balance-percent/Cargo.toml | 10 + .../examples/sum-balance-percent/src/main.rs | 23 ++ .../examples/sum-balance}/Cargo.toml | 5 +- pvq-program/examples/sum-balance/src/main.rs | 21 ++ .../total-supply-hand-written/Cargo.toml | 10 + .../total-supply-hand-written/src/main.rs | 71 ++++ .../examples}/total-supply/Cargo.toml | 5 +- pvq-program/examples/total-supply/src/main.rs | 13 + .../transparent-call-hand-written}/Cargo.toml | 3 +- .../src/main.rs | 0 .../procedural/Cargo.toml | 6 +- pvq-program/procedural/src/lib.rs | 30 ++ .../procedural/src/program/expand/mod.rs | 120 +++++++ .../procedural/src/program/expand/preludes.rs | 43 +++ .../procedural/src/program/mod.rs | 4 +- .../src/program/parse/entrypoint.rs | 31 ++ .../src/program/parse/extension_fn.rs | 57 +++ .../procedural/src/program/parse/helper.rs | 18 +- .../procedural/src/program/parse/mod.rs | 98 +++++ pvq-program/procedural/tests/tests.rs | 9 + .../procedural/tests/ui/sum-balance.rs | 21 ++ .../rust-toolchain.toml | 0 {pvq-api => pvq-program}/src/lib.rs | 2 +- pvq-test-runner/src/main.rs | 43 ++- 48 files changed, 875 insertions(+), 833 deletions(-) delete mode 100644 poc/guests/.cargo/config.toml delete mode 100644 poc/guests/Cargo.toml delete mode 100644 poc/guests/sum-balance-percent/src/main.rs delete mode 100644 poc/guests/sum-balance/src/main.rs delete mode 100644 poc/guests/total-supply/src/main.rs delete mode 100644 pvq-api/Cargo.toml delete mode 100644 pvq-api/procedural/src/lib.rs delete mode 100644 pvq-api/procedural/src/program/expand/mod.rs delete mode 100644 pvq-api/procedural/src/program/parse/call.rs delete mode 100644 pvq-api/procedural/src/program/parse/entrypoint.rs delete mode 100644 pvq-api/procedural/src/program/parse/mod.rs create mode 100644 pvq-program/.cargo/config.toml create mode 100644 pvq-program/Cargo.toml rename {poc/guests => pvq-program/examples}/Cargo.lock (81%) create mode 100644 pvq-program/examples/Cargo.toml rename {poc/guests/sum-balance-percent => pvq-program/examples/sum-balance-hand-written}/Cargo.toml (52%) create mode 100644 pvq-program/examples/sum-balance-hand-written/src/main.rs create mode 100644 pvq-program/examples/sum-balance-percent/Cargo.toml create mode 100644 pvq-program/examples/sum-balance-percent/src/main.rs rename {poc/guests/transparent-call => pvq-program/examples/sum-balance}/Cargo.toml (52%) create mode 100644 pvq-program/examples/sum-balance/src/main.rs create mode 100644 pvq-program/examples/total-supply-hand-written/Cargo.toml create mode 100644 pvq-program/examples/total-supply-hand-written/src/main.rs rename {poc/guests => pvq-program/examples}/total-supply/Cargo.toml (66%) create mode 100644 pvq-program/examples/total-supply/src/main.rs rename {poc/guests/sum-balance => pvq-program/examples/transparent-call-hand-written}/Cargo.toml (64%) rename {poc/guests/transparent-call => pvq-program/examples/transparent-call-hand-written}/src/main.rs (100%) rename {pvq-api => pvq-program}/procedural/Cargo.toml (72%) create mode 100644 pvq-program/procedural/src/lib.rs create mode 100644 pvq-program/procedural/src/program/expand/mod.rs create mode 100644 pvq-program/procedural/src/program/expand/preludes.rs rename {pvq-api => pvq-program}/procedural/src/program/mod.rs (69%) create mode 100644 pvq-program/procedural/src/program/parse/entrypoint.rs create mode 100644 pvq-program/procedural/src/program/parse/extension_fn.rs rename {pvq-api => pvq-program}/procedural/src/program/parse/helper.rs (78%) create mode 100644 pvq-program/procedural/src/program/parse/mod.rs create mode 100644 pvq-program/procedural/tests/tests.rs create mode 100644 pvq-program/procedural/tests/ui/sum-balance.rs rename {poc/guests => pvq-program}/rust-toolchain.toml (100%) rename {pvq-api => pvq-program}/src/lib.rs (51%) diff --git a/Cargo.lock b/Cargo.lock index c5b8148..8f77c04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2963,6 +2963,13 @@ dependencies = [ "polkavm-derive-impl-macro 0.18.0", ] +[[package]] +name = "polkavm-derive" +version = "0.21.0" +dependencies = [ + "polkavm-derive-impl-macro 0.21.0", +] + [[package]] name = "polkavm-derive-impl" version = "0.9.0" @@ -2987,6 +2994,16 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "polkavm-derive-impl" +version = "0.21.0" +dependencies = [ + "polkavm-common 0.21.0", + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "polkavm-derive-impl-macro" version = "0.9.0" @@ -3007,6 +3024,14 @@ dependencies = [ "syn 2.0.96", ] +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.21.0" +dependencies = [ + "polkavm-derive-impl 0.21.0", + "syn 2.0.96", +] + [[package]] name = "polkavm-linker" version = "0.9.2" @@ -3128,32 +3153,11 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "pvq-api" -version = "0.1.0" -dependencies = [ - "pvq-api-procedural", -] - -[[package]] -name = "pvq-api-procedural" -version = "0.1.0" -dependencies = [ - "Inflector", - "parity-scale-codec", - "proc-macro-crate", - "proc-macro2", - "quote", - "scale-info", - "syn 2.0.96", -] - [[package]] name = "pvq-executor" version = "0.1.0" dependencies = [ "polkavm", - "pvq-api", "pvq-primitives", "tracing", ] @@ -3214,6 +3218,32 @@ dependencies = [ "scale-info", ] +[[package]] +name = "pvq-program" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "polkavm-derive 0.21.0", + "pvq-program-procedural", + "scale-info", + "trybuild", +] + +[[package]] +name = "pvq-program-procedural" +version = "0.1.0" +dependencies = [ + "Inflector", + "parity-scale-codec", + "polkavm-derive 0.21.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "scale-info", + "syn 2.0.96", + "trybuild", +] + [[package]] name = "pvq-runtime-api" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7bab53b..980e19c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ resolver = "2" members = [ "poc/runtime", - "pvq-api", + "pvq-program", "pvq-executor", "pvq-extension-core", "pvq-extension-fungibles", @@ -31,7 +31,7 @@ opt-level = 3 [workspace.dependencies] # local -pvq-api = { path = "pvq-api", default-features = false } +pvq-program = { path = "pvq-program", default-features = false } pvq-executor = { path = "pvq-executor", default-features = false } pvq-extension-core = { path = "pvq-extension-core", default-features = false } pvq-extension-fungibles = { path = "pvq-extension-fungibles", default-features = false } @@ -42,6 +42,7 @@ pvq-test-runner = { path = "pvq-test-runner", default-features = false } # polkavm polkavm = { path = "vendor/polkavm/crates/polkavm", default-features = false } +polkavm-derive = { path = "vendor/polkavm/crates/polkavm-derive", default-features = false } # polkadot-sdk sp-api = { path = "vendor/polkadot-sdk/substrate/primitives/api", default-features = false } @@ -69,3 +70,4 @@ quote = "1" proc-macro2 = "1" proc-macro-crate = "3" Inflector = { version = "0.11.4" } +trybuild = "1" diff --git a/Makefile b/Makefile index 49a1951..36b4c60 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,22 @@ run: chainspec bunx @acala-network/chopsticks@latest --config poc/runtime/chopsticks.yml --genesis output/chainspec.json -poc-guests: poc-guest-sum-balance poc-guest-sum-balance-percent poc-guest-total-supply poc-guest-transparent-call +EXAMPLE_TARGETS = $(shell cd pvq-program/examples && cargo metadata --no-deps --format-version 1 | jq -r '.workspace_members[] | split(" ")[0] | split("\#")[0]' | grep -v "^pvq-program-examples$$") +GUEST_TARGETS = $(patsubst %,guest-%,$(notdir $(EXAMPLE_TARGETS))) +DUMMY_GUEST_TARGETS = $(patsubst %,dummy-guest-%,$(notdir $(EXAMPLE_TARGETS))) -dummy-poc-guests: dummy-poc-guest-sum-balance dummy-poc-guest-sum-balance-percent dummy-poc-guest-total-supply dummy-poc-guest-transparent-call +guests: $(GUEST_TARGETS) -poc-guest-%: - cd poc/guests; RUSTFLAGS="-D warnings" cargo build -q --release -Z build-std=core,alloc --target "../../vendor/polkavm/crates/polkavm-linker/riscv32emac-unknown-none-polkavm.json" --bin poc-guest-$* -p poc-guest-$* +dummy-guests: $(DUMMY_GUEST_TARGETS) + +guest-%: + cd pvq-program/examples; RUSTFLAGS="-D warnings" cargo build -q --release -Z build-std=core,alloc --target "../../vendor/polkavm/crates/polkavm-linker/riscv32emac-unknown-none-polkavm.json" --bin guest-$* -p guest-$* mkdir -p output - polkatool link --run-only-if-newer -s poc/guests/target/riscv32emac-unknown-none-polkavm/release/poc-guest-$* -o output/poc-guest-$*.polkavm + polkatool link --run-only-if-newer -s pvq-program/examples/target/riscv32emac-unknown-none-polkavm/release/guest-$* -o output/guest-$*.polkavm -dummy-poc-guest-%: +dummy-guest-%: mkdir -p output - touch output/poc-guest-$*.polkavm + touch output/guest-$*.polkavm tools: polkatool chain-spec-builder @@ -26,16 +30,16 @@ fmt: cargo fmt --all check-wasm: - cargo check --no-default-features --target=wasm32-unknown-unknown -p pvq-api -p pvq-executor -p pvq-extension-core -p pvq-extension-fungibles -p pvq-extension -p pvq-primitives -p pvq-runtime-api + cargo check --no-default-features --target=wasm32-unknown-unknown -p pvq-program -p pvq-executor -p pvq-extension-core -p pvq-extension-fungibles -p pvq-extension -p pvq-primitives -p pvq-runtime-api SKIP_WASM_BUILD= cargo check --no-default-features --target=wasm32-unknown-unknown -p poc-runtime check: check-wasm SKIP_WASM_BUILD= cargo check - cd poc/guests; cargo check + cd pvq-program/examples; cargo check clippy: SKIP_WASM_BUILD= cargo clippy -- -D warnings - cd poc/guests; RUSTFLAGS="-D warnings" cargo clippy -Z build-std=core,alloc --target "../../vendor/polkavm/crates/polkavm-linker/riscv32emac-unknown-none-polkavm.json" --all + cd pvq-program/examples; RUSTFLAGS="-D warnings" cargo clippy -Z build-std=core,alloc --target "../../vendor/polkavm/crates/polkavm-linker/riscv32emac-unknown-none-polkavm.json" --all test: SKIP_WASM_BUILD= cargo test diff --git a/poc/guests/.cargo/config.toml b/poc/guests/.cargo/config.toml deleted file mode 100644 index dc6aea3..0000000 --- a/poc/guests/.cargo/config.toml +++ /dev/null @@ -1,5 +0,0 @@ -[build] -target = "../../vendor/polkavm/crates/polkavm-linker/riscv32emac-unknown-none-polkavm.json" - -[unstable] -build-std = ["core", "alloc"] diff --git a/poc/guests/Cargo.toml b/poc/guests/Cargo.toml deleted file mode 100644 index 81e10d4..0000000 --- a/poc/guests/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[workspace] -members = [ - "sum-balance", - "sum-balance-percent", - "total-supply", - "transparent-call", -] -resolver = "2" - - -[workspace.dependencies] -polkavm-derive = { path = "../../vendor/polkavm/crates/polkavm-derive" } -pvq-api = { path = "../../pvq-api", default-features = false } diff --git a/poc/guests/sum-balance-percent/src/main.rs b/poc/guests/sum-balance-percent/src/main.rs deleted file mode 100644 index eeaa771..0000000 --- a/poc/guests/sum-balance-percent/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![no_std] -#![no_main] -#[global_allocator] -static GLOBAL: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; -use alloc::vec::Vec; -#[pvq_api::program] -mod sum_balance { - #[pvq::call_def(extension_id = 4071833530116166512u64, call_index = 1)] - fn balance(asset: u32, who: [u8; 32]) -> u64 {} - #[pvq::call_def(extension_id = 4071833530116166512u64, call_index = 0)] - fn total_supply(asset: u32) -> u64 {} - - #[pvq::entrypoint] - fn sum_balance(balances: Vec, total_supply: TotalSupplyCall) -> u64 { - let mut sum_balance = 0; - for call in balances { - sum_balance += call.call(); - } - sum_balance * 100 / total_supply.call() - } -} diff --git a/poc/guests/sum-balance/src/main.rs b/poc/guests/sum-balance/src/main.rs deleted file mode 100644 index b23c378..0000000 --- a/poc/guests/sum-balance/src/main.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_std] -#![no_main] -#[global_allocator] -static GLOBAL: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; -use alloc::vec::Vec; -// An example instance of pvq program with specific arg types -#[pvq_api::program] -mod sum_balance { - #[pvq::call_def(extension_id = 4071833530116166512u64, call_index = 1)] - fn balance(asset: u32, who: [u8; 32]) -> u64 {} - #[pvq::entrypoint] - fn sum_balance(calls: Vec) -> u64 { - let mut sum = 0; - for call in calls { - sum += call.call(); - } - sum - } -} diff --git a/poc/guests/total-supply/src/main.rs b/poc/guests/total-supply/src/main.rs deleted file mode 100644 index 4486276..0000000 --- a/poc/guests/total-supply/src/main.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_std] -#![no_main] -#[global_allocator] -static GLOBAL: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; -#[pvq_api::program] -mod query_total_supply { - #[pvq::call_def(extension_id = 4071833530116166512u64, call_index = 0)] - fn total_supply(asset: u32) -> u64 {} - - #[pvq::entrypoint] - fn get_total_supply(call: TotalSupplyCall) -> u64 { - call.call() - } -} diff --git a/pvq-api/Cargo.toml b/pvq-api/Cargo.toml deleted file mode 100644 index cd97fad..0000000 --- a/pvq-api/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "pvq-api" -description = "API between PVQ host and guest program" -authors.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true -version.workspace = true - -[dependencies] -pvq-api-procedural = { path = "procedural" } - -[features] -default = ["std"] -std = [] diff --git a/pvq-api/procedural/src/lib.rs b/pvq-api/procedural/src/lib.rs deleted file mode 100644 index fd9c5b7..0000000 --- a/pvq-api/procedural/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -/// Declare the calls used in PVQ program -/// ```ignore -/// #[pvq::program] -/// mod query_fungibles { -/// #[pvq::call_def(extension_id = 123456, extern_types = [AssetId, AccountId, Balance])] -/// fn balance(asset: AssetId, who: AccountId) -> Balance; -/// -/// #[pvq::entrypoint] -/// fn sum_balance(calls: Vec) -> u64 { -/// let mut sum = 0; -/// for call in calls { -/// // calculation requires a known balance type, we can use assert-type here -/// sum += call.call(); -/// } -/// sum -/// } -/// } -/// ``` -/// -mod program; -use proc_macro::TokenStream; - -#[proc_macro_attribute] -pub fn program(attr: TokenStream, item: TokenStream) -> TokenStream { - program::program(attr, item) -} diff --git a/pvq-api/procedural/src/program/expand/mod.rs b/pvq-api/procedural/src/program/expand/mod.rs deleted file mode 100644 index bd8c8ca..0000000 --- a/pvq-api/procedural/src/program/expand/mod.rs +++ /dev/null @@ -1,337 +0,0 @@ -use super::{CallDef, Def, EntrypointDef}; -use inflector::Inflector; -use parity_scale_codec::Encode; -use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote, ToTokens}; -use scale_info::{meta_type, PortableRegistry, Registry}; -use syn::{ItemFn, PathArguments, Result}; -pub fn expand(def: Def) -> Result { - let preludes = generate_preludes(); - // eprintln!("def{:?}", def.calls); - let host_calls = def - .calls - .iter() - .map(|call_def| generate_call(&call_def.item_fn)) - .collect::>>()?; - let entrypoint_def = generate_entrypoint(&def.entrypoint)?; - let main_fn = generate_main(&def.calls, &def.entrypoint)?; - Ok(quote! { - #preludes - #entrypoint_def - #(#host_calls)* - #main_fn - }) -} - -// Generate a callable that holds the call data and a method to perform the call -// At compile time: extension_id and call_index are specified -// and they can be used to construct the runtime call data by front-end. -// At run time: we only forward call_data(including call_ptr and size) to host, -// and then we got the return bytes and convert to concrete numeric type -fn generate_call(item: &ItemFn) -> Result { - let camel_case_ident = syn::Ident::new(&item.sig.ident.to_string().to_pascal_case(), item.sig.ident.span()); - let call_name = format_ident!("{}Call", camel_case_ident); - // This return_ty is a concrete unsigned integer type - let return_ty = match &item.sig.output { - syn::ReturnType::Type(_, return_ty) => return_ty, - _ => { - return Err(syn::Error::new_spanned( - item.sig.fn_token, - "expected function to have a return type", - )) - } - }; - let expand = quote! { - struct #call_name { - pub extension_id: u64, - pub call_ptr: u32, - pub call_size: u32, - } - impl #call_name { - pub fn call(&self) -> #return_ty { - let res = unsafe { - host_call(self.extension_id, self.call_ptr, self.call_size) - }; - let res_len = (res >> 32) as u32; - let res_ptr = (res & 0xffffffff) as *const u8; - let res_bytes = unsafe { - core::slice::from_raw_parts(res_ptr, res_len as usize) - }; - let (int_bytes, _) = res_bytes.split_at(core::mem::size_of::<#return_ty>()); - #return_ty::from_le_bytes(int_bytes.try_into().unwrap()) - } - } - }; - Ok(expand) -} - -// Modify the calculation parts in the entrypoint function -// use type assertion to get the return type at runtime -fn generate_entrypoint(entrypoint: &EntrypointDef) -> Result { - Ok(entrypoint.item_fn.to_token_stream()) -} - -fn pass_byte_to_host() -> TokenStream2 { - // TODO check res type to determine the appropriate serializing method - quote! { - let res_bytes = res.to_le_bytes(); - let res_ptr = polkavm_derive::sbrk(0); - let end_ptr = polkavm_derive::sbrk(res_bytes.len()); - if end_ptr.is_null(){ - return 0; - } - unsafe { - core::ptr::copy_nonoverlapping(res_bytes.as_ptr(),res_ptr,res_bytes.len()); - } - (res_bytes.len() as u64) << 32 | (res_ptr as u64) - } -} - -fn generate_return_ty_assertion(call_def: &CallDef) -> Result { - let call_ty = &call_def.item_fn.sig.output; - // TODO: bytes representation is to be decided - let expected_ty_bytes = match call_ty { - syn::ReturnType::Type(_, return_ty) => match return_ty.as_ref() { - syn::Type::Path(path) => { - let last_segment = path - .path - .segments - .last() - .ok_or_else(|| syn::Error::new_spanned(path, "expected function return type to be a path"))?; - match last_segment.ident.to_string().as_str() { - "u8" => { - let ty = meta_type::(); - let mut registry = Registry::new(); - registry.register_type(&ty); - let portable_registry = PortableRegistry::from(registry); - let ty_bytes = portable_registry.encode(); - quote! { - &[#(#ty_bytes),*] - } - } - "u16" => { - let ty = meta_type::(); - let mut registry = Registry::new(); - registry.register_type(&ty); - let portable_registry = PortableRegistry::from(registry); - let ty_bytes = portable_registry.encode(); - quote! { - &[#(#ty_bytes),*] - } - } - "u32" => { - let ty = meta_type::(); - let mut registry = Registry::new(); - registry.register_type(&ty); - let portable_registry = PortableRegistry::from(registry); - let ty_bytes = portable_registry.encode(); - quote! { - &[#(#ty_bytes),*] - } - } - "u64" => { - let ty = meta_type::(); - let mut registry = Registry::new(); - registry.register_type(&ty); - let portable_registry = PortableRegistry::from(registry); - let ty_bytes = portable_registry.encode(); - quote! { - &[#(#ty_bytes),*] - } - } - "u128" => { - let ty = meta_type::(); - let mut registry = Registry::new(); - registry.register_type(&ty); - let portable_registry = PortableRegistry::from(registry); - let ty_bytes = portable_registry.encode(); - quote! { - &[#(#ty_bytes),*] - } - } - "Vec" => { - if let PathArguments::AngleBracketed(generic_args) = &last_segment.arguments { - if generic_args.args.len() == 1 { - match generic_args.args.first() { - Some(syn::GenericArgument::Type(syn::Type::Path(path))) - if path.path.is_ident("u8") => - { - let ty = meta_type::>(); - let mut registry = Registry::new(); - registry.register_type(&ty); - let portable_registry = PortableRegistry::from(registry); - let ty_bytes = portable_registry.encode(); - quote! { - &[#(#ty_bytes),*] - } - } - _ => quote! { &[0u8] }, - } - } else { - quote! { &[0u8] } - } - } else { - quote! {&[0u8]} - } - } - _ => quote! { &[0u8] }, - } - } - _ => { - return Err(syn::Error::new_spanned( - call_ty, - "expected function return type to be a path", - )) - } - }, - _ => { - return Err(syn::Error::new_spanned( - call_ty, - "expected function return type to be a path", - )) - } - }; - let extension_id = call_def.extension_id; - let call_index = call_def.call_index; - let item_fn_ident_string = &call_def.item_fn.sig.ident.to_string(); - let expanded = quote! { - if !assert_return_ty(#expected_ty_bytes, #extension_id, #call_index) { - panic!("function {} (extension {} call {}) return type mismatch", #item_fn_ident_string, #extension_id, #call_index); - } - }; - Ok(expanded) -} - -fn generate_main(call_defs: &[CallDef], entrypoint: &EntrypointDef) -> Result { - let assertions = call_defs - .iter() - .map(generate_return_ty_assertion) - .collect::>>()?; - let assert_program_types_match = quote! { - #(#assertions)* - }; - // Construct call_data - let mut get_call_data = TokenStream2::new(); - for (arg_type_index, arg_type) in entrypoint.arg_types.iter().enumerate() { - let ty = &arg_type.ty; - let calls_ident = format_ident!("calls_{}", arg_type_index); - if arg_type.multi { - get_call_data.extend( - quote! { - let mut #calls_ident:alloc::vec::Vec<#ty> = alloc::vec::Vec::new(); - } - .into_iter(), - ); - get_call_data.extend({ - quote! { - // TODO: extension_id can be eliminated since we have call_def indicating it - let extension_id = unsafe {core::ptr::read_volatile((arg_ptr) as *const u64)}; - // for multi calls, we assume the number of calls are given in the call data - let call_num = unsafe {core::ptr::read_volatile((arg_ptr+8) as *const u8)}; - let call_size = unsafe {core::ptr::read_volatile((arg_ptr+9) as *const u8)}; - for i in 0..call_num { - #calls_ident.push(#ty { - extension_id: extension_id, - call_ptr: arg_ptr+10+(i as u32)*(call_size as u32), - call_size: call_size as u32 - }); - } - arg_ptr += 10 + (call_num as u32)*(call_size as u32); - } - .into_iter() - }) - } else { - get_call_data.extend({ - quote! { - let extension_id = unsafe {core::ptr::read_volatile((arg_ptr) as *const u64)}; - let call_size = unsafe {core::ptr::read_volatile((arg_ptr+8) as *const u8)}; - let #calls_ident = #ty { - extension_id: extension_id, - call_ptr: arg_ptr+9, - call_size: call_size as u32 - }; - arg_ptr += 9 + call_size as u32; - } - .into_iter() - }) - } - } - // call entrypoint - let entrypoint_call_args = (0..entrypoint.arg_types.len()) - .map(|arg_type_index| { - let calls_ident = format_ident!("calls_{}", arg_type_index); - quote! { - #calls_ident - } - }) - .collect::>(); - let fn_ident = &entrypoint.item_fn.sig.ident; - let call_entrypoint = quote! { - let res = #fn_ident(#(#entrypoint_call_args),*); - }; - // pass bytes back to host - let pass_bytes_back = pass_byte_to_host(); - - let main = quote! { - #[polkavm_derive::polkavm_export] - extern "C" fn main(mut arg_ptr: u32, size:u32) -> u64 { - #assert_program_types_match - #get_call_data - #call_entrypoint - #pass_bytes_back - } - }; - Ok(main) -} - -fn generate_preludes() -> TokenStream2 { - let extern_crate = quote! { - extern crate alloc; - }; - let panic_fn = quote! { - #[panic_handler] - fn panic(_info: &core::panic::PanicInfo) -> ! { - unsafe { - core::arch::asm!("unimp", options(noreturn)); - } - } - }; - - let host_call_fn = quote! { - #[polkavm_derive::polkavm_import] - extern "C" { - fn host_call(extension_id:u64, call_ptr:u32, call_len: u32) -> u64; - } - }; - - let host_return_ty_fn = quote! { - #[polkavm_derive::polkavm_import] - extern "C" { - fn return_ty(extension_id:u64, call_index:u32) -> u64; - } - }; - - let assert_return_ty_fn = quote! { - fn assert_return_ty(expected_ty_bytes: &[u8],extension_id:u64, call_index:u32) -> bool { - let return_ty = unsafe {return_ty(extension_id, call_index)}; - let ty_len = (return_ty >> 32) as u32; - let ty_ptr = (return_ty & 0xffffffff) as *const u8; - let ty_bytes = unsafe { - core::slice::from_raw_parts(ty_ptr, ty_len as usize) - }; - expected_ty_bytes == ty_bytes - } - }; - quote! { - - #extern_crate - - #panic_fn - - #host_call_fn - - #host_return_ty_fn - - #assert_return_ty_fn - } -} diff --git a/pvq-api/procedural/src/program/parse/call.rs b/pvq-api/procedural/src/program/parse/call.rs deleted file mode 100644 index 419b776..0000000 --- a/pvq-api/procedural/src/program/parse/call.rs +++ /dev/null @@ -1,47 +0,0 @@ -use super::ExternTypesAttr; -use proc_macro2::Span; -use syn::spanned::Spanned; -use syn::{Item, ItemFn}; - -#[derive(Debug, Clone)] -pub struct CallDef { - pub item_fn: ItemFn, - pub extension_id: u64, - pub call_index: u32, - #[allow(unused)] - pub extern_types: Option, -} - -impl CallDef { - pub fn try_from( - span: Span, - item: &mut Item, - extension_id: Option, - call_index: Option, - extern_types: Option, - ) -> syn::Result { - let extension_id = extension_id.ok_or_else(|| { - syn::Error::new( - span, - "Missing extension_id for pvq::call_def, expected #[pvq::call_def(extension_id = SOME_U64)]", - ) - })?; - let item_fn = if let Item::Fn(item_fn) = item { - item_fn - } else { - return Err(syn::Error::new(item.span(), "Invalid pvq::call_def, expected item fn")); - }; - let call_index = call_index.ok_or_else(|| { - syn::Error::new( - span, - "Missing call_index for pvq::call_def, expected #[pvq::call_def(call_index = SOME_U32)]", - ) - })?; - Ok(Self { - item_fn: item_fn.clone(), - extension_id, - call_index, - extern_types, - }) - } -} diff --git a/pvq-api/procedural/src/program/parse/entrypoint.rs b/pvq-api/procedural/src/program/parse/entrypoint.rs deleted file mode 100644 index b83b035..0000000 --- a/pvq-api/procedural/src/program/parse/entrypoint.rs +++ /dev/null @@ -1,73 +0,0 @@ -use syn::spanned::Spanned; -use syn::{AngleBracketedGenericArguments, Item, ItemFn, PathArguments, Result, TypePath}; -#[derive(Debug)] -pub struct EntrypointDef { - pub item_fn: ItemFn, - pub arg_types: Vec, -} - -#[derive(Debug)] -pub struct ArgType { - pub multi: bool, - pub ty: Box, -} - -impl EntrypointDef { - pub fn try_from(_span: proc_macro2::Span, item: &mut Item) -> syn::Result { - if let syn::Item::Fn(item_fn) = item { - let mut arg_types = Vec::new(); - for input in &item_fn.sig.inputs { - if let syn::FnArg::Typed(pat_type) = input { - // match vec - let multi = if let syn::Type::Path(type_path) = pat_type.ty.as_ref() { - // TODO: more accurate way to detect vector usage - type_path.path.segments.iter().any(|segment| segment.ident == "Vec") - } else { - return Err(syn::Error::new(input.span(), "entrypoint args must be owned types")); - }; - if multi { - let inner_type = extract_inner_type(pat_type.ty.as_ref())?; - arg_types.push(ArgType { - multi: true, - ty: Box::new(inner_type.clone()), - }); - } else { - arg_types.push(ArgType { - multi: false, - ty: pat_type.ty.clone(), - }) - } - } else { - return Err(syn::Error::new( - input.span(), - "Invalid pvq::entrypoint, expected fn to have typed arguments", - )); - } - } - Ok(Self { - item_fn: item_fn.clone(), - arg_types, - }) - } else { - Err(syn::Error::new( - item.span(), - "Invalid pvq::entrypoint, expected item fn", - )) - } - } -} - -fn extract_inner_type(ty: &syn::Type) -> Result<&syn::Type> { - if let syn::Type::Path(TypePath { path, .. }) = ty { - if let Some(segment) = path.segments.first() { - if segment.ident == "Vec" { - if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = &segment.arguments { - if let Some(syn::GenericArgument::Type(inner_type)) = args.first() { - return Ok(inner_type); - } - } - } - } - } - Err(syn::Error::new_spanned(ty, "Expected Vec<_>")) -} diff --git a/pvq-api/procedural/src/program/parse/mod.rs b/pvq-api/procedural/src/program/parse/mod.rs deleted file mode 100644 index 6dda8bb..0000000 --- a/pvq-api/procedural/src/program/parse/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -use syn::spanned::Spanned; -use syn::{Error, ItemMod, LitInt, Result}; -mod call; -pub use call::CallDef; -mod entrypoint; -pub use entrypoint::EntrypointDef; -mod helper; -// program definition -pub struct Def { - pub calls: Vec, - pub entrypoint: entrypoint::EntrypointDef, -} - -impl Def { - pub fn try_from(mut item_mod: ItemMod) -> Result { - let mod_span = item_mod.span(); - let items = &mut item_mod - .content - .as_mut() - .ok_or_else(|| { - let msg = "No content inside the PVQ program definition"; - syn::Error::new(mod_span, msg) - })? - .1; - - let mut calls = Vec::new(); - let mut entrypoint = None; - - for item in items.iter_mut() { - let pvq_attr = helper::take_first_pvq_attr(item)?; - - if let Some(attr) = pvq_attr { - if let Some(last_segment) = attr.path().segments.last() { - if last_segment.ident == "call_def" { - let mut extern_types = None; - let mut extension_id = None; - let mut call_index = None; - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("extension_id") { - let value = meta.value()?; - extension_id = Some(value.parse::()?.base10_parse::()?); - } else if meta.path.is_ident("call_index") { - let value = meta.value()?; - call_index = Some(value.parse::()?.base10_parse::()?); - } else if meta.path.is_ident("extern_types") { - let value = meta.value()?; - extern_types = Some(value.parse::()?); - } else { - return Err(Error::new(meta.path.span(), "Invalid attribute for `call_def`")); - } - Ok(()) - })?; - let call = call::CallDef::try_from(attr.span(), item, extension_id, call_index, extern_types)?; - calls.push(call); - } else if last_segment.ident == "entrypoint" { - if entrypoint.is_some() { - return Err(Error::new(attr.span(), "Only one entrypoint function is allowed")); - } - entrypoint = Some(entrypoint::EntrypointDef::try_from(attr.span(), item)?); - } else { - return Err(Error::new( - item.span(), - "Invalid attribute, expected `#[pvq::call_def]` or `#[pvq::entrypoint]`", - )); - } - } - } - } - - let entrypoint = entrypoint.ok_or_else(|| Error::new(mod_span, "No entrypoint function found"))?; - let def = Def { calls, entrypoint }; - - Ok(def) - } -} - -/// List of additional token to be used for parsing. -mod keyword { - syn::custom_keyword!(pvq); - syn::custom_keyword!(call_def); - syn::custom_keyword!(extension_id); - syn::custom_keyword!(extern_types); - syn::custom_keyword!(entrypoint); -} -#[derive(Debug, Clone)] -pub struct ExternTypesAttr { - #[allow(unused)] - pub types: Vec, - #[allow(unused)] - pub span: proc_macro2::Span, -} - -impl syn::parse::Parse for ExternTypesAttr { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let content; - syn::bracketed!(content in input); - let extern_types = content.parse_terminated(syn::Type::parse, syn::Token![,])?; - Ok(ExternTypesAttr { - types: extern_types.into_iter().collect(), - span: content.span(), - }) - } -} diff --git a/pvq-executor/Cargo.toml b/pvq-executor/Cargo.toml index 8b77290..795a8e6 100644 --- a/pvq-executor/Cargo.toml +++ b/pvq-executor/Cargo.toml @@ -12,9 +12,8 @@ tracing = { workspace = true } polkavm = { workspace = true } -pvq-api = { workspace = true } pvq-primitives = { workspace = true } [features] default = ["std"] -std = ["tracing/std", "polkavm/std", "pvq-api/std", "pvq-primitives/std"] +std = ["tracing/std", "polkavm/std", "pvq-primitives/std"] diff --git a/pvq-executor/src/lib.rs b/pvq-executor/src/lib.rs index 565c646..2adf1de 100644 --- a/pvq-executor/src/lib.rs +++ b/pvq-executor/src/lib.rs @@ -75,15 +75,16 @@ impl PvqExecutor { let mut instance = instance_pre.instantiate()?; instance.write_memory(module.memory_map().aux_data_address(), args)?; - + tracing::info!("Calling entrypoint with args: {:?}", args); let res = instance.call_typed_and_get_result::( self.context.data(), - "main", + "pvq", (module.memory_map().aux_data_address(), args.len() as u32), )?; let res_size = (res >> 32) as u32; let res_ptr = (res & 0xffffffff) as u32; let result = instance.read_memory(res_ptr, res_size)?; + tracing::info!("Result: {:?}", result); Ok(result) } } diff --git a/pvq-extension/procedural/Cargo.toml b/pvq-extension/procedural/Cargo.toml index 651eee9..b954dae 100644 --- a/pvq-extension/procedural/Cargo.toml +++ b/pvq-extension/procedural/Cargo.toml @@ -20,4 +20,4 @@ twox-hash = "1.6.3" pvq-extension = { workspace = true } scale-info = { workspace = true } parity-scale-codec = { workspace = true } -trybuild = "1.0" +trybuild = { workspace = true } diff --git a/pvq-extension/src/context.rs b/pvq-extension/src/context.rs index 27dfff1..976c0c6 100644 --- a/pvq-extension/src/context.rs +++ b/pvq-extension/src/context.rs @@ -55,14 +55,14 @@ impl PvqExecutorContext for Context Result { - // Read the call data from memory - let call_bytes = caller.instance.read_memory(call_ptr, call_len)?; - tracing::info!("(host call): call_ptr: {}, call_len: {:?}", call_ptr, call_len); tracing::info!( - "(host call): extension_id: {}, call_bytes: {:?}", + "(host call): extension_id: {}, call_ptr: {}, call_len: {}", extension_id, - call_bytes + call_ptr, + call_len ); + // Read the call data from memory + let call_bytes = caller.instance.read_memory(call_ptr, call_len)?; // Check permissions if !P::is_allowed(extension_id, &call_bytes, invoke_source) { @@ -71,7 +71,7 @@ impl PvqExecutorContext for Context ! { + unsafe { + core::arch::asm!("unimp", options(noreturn)); + } +} + +#[polkavm_derive::polkavm_import] +extern "C" { + fn host_call(extension_id: u64, call_ptr: u32, call_len: u32) -> u64; +} + +type AssetId = u32; +type AccountId = [u8; 32]; +type Balance = u64; + +// #[program::extension(extension_id = 4071833530116166512u64, fn_index = 1)] +// fn balance(asset: AssetId, who: AccountId) -> Balance {} +// expands to +#[allow(non_camel_case_types)] +#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] +enum BalanceCall { + #[codec(index = 1)] + balance { asset: AssetId, who: AccountId }, +} +fn balance(asset: AssetId, who: AccountId) -> Balance { + let encoded_call = parity_scale_codec::Encode::encode(&BalanceCall::balance { asset, who }); + let res = unsafe { + host_call( + 4071833530116166512u64, + encoded_call.as_ptr() as u32, + encoded_call.len() as u32, + ) + }; + let res_ptr = res as u32 as *const u8; + let res_len = (res >> 32) as usize; + let mut res_bytes = unsafe { core::slice::from_raw_parts(res_ptr, res_len) }; + parity_scale_codec::Decode::decode(&mut res_bytes).expect("Failed to decode result") +} + +// #[program::entrypoint] +// fn sum_balance(asset: AssetId, accounts: Vec) -> Balance { +// let mut sum = 0; +// for account in accounts { +// sum += balance(asset, account); +// } +// sum +// } +// expands to +#[polkavm_derive::polkavm_export] +extern "C" fn main(arg_ptr: u32, size: u32) -> u64 { + // Decode the arguments + let mut arg_bytes = unsafe { core::slice::from_raw_parts(arg_ptr as *const u8, size as usize) }; + let asset: AssetId = parity_scale_codec::Decode::decode(&mut arg_bytes).expect("Failed to decode asset"); + let accounts: Vec = + parity_scale_codec::Decode::decode(&mut arg_bytes).expect("Failed to decode accounts"); + + // Call the function + let res = sum_balance(asset, accounts); + + // Encode the result + let encoded_res = parity_scale_codec::Encode::encode(&res); + + // Return the result + (encoded_res.len() as u64) << 32 | (encoded_res.as_ptr() as u64) +} + +fn sum_balance(asset: AssetId, accounts: Vec) -> Balance { + let mut sum = 0; + for account in accounts { + sum += balance(asset, account); + } + sum +} diff --git a/pvq-program/examples/sum-balance-percent/Cargo.toml b/pvq-program/examples/sum-balance-percent/Cargo.toml new file mode 100644 index 0000000..5196d27 --- /dev/null +++ b/pvq-program/examples/sum-balance-percent/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "guest-sum-balance-percent" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +polkavm-derive = { workspace = true } +pvq-program = { workspace = true } +parity-scale-codec = { workspace = true } diff --git a/pvq-program/examples/sum-balance-percent/src/main.rs b/pvq-program/examples/sum-balance-percent/src/main.rs new file mode 100644 index 0000000..562fc0b --- /dev/null +++ b/pvq-program/examples/sum-balance-percent/src/main.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +#[pvq_program::program] +mod sum_balance_percent { + type AssetId = u32; + type AccountId = [u8; 32]; + type Balance = u64; + use alloc::vec::Vec; + #[program::extension_fn(extension_id = 4071833530116166512u64, fn_index = 1)] + fn balance(asset: AssetId, who: AccountId) -> Balance {} + #[program::extension_fn(extension_id = 4071833530116166512u64, fn_index = 0)] + fn total_supply(asset: AssetId) -> Balance {} + + #[program::entrypoint] + fn sum_balance(asset: AssetId, accounts: Vec) -> Balance { + let mut sum_balance = 0; + for account in accounts { + sum_balance += balance(asset, account); + } + sum_balance * 100 / total_supply(asset) + } +} diff --git a/poc/guests/transparent-call/Cargo.toml b/pvq-program/examples/sum-balance/Cargo.toml similarity index 52% rename from poc/guests/transparent-call/Cargo.toml rename to pvq-program/examples/sum-balance/Cargo.toml index d03437c..1239970 100644 --- a/poc/guests/transparent-call/Cargo.toml +++ b/pvq-program/examples/sum-balance/Cargo.toml @@ -1,9 +1,10 @@ [package] -name = "poc-guest-transparent-call" +name = "guest-sum-balance" version = "0.1.0" edition = "2021" publish = false [dependencies] +parity-scale-codec = { workspace = true } polkavm-derive = { workspace = true } -pvq-api = { workspace = true } +pvq-program = { workspace = true } diff --git a/pvq-program/examples/sum-balance/src/main.rs b/pvq-program/examples/sum-balance/src/main.rs new file mode 100644 index 0000000..771a33f --- /dev/null +++ b/pvq-program/examples/sum-balance/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +#[pvq_program::program] +mod sum_balance { + type AccountId = [u8; 32]; + type AssetId = u32; + type Balance = u64; + + #[program::extension_fn(extension_id = 4071833530116166512u64, fn_index = 1)] + fn balance(asset: AssetId, who: AccountId) -> Balance {} + + #[program::entrypoint] + fn sum_balance(asset: AssetId, accounts: alloc::vec::Vec) -> Balance { + let mut sum = 0; + for account in accounts { + sum += balance(asset, account); + } + sum + } +} diff --git a/pvq-program/examples/total-supply-hand-written/Cargo.toml b/pvq-program/examples/total-supply-hand-written/Cargo.toml new file mode 100644 index 0000000..2f967c2 --- /dev/null +++ b/pvq-program/examples/total-supply-hand-written/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "guest-total-supply-hand-written" +version = "0.1.0" +edition = "2021" +publish = false +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +polkavm-derive = { workspace = true } +parity-scale-codec = { workspace = true, features = ["derive"] } diff --git a/pvq-program/examples/total-supply-hand-written/src/main.rs b/pvq-program/examples/total-supply-hand-written/src/main.rs new file mode 100644 index 0000000..244469a --- /dev/null +++ b/pvq-program/examples/total-supply-hand-written/src/main.rs @@ -0,0 +1,71 @@ +// Generated by macro +#![no_std] +#![no_main] +extern crate alloc; + +#[global_allocator] +static GLOBAL: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { + core::arch::asm!("unimp", options(noreturn)); + } +} + +#[polkavm_derive::polkavm_import] +extern "C" { + fn host_call(extension_id: u64, call_ptr: u32, call_len: u32) -> u64; +} + +type AssetId = u32; +type Balance = u64; + +// #[program::extension(extension_id = 4071833530116166512u64, call_index = 1)] +// fn total_supply(asset: AssetId) -> Balance {} +// expands to +#[allow(non_camel_case_types)] +#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] +enum TotalSupplyCall { + #[codec(index = 0)] + total_supply { asset: AssetId }, +} +fn total_supply(asset: AssetId) -> Balance { + let encoded_call = parity_scale_codec::Encode::encode(&TotalSupplyCall::total_supply { asset }); + let res = unsafe { + host_call( + 4071833530116166512u64, + encoded_call.as_ptr() as u32, + encoded_call.len() as u32, + ) + }; + let res_ptr = res as u32 as *const u8; + let res_len = (res >> 32) as usize; + let mut res_bytes = unsafe { core::slice::from_raw_parts(res_ptr, res_len) }; + parity_scale_codec::Decode::decode(&mut res_bytes).expect("Failed to decode result") +} + +// #[program::entrypoint] +// fn total_supply_(asset: AssetId) -> Balance { +// total_supply(asset) +// } +// expands to +#[polkavm_derive::polkavm_export] +extern "C" fn main(arg_ptr: u32, size: u32) -> u64 { + // Decode the arguments + let mut arg_bytes = unsafe { core::slice::from_raw_parts(arg_ptr as *const u8, size as usize) }; + let asset: AssetId = parity_scale_codec::Decode::decode(&mut arg_bytes).expect("Failed to decode asset"); + + // Call the function + let res = total_supply_(asset); + + // Encode the result + let encoded_res = parity_scale_codec::Encode::encode(&res); + + // Return the result + (encoded_res.len() as u64) << 32 | (encoded_res.as_ptr() as u64) +} + +fn total_supply_(asset: AssetId) -> Balance { + total_supply(asset) +} diff --git a/poc/guests/total-supply/Cargo.toml b/pvq-program/examples/total-supply/Cargo.toml similarity index 66% rename from poc/guests/total-supply/Cargo.toml rename to pvq-program/examples/total-supply/Cargo.toml index cad242f..eb1162c 100644 --- a/poc/guests/total-supply/Cargo.toml +++ b/pvq-program/examples/total-supply/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "poc-guest-total-supply" +name = "guest-total-supply" version = "0.1.0" edition = "2021" publish = false @@ -7,4 +7,5 @@ publish = false [dependencies] polkavm-derive = { workspace = true } -pvq-api = { workspace = true } +pvq-program = { workspace = true } +parity-scale-codec = { workspace = true } diff --git a/pvq-program/examples/total-supply/src/main.rs b/pvq-program/examples/total-supply/src/main.rs new file mode 100644 index 0000000..cdda439 --- /dev/null +++ b/pvq-program/examples/total-supply/src/main.rs @@ -0,0 +1,13 @@ +#![no_std] +#![no_main] + +#[pvq_program::program] +mod query_total_supply { + #[program::extension_fn(extension_id = 4071833530116166512u64, fn_index = 0)] + fn total_supply(asset: u32) -> u64 {} + + #[program::entrypoint] + fn get_total_supply(asset: u32) -> u64 { + total_supply(asset) + } +} diff --git a/poc/guests/sum-balance/Cargo.toml b/pvq-program/examples/transparent-call-hand-written/Cargo.toml similarity index 64% rename from poc/guests/sum-balance/Cargo.toml rename to pvq-program/examples/transparent-call-hand-written/Cargo.toml index 2fc3ac9..dc04352 100644 --- a/poc/guests/sum-balance/Cargo.toml +++ b/pvq-program/examples/transparent-call-hand-written/Cargo.toml @@ -1,9 +1,8 @@ [package] -name = "poc-guest-sum-balance" +name = "guest-transparent-call-hand-written" version = "0.1.0" edition = "2021" publish = false [dependencies] polkavm-derive = { workspace = true } -pvq-api = { workspace = true } diff --git a/poc/guests/transparent-call/src/main.rs b/pvq-program/examples/transparent-call-hand-written/src/main.rs similarity index 100% rename from poc/guests/transparent-call/src/main.rs rename to pvq-program/examples/transparent-call-hand-written/src/main.rs diff --git a/pvq-api/procedural/Cargo.toml b/pvq-program/procedural/Cargo.toml similarity index 72% rename from pvq-api/procedural/Cargo.toml rename to pvq-program/procedural/Cargo.toml index dc16564..7eb7cf2 100644 --- a/pvq-api/procedural/Cargo.toml +++ b/pvq-program/procedural/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pvq-api-procedural" +name = "pvq-program-procedural" version = "0.1.0" edition = "2021" @@ -12,5 +12,9 @@ syn = { workspace = true } proc-macro2 = { workspace = true } proc-macro-crate = { workspace = true } Inflector = { workspace = true } + +[dev-dependencies] parity-scale-codec = { workspace = true } scale-info = { workspace = true } +polkavm-derive = { workspace = true } +trybuild = { workspace = true } diff --git a/pvq-program/procedural/src/lib.rs b/pvq-program/procedural/src/lib.rs new file mode 100644 index 0000000..37babc0 --- /dev/null +++ b/pvq-program/procedural/src/lib.rs @@ -0,0 +1,30 @@ +/// Declare the calls used in PVQ program +/// ```ignore +/// #[program] +/// mod query_fungibles { +/// // The types to be used in the program, which matches the runtime implementation +/// type AssetId = u32; +/// type AccountId = [u8; 32]; +/// type Balance = u64; +/// +/// #[program::extension_fn(extension_id = 123456u64, fn_index = 1u8)] +/// fn balance(asset: AssetId, who: AccountId) -> Balance; +/// +/// #[program::entrypoint] +/// fn sum_balance(accounts: Vec) -> Balance { +/// let mut sum = 0; +/// for account in accounts { +/// sum += balance(0, account); +/// } +/// sum +/// } +/// } +/// ``` +/// +mod program; +use proc_macro::TokenStream; + +#[proc_macro_attribute] +pub fn program(attr: TokenStream, item: TokenStream) -> TokenStream { + program::program(attr, item) +} diff --git a/pvq-program/procedural/src/program/expand/mod.rs b/pvq-program/procedural/src/program/expand/mod.rs new file mode 100644 index 0000000..d2769c5 --- /dev/null +++ b/pvq-program/procedural/src/program/expand/mod.rs @@ -0,0 +1,120 @@ +mod preludes; +use super::{Def, ExtensionFn}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote, ToTokens}; +pub fn expand(mut def: Def) -> TokenStream2 { + let preludes = preludes::generate_preludes(&def); + + let expanded_extension_fns = def + .extension_fns + .iter_mut() + .map(|extension_fn| expand_extension_fn(extension_fn, &def.parity_scale_codec)) + .collect::>(); + + let main_fn = expand_main(&def); + + let new_items = quote! { + #preludes + #(#expanded_extension_fns)* + #main_fn + }; + + def.item + .content + .as_mut() + .expect("This is checked by parsing") + .1 + .push(syn::Item::Verbatim(new_items)); + def.item.into_token_stream() +} + +fn expand_extension_fn(extension_fn: &mut ExtensionFn, parity_scale_codec: &syn::Path) -> TokenStream2 { + let extension_id = extension_fn.extension_id; + let fn_index = extension_fn.fn_index; + let fn_name = &extension_fn.item_fn.sig.ident; + let args = &extension_fn.item_fn.sig.inputs; + let enum_name = format_ident!("{}Call", fn_name); + let expanded_enum = quote! ( + #[allow(non_camel_case_types)] + #[derive(#parity_scale_codec::Encode, #parity_scale_codec::Decode)] + enum #enum_name { + #[codec(index = #fn_index)] + #fn_name { + #args + } + } + ); + let arg_names = args + .iter() + .map(|arg| { + let syn::FnArg::Typed(pat_type) = arg else { + unreachable!("Checked in parse stage") + }; + &pat_type.pat + }) + .collect::>(); + + let fn_name_str = fn_name.to_string(); + extension_fn.item_fn.block = Box::new(syn::parse_quote!( + { + let encoded_call = #parity_scale_codec::Encode::encode(&#enum_name::#fn_name { + #(#arg_names),* + }); + let res = unsafe { + host_call(#extension_id, encoded_call.as_ptr() as u32, encoded_call.len() as u32) + }; + let res_ptr = res as u32 as *const u8; + let res_len = (res >> 32) as usize; + let mut res_bytes = unsafe { core::slice::from_raw_parts(res_ptr, res_len) }; + #parity_scale_codec::Decode::decode(&mut res_bytes).expect(concat!("Failed to decode result of ", #fn_name_str)) + } + )); + let modified_extension_fn = &extension_fn.item_fn; + quote!( + #expanded_enum + #modified_extension_fn + ) +} + +fn expand_main(def: &Def) -> TokenStream2 { + let parity_scale_codec = &def.parity_scale_codec; + + // Get `ident: Type`s + let arg_pats = def.entrypoint.item_fn.sig.inputs.iter().collect::>(); + // Get `ident`s + let arg_identifiers = arg_pats + .iter() + .map(|arg| { + if let syn::FnArg::Typed(pat_type) = arg { + pat_type.pat.to_token_stream() + } else { + unreachable!("Checked in parse stage") + } + }) + .collect::>(); + let arg_identifiers_str = arg_identifiers.iter().map(|arg| arg.to_string()).collect::>(); + + let decode_args = quote! { + #(let #arg_pats = #parity_scale_codec::Decode::decode(&mut arg_bytes).expect(concat!("Failed to decode ", #arg_identifiers_str));)* + }; + + let entrypoint_ident = &def.entrypoint.item_fn.sig.ident; + let call_entrypoint = quote! { + let res = #entrypoint_ident(#(#arg_identifiers),*); + }; + + quote! { + #[polkavm_derive::polkavm_export] + extern "C" fn pvq(arg_ptr: u32, size: u32) -> u64 { + let mut arg_bytes = unsafe { core::slice::from_raw_parts(arg_ptr as *const u8, size as usize) }; + + #decode_args + + #call_entrypoint + + let encoded_res = #parity_scale_codec::Encode::encode(&res); + (encoded_res.len() as u64) << 32 | (encoded_res.as_ptr() as u64) + + } + } +} diff --git a/pvq-program/procedural/src/program/expand/preludes.rs b/pvq-program/procedural/src/program/expand/preludes.rs new file mode 100644 index 0000000..2b0452e --- /dev/null +++ b/pvq-program/procedural/src/program/expand/preludes.rs @@ -0,0 +1,43 @@ +use super::Def; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +pub fn generate_preludes(def: &Def) -> TokenStream2 { + let extern_crate = quote! { + extern crate alloc; + }; + + let global_allocator = quote! { + #[global_allocator] + static GLOBAL: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; + }; + + let panic_fn = quote! { + #[panic_handler] + fn panic(_info: &core::panic::PanicInfo) -> ! { + unsafe { + core::arch::asm!("unimp", options(noreturn)); + } + } + }; + + let polkavm_derive = &def.polkavm_derive; + + let host_call_fn = quote! { + #[#polkavm_derive::polkavm_import] + extern "C" { + fn host_call(extension_id:u64, call_ptr:u32, call_len: u32) -> u64; + } + }; + + quote! { + + #extern_crate + + #global_allocator + + #panic_fn + + #host_call_fn + } +} diff --git a/pvq-api/procedural/src/program/mod.rs b/pvq-program/procedural/src/program/mod.rs similarity index 69% rename from pvq-api/procedural/src/program/mod.rs rename to pvq-program/procedural/src/program/mod.rs index f8ac0df..06882cf 100644 --- a/pvq-api/procedural/src/program/mod.rs +++ b/pvq-program/procedural/src/program/mod.rs @@ -2,12 +2,12 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, ItemMod}; mod expand; mod parse; -pub use parse::{CallDef, Def, EntrypointDef}; +pub(crate) use parse::{Def, ExtensionFn}; pub fn program(_attr: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as ItemMod); match parse::Def::try_from(item) { - Ok(def) => expand::expand(def).unwrap_or_else(|e| e.to_compile_error()).into(), + Ok(def) => expand::expand(def).into(), Err(e) => e.to_compile_error().into(), } } diff --git a/pvq-program/procedural/src/program/parse/entrypoint.rs b/pvq-program/procedural/src/program/parse/entrypoint.rs new file mode 100644 index 0000000..99476e5 --- /dev/null +++ b/pvq-program/procedural/src/program/parse/entrypoint.rs @@ -0,0 +1,31 @@ +use syn::spanned::Spanned; +#[derive(Debug)] +pub struct EntrypointDef { + pub item_fn: syn::ItemFn, +} + +impl EntrypointDef { + pub fn try_from(_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result { + if let syn::Item::Fn(item_fn) = item { + if item_fn + .sig + .inputs + .iter() + .any(|arg| matches!(arg, syn::FnArg::Receiver(_))) + { + return Err(syn::Error::new( + item_fn.span(), + "Invalid program::entrypoint, expected fn args are not receiver type", + )); + } + Ok(Self { + item_fn: item_fn.clone(), + }) + } else { + Err(syn::Error::new( + item.span(), + "Invalid program::entrypoint, expected item fn", + )) + } + } +} diff --git a/pvq-program/procedural/src/program/parse/extension_fn.rs b/pvq-program/procedural/src/program/parse/extension_fn.rs new file mode 100644 index 0000000..4d505be --- /dev/null +++ b/pvq-program/procedural/src/program/parse/extension_fn.rs @@ -0,0 +1,57 @@ +use proc_macro2::Span; +use syn::spanned::Spanned; + +#[derive(Debug)] +pub struct ExtensionFn { + pub item_fn: syn::ItemFn, + pub extension_id: u64, + pub fn_index: u32, +} + +impl ExtensionFn { + pub fn try_from( + span: Span, + item: syn::Item, + extension_id: Option, + fn_index: Option, + ) -> syn::Result { + let extension_id = extension_id.ok_or_else(|| { + syn::Error::new( + span, + "Missing extension_id for program::extension_fn, expected #[program::extension_fn(extension_id = SOME_U64)]", + ) + })?; + let item_fn = if let syn::Item::Fn(item_fn) = item { + item_fn + } else { + return Err(syn::Error::new( + item.span(), + "Invalid program::extension_fn, expected item fn", + )); + }; + // Check that the inputs of the function are all not self + if item_fn + .sig + .inputs + .iter() + .any(|arg| matches!(arg, syn::FnArg::Receiver(_))) + { + return Err(syn::Error::new( + item_fn.span(), + "Invalid program::extension_fn, expected function inputs to not be receiver", + )); + } + + let fn_index = fn_index.ok_or_else(|| { + syn::Error::new( + span, + "Missing fn_index for program::extension_fn, expected #[program::extension_fn(fn_index = SOME_U32)]", + ) + })?; + Ok(Self { + item_fn, + extension_id, + fn_index, + }) + } +} diff --git a/pvq-api/procedural/src/program/parse/helper.rs b/pvq-program/procedural/src/program/parse/helper.rs similarity index 78% rename from pvq-api/procedural/src/program/parse/helper.rs rename to pvq-program/procedural/src/program/parse/helper.rs index b3f7541..8e9575c 100644 --- a/pvq-api/procedural/src/program/parse/helper.rs +++ b/pvq-program/procedural/src/program/parse/helper.rs @@ -1,8 +1,10 @@ +use proc_macro2::Span; +use proc_macro_crate::{crate_name, FoundCrate}; pub trait MutItemAttrs { fn mut_item_attrs(&mut self) -> Option<&mut Vec>; } /// Take the first item attribute (e.g. attribute like `#[pvq..]`) and decode it to `Attr` -pub(crate) fn take_first_pvq_attr(item: &mut impl MutItemAttrs) -> syn::Result> { +pub(crate) fn take_first_program_attr(item: &mut impl MutItemAttrs) -> syn::Result> { let Some(attrs) = item.mut_item_attrs() else { return Ok(None); }; @@ -11,7 +13,7 @@ pub(crate) fn take_first_pvq_attr(item: &mut impl MutItemAttrs) -> syn::Result syn::Result { + let ident = match crate_name(def_crate) { + Ok(FoundCrate::Itself) => { + let name = def_crate.replace('-', "_"); + Ok(syn::Ident::new(&name, Span::call_site())) + } + Ok(FoundCrate::Name(name)) => Ok(syn::Ident::new(&name, Span::call_site())), + Err(e) => Err(syn::Error::new(Span::call_site(), e)), + }?; + Ok(syn::Path::from(ident)) +} diff --git a/pvq-program/procedural/src/program/parse/mod.rs b/pvq-program/procedural/src/program/parse/mod.rs new file mode 100644 index 0000000..0101cdd --- /dev/null +++ b/pvq-program/procedural/src/program/parse/mod.rs @@ -0,0 +1,98 @@ +use syn::spanned::Spanned; +mod extension_fn; +pub use extension_fn::ExtensionFn; +mod entrypoint; +pub use entrypoint::EntrypointDef; +mod helper; +// program definition +pub struct Def { + pub item: syn::ItemMod, + pub extension_fns: Vec, + pub entrypoint: EntrypointDef, + pub parity_scale_codec: syn::Path, + pub polkavm_derive: syn::Path, +} + +impl Def { + pub fn try_from(mut item: syn::ItemMod) -> syn::Result { + let parity_scale_codec = helper::generate_crate_access("parity-scale-codec")?; + let polkavm_derive = helper::generate_crate_access("polkavm-derive")?; + let mod_span = item.span(); + let items = &mut item + .content + .as_mut() + .ok_or_else(|| { + let msg = "Invalid #[program] definition, expected mod to be inline."; + syn::Error::new(mod_span, msg) + })? + .1; + + let mut extension_fns = Vec::new(); + let mut entrypoint = None; + + let mut i = 0; + while i < items.len() { + let item = &mut items[i]; + if let Some(attr) = helper::take_first_program_attr(item)? { + if let Some(last_segment) = attr.path().segments.last() { + if last_segment.ident == "extension_fn" { + let mut extension_id = None; + let mut fn_index = None; + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("extension_id") { + let value = meta.value()?; + extension_id = Some(value.parse::()?.base10_parse::()?); + } else if meta.path.is_ident("fn_index") { + let value = meta.value()?; + fn_index = Some(value.parse::()?.base10_parse::()?); + } else { + return Err(syn::Error::new( + meta.path.span(), + "Invalid attribute meta, expected `extension_id` or `fn_index`", + )); + } + Ok(()) + })?; + + let removed_item = items.remove(i); + let extension_fn = ExtensionFn::try_from(attr.span(), removed_item, extension_id, fn_index)?; + extension_fns.push(extension_fn); + continue; + } else if last_segment.ident == "entrypoint" { + if entrypoint.is_some() { + return Err(syn::Error::new(attr.span(), "Only one entrypoint function is allowed")); + } + entrypoint = Some(EntrypointDef::try_from(attr.span(), item)?); + continue; + } else { + return Err(syn::Error::new( + item.span(), + "Invalid attribute, expected `#[program::extension_fn]` or `#[program::entrypoint]`", + )); + } + } + } + i += 1; + } + + let entrypoint = + entrypoint.ok_or_else(|| syn::Error::new(mod_span, "At least one entrypoint function is required"))?; + let def = Def { + item, + extension_fns, + entrypoint, + parity_scale_codec, + polkavm_derive, + }; + + Ok(def) + } +} + +/// List of additional token to be used for parsing. +mod keyword { + syn::custom_keyword!(program); + syn::custom_keyword!(extension_id); + syn::custom_keyword!(fn_index); + syn::custom_keyword!(entrypoint); +} diff --git a/pvq-program/procedural/tests/tests.rs b/pvq-program/procedural/tests/tests.rs new file mode 100644 index 0000000..622715a --- /dev/null +++ b/pvq-program/procedural/tests/tests.rs @@ -0,0 +1,9 @@ +#[test] +fn test_macros() { + let t = trybuild::TestCases::new(); + // Test successful cases + t.pass("tests/ui/*.rs"); + + // Test failing cases + t.compile_fail("tests/ui/fail/*.rs"); +} diff --git a/pvq-program/procedural/tests/ui/sum-balance.rs b/pvq-program/procedural/tests/ui/sum-balance.rs new file mode 100644 index 0000000..d3ec35d --- /dev/null +++ b/pvq-program/procedural/tests/ui/sum-balance.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +#[pvq_program_procedural::program] +mod sum_balance { + type AccountId = [u8; 32]; + type AssetId = u32; + type Balance = u64; + + #[program::extension_fn(extension_id = 4071833530116166512u64, fn_index = 1)] + fn balance(asset: AssetId, who: AccountId) -> Balance {} + + #[program::entrypoint] + fn sum_balance(asset: AssetId, accounts: alloc::vec::Vec) -> Balance { + let mut sum = 0; + for account in accounts { + sum += balance(asset, account); + } + sum + } +} diff --git a/poc/guests/rust-toolchain.toml b/pvq-program/rust-toolchain.toml similarity index 100% rename from poc/guests/rust-toolchain.toml rename to pvq-program/rust-toolchain.toml diff --git a/pvq-api/src/lib.rs b/pvq-program/src/lib.rs similarity index 51% rename from pvq-api/src/lib.rs rename to pvq-program/src/lib.rs index b0c9c85..dd2d335 100644 --- a/pvq-api/src/lib.rs +++ b/pvq-program/src/lib.rs @@ -1,2 +1,2 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub use pvq_api_procedural::program; +pub use pvq_program_procedural::program; diff --git a/pvq-test-runner/src/main.rs b/pvq-test-runner/src/main.rs index b17b3a9..43fdcef 100644 --- a/pvq-test-runner/src/main.rs +++ b/pvq-test-runner/src/main.rs @@ -25,30 +25,41 @@ fn main() { let cli = Cli::parse(); - let blob = std::fs::read(cli.program).expect("Failed to read program"); + let blob = std::fs::read(&cli.program).expect("Failed to read program"); let mut executor = ExtensionsExecutor::::new(InvokeSource::RuntimeAPI); - let mut input_data = pvq_extension_core::extension::extension_id().encode(); - input_data.extend_from_slice(&[2u8]); - let method1 = pvq_extension_fungibles::extension::Functions::::balance { - asset: 1, - who: [0u8; 32], + let mut input_data = Vec::new(); + let program_str = cli.program.to_string_lossy(); + if program_str.contains("sum-balance") { + input_data.extend_from_slice(&0u32.encode()); + input_data.extend_from_slice(&vec![[0u8; 32], [1u8; 32]].encode()); + } else if program_str.contains("total-supply") { + input_data.extend_from_slice(&0u32.encode()); + } else if program_str.contains("transparent-call") { + input_data.extend_from_slice(&4071833530116166512u64.encode()); + input_data.extend_from_slice( + &ExtensionFungiblesFunctions::balance { + asset: 0, + who: [1u8; 32], + } + .encode(), + ); } - .encode(); - let method1_encoded = method1.encode(); - input_data.extend_from_slice(&[method1_encoded.len() as u8]); - let method2 = pvq_extension_fungibles::extension::Functions::::balance { - asset: 1, - who: [1u8; 32], - } - .encode(); - input_data.extend_from_slice(&method1_encoded); - input_data.extend_from_slice(&method2.encode()); tracing::info!("Input data: {:?}", input_data); let res = executor.execute_method(&blob, &input_data, 0).unwrap(); tracing::info!("Result: {:?}", res); } +#[derive(Encode)] +#[allow(non_camel_case_types)] +#[allow(dead_code)] +enum ExtensionFungiblesFunctions { + #[codec(index = 0)] + total_supply { asset: u32 }, + #[codec(index = 1)] + balance { asset: u32, who: [u8; 32] }, +} + #[extensions_impl] pub mod extensions { #[extensions_impl::impl_struct] From 77ad3215b3408cb4d6e9c4449a1a8a60352637fc Mon Sep 17 00:00:00 2001 From: indirection42 Date: Tue, 18 Mar 2025 14:39:52 +0800 Subject: [PATCH 2/4] fix test --- .github/workflows/build.yml | 28 +-- Cargo.lock | 5 - Cargo.toml | 3 +- Makefile | 12 +- .../.cargo/config.toml | 2 + .../examples => guest-examples}/Cargo.lock | 55 ------ .../examples => guest-examples}/Cargo.toml | 4 +- .../sum-balance-hand-written/Cargo.toml | 0 .../sum-balance-hand-written/src/main.rs | 0 .../sum-balance-percent/Cargo.toml | 0 .../sum-balance-percent/src/main.rs | 0 .../sum-balance/Cargo.toml | 0 .../sum-balance/src/main.rs | 0 .../total-supply-hand-written/Cargo.toml | 0 .../total-supply-hand-written/src/main.rs | 0 .../total-supply/Cargo.toml | 0 .../total-supply/src/main.rs | 0 .../transparent-call-hand-written/Cargo.toml | 0 .../transparent-call-hand-written/src/main.rs | 0 poc/runtime/src/pvq.rs | 80 -------- pvq-extension/tests/lib.rs | 186 ------------------ pvq-program/procedural/Cargo.toml | 7 - pvq-program/procedural/tests/tests.rs | 9 - .../procedural/tests/ui/sum-balance.rs | 21 -- pvq-program/rust-toolchain.toml | 3 - 25 files changed, 26 insertions(+), 389 deletions(-) rename {pvq-program => guest-examples}/.cargo/config.toml (81%) rename {pvq-program/examples => guest-examples}/Cargo.lock (81%) rename {pvq-program/examples => guest-examples}/Cargo.toml (70%) rename {pvq-program/examples => guest-examples}/sum-balance-hand-written/Cargo.toml (100%) rename {pvq-program/examples => guest-examples}/sum-balance-hand-written/src/main.rs (100%) rename {pvq-program/examples => guest-examples}/sum-balance-percent/Cargo.toml (100%) rename {pvq-program/examples => guest-examples}/sum-balance-percent/src/main.rs (100%) rename {pvq-program/examples => guest-examples}/sum-balance/Cargo.toml (100%) rename {pvq-program/examples => guest-examples}/sum-balance/src/main.rs (100%) rename {pvq-program/examples => guest-examples}/total-supply-hand-written/Cargo.toml (100%) rename {pvq-program/examples => guest-examples}/total-supply-hand-written/src/main.rs (100%) rename {pvq-program/examples => guest-examples}/total-supply/Cargo.toml (100%) rename {pvq-program/examples => guest-examples}/total-supply/src/main.rs (100%) rename {pvq-program/examples => guest-examples}/transparent-call-hand-written/Cargo.toml (100%) rename {pvq-program/examples => guest-examples}/transparent-call-hand-written/src/main.rs (100%) delete mode 100644 pvq-extension/tests/lib.rs delete mode 100644 pvq-program/procedural/tests/tests.rs delete mode 100644 pvq-program/procedural/tests/ui/sum-balance.rs delete mode 100644 pvq-program/rust-toolchain.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index af63b06..4591fc9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,17 +32,17 @@ jobs: - name: Check format run: cargo fmt --all -- --check - - name: Make dummy poc-guest-%.polkavm files - run: make dummy-poc-guests - - - name: Cargo clippy - run: SKIP_WASM_BUILD= cargo clippy -- -D warnings + - name: Make dummy poc-guest files + run: make dummy-guests - name: Check no-std run: make check-wasm - - name: Check std - run: SKIP_WASM_BUILD= cargo check + - name: Clippy + run: make clippy + + - name: Run tests + run: make test build-guest: runs-on: ubuntu-latest @@ -54,16 +54,18 @@ jobs: - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: - targets: wasm32-unknown-unknown - components: rustfmt, clippy + targets: wasm32-unknown-unknown, riscv32emac-unknown-none-polkavm + components: rust-src, rustfmt, clippy - uses: Swatinem/rust-cache@v2 with: - workspaces: poc/guests -> poc/guests/target + workspaces: | + guest-examples -> guest-examples/target cache-all-crates: true - - name: Cargo clippy - run: cd poc/guests; cargo clippy -- -D warnings - - name: Install polkatool run: make polkatool + + - name: Build guests + run: make guests + diff --git a/Cargo.lock b/Cargo.lock index 8f77c04..45b28ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3233,15 +3233,10 @@ dependencies = [ name = "pvq-program-procedural" version = "0.1.0" dependencies = [ - "Inflector", - "parity-scale-codec", - "polkavm-derive 0.21.0", "proc-macro-crate", "proc-macro2", "quote", - "scale-info", "syn 2.0.96", - "trybuild", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 980e19c..cae6676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ "examples/example-fungibles", "examples/example-helloworld", ] -exclude = ["poc/guests", "vendor"] +exclude = ["guest-examples", "vendor"] [profile.release] # runtime requires unwinding. @@ -69,5 +69,4 @@ syn = { version = "2", features = ["full", "visit-mut", "extra-traits"] } quote = "1" proc-macro2 = "1" proc-macro-crate = "3" -Inflector = { version = "0.11.4" } trybuild = "1" diff --git a/Makefile b/Makefile index 36b4c60..e620513 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,18 @@ run: chainspec bunx @acala-network/chopsticks@latest --config poc/runtime/chopsticks.yml --genesis output/chainspec.json -EXAMPLE_TARGETS = $(shell cd pvq-program/examples && cargo metadata --no-deps --format-version 1 | jq -r '.workspace_members[] | split(" ")[0] | split("\#")[0]' | grep -v "^pvq-program-examples$$") -GUEST_TARGETS = $(patsubst %,guest-%,$(notdir $(EXAMPLE_TARGETS))) -DUMMY_GUEST_TARGETS = $(patsubst %,dummy-guest-%,$(notdir $(EXAMPLE_TARGETS))) +GUEST_EXAMPLES = $(shell find guest-examples -name "Cargo.toml" -not -path "guest-examples/Cargo.toml" | xargs -n1 dirname | xargs -n1 basename) +GUEST_TARGETS = $(patsubst %,guest-%,$(GUEST_EXAMPLES)) +DUMMY_GUEST_TARGETS = $(patsubst %,dummy-guest-%,$(GUEST_EXAMPLES)) guests: $(GUEST_TARGETS) dummy-guests: $(DUMMY_GUEST_TARGETS) guest-%: - cd pvq-program/examples; RUSTFLAGS="-D warnings" cargo build -q --release -Z build-std=core,alloc --target "../../vendor/polkavm/crates/polkavm-linker/riscv32emac-unknown-none-polkavm.json" --bin guest-$* -p guest-$* + cd guest-examples; cargo build -q --release --bin guest-$* -p guest-$* mkdir -p output - polkatool link --run-only-if-newer -s pvq-program/examples/target/riscv32emac-unknown-none-polkavm/release/guest-$* -o output/guest-$*.polkavm + polkatool link --run-only-if-newer -s guest-examples/target/riscv32emac-unknown-none-polkavm/release/guest-$* -o output/guest-$*.polkavm dummy-guest-%: mkdir -p output @@ -39,7 +39,7 @@ check: check-wasm clippy: SKIP_WASM_BUILD= cargo clippy -- -D warnings - cd pvq-program/examples; RUSTFLAGS="-D warnings" cargo clippy -Z build-std=core,alloc --target "../../vendor/polkavm/crates/polkavm-linker/riscv32emac-unknown-none-polkavm.json" --all + cd guest-examples; cargo clippy --all test: SKIP_WASM_BUILD= cargo test diff --git a/pvq-program/.cargo/config.toml b/guest-examples/.cargo/config.toml similarity index 81% rename from pvq-program/.cargo/config.toml rename to guest-examples/.cargo/config.toml index 59de472..c4e725a 100644 --- a/pvq-program/.cargo/config.toml +++ b/guest-examples/.cargo/config.toml @@ -1,5 +1,7 @@ [build] target = "../vendor/polkavm/crates/polkavm-linker/riscv32emac-unknown-none-polkavm.json" +rustflags = ["-D", "warnings"] + [unstable] build-std = ["core", "alloc"] diff --git a/pvq-program/examples/Cargo.lock b/guest-examples/Cargo.lock similarity index 81% rename from pvq-program/examples/Cargo.lock rename to guest-examples/Cargo.lock index 5e41751..2093557 100644 --- a/pvq-program/examples/Cargo.lock +++ b/guest-examples/Cargo.lock @@ -2,25 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "arrayvec" version = "0.7.6" @@ -136,12 +117,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "memchr" version = "2.7.4" @@ -232,7 +207,6 @@ dependencies = [ name = "pvq-program-procedural" version = "0.1.0" dependencies = [ - "Inflector", "proc-macro-crate", "proc-macro2", "quote", @@ -248,35 +222,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "rustversion" version = "1.0.20" diff --git a/pvq-program/examples/Cargo.toml b/guest-examples/Cargo.toml similarity index 70% rename from pvq-program/examples/Cargo.toml rename to guest-examples/Cargo.toml index 0feccf6..32d03e6 100644 --- a/pvq-program/examples/Cargo.toml +++ b/guest-examples/Cargo.toml @@ -13,5 +13,5 @@ resolver = "2" parity-scale-codec = { version = "3", default-features = false, features = [ "derive", ] } -pvq-program = { path = "..", default-features = false } -polkavm-derive = { path = "../../vendor/polkavm/crates/polkavm-derive" } +pvq-program = { path = "../pvq-program", default-features = false } +polkavm-derive = { path = "../vendor/polkavm/crates/polkavm-derive" } diff --git a/pvq-program/examples/sum-balance-hand-written/Cargo.toml b/guest-examples/sum-balance-hand-written/Cargo.toml similarity index 100% rename from pvq-program/examples/sum-balance-hand-written/Cargo.toml rename to guest-examples/sum-balance-hand-written/Cargo.toml diff --git a/pvq-program/examples/sum-balance-hand-written/src/main.rs b/guest-examples/sum-balance-hand-written/src/main.rs similarity index 100% rename from pvq-program/examples/sum-balance-hand-written/src/main.rs rename to guest-examples/sum-balance-hand-written/src/main.rs diff --git a/pvq-program/examples/sum-balance-percent/Cargo.toml b/guest-examples/sum-balance-percent/Cargo.toml similarity index 100% rename from pvq-program/examples/sum-balance-percent/Cargo.toml rename to guest-examples/sum-balance-percent/Cargo.toml diff --git a/pvq-program/examples/sum-balance-percent/src/main.rs b/guest-examples/sum-balance-percent/src/main.rs similarity index 100% rename from pvq-program/examples/sum-balance-percent/src/main.rs rename to guest-examples/sum-balance-percent/src/main.rs diff --git a/pvq-program/examples/sum-balance/Cargo.toml b/guest-examples/sum-balance/Cargo.toml similarity index 100% rename from pvq-program/examples/sum-balance/Cargo.toml rename to guest-examples/sum-balance/Cargo.toml diff --git a/pvq-program/examples/sum-balance/src/main.rs b/guest-examples/sum-balance/src/main.rs similarity index 100% rename from pvq-program/examples/sum-balance/src/main.rs rename to guest-examples/sum-balance/src/main.rs diff --git a/pvq-program/examples/total-supply-hand-written/Cargo.toml b/guest-examples/total-supply-hand-written/Cargo.toml similarity index 100% rename from pvq-program/examples/total-supply-hand-written/Cargo.toml rename to guest-examples/total-supply-hand-written/Cargo.toml diff --git a/pvq-program/examples/total-supply-hand-written/src/main.rs b/guest-examples/total-supply-hand-written/src/main.rs similarity index 100% rename from pvq-program/examples/total-supply-hand-written/src/main.rs rename to guest-examples/total-supply-hand-written/src/main.rs diff --git a/pvq-program/examples/total-supply/Cargo.toml b/guest-examples/total-supply/Cargo.toml similarity index 100% rename from pvq-program/examples/total-supply/Cargo.toml rename to guest-examples/total-supply/Cargo.toml diff --git a/pvq-program/examples/total-supply/src/main.rs b/guest-examples/total-supply/src/main.rs similarity index 100% rename from pvq-program/examples/total-supply/src/main.rs rename to guest-examples/total-supply/src/main.rs diff --git a/pvq-program/examples/transparent-call-hand-written/Cargo.toml b/guest-examples/transparent-call-hand-written/Cargo.toml similarity index 100% rename from pvq-program/examples/transparent-call-hand-written/Cargo.toml rename to guest-examples/transparent-call-hand-written/Cargo.toml diff --git a/pvq-program/examples/transparent-call-hand-written/src/main.rs b/guest-examples/transparent-call-hand-written/src/main.rs similarity index 100% rename from pvq-program/examples/transparent-call-hand-written/src/main.rs rename to guest-examples/transparent-call-hand-written/src/main.rs diff --git a/poc/runtime/src/pvq.rs b/poc/runtime/src/pvq.rs index bff69ae..9dd00d1 100644 --- a/poc/runtime/src/pvq.rs +++ b/poc/runtime/src/pvq.rs @@ -50,83 +50,3 @@ pub fn execute_query(query: &[u8], input: &[u8]) -> PvqResult { pub fn metadata() -> Metadata { extensions::metadata() } - -#[cfg(test)] -mod tests { - - use super::*; - use crate::interface::{AccountId, AssetId}; - use frame::deps::codec::{Decode, Encode}; - use frame::deps::sp_core::{sr25519, Pair}; - - #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] - enum FungiblesMethod { - TotalSupply { asset: AssetId }, - Balance { asset: AssetId, who: AccountId }, - } - - #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] - enum CoreMethod { - HasExtension { id: u64 }, - } - #[test] - fn call_transparent_data_hex() { - let raw_blob = include_bytes!("../../../output/poc-guest-transparent-call.polkavm"); - // call fungible extension - let mut data = pvq_extension_fungibles::EXTENSION_ID.encode(); - let method = FungiblesMethod::TotalSupply { asset: 21 }; - data.extend_from_slice(&method.encode()); - dbg!(hex::encode((raw_blob.to_vec(), data).encode())); - } - - #[test] - fn call_fungibles_hex() { - let raw_blob = include_bytes!("../../../output/poc-guest-sum-balance.polkavm"); - let alice_public = sr25519::Pair::from_string("//Alice", None) - .expect("static values are valid; qed") - .public(); - let alice_account = AccountId::from(alice_public); - // query num - let mut data = pvq_extension_fungibles::EXTENSION_ID.encode(); - data.extend_from_slice(&vec![2u8]); - let method1 = FungiblesMethod::Balance { - asset: 21, - who: alice_account.clone().into(), - }; - let method1_encoded = method1.encode(); - data.extend_from_slice(&vec![method1_encoded.len() as u8]); - let method2 = FungiblesMethod::Balance { - asset: 1984, - who: alice_account.into(), - }; - let method2_encoded = method2.encode(); - data.extend_from_slice(&method1_encoded); - data.extend_from_slice(&method2_encoded); - dbg!(hex::encode((raw_blob.to_vec(), data).encode())); - } - - #[test] - fn check_supply() { - let bytes = hex::decode("2000ca9a3b00000000").unwrap(); - let decoded_bytes = Vec::::decode(&mut &bytes[..]).unwrap(); - let balance = u64::decode(&mut &decoded_bytes[..]).unwrap(); - assert_eq!(balance, 1_000_000_000); - } - - #[test] - fn check_balance_sum() { - let bytes = hex::decode("200094357700000000").unwrap(); - let decoded_bytes = Vec::::decode(&mut &bytes[..]).unwrap(); - let balance = u64::decode(&mut &decoded_bytes[..]).unwrap(); - assert_eq!(balance, 2_000_000_000); - } - - #[test] - fn check_bool() { - // paste from e2e result - let bytes = hex::decode("0401").unwrap(); - let decoded_bytes = Vec::::decode(&mut &bytes[..]).unwrap(); - let true_value = bool::decode(&mut &decoded_bytes[..]).unwrap(); - assert!(true_value); - } -} diff --git a/pvq-extension/tests/lib.rs b/pvq-extension/tests/lib.rs deleted file mode 100644 index a863114..0000000 --- a/pvq-extension/tests/lib.rs +++ /dev/null @@ -1,186 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -mod extension_core { - use parity_scale_codec::{Codec, Decode, Encode}; - use pvq_extension::{DispatchError, Dispatchable, ExtensionId, ExtensionIdTy}; - - pub trait ExtensionCore { - type ExtensionId: Codec + scale_info::TypeInfo + 'static; - fn has_extension(id: Self::ExtensionId) -> bool; - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] - #[allow(non_camel_case_types)] - pub enum Functions { - has_extension { - id: Impl::ExtensionId, - }, - #[doc(hidden)] - __marker(core::marker::PhantomData), - } - - impl Dispatchable for Functions { - fn dispatch(self) -> Result, DispatchError> { - match self { - Functions::has_extension { id } => Ok(Impl::has_extension(id).encode()), - Functions::__marker(_) => Err(DispatchError::PhantomData), - } - } - } - - impl ExtensionId for Functions { - const EXTENSION_ID: ExtensionIdTy = 0u64; - } - - pub fn metadata() -> pvq_extension::metadata::ExtensionMetadata { - pvq_extension::metadata::ExtensionMetadata { - name: "ExtensionCore", - functions: vec![pvq_extension::metadata::FunctionMetadata { - name: "has_extension", - inputs: vec![pvq_extension::metadata::FunctionParamMetadata { - name: "id", - ty: scale_info::meta_type::(), - }], - output: scale_info::meta_type::(), - }], - } - } -} - -mod extension_fungibles { - use parity_scale_codec::{Codec, Decode, Encode}; - use pvq_extension::{DispatchError, Dispatchable, ExtensionId, ExtensionIdTy}; - - pub trait ExtensionFungibles { - type AssetId: Codec + scale_info::TypeInfo + 'static; - type AccountId: Codec + scale_info::TypeInfo + 'static; - type Balance: Codec + scale_info::TypeInfo + 'static; - fn total_supply(asset: Self::AssetId) -> Self::Balance; - fn balance(asset: Self::AssetId, who: Self::AccountId) -> Self::Balance; - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] - #[allow(non_camel_case_types)] - pub enum Functions { - total_supply { - asset: Impl::AssetId, - }, - balance { - asset: Impl::AssetId, - who: Impl::AccountId, - }, - #[doc(hidden)] - __marker(core::marker::PhantomData), - } - - impl Dispatchable for Functions { - fn dispatch(self) -> Result, DispatchError> { - match self { - Functions::total_supply { asset } => Ok(Impl::total_supply(asset).encode()), - Functions::balance { asset, who } => Ok(Impl::balance(asset, who).encode()), - Functions::__marker(_) => Err(DispatchError::PhantomData), - } - } - } - - impl ExtensionId for Functions { - const EXTENSION_ID: ExtensionIdTy = 1u64; - } - - pub fn metadata() -> pvq_extension::metadata::ExtensionMetadata { - pvq_extension::metadata::ExtensionMetadata { - name: "ExtensionFungibles", - functions: vec![ - pvq_extension::metadata::FunctionMetadata { - name: "total_supply", - inputs: vec![pvq_extension::metadata::FunctionParamMetadata { - name: "asset", - ty: scale_info::meta_type::(), - }], - output: scale_info::meta_type::(), - }, - pvq_extension::metadata::FunctionMetadata { - name: "balance", - inputs: vec![ - pvq_extension::metadata::FunctionParamMetadata { - name: "asset", - ty: scale_info::meta_type::(), - }, - pvq_extension::metadata::FunctionParamMetadata { - name: "who", - ty: scale_info::meta_type::(), - }, - ], - output: scale_info::meta_type::(), - }, - ], - } - } -} - -mod extensions_impl { - pub struct ExtensionsImpl; - use super::*; - - impl extension_core::ExtensionCore for ExtensionsImpl { - type ExtensionId = u64; - fn has_extension(id: u64) -> bool { - matches!(id, 0 | 1) - } - } - - impl extension_fungibles::ExtensionFungibles for ExtensionsImpl { - type AssetId = u32; - type AccountId = [u8; 32]; - type Balance = u64; - fn total_supply(asset: u32) -> u64 { - 200 - } - fn balance(asset: u32, who: [u8; 32]) -> u64 { - 100 - } - } - - pub type Extensions = ( - extension_core::Functions, - extension_fungibles::Functions, - ); - - pub fn metadata() -> pvq_extension::metadata::Metadata { - pvq_extension::metadata::Metadata::new(vec![ - extension_core::metadata::(), - extension_fungibles::metadata::(), - ]) - } -} - -mod tests { - use super::*; - use parity_scale_codec::Encode; - use pvq_extension::{ExtensionsExecutor, InvokeSource}; - use tracing_subscriber::prelude::*; - - #[test] - fn test_runtime_executor() { - let registry = tracing_subscriber::registry(); - - let filter = tracing_subscriber::EnvFilter::builder() - .with_default_directive(tracing::Level::DEBUG.into()) - .from_env_lossy(); - - registry - .with(tracing_subscriber::fmt::layer().with_filter(filter)) - .try_init() - .expect("Failed to initialize tracing"); - - let mut executor = ExtensionsExecutor::::new(InvokeSource::Runtime); - let program_blob = include_bytes!("../../output/poc-guest-transparent-call.polkavm").to_vec(); - let mut args = vec![]; - args.extend(1u64.to_le_bytes()); - extension_fungibles::Functions::::total_supply { asset: 1u32 } - .encode_to(&mut args); - let res = executor.execute_method(&program_blob, &args, 0); - println!("res: {:?}", res); - assert_eq!(res, Ok(200u64.to_le_bytes().to_vec())); - } -} diff --git a/pvq-program/procedural/Cargo.toml b/pvq-program/procedural/Cargo.toml index 7eb7cf2..95c2e8a 100644 --- a/pvq-program/procedural/Cargo.toml +++ b/pvq-program/procedural/Cargo.toml @@ -11,10 +11,3 @@ quote = { workspace = true } syn = { workspace = true } proc-macro2 = { workspace = true } proc-macro-crate = { workspace = true } -Inflector = { workspace = true } - -[dev-dependencies] -parity-scale-codec = { workspace = true } -scale-info = { workspace = true } -polkavm-derive = { workspace = true } -trybuild = { workspace = true } diff --git a/pvq-program/procedural/tests/tests.rs b/pvq-program/procedural/tests/tests.rs deleted file mode 100644 index 622715a..0000000 --- a/pvq-program/procedural/tests/tests.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[test] -fn test_macros() { - let t = trybuild::TestCases::new(); - // Test successful cases - t.pass("tests/ui/*.rs"); - - // Test failing cases - t.compile_fail("tests/ui/fail/*.rs"); -} diff --git a/pvq-program/procedural/tests/ui/sum-balance.rs b/pvq-program/procedural/tests/ui/sum-balance.rs deleted file mode 100644 index d3ec35d..0000000 --- a/pvq-program/procedural/tests/ui/sum-balance.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![no_std] -#![no_main] - -#[pvq_program_procedural::program] -mod sum_balance { - type AccountId = [u8; 32]; - type AssetId = u32; - type Balance = u64; - - #[program::extension_fn(extension_id = 4071833530116166512u64, fn_index = 1)] - fn balance(asset: AssetId, who: AccountId) -> Balance {} - - #[program::entrypoint] - fn sum_balance(asset: AssetId, accounts: alloc::vec::Vec) -> Balance { - let mut sum = 0; - for account in accounts { - sum += balance(asset, account); - } - sum - } -} diff --git a/pvq-program/rust-toolchain.toml b/pvq-program/rust-toolchain.toml deleted file mode 100644 index 303a7f4..0000000 --- a/pvq-program/rust-toolchain.toml +++ /dev/null @@ -1,3 +0,0 @@ -[toolchain] -channel = "nightly-2024-11-01" -components = ["rust-src"] From 482f83f00f4188cb4422d22b793e8d8c5c174899 Mon Sep 17 00:00:00 2001 From: indirection42 Date: Tue, 18 Mar 2025 14:45:38 +0800 Subject: [PATCH 3/4] fix build --- guest-examples/rust-toolchain.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 guest-examples/rust-toolchain.toml diff --git a/guest-examples/rust-toolchain.toml b/guest-examples/rust-toolchain.toml new file mode 100644 index 0000000..303a7f4 --- /dev/null +++ b/guest-examples/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2024-11-01" +components = ["rust-src"] From 6e4ba2a058d6d1c604dc68aa6e7a44ff94732a46 Mon Sep 17 00:00:00 2001 From: indirection42 Date: Tue, 18 Mar 2025 14:51:27 +0800 Subject: [PATCH 4/4] fix ci --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4591fc9..e656c8c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,6 @@ jobs: - name: Install toolchain uses: dtolnay/rust-toolchain@stable with: - targets: wasm32-unknown-unknown, riscv32emac-unknown-none-polkavm components: rust-src, rustfmt, clippy - uses: Swatinem/rust-cache@v2