mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 13:31:10 +00:00
XCM builder pattern (#2107)
Added a proc macro to be able to write XCMs using the builder pattern.
This means we go from having to do this:
```rust
let message: Xcm<()> = Xcm(vec![
WithdrawAsset(assets),
BuyExecution { fees: asset, weight_limit: Unlimited },
DepositAsset { assets, beneficiary },
]);
```
to this:
```rust
let message: Xcm<()> = Xcm::builder()
.withdraw_asset(assets)
.buy_execution(asset, Unlimited),
.deposit_asset(assets, beneficiary)
.build();
```
---------
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
8ebb5c3319
commit
0524aa51d3
Generated
+1
@@ -21176,6 +21176,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
"trybuild",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
[package]
|
||||
name = "xcm-procedural"
|
||||
description = "Procedural macros for XCM"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version = "1.0.0"
|
||||
publish = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
@@ -13,3 +15,6 @@ proc-macro2 = "1.0.56"
|
||||
quote = "1.0.28"
|
||||
syn = "2.0.38"
|
||||
Inflector = "0.11.4"
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = { version = "1.0.74", features = ["diff"] }
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Derive macro for creating XCMs with a builder pattern
|
||||
|
||||
use inflector::Inflector;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
parse_macro_input, Data, DeriveInput, Error, Expr, ExprLit, Fields, Lit, Meta, MetaNameValue,
|
||||
};
|
||||
|
||||
pub fn derive(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let builder_impl = match &input.data {
|
||||
Data::Enum(data_enum) => generate_methods_for_enum(input.ident, data_enum),
|
||||
_ =>
|
||||
return Error::new_spanned(&input, "Expected the `Instruction` enum")
|
||||
.to_compile_error()
|
||||
.into(),
|
||||
};
|
||||
let output = quote! {
|
||||
pub struct XcmBuilder<Call>(Vec<Instruction<Call>>);
|
||||
impl<Call> Xcm<Call> {
|
||||
pub fn builder() -> XcmBuilder<Call> {
|
||||
XcmBuilder::<Call>(Vec::new())
|
||||
}
|
||||
}
|
||||
#builder_impl
|
||||
};
|
||||
output.into()
|
||||
}
|
||||
|
||||
fn generate_methods_for_enum(name: syn::Ident, data_enum: &syn::DataEnum) -> TokenStream2 {
|
||||
let methods = data_enum.variants.iter().map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
let method_name_string = &variant_name.to_string().to_snake_case();
|
||||
let method_name = syn::Ident::new(&method_name_string, variant_name.span());
|
||||
let docs: Vec<_> = variant
|
||||
.attrs
|
||||
.iter()
|
||||
.filter_map(|attr| match &attr.meta {
|
||||
Meta::NameValue(MetaNameValue {
|
||||
value: Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }),
|
||||
..
|
||||
}) if attr.path().is_ident("doc") => Some(literal.value()),
|
||||
_ => None,
|
||||
})
|
||||
.map(|doc| syn::parse_str::<TokenStream2>(&format!("/// {}", doc)).unwrap())
|
||||
.collect();
|
||||
let method = match &variant.fields {
|
||||
Fields::Unit => {
|
||||
quote! {
|
||||
pub fn #method_name(mut self) -> Self {
|
||||
self.0.push(#name::<Call>::#variant_name);
|
||||
self
|
||||
}
|
||||
}
|
||||
},
|
||||
Fields::Unnamed(fields) => {
|
||||
let arg_names: Vec<_> = fields
|
||||
.unnamed
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, _)| format_ident!("arg{}", index))
|
||||
.collect();
|
||||
let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect();
|
||||
quote! {
|
||||
pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self {
|
||||
self.0.push(#name::<Call>::#variant_name(#(#arg_names),*));
|
||||
self
|
||||
}
|
||||
}
|
||||
},
|
||||
Fields::Named(fields) => {
|
||||
let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect();
|
||||
let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect();
|
||||
quote! {
|
||||
pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self {
|
||||
self.0.push(#name::<Call>::#variant_name { #(#arg_names),* });
|
||||
self
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
quote! {
|
||||
#(#docs)*
|
||||
#method
|
||||
}
|
||||
});
|
||||
let output = quote! {
|
||||
impl<Call> XcmBuilder<Call> {
|
||||
#(#methods)*
|
||||
|
||||
pub fn build(self) -> Xcm<Call> {
|
||||
Xcm(self.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
output
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
mod builder_pattern;
|
||||
mod v2;
|
||||
mod v3;
|
||||
mod weight_info;
|
||||
@@ -47,3 +48,15 @@ pub fn impl_conversion_functions_for_junctions_v3(input: TokenStream) -> TokenSt
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// This is called on the `Instruction` enum, not on the `Xcm` struct,
|
||||
/// and allows for the following syntax for building XCMs:
|
||||
/// let message = Xcm::builder()
|
||||
/// .withdraw_asset(assets)
|
||||
/// .buy_execution(fees, weight_limit)
|
||||
/// .deposit_asset(assets, beneficiary)
|
||||
/// .build();
|
||||
#[proc_macro_derive(Builder)]
|
||||
pub fn derive_builder(input: TokenStream) -> TokenStream {
|
||||
builder_pattern::derive(input)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! UI tests for XCM procedural macros
|
||||
|
||||
#[cfg(not(feature = "disable-ui-tests"))]
|
||||
#[test]
|
||||
fn ui() {
|
||||
// Only run the ui tests when `RUN_UI_TESTS` is set.
|
||||
if std::env::var("RUN_UI_TESTS").is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// As trybuild is using `cargo check`, we don't need the real WASM binaries.
|
||||
std::env::set_var("SKIP_WASM_BUILD", "1");
|
||||
|
||||
let t = trybuild::TestCases::new();
|
||||
t.compile_fail("tests/ui/*.rs");
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Polkadot is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Test error when attaching the derive builder macro to something
|
||||
//! other than the XCM `Instruction` enum.
|
||||
|
||||
use xcm_procedural::Builder;
|
||||
|
||||
#[derive(Builder)]
|
||||
struct SomeStruct;
|
||||
|
||||
fn main() {}
|
||||
@@ -0,0 +1,5 @@
|
||||
error: Expected the `Instruction` enum
|
||||
--> tests/ui/builder_pattern.rs:23:1
|
||||
|
|
||||
23 | struct SomeStruct;
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
@@ -404,7 +404,14 @@ impl XcmContext {
|
||||
///
|
||||
/// This is the inner XCM format and is version-sensitive. Messages are typically passed using the
|
||||
/// outer XCM format, known as `VersionedXcm`.
|
||||
#[derive(Derivative, Encode, Decode, TypeInfo, xcm_procedural::XcmWeightInfoTrait)]
|
||||
#[derive(
|
||||
Derivative,
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
xcm_procedural::XcmWeightInfoTrait,
|
||||
xcm_procedural::Builder,
|
||||
)]
|
||||
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
|
||||
#[codec(encode_bound())]
|
||||
#[codec(decode_bound())]
|
||||
|
||||
@@ -649,4 +649,23 @@ mod tests {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builder_pattern_works() {
|
||||
let asset: MultiAsset = (Here, 100u128).into();
|
||||
let beneficiary: MultiLocation = AccountId32 { id: [0u8; 32], network: None }.into();
|
||||
let message: Xcm<()> = Xcm::builder()
|
||||
.withdraw_asset(asset.clone().into())
|
||||
.buy_execution(asset.clone(), Unlimited)
|
||||
.deposit_asset(asset.clone().into(), beneficiary)
|
||||
.build();
|
||||
assert_eq!(
|
||||
message,
|
||||
Xcm(vec![
|
||||
WithdrawAsset(asset.clone().into()),
|
||||
BuyExecution { fees: asset.clone(), weight_limit: Unlimited },
|
||||
DepositAsset { assets: asset.into(), beneficiary },
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Schema: Parity PR Documentation Schema (prdoc)
|
||||
# See doc at https://github.com/paritytech/prdoc
|
||||
|
||||
title: Add a builder pattern to create XCM programs
|
||||
|
||||
doc:
|
||||
- audience: Core Dev
|
||||
description: |
|
||||
XCMs can now be built using a builder pattern like so:
|
||||
Xcm::builder()
|
||||
.withdraw_asset(assets)
|
||||
.buy_execution(fees, weight_limit)
|
||||
.deposit_asset(assets, beneficiary)
|
||||
.build();
|
||||
|
||||
migrations:
|
||||
db: []
|
||||
|
||||
runtime: []
|
||||
|
||||
crates:
|
||||
- name: xcm
|
||||
|
||||
host_functions: []
|
||||
Reference in New Issue
Block a user