From 1efa3234e9fd11a58bdcd217039f277e705d195e Mon Sep 17 00:00:00 2001 From: Jiyuan Zheng Date: Fri, 23 Aug 2024 10:01:14 +0800 Subject: [PATCH] Feat(xcq-api): basic support (#40) --- Cargo.lock | 14 ++ Makefile | 7 +- poc/guests/Cargo.lock | 145 +++++++++++++- poc/guests/Cargo.toml | 8 +- .../query-balance-fungibles/src/main.rs | 41 ---- poc/guests/query-balance/src/main.rs | 35 ---- .../Cargo.toml | 3 +- poc/guests/sum-balance-percent/src/main.rs | 21 ++ .../Cargo.toml | 3 +- poc/guests/sum-balance/src/main.rs | 19 ++ poc/guests/total-supply/Cargo.toml | 10 + poc/guests/total-supply/src/main.rs | 14 ++ poc/guests/transparent-call/Cargo.toml | 1 + poc/guests/transparent-call/src/main.rs | 4 +- poc/runtime/src/xcq.rs | 9 +- xcq-api/Cargo.toml | 1 + xcq-api/procedural/Cargo.toml | 14 ++ xcq-api/procedural/src/lib.rs | 23 +++ xcq-api/procedural/src/program/expand/mod.rs | 182 ++++++++++++++++++ xcq-api/procedural/src/program/mod.rs | 13 ++ xcq-api/procedural/src/program/parse/call.rs | 31 +++ .../src/program/parse/entrypoint.rs | 73 +++++++ .../procedural/src/program/parse/helper.rs | 83 ++++++++ xcq-api/procedural/src/program/parse/mod.rs | 125 ++++++++++++ xcq-api/src/lib.rs | 16 +- xcq-executor/src/lib.rs | 1 + xcq-extension/src/lib.rs | 7 +- .../tests/with_associated_types_works.rs | 59 +++++- xcq-test-runner/src/main.rs | 36 ++-- 29 files changed, 872 insertions(+), 126 deletions(-) delete mode 100644 poc/guests/query-balance-fungibles/src/main.rs delete mode 100644 poc/guests/query-balance/src/main.rs rename poc/guests/{query-balance => sum-balance-percent}/Cargo.toml (62%) create mode 100644 poc/guests/sum-balance-percent/src/main.rs rename poc/guests/{query-balance-fungibles => sum-balance}/Cargo.toml (64%) create mode 100644 poc/guests/sum-balance/src/main.rs create mode 100644 poc/guests/total-supply/Cargo.toml create mode 100644 poc/guests/total-supply/src/main.rs create mode 100644 xcq-api/procedural/Cargo.toml create mode 100644 xcq-api/procedural/src/lib.rs create mode 100644 xcq-api/procedural/src/program/expand/mod.rs create mode 100644 xcq-api/procedural/src/program/mod.rs create mode 100644 xcq-api/procedural/src/program/parse/call.rs create mode 100644 xcq-api/procedural/src/program/parse/entrypoint.rs create mode 100644 xcq-api/procedural/src/program/parse/helper.rs create mode 100644 xcq-api/procedural/src/program/parse/mod.rs diff --git a/Cargo.lock b/Cargo.lock index e07ab91..137a599 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4449,6 +4449,20 @@ dependencies = [ [[package]] name = "xcq-api" version = "0.1.0" +dependencies = [ + "xcq-api-procedural", +] + +[[package]] +name = "xcq-api-procedural" +version = "0.1.0" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.66", +] [[package]] name = "xcq-executor" diff --git a/Makefile b/Makefile index 61960e3..1104669 100644 --- a/Makefile +++ b/Makefile @@ -3,12 +3,9 @@ GUEST_RUST_FLAGS="-C relocation-model=pie -C link-arg=--emit-relocs -C link-arg= run: chainspec bunx @acala-network/chopsticks@latest --config poc/runtime/chopsticks.yml --genesis output/chainspec.json -poc-host-%: poc-guest-% - RUST_LOG=trace cargo run -p poc-host-$* +poc-guests: poc-guest-sum-balance poc-guest-sum-balance-percent poc-guest-total-supply poc-guest-transparent-call -poc-guests: poc-guest-query-balance poc-guest-query-balance-fungibles poc-guest-transparent-call - -dummy-poc-guests: dummy-poc-guest-query-balance dummy-poc-guest-query-balance-fungibles dummy-poc-guest-transparent-call +dummy-poc-guests: dummy-poc-guest-sum-balance dummy-poc-guest-sum-balance-percent dummy-poc-guest-total-supply dummy-poc-guest-transparent-call poc-guest-%: cd poc/guests; RUSTFLAGS=$(GUEST_RUST_FLAGS) cargo build -q --release --bin poc-guest-$* -p poc-guest-$* diff --git a/poc/guests/Cargo.lock b/poc/guests/Cargo.lock index 3dc4aac..3479655 100644 --- a/poc/guests/Cargo.lock +++ b/poc/guests/Cargo.lock @@ -3,24 +3,80 @@ version = 3 [[package]] -name = "poc-guest-pass-custom-type" +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 = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "indexmap" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +dependencies = [ + "equivalent", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "poc-guest-sum-balance" version = "0.1.0" dependencies = [ "polkavm-derive", + "xcq-api", ] [[package]] -name = "poc-guest-query-balance" +name = "poc-guest-sum-balance-percent" version = "0.1.0" dependencies = [ "polkavm-derive", + "xcq-api", ] [[package]] -name = "poc-guest-query-balance-fungibles" +name = "poc-guest-total-supply" version = "0.1.0" dependencies = [ "polkavm-derive", + "xcq-api", ] [[package]] @@ -28,6 +84,7 @@ name = "poc-guest-transparent-call" version = "0.1.0" dependencies = [ "polkavm-derive", + "xcq-api", ] [[package]] @@ -59,6 +116,15 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.82" @@ -77,6 +143,35 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + [[package]] name = "syn" version = "2.0.63" @@ -88,8 +183,52 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "xcq-api" +version = "0.1.0" +dependencies = [ + "xcq-api-procedural", +] + +[[package]] +name = "xcq-api-procedural" +version = "0.1.0" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] diff --git a/poc/guests/Cargo.toml b/poc/guests/Cargo.toml index 21e969a..dc42014 100644 --- a/poc/guests/Cargo.toml +++ b/poc/guests/Cargo.toml @@ -1,7 +1,13 @@ [workspace] -members = ["query-balance", "query-balance-fungibles", "transparent-call"] +members = [ + "sum-balance", + "sum-balance-percent", + "total-supply", + "transparent-call", +] resolver = "2" [workspace.dependencies] polkavm-derive = { path = "../../vendor/polkavm/crates/polkavm-derive" } +xcq-api = { path = "../../xcq-api", default-features = false } diff --git a/poc/guests/query-balance-fungibles/src/main.rs b/poc/guests/query-balance-fungibles/src/main.rs deleted file mode 100644 index b364657..0000000 --- a/poc/guests/query-balance-fungibles/src/main.rs +++ /dev/null @@ -1,41 +0,0 @@ -#![no_std] -#![no_main] - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - unsafe { - core::arch::asm!("unimp", options(noreturn)); - } -} - -#[polkavm_derive::polkavm_import] -extern "C" { - fn call(extension_id: u64, call_ptr: u32, call_len: u32) -> u64; -} - -#[polkavm_derive::polkavm_export] -extern "C" fn main(ptr: u32, size: u32) -> u64 { - // no variant for this input, since the return type is same for total_supply/balance - let extension_id = unsafe { core::ptr::read_volatile(ptr as *const u64) }; - let num_query = unsafe { core::ptr::read_volatile((ptr + 8) as *const u8) }; - let query_size = (size - 9) / num_query as u32; - let mut sum = 0u64; - // in this settings, the return type is same for total_supply/balance - // otherwise, we need to recognize return type through input data - for i in 0..num_query { - let res = unsafe { call(extension_id, ptr + 9 + query_size * i as u32, query_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) }; - sum += u64::from_le_bytes(res_bytes.try_into().unwrap()); - } - let sum_bytes = sum.to_le_bytes(); - let ptr = polkavm_derive::sbrk(sum_bytes.len()); - if ptr.is_null() { - return 0; - } - unsafe { - core::ptr::copy_nonoverlapping(sum_bytes.as_ptr(), ptr, sum_bytes.len()); - } - (sum_bytes.len() as u64) << 32 | (ptr as u64) -} diff --git a/poc/guests/query-balance/src/main.rs b/poc/guests/query-balance/src/main.rs deleted file mode 100644 index e42a6c8..0000000 --- a/poc/guests/query-balance/src/main.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![no_std] -#![no_main] - -#[panic_handler] -fn panic(_info: &core::panic::PanicInfo) -> ! { - unsafe { - core::arch::asm!("unimp", options(noreturn)); - } -} - -#[polkavm_derive::polkavm_import] -extern "C" { - fn query_balance(variant: u32, account_id_ptr: u32, account_id_size: u32) -> u64; -} -// return value is u64 instead of (u32, u32) due to https://github.com/koute/polkavm/issues/116 -// higher 32bits are address, lower 32bits are size -#[polkavm_derive::polkavm_export] -extern "C" fn main(ptr: u32, size: u32) -> u64 { - // ready first byte from ptr - let mut sum = 0u64; - let variant = unsafe { core::ptr::read_volatile(ptr as *const u8) }; - // hardcode since we know account_id_num - let account_id_size = (size - 1) / 2; - for i in 0..2 { - sum += unsafe { query_balance(variant as u32, ptr + 1 + (account_id_size * i), account_id_size) }; - } - let ptr = polkavm_derive::sbrk(core::mem::size_of_val(&sum)); - if ptr.is_null() { - return 0; - } - unsafe { - core::ptr::write_volatile(ptr as *mut u64, sum); - } - (core::mem::size_of::() as u64) << 32 | (ptr as u64) -} diff --git a/poc/guests/query-balance/Cargo.toml b/poc/guests/sum-balance-percent/Cargo.toml similarity index 62% rename from poc/guests/query-balance/Cargo.toml rename to poc/guests/sum-balance-percent/Cargo.toml index 0db5430..1749879 100644 --- a/poc/guests/query-balance/Cargo.toml +++ b/poc/guests/sum-balance-percent/Cargo.toml @@ -1,8 +1,9 @@ [package] -name = "poc-guest-query-balance" +name = "poc-guest-sum-balance-percent" version = "0.1.0" edition = "2021" publish = false [dependencies] polkavm-derive = { workspace = true } +xcq-api = { workspace = true } diff --git a/poc/guests/sum-balance-percent/src/main.rs b/poc/guests/sum-balance-percent/src/main.rs new file mode 100644 index 0000000..9037ab7 --- /dev/null +++ b/poc/guests/sum-balance-percent/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] +#[global_allocator] +static GLOBAL: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; +use alloc::vec::Vec; +#[xcq_api::program] +mod sum_balance { + #[xcq::call_def] + fn balance(asset: u32, who: [u8; 32]) -> u64 {} + #[xcq::call_def] + fn total_supply(asset: u32) -> u64 {} + + #[xcq::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/query-balance-fungibles/Cargo.toml b/poc/guests/sum-balance/Cargo.toml similarity index 64% rename from poc/guests/query-balance-fungibles/Cargo.toml rename to poc/guests/sum-balance/Cargo.toml index 4f513d9..92f4c7b 100644 --- a/poc/guests/query-balance-fungibles/Cargo.toml +++ b/poc/guests/sum-balance/Cargo.toml @@ -1,8 +1,9 @@ [package] -name = "poc-guest-query-balance-fungibles" +name = "poc-guest-sum-balance" version = "0.1.0" edition = "2021" publish = false [dependencies] polkavm-derive = { workspace = true } +xcq-api = { workspace = true } diff --git a/poc/guests/sum-balance/src/main.rs b/poc/guests/sum-balance/src/main.rs new file mode 100644 index 0000000..7cd7810 --- /dev/null +++ b/poc/guests/sum-balance/src/main.rs @@ -0,0 +1,19 @@ +#![no_std] +#![no_main] +#[global_allocator] +static GLOBAL: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; +use alloc::vec::Vec; +#[xcq_api::program] +mod sum_balance { + #[xcq::call_def] + fn balance(asset: u32, who: [u8; 32]) -> u64 {} + + #[xcq::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/Cargo.toml b/poc/guests/total-supply/Cargo.toml new file mode 100644 index 0000000..cf8dcbe --- /dev/null +++ b/poc/guests/total-supply/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "poc-guest-total-supply" +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 } +xcq-api = { workspace = true } diff --git a/poc/guests/total-supply/src/main.rs b/poc/guests/total-supply/src/main.rs new file mode 100644 index 0000000..908d6a7 --- /dev/null +++ b/poc/guests/total-supply/src/main.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] +#[global_allocator] +static GLOBAL: polkavm_derive::LeakingAllocator = polkavm_derive::LeakingAllocator; +#[xcq_api::program] +mod query_total_supply { + #[xcq::call_def] + fn total_supply(asset: u32) -> u64 {} + + #[xcq::entrypoint] + fn get_total_supply(call: TotalSupplyCall) -> u64 { + call.call() + } +} diff --git a/poc/guests/transparent-call/Cargo.toml b/poc/guests/transparent-call/Cargo.toml index 43de049..2664683 100644 --- a/poc/guests/transparent-call/Cargo.toml +++ b/poc/guests/transparent-call/Cargo.toml @@ -6,3 +6,4 @@ publish = false [dependencies] polkavm-derive = { workspace = true } +xcq-api = { workspace = true } diff --git a/poc/guests/transparent-call/src/main.rs b/poc/guests/transparent-call/src/main.rs index c05171a..b6760bd 100644 --- a/poc/guests/transparent-call/src/main.rs +++ b/poc/guests/transparent-call/src/main.rs @@ -10,11 +10,11 @@ fn panic(_info: &core::panic::PanicInfo) -> ! { #[polkavm_derive::polkavm_import] extern "C" { - fn call(extension_id: u64, call_ptr: u32, call_len: u32) -> u64; + fn host_call(extension_id: u64, call_ptr: u32, call_len: u32) -> u64; } #[polkavm_derive::polkavm_export] extern "C" fn main(ptr: u32, size: u32) -> u64 { let extension_id = unsafe { core::ptr::read_volatile(ptr as *const u64) }; - unsafe { call(extension_id, ptr + 8, size - 8) } + unsafe { host_call(extension_id, ptr + 8, size - 8) } } diff --git a/poc/runtime/src/xcq.rs b/poc/runtime/src/xcq.rs index 6d77248..7d2efcd 100644 --- a/poc/runtime/src/xcq.rs +++ b/poc/runtime/src/xcq.rs @@ -119,7 +119,7 @@ mod tests { #[test] fn call_fungibles_hex() { - let raw_blob = include_bytes!("../../../output/poc-guest-query-balance-fungibles.polkavm"); + let raw_blob = include_bytes!("../../../output/poc-guest-test-xcq-api.polkavm"); let alice_public = sr25519::Pair::from_string("//Alice", None) .expect("static values are valid; qed") .public(); @@ -131,12 +131,15 @@ mod tests { 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(), }; - data.extend_from_slice(&method1.encode()); - data.extend_from_slice(&method2.encode()); + 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())); } diff --git a/xcq-api/Cargo.toml b/xcq-api/Cargo.toml index 31bf03f..3f61d1c 100644 --- a/xcq-api/Cargo.toml +++ b/xcq-api/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true version.workspace = true [dependencies] +xcq-api-procedural = { path = "procedural" } [features] default = ["std"] diff --git a/xcq-api/procedural/Cargo.toml b/xcq-api/procedural/Cargo.toml new file mode 100644 index 0000000..e65d246 --- /dev/null +++ b/xcq-api/procedural/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xcq-api-procedural" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = { workspace = true } +syn = { workspace = true } +proc-macro2 = { workspace = true } +proc-macro-crate = { workspace = true } +Inflector = { workspace = true } diff --git a/xcq-api/procedural/src/lib.rs b/xcq-api/procedural/src/lib.rs new file mode 100644 index 0000000..81ccafb --- /dev/null +++ b/xcq-api/procedural/src/lib.rs @@ -0,0 +1,23 @@ +/// Declare the calls used in XCQ program +/// #[xcq::program] +/// mod query_fungibles { +/// #[xcq::call(extern_types = [AssetId, AccountId, Balance]])]] +/// fn balance(asset: AssetId, who: AccountId) -> Balance; +/// +/// #[xcq(entrypoint)] +/// fn sum_balance(calls: Vec) -> u64 { +/// let mut sum = 0; +/// for call in calls { +/// 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/xcq-api/procedural/src/program/expand/mod.rs b/xcq-api/procedural/src/program/expand/mod.rs new file mode 100644 index 0000000..1eddf8f --- /dev/null +++ b/xcq-api/procedural/src/program/expand/mod.rs @@ -0,0 +1,182 @@ +use super::{Def, EntrypointDef}; +use inflector::Inflector; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{ItemFn, 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 = &def.entrypoint.item_fn; + let main_fn = generate_main(&def.entrypoint)?; + Ok(quote! { + #preludes + #entrypoint_def + #(#host_calls)* + #main_fn + }) +} + +// At guest side, we only need call_ptr and size to perform call, +// the actual function signature is used at host side to construct the call data +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); + 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) +} +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 ptr = polkavm_derive::sbrk(res_bytes.len()); + if ptr.is_null(){ + return 0; + } + unsafe { + core::ptr::copy_nonoverlapping(res_bytes.as_ptr(),ptr,res_bytes.len()); + } + (res_bytes.len() as u64) << 32 | (ptr as u64) + } +} + +fn generate_main(entrypoint: &EntrypointDef) -> Result { + let move_to_stack = quote! { + let arg_bytes = unsafe {alloc::vec::Vec::from_raw_parts(ptr as *mut u8, size as usize, size as usize)}; + let mut arg_ptr = arg_bytes.as_ptr() as u32; + }; + // 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! { + 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(ptr: u32, size:u32) -> u64 { + #move_to_stack + #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; + } + }; + + quote! { + + #extern_crate + + #panic_fn + + #host_call_fn + } +} diff --git a/xcq-api/procedural/src/program/mod.rs b/xcq-api/procedural/src/program/mod.rs new file mode 100644 index 0000000..8c4fab9 --- /dev/null +++ b/xcq-api/procedural/src/program/mod.rs @@ -0,0 +1,13 @@ +use proc_macro::TokenStream; +use syn::{parse_macro_input, ItemMod}; +mod expand; +mod parse; +pub use parse::{Def, EntrypointDef}; + +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(), + Err(e) => e.to_compile_error().into(), + } +} diff --git a/xcq-api/procedural/src/program/parse/call.rs b/xcq-api/procedural/src/program/parse/call.rs new file mode 100644 index 0000000..002d59e --- /dev/null +++ b/xcq-api/procedural/src/program/parse/call.rs @@ -0,0 +1,31 @@ +use super::ExternTypesAttr; +use proc_macro2::Span; +use syn::spanned::Spanned; +use syn::{Item, ItemFn}; + +#[derive(Debug, Clone)] +pub struct CallDef { + pub index: usize, + pub item_fn: ItemFn, + pub extern_types: Option, +} + +impl CallDef { + pub fn try_from( + _span: Span, + index: usize, + item: &mut Item, + extern_types: Option, + ) -> syn::Result { + let item_fn = if let Item::Fn(item_fn) = item { + item_fn + } else { + return Err(syn::Error::new(item.span(), "Invalid xcq::call, expected item fn")); + }; + Ok(Self { + index, + item_fn: item_fn.clone(), + extern_types, + }) + } +} diff --git a/xcq-api/procedural/src/program/parse/entrypoint.rs b/xcq-api/procedural/src/program/parse/entrypoint.rs new file mode 100644 index 0000000..6317dab --- /dev/null +++ b/xcq-api/procedural/src/program/parse/entrypoint.rs @@ -0,0 +1,73 @@ +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, _index: usize, 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 xcq::entrypoint, expected fn to have typed arguments", + )); + } + } + Ok(Self { + item_fn: item_fn.clone(), + arg_types, + }) + } else { + Err(syn::Error::new( + item.span(), + "Invalid xcq::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/xcq-api/procedural/src/program/parse/helper.rs b/xcq-api/procedural/src/program/parse/helper.rs new file mode 100644 index 0000000..2f22194 --- /dev/null +++ b/xcq-api/procedural/src/program/parse/helper.rs @@ -0,0 +1,83 @@ +use quote::ToTokens; +pub trait MutItemAttrs { + fn mut_item_attrs(&mut self) -> Option<&mut Vec>; +} +/// Take the first pallet attribute (e.g. attribute like `#[xcq..]`) and decode it to `Attr` +pub(crate) fn take_first_item_xcq_attr(item: &mut impl MutItemAttrs) -> syn::Result> +where + Attr: syn::parse::Parse, +{ + let Some(attrs) = item.mut_item_attrs() else { + return Ok(None); + }; + + let Some(index) = attrs.iter().position(|attr| { + attr.path() + .segments + .first() + .map_or(false, |segment| segment.ident == "xcq") + }) else { + return Ok(None); + }; + + let xcq_attr = attrs.remove(index); + Ok(Some(syn::parse2(xcq_attr.into_token_stream())?)) +} +impl MutItemAttrs for syn::Item { + fn mut_item_attrs(&mut self) -> Option<&mut Vec> { + match self { + Self::Const(item) => Some(item.attrs.as_mut()), + Self::Enum(item) => Some(item.attrs.as_mut()), + Self::ExternCrate(item) => Some(item.attrs.as_mut()), + Self::Fn(item) => Some(item.attrs.as_mut()), + Self::ForeignMod(item) => Some(item.attrs.as_mut()), + Self::Impl(item) => Some(item.attrs.as_mut()), + Self::Macro(item) => Some(item.attrs.as_mut()), + Self::Mod(item) => Some(item.attrs.as_mut()), + Self::Static(item) => Some(item.attrs.as_mut()), + Self::Struct(item) => Some(item.attrs.as_mut()), + Self::Trait(item) => Some(item.attrs.as_mut()), + Self::TraitAlias(item) => Some(item.attrs.as_mut()), + Self::Type(item) => Some(item.attrs.as_mut()), + Self::Union(item) => Some(item.attrs.as_mut()), + Self::Use(item) => Some(item.attrs.as_mut()), + _ => None, + } + } +} + +impl MutItemAttrs for syn::TraitItem { + fn mut_item_attrs(&mut self) -> Option<&mut Vec> { + match self { + Self::Const(item) => Some(item.attrs.as_mut()), + Self::Fn(item) => Some(item.attrs.as_mut()), + Self::Type(item) => Some(item.attrs.as_mut()), + Self::Macro(item) => Some(item.attrs.as_mut()), + _ => None, + } + } +} + +impl MutItemAttrs for Vec { + fn mut_item_attrs(&mut self) -> Option<&mut Vec> { + Some(self) + } +} + +impl MutItemAttrs for syn::ItemMod { + fn mut_item_attrs(&mut self) -> Option<&mut Vec> { + Some(&mut self.attrs) + } +} + +impl MutItemAttrs for syn::ImplItemFn { + fn mut_item_attrs(&mut self) -> Option<&mut Vec> { + Some(&mut self.attrs) + } +} + +impl MutItemAttrs for syn::ItemType { + fn mut_item_attrs(&mut self) -> Option<&mut Vec> { + Some(&mut self.attrs) + } +} diff --git a/xcq-api/procedural/src/program/parse/mod.rs b/xcq-api/procedural/src/program/parse/mod.rs new file mode 100644 index 0000000..2e2129e --- /dev/null +++ b/xcq-api/procedural/src/program/parse/mod.rs @@ -0,0 +1,125 @@ +use syn::spanned::Spanned; +use syn::{Error, ItemMod, Result}; +mod call; +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 = "Invalid pallet definition, expected mod to be inlined."; + syn::Error::new(mod_span, msg) + })? + .1; + + let mut calls = Vec::new(); + let mut entrypoint = None; + + for (index, item) in items.iter_mut().enumerate() { + let xcq_attr: Option = helper::take_first_item_xcq_attr(item)?; + + match xcq_attr { + Some(XcqAttr::CallDef(span, extern_types)) => { + calls.push(call::CallDef::try_from(span, index, item, extern_types)?); + } + Some(XcqAttr::Entrypoint(span)) => { + if entrypoint.is_some() { + return Err(Error::new(span, "Only one entrypoint function is allowed")); + } + let e = entrypoint::EntrypointDef::try_from(span, index, item)?; + entrypoint = Some(e); + } + None => { + return Err(Error::new( + item.span(), + "Invalid attribute, expected `#[xcq::call_def]` or `#[xcq::entrypoint]`", + )); + } + } + } + let entrypoint = match entrypoint { + Some(entrypoint) => entrypoint, + None => { + return Err(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!(xcq); + syn::custom_keyword!(call_def); + syn::custom_keyword!(extern_types); + syn::custom_keyword!(entrypoint); +} +enum XcqAttr { + CallDef(proc_macro2::Span, Option), + Entrypoint(proc_macro2::Span), +} + +// Custom parsing for xcq attribute +impl syn::parse::Parse for XcqAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + input.parse::()?; + let content; + syn::bracketed!(content in input); + content.parse::()?; + content.parse::()?; + + let lookahead = content.lookahead1(); + if lookahead.peek(keyword::call_def) { + let span = content.parse::().expect("peeked").span(); + let extern_types = match content.is_empty() { + true => None, + false => Some(ExternTypesAttr::parse(&content)?), + }; + Ok(XcqAttr::CallDef(span, extern_types)) + } else if lookahead.peek(keyword::entrypoint) { + Ok(XcqAttr::Entrypoint(content.parse::()?.span())) + } else { + Err(lookahead.error()) + } + } +} + +#[derive(Debug, Clone)] +pub struct ExternTypesAttr { + pub types: Vec, + pub span: proc_macro2::Span, +} + +impl syn::parse::Parse for ExternTypesAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + syn::parenthesized!(content in input); + + let lookahead = content.lookahead1(); + if lookahead.peek(keyword::extern_types) { + let span = content.parse::().expect("peeked").span(); + content.parse::().expect("peeked"); + let list; + syn::bracketed!(list in content); + let types = list.parse_terminated(syn::Type::parse, syn::Token![,])?; + Ok(ExternTypesAttr { + types: types.into_iter().collect(), + span, + }) + } else { + Err(lookahead.error()) + } + } +} diff --git a/xcq-api/src/lib.rs b/xcq-api/src/lib.rs index 58456cf..414f4b7 100644 --- a/xcq-api/src/lib.rs +++ b/xcq-api/src/lib.rs @@ -1,16 +1,2 @@ #![cfg_attr(not(feature = "std"), no_std)] - -pub fn add(left: usize, right: usize) -> usize { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub use xcq_api_procedural::program; diff --git a/xcq-executor/src/lib.rs b/xcq-executor/src/lib.rs index 2d318ba..77f82c1 100644 --- a/xcq-executor/src/lib.rs +++ b/xcq-executor/src/lib.rs @@ -75,6 +75,7 @@ impl XcqExecutor { } else { 0 }; + tracing::info!("(passing args): input_ptr: {}, input_len: {:?}", input_ptr, input.len()); let res = instance.call_typed::<(u32, u32), u64>(&mut self.context, method, (input_ptr, input.len() as u32))?; let res_size = (res >> 32) as u32; diff --git a/xcq-extension/src/lib.rs b/xcq-extension/src/lib.rs index 80c93d2..6fb2ea8 100644 --- a/xcq-extension/src/lib.rs +++ b/xcq-extension/src/lib.rs @@ -53,14 +53,15 @@ impl XcqExecutorContext for Context let invoke_source = self.invoke_source; linker .func_wrap( - "call", + "host_call", move |mut caller: Caller<_>, extension_id: u64, call_ptr: u32, call_len: u32| -> u64 { // useful closure to handle early return let mut func_with_result = || -> Result { let call_bytes = caller .read_memory_into_vec(call_ptr, call_len) .map_err(|_| ExtensionError::PolkavmError)?; - tracing::trace!( + tracing::info!("(host call): call_ptr: {}, call_len: {:?}", call_ptr, call_len); + tracing::info!( "(host call): extension_id: {}, call_bytes: {:?}", extension_id, call_bytes @@ -69,7 +70,7 @@ impl XcqExecutorContext for Context return Err(ExtensionError::PermissionError); } let res_bytes = E::dispatch(extension_id, &call_bytes)?; - tracing::trace!("(host call): res_bytes: {:?}", res_bytes); + tracing::debug!("(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 diff --git a/xcq-extension/tests/with_associated_types_works.rs b/xcq-extension/tests/with_associated_types_works.rs index 3daa3ca..937379f 100644 --- a/xcq-extension/tests/with_associated_types_works.rs +++ b/xcq-extension/tests/with_associated_types_works.rs @@ -52,7 +52,7 @@ impl_extensions! { type Config = ExtensionImpl; #[allow(unused_variables)] fn total_supply(asset: ::AssetId) -> ::Balance { - 100 + 200 } #[allow(unused_variables)] fn balance(asset: ::AssetId, who: ::AccountId) -> ::Balance { @@ -120,10 +120,41 @@ fn call_core_works() { let res = executor.execute_method(guest, input).unwrap(); assert_eq!(res, vec![1]); } +#[test] +fn multi_calls_works() { + let blob = include_bytes!("../../output/poc-guest-balance-sum-percent.polkavm"); + let mut executor = ExtensionsExecutor::::new(InvokeSource::RuntimeAPI); + let guest = GuestImpl { program: blob.to_vec() }; + let mut input_data = extension_fungibles::EXTENSION_ID.encode(); + input_data.extend_from_slice(&[2u8]); + let method1 = FungiblesMethod::Balance { + asset: 1, + who: [0u8; 32], + }; + let method1_encoded = method1.encode(); + input_data.extend_from_slice(&[method1_encoded.len() as u8]); + let method2 = FungiblesMethod::Balance { + asset: 1, + who: [1u8; 32], + }; + input_data.extend_from_slice(&method1_encoded); + input_data.extend_from_slice(&method2.encode()); + input_data.extend_from_slice(&extension_fungibles::EXTENSION_ID.encode()); + let method3 = FungiblesMethod::TotalSupply { asset: 1 }; + let method3_encoded = method3.encode(); + input_data.extend_from_slice(&[method3_encoded.len() as u8]); + input_data.extend_from_slice(&method3_encoded); + 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, 0u8, 0u8, 0u8, 0u8]); +} #[test] -fn call_fungibles_works() { - let blob = include_bytes!("../../output/poc-guest-query-balance-fungibles.polkavm"); +fn calls_vec_works() { + let blob = include_bytes!("../../output/poc-guest-sum-balance.polkavm"); let mut executor = ExtensionsExecutor::::new(InvokeSource::RuntimeAPI); let guest = GuestImpl { program: blob.to_vec() }; let mut input_data = extension_fungibles::EXTENSION_ID.encode(); @@ -132,11 +163,13 @@ fn call_fungibles_works() { asset: 1, who: [0u8; 32], }; + let method1_encoded = method1.encode(); + input_data.extend_from_slice(&vec![method1_encoded.len() as u8]); let method2 = FungiblesMethod::Balance { asset: 2, who: [0u8; 32], }; - input_data.extend_from_slice(&method1.encode()); + input_data.extend_from_slice(&method1_encoded); input_data.extend_from_slice(&method2.encode()); let input = InputImpl { method: "main".to_string(), @@ -146,6 +179,24 @@ fn call_fungibles_works() { assert_eq!(res, vec![200u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]); } +#[test] +fn single_call_works() { + let blob = include_bytes!("../../output/poc-guest-total-supply.polkavm"); + let mut executor = ExtensionsExecutor::::new(InvokeSource::RuntimeAPI); + let guest = GuestImpl { program: blob.to_vec() }; + let mut input_data = extension_fungibles::EXTENSION_ID.encode(); + let method1 = FungiblesMethod::TotalSupply { asset: 1 }; + let method1_encoded = method1.encode(); + input_data.extend_from_slice(&vec![method1_encoded.len() as u8]); + input_data.extend_from_slice(&method1_encoded); + let input = InputImpl { + method: "main".to_string(), + args: input_data, + }; + let res = executor.execute_method(guest, input).unwrap(); + assert_eq!(res, vec![200u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8]); +} + #[test] fn metadata_works() { let metadata: Metadata = ExtensionImpl::runtime_metadata().into(); diff --git a/xcq-test-runner/src/main.rs b/xcq-test-runner/src/main.rs index 519f856..1b550a8 100644 --- a/xcq-test-runner/src/main.rs +++ b/xcq-test-runner/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use parity_scale_codec::{Decode, Encode}; use tracing_subscriber::prelude::*; -use xcq_extension::{impl_extensions, ExtensionId, ExtensionsExecutor, Guest, Input, InvokeSource, Method}; +use xcq_extension::{impl_extensions, ExtensionsExecutor, Guest, Input, InvokeSource, Method}; #[derive(Parser, Debug)] #[command(version, about)] @@ -25,23 +25,35 @@ fn main() { let cli = Cli::parse(); - let raw_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 guest = GuestImpl { - program: raw_blob.to_vec(), + let guest = GuestImpl { program: blob.to_vec() }; + let mut input_data = xcq_extension_fungibles::EXTENSION_ID.encode(); + input_data.extend_from_slice(&[2u8]); + let method1 = FungiblesMethod::Balance { + asset: 1, + who: [0u8; 32], }; - let method = CoreMethod::HasExtension { id: 0 }; - let mut input_data = - as ExtensionId>::EXTENSION_ID - .encode(); - input_data.extend_from_slice(&method.encode()); + let method1_encoded = method1.encode(); + input_data.extend_from_slice(&[method1_encoded.len() as u8]); + let method2 = FungiblesMethod::Balance { + asset: 1, + who: [1u8; 32], + }; + input_data.extend_from_slice(&method1_encoded); + input_data.extend_from_slice(&method2.encode()); + input_data.extend_from_slice(&xcq_extension_fungibles::EXTENSION_ID.encode()); + let method3 = FungiblesMethod::TotalSupply { asset: 1 }; + let method3_encoded = method3.encode(); + input_data.extend_from_slice(&[method3_encoded.len() as u8]); + input_data.extend_from_slice(&method3_encoded); + tracing::info!("Input data: {:?}", input_data); let input = InputImpl { method: "main".to_string(), args: input_data, }; let res = executor.execute_method(guest, input).unwrap(); + tracing::info!("Result: {:?}", res); } @@ -76,7 +88,7 @@ impl_extensions! { } #[allow(unused_variables)] fn total_supply(asset: ::AssetId) -> ::Balance { - 0 + 200 } } }