mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-24 08:55:47 +00:00
Different XCM builders, default one requires fee payment (#2253)
Adding on top of the new builder pattern for creating XCM programs, I'm adding some more APIs: ```rust let paying_fees: Xcm<()> = Xcm::builder() // Only allow paying for fees .withdraw_asset() // First instruction has to load the holding register .buy_execution() // Second instruction has to be `buy_execution` .build(); let paying_fees_invalid: Xcm<()> = Xcm::builder() .withdraw_asset() .build(); // Invalid, need to pay for fees let not_paying_fees: Xcm<()> = Xcm::builder_unpaid() .unpaid_execution() // Needed .withdraw_asset() .deposit_asset() .build(); let all_goes: Xcm<()> = Xcm::builder_unsafe() // You can do anything .withdraw_asset() .deposit_asset() .build(); ``` The invalid bits are because the methods don't even exist on the types that you'd want to call them on. --------- Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
b25d29a502
commit
b3841b6b71
Generated
+1
@@ -21512,6 +21512,7 @@ dependencies = [
|
|||||||
"Inflector",
|
"Inflector",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"staging-xcm",
|
||||||
"syn 2.0.38",
|
"syn 2.0.38",
|
||||||
"trybuild",
|
"trybuild",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -18,3 +18,4 @@ Inflector = "0.11.4"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
trybuild = { version = "1.0.74", features = ["diff"] }
|
trybuild = { version = "1.0.74", features = ["diff"] }
|
||||||
|
xcm = { package = "staging-xcm", path = ".." }
|
||||||
|
|||||||
@@ -17,56 +17,83 @@
|
|||||||
//! Derive macro for creating XCMs with a builder pattern
|
//! Derive macro for creating XCMs with a builder pattern
|
||||||
|
|
||||||
use inflector::Inflector;
|
use inflector::Inflector;
|
||||||
use proc_macro::TokenStream;
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input, Data, DeriveInput, Error, Expr, ExprLit, Fields, Lit, Meta, MetaNameValue,
|
Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Fields, Ident, Lit, Meta, MetaNameValue,
|
||||||
|
Result, Variant,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn derive(input: TokenStream) -> TokenStream {
|
pub fn derive(input: DeriveInput) -> Result<TokenStream2> {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let data_enum = match &input.data {
|
||||||
let builder_impl = match &input.data {
|
Data::Enum(data_enum) => data_enum,
|
||||||
Data::Enum(data_enum) => generate_methods_for_enum(input.ident, data_enum),
|
_ => return Err(Error::new_spanned(&input, "Expected the `Instruction` enum")),
|
||||||
_ =>
|
|
||||||
return Error::new_spanned(&input, "Expected the `Instruction` enum")
|
|
||||||
.to_compile_error()
|
|
||||||
.into(),
|
|
||||||
};
|
};
|
||||||
|
let builder_raw_impl = generate_builder_raw_impl(&input.ident, data_enum);
|
||||||
|
let builder_impl = generate_builder_impl(&input.ident, data_enum)?;
|
||||||
|
let builder_unpaid_impl = generate_builder_unpaid_impl(&input.ident, data_enum)?;
|
||||||
let output = quote! {
|
let output = quote! {
|
||||||
pub struct XcmBuilder<Call>(Vec<Instruction<Call>>);
|
/// A trait for types that track state inside the XcmBuilder
|
||||||
|
pub trait XcmBuilderState {}
|
||||||
|
|
||||||
|
/// Access to all the instructions
|
||||||
|
pub enum AnythingGoes {}
|
||||||
|
/// You need to pay for execution
|
||||||
|
pub enum PaymentRequired {}
|
||||||
|
/// The holding register was loaded, now to buy execution
|
||||||
|
pub enum LoadedHolding {}
|
||||||
|
/// Need to explicitly state it won't pay for fees
|
||||||
|
pub enum ExplicitUnpaidRequired {}
|
||||||
|
|
||||||
|
impl XcmBuilderState for AnythingGoes {}
|
||||||
|
impl XcmBuilderState for PaymentRequired {}
|
||||||
|
impl XcmBuilderState for LoadedHolding {}
|
||||||
|
impl XcmBuilderState for ExplicitUnpaidRequired {}
|
||||||
|
|
||||||
|
/// Type used to build XCM programs
|
||||||
|
pub struct XcmBuilder<Call, S: XcmBuilderState> {
|
||||||
|
pub(crate) instructions: Vec<Instruction<Call>>,
|
||||||
|
pub state: core::marker::PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<Call> Xcm<Call> {
|
impl<Call> Xcm<Call> {
|
||||||
pub fn builder() -> XcmBuilder<Call> {
|
pub fn builder() -> XcmBuilder<Call, PaymentRequired> {
|
||||||
XcmBuilder::<Call>(Vec::new())
|
XcmBuilder::<Call, PaymentRequired> {
|
||||||
|
instructions: Vec::new(),
|
||||||
|
state: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn builder_unpaid() -> XcmBuilder<Call, ExplicitUnpaidRequired> {
|
||||||
|
XcmBuilder::<Call, ExplicitUnpaidRequired> {
|
||||||
|
instructions: Vec::new(),
|
||||||
|
state: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn builder_unsafe() -> XcmBuilder<Call, AnythingGoes> {
|
||||||
|
XcmBuilder::<Call, AnythingGoes> {
|
||||||
|
instructions: Vec::new(),
|
||||||
|
state: core::marker::PhantomData,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#builder_impl
|
#builder_impl
|
||||||
|
#builder_unpaid_impl
|
||||||
|
#builder_raw_impl
|
||||||
};
|
};
|
||||||
output.into()
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_methods_for_enum(name: syn::Ident, data_enum: &syn::DataEnum) -> TokenStream2 {
|
fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> TokenStream2 {
|
||||||
let methods = data_enum.variants.iter().map(|variant| {
|
let methods = data_enum.variants.iter().map(|variant| {
|
||||||
let variant_name = &variant.ident;
|
let variant_name = &variant.ident;
|
||||||
let method_name_string = &variant_name.to_string().to_snake_case();
|
let method_name_string = &variant_name.to_string().to_snake_case();
|
||||||
let method_name = syn::Ident::new(&method_name_string, variant_name.span());
|
let method_name = syn::Ident::new(&method_name_string, variant_name.span());
|
||||||
let docs: Vec<_> = variant
|
let docs = get_doc_comments(&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 {
|
let method = match &variant.fields {
|
||||||
Fields::Unit => {
|
Fields::Unit => {
|
||||||
quote! {
|
quote! {
|
||||||
pub fn #method_name(mut self) -> Self {
|
pub fn #method_name(mut self) -> Self {
|
||||||
self.0.push(#name::<Call>::#variant_name);
|
self.instructions.push(#name::<Call>::#variant_name);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +108,7 @@ fn generate_methods_for_enum(name: syn::Ident, data_enum: &syn::DataEnum) -> Tok
|
|||||||
let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect();
|
let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect();
|
||||||
quote! {
|
quote! {
|
||||||
pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self {
|
pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self {
|
||||||
self.0.push(#name::<Call>::#variant_name(#(#arg_names),*));
|
self.instructions.push(#name::<Call>::#variant_name(#(#arg_names),*));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,7 +118,7 @@ fn generate_methods_for_enum(name: syn::Ident, data_enum: &syn::DataEnum) -> Tok
|
|||||||
let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect();
|
let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect();
|
||||||
quote! {
|
quote! {
|
||||||
pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self {
|
pub fn #method_name(mut self, #(#arg_names: #arg_types),*) -> Self {
|
||||||
self.0.push(#name::<Call>::#variant_name { #(#arg_names),* });
|
self.instructions.push(#name::<Call>::#variant_name { #(#arg_names),* });
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,13 +130,209 @@ fn generate_methods_for_enum(name: syn::Ident, data_enum: &syn::DataEnum) -> Tok
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let output = quote! {
|
let output = quote! {
|
||||||
impl<Call> XcmBuilder<Call> {
|
impl<Call> XcmBuilder<Call, AnythingGoes> {
|
||||||
#(#methods)*
|
#(#methods)*
|
||||||
|
|
||||||
pub fn build(self) -> Xcm<Call> {
|
pub fn build(self) -> Xcm<Call> {
|
||||||
Xcm(self.0)
|
Xcm(self.instructions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> {
|
||||||
|
// We first require an instruction that load the holding register
|
||||||
|
let load_holding_variants = data_enum
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.map(|variant| {
|
||||||
|
let maybe_builder_attr = variant.attrs.iter().find(|attr| match attr.meta {
|
||||||
|
Meta::List(ref list) => {
|
||||||
|
return list.path.is_ident("builder");
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
let builder_attr = match maybe_builder_attr {
|
||||||
|
Some(builder) => builder.clone(),
|
||||||
|
None => return Ok(None), /* It's not going to be an instruction that loads the
|
||||||
|
* holding register */
|
||||||
|
};
|
||||||
|
let Meta::List(ref list) = builder_attr.meta else { unreachable!("We checked before") };
|
||||||
|
let inner_ident: Ident = syn::parse2(list.tokens.clone().into()).map_err(|_| {
|
||||||
|
Error::new_spanned(&builder_attr, "Expected `builder(loads_holding)`")
|
||||||
|
})?;
|
||||||
|
let ident_to_match: Ident = syn::parse_quote!(loads_holding);
|
||||||
|
if inner_ident == ident_to_match {
|
||||||
|
Ok(Some(variant))
|
||||||
|
} else {
|
||||||
|
Err(Error::new_spanned(&builder_attr, "Expected `builder(loads_holding)`"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
let load_holding_methods = load_holding_variants
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.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 = get_doc_comments(&variant);
|
||||||
|
let method = match &variant.fields {
|
||||||
|
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! {
|
||||||
|
#(#docs)*
|
||||||
|
pub fn #method_name(self, #(#arg_names: #arg_types),*) -> XcmBuilder<Call, LoadedHolding> {
|
||||||
|
let mut new_instructions = self.instructions;
|
||||||
|
new_instructions.push(#name::<Call>::#variant_name(#(#arg_names),*));
|
||||||
|
XcmBuilder {
|
||||||
|
instructions: new_instructions,
|
||||||
|
state: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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! {
|
||||||
|
#(#docs)*
|
||||||
|
pub fn #method_name(self, #(#arg_names: #arg_types),*) -> XcmBuilder<Call, LoadedHolding> {
|
||||||
|
let mut new_instructions = self.instructions;
|
||||||
|
new_instructions.push(#name::<Call>::#variant_name { #(#arg_names),* });
|
||||||
|
XcmBuilder {
|
||||||
|
instructions: new_instructions,
|
||||||
|
state: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ =>
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&variant,
|
||||||
|
"Instructions that load the holding register should take operands",
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
Ok(method)
|
||||||
|
})
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let first_impl = quote! {
|
||||||
|
impl<Call> XcmBuilder<Call, PaymentRequired> {
|
||||||
|
#(#load_holding_methods)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Then we require fees to be paid
|
||||||
|
let buy_execution_method = data_enum
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.find(|variant| variant.ident.to_string() == "BuyExecution")
|
||||||
|
.map_or(
|
||||||
|
Err(Error::new_spanned(&data_enum.variants, "No BuyExecution instruction")),
|
||||||
|
|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 = get_doc_comments(&variant);
|
||||||
|
let fields = match &variant.fields {
|
||||||
|
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! {
|
||||||
|
#(#docs)*
|
||||||
|
pub fn #method_name(self, #(#arg_names: #arg_types),*) -> XcmBuilder<Call, AnythingGoes> {
|
||||||
|
let mut new_instructions = self.instructions;
|
||||||
|
new_instructions.push(#name::<Call>::#variant_name { #(#arg_names),* });
|
||||||
|
XcmBuilder {
|
||||||
|
instructions: new_instructions,
|
||||||
|
state: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ =>
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&variant,
|
||||||
|
"BuyExecution should have named fields",
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
Ok(fields)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let second_impl = quote! {
|
||||||
|
impl<Call> XcmBuilder<Call, LoadedHolding> {
|
||||||
|
#buy_execution_method
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = quote! {
|
||||||
|
#first_impl
|
||||||
|
#second_impl
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_builder_unpaid_impl(name: &Ident, data_enum: &DataEnum) -> Result<TokenStream2> {
|
||||||
|
let unpaid_execution_variant = data_enum
|
||||||
|
.variants
|
||||||
|
.iter()
|
||||||
|
.find(|variant| variant.ident.to_string() == "UnpaidExecution")
|
||||||
|
.ok_or(Error::new_spanned(&data_enum.variants, "No UnpaidExecution instruction"))?;
|
||||||
|
let unpaid_execution_ident = &unpaid_execution_variant.ident;
|
||||||
|
let unpaid_execution_method_name = Ident::new(
|
||||||
|
&unpaid_execution_ident.to_string().to_snake_case(),
|
||||||
|
unpaid_execution_ident.span(),
|
||||||
|
);
|
||||||
|
let docs = get_doc_comments(&unpaid_execution_variant);
|
||||||
|
let fields = match &unpaid_execution_variant.fields {
|
||||||
|
Fields::Named(fields) => fields,
|
||||||
|
_ =>
|
||||||
|
return Err(Error::new_spanned(
|
||||||
|
&unpaid_execution_variant,
|
||||||
|
"UnpaidExecution should have 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();
|
||||||
|
Ok(quote! {
|
||||||
|
impl<Call> XcmBuilder<Call, ExplicitUnpaidRequired> {
|
||||||
|
#(#docs)*
|
||||||
|
pub fn #unpaid_execution_method_name(self, #(#arg_names: #arg_types),*) -> XcmBuilder<Call, AnythingGoes> {
|
||||||
|
let mut new_instructions = self.instructions;
|
||||||
|
new_instructions.push(#name::<Call>::#unpaid_execution_ident { #(#arg_names),* });
|
||||||
|
XcmBuilder {
|
||||||
|
instructions: new_instructions,
|
||||||
|
state: core::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_doc_comments(variant: &Variant) -> Vec<TokenStream2> {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
//! Procedural macros used in XCM.
|
//! Procedural macros used in XCM.
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
mod builder_pattern;
|
mod builder_pattern;
|
||||||
mod v2;
|
mod v2;
|
||||||
@@ -56,7 +57,10 @@ pub fn impl_conversion_functions_for_junctions_v3(input: TokenStream) -> TokenSt
|
|||||||
/// .buy_execution(fees, weight_limit)
|
/// .buy_execution(fees, weight_limit)
|
||||||
/// .deposit_asset(assets, beneficiary)
|
/// .deposit_asset(assets, beneficiary)
|
||||||
/// .build();
|
/// .build();
|
||||||
#[proc_macro_derive(Builder)]
|
#[proc_macro_derive(Builder, attributes(builder))]
|
||||||
pub fn derive_builder(input: TokenStream) -> TokenStream {
|
pub fn derive_builder(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
builder_pattern::derive(input)
|
builder_pattern::derive(input)
|
||||||
|
.unwrap_or_else(syn::Error::into_compile_error)
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// 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 the methods generated by the Builder derive macro.
|
||||||
|
//! Tests directly on the actual Xcm struct and Instruction enum.
|
||||||
|
|
||||||
|
use xcm::latest::prelude::*;
|
||||||
|
|
||||||
|
#[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()
|
||||||
|
.receive_teleported_asset(asset.clone().into())
|
||||||
|
.buy_execution(asset.clone(), Unlimited)
|
||||||
|
.deposit_asset(asset.clone().into(), beneficiary)
|
||||||
|
.build();
|
||||||
|
assert_eq!(
|
||||||
|
message,
|
||||||
|
Xcm(vec![
|
||||||
|
ReceiveTeleportedAsset(asset.clone().into()),
|
||||||
|
BuyExecution { fees: asset.clone(), weight_limit: Unlimited },
|
||||||
|
DepositAsset { assets: asset.into(), beneficiary },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_builder_requires_buy_execution() {
|
||||||
|
let asset: MultiAsset = (Here, 100u128).into();
|
||||||
|
let beneficiary: MultiLocation = AccountId32 { id: [0u8; 32], network: None }.into();
|
||||||
|
// This is invalid, since it doesn't pay for fees.
|
||||||
|
// This is enforced by the runtime, because the build() method doesn't exist
|
||||||
|
// on the resulting type.
|
||||||
|
// let message: Xcm<()> = Xcm::builder()
|
||||||
|
// .withdraw_asset(asset.clone().into())
|
||||||
|
// .deposit_asset(asset.into(), beneficiary)
|
||||||
|
// .build();
|
||||||
|
|
||||||
|
// To be able to do that, we need to use the explicitly unpaid variant
|
||||||
|
let message: Xcm<()> = Xcm::builder_unpaid()
|
||||||
|
.unpaid_execution(Unlimited, None)
|
||||||
|
.withdraw_asset(asset.clone().into())
|
||||||
|
.deposit_asset(asset.clone().into(), beneficiary)
|
||||||
|
.build(); // This works
|
||||||
|
assert_eq!(
|
||||||
|
message,
|
||||||
|
Xcm(vec![
|
||||||
|
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
|
||||||
|
WithdrawAsset(asset.clone().into()),
|
||||||
|
DepositAsset { assets: asset.clone().into(), beneficiary },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// The other option doesn't have any limits whatsoever, so it should
|
||||||
|
// only be used when you really know what you're doing.
|
||||||
|
let message: Xcm<()> = Xcm::builder_unsafe()
|
||||||
|
.withdraw_asset(asset.clone().into())
|
||||||
|
.deposit_asset(asset.clone().into(), beneficiary)
|
||||||
|
.build();
|
||||||
|
assert_eq!(
|
||||||
|
message,
|
||||||
|
Xcm(vec![
|
||||||
|
WithdrawAsset(asset.clone().into()),
|
||||||
|
DepositAsset { assets: asset.clone().into(), beneficiary },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,5 +28,5 @@ fn ui() {
|
|||||||
std::env::set_var("SKIP_WASM_BUILD", "1");
|
std::env::set_var("SKIP_WASM_BUILD", "1");
|
||||||
|
|
||||||
let t = trybuild::TestCases::new();
|
let t = trybuild::TestCases::new();
|
||||||
t.compile_fail("tests/ui/*.rs");
|
t.compile_fail("tests/ui/**/*.rs");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/>.
|
||||||
|
|
||||||
|
//! Test error when using a badly formatted attribute.
|
||||||
|
|
||||||
|
use xcm_procedural::Builder;
|
||||||
|
|
||||||
|
struct Xcm<Call>(pub Vec<Instruction<Call>>);
|
||||||
|
|
||||||
|
#[derive(Builder)]
|
||||||
|
enum Instruction<Call> {
|
||||||
|
#[builder(funds_holding = 2)]
|
||||||
|
WithdrawAsset(u128),
|
||||||
|
BuyExecution { fees: u128 },
|
||||||
|
UnpaidExecution { weight_limit: (u32, u32) },
|
||||||
|
Transact { call: Call },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: Expected `builder(loads_holding)`
|
||||||
|
--> tests/ui/builder_pattern/badly_formatted_attribute.rs:25:5
|
||||||
|
|
|
||||||
|
25 | #[builder(funds_holding = 2)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// 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 the `BuyExecution` instruction doesn't take named fields.
|
||||||
|
|
||||||
|
use xcm_procedural::Builder;
|
||||||
|
|
||||||
|
struct Xcm<Call>(pub Vec<Instruction<Call>>);
|
||||||
|
|
||||||
|
#[derive(Builder)]
|
||||||
|
enum Instruction<Call> {
|
||||||
|
BuyExecution(u128),
|
||||||
|
UnpaidExecution { weight_limit: (u32, u32) },
|
||||||
|
Transact { call: Call },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: BuyExecution should have named fields
|
||||||
|
--> tests/ui/builder_pattern/buy_execution_named_fields.rs:25:5
|
||||||
|
|
|
||||||
|
25 | BuyExecution(u128),
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
@@ -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/>.
|
||||||
|
|
||||||
|
//! Test error when an instruction that loads the holding register doesn't take operands.
|
||||||
|
|
||||||
|
use xcm_procedural::Builder;
|
||||||
|
|
||||||
|
struct Xcm<Call>(pub Vec<Instruction<Call>>);
|
||||||
|
|
||||||
|
#[derive(Builder)]
|
||||||
|
enum Instruction<Call> {
|
||||||
|
#[builder(loads_holding)]
|
||||||
|
WithdrawAsset,
|
||||||
|
BuyExecution { fees: u128 },
|
||||||
|
UnpaidExecution { weight_limit: (u32, u32) },
|
||||||
|
Transact { call: Call },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
error: Instructions that load the holding register should take operands
|
||||||
|
--> tests/ui/builder_pattern/loads_holding_no_operands.rs:25:5
|
||||||
|
|
|
||||||
|
25 | / #[builder(loads_holding)]
|
||||||
|
26 | | WithdrawAsset,
|
||||||
|
| |_________________^
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// 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 there's no `BuyExecution` instruction.
|
||||||
|
|
||||||
|
use xcm_procedural::Builder;
|
||||||
|
|
||||||
|
struct Xcm<Call>(pub Vec<Instruction<Call>>);
|
||||||
|
|
||||||
|
#[derive(Builder)]
|
||||||
|
enum Instruction<Call> {
|
||||||
|
UnpaidExecution { weight_limit: (u32, u32) },
|
||||||
|
Transact { call: Call },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
error: No BuyExecution instruction
|
||||||
|
--> tests/ui/builder_pattern/no_buy_execution.rs:25:5
|
||||||
|
|
|
||||||
|
25 | / UnpaidExecution { weight_limit: (u32, u32) },
|
||||||
|
26 | | Transact { call: Call },
|
||||||
|
| |____________________________^
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
// 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 there's no `UnpaidExecution` instruction.
|
||||||
|
|
||||||
|
use xcm_procedural::Builder;
|
||||||
|
|
||||||
|
struct Xcm<Call>(pub Vec<Instruction<Call>>);
|
||||||
|
|
||||||
|
#[derive(Builder)]
|
||||||
|
enum Instruction<Call> {
|
||||||
|
BuyExecution { fees: u128 },
|
||||||
|
Transact { call: Call },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
error: No UnpaidExecution instruction
|
||||||
|
--> tests/ui/builder_pattern/no_unpaid_execution.rs:25:5
|
||||||
|
|
|
||||||
|
25 | / BuyExecution { fees: u128 },
|
||||||
|
26 | | Transact { call: Call },
|
||||||
|
| |____________________________^
|
||||||
@@ -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/>.
|
||||||
|
|
||||||
|
//! Test error when using wrong attribute.
|
||||||
|
|
||||||
|
use xcm_procedural::Builder;
|
||||||
|
|
||||||
|
struct Xcm<Call>(pub Vec<Instruction<Call>>);
|
||||||
|
|
||||||
|
#[derive(Builder)]
|
||||||
|
enum Instruction<Call> {
|
||||||
|
#[builder(funds_holding)]
|
||||||
|
WithdrawAsset(u128),
|
||||||
|
BuyExecution { fees: u128 },
|
||||||
|
UnpaidExecution { weight_limit: (u32, u32) },
|
||||||
|
Transact { call: Call },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: Expected `builder(loads_holding)`
|
||||||
|
--> tests/ui/builder_pattern/unexpected_attribute.rs:25:5
|
||||||
|
|
|
||||||
|
25 | #[builder(funds_holding)]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// 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 the `BuyExecution` instruction doesn't take named fields.
|
||||||
|
|
||||||
|
use xcm_procedural::Builder;
|
||||||
|
|
||||||
|
struct Xcm<Call>(pub Vec<Instruction<Call>>);
|
||||||
|
|
||||||
|
#[derive(Builder)]
|
||||||
|
enum Instruction<Call> {
|
||||||
|
BuyExecution { fees: u128 },
|
||||||
|
UnpaidExecution(u32, u32),
|
||||||
|
Transact { call: Call },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
error: UnpaidExecution should have named fields
|
||||||
|
--> tests/ui/builder_pattern/unpaid_execution_named_fields.rs:26:5
|
||||||
|
|
|
||||||
|
26 | UnpaidExecution(u32, u32),
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
error: Expected the `Instruction` enum
|
error: Expected the `Instruction` enum
|
||||||
--> tests/ui/builder_pattern.rs:23:1
|
--> tests/ui/builder_pattern/wrong_target.rs:23:1
|
||||||
|
|
|
|
||||||
23 | struct SomeStruct;
|
23 | struct SomeStruct;
|
||||||
| ^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
@@ -426,6 +426,7 @@ pub enum Instruction<Call> {
|
|||||||
/// Kind: *Command*.
|
/// Kind: *Command*.
|
||||||
///
|
///
|
||||||
/// Errors:
|
/// Errors:
|
||||||
|
#[builder(loads_holding)]
|
||||||
WithdrawAsset(MultiAssets),
|
WithdrawAsset(MultiAssets),
|
||||||
|
|
||||||
/// Asset(s) (`assets`) have been received into the ownership of this system on the `origin`
|
/// Asset(s) (`assets`) have been received into the ownership of this system on the `origin`
|
||||||
@@ -439,6 +440,7 @@ pub enum Instruction<Call> {
|
|||||||
/// Kind: *Trusted Indication*.
|
/// Kind: *Trusted Indication*.
|
||||||
///
|
///
|
||||||
/// Errors:
|
/// Errors:
|
||||||
|
#[builder(loads_holding)]
|
||||||
ReserveAssetDeposited(MultiAssets),
|
ReserveAssetDeposited(MultiAssets),
|
||||||
|
|
||||||
/// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should
|
/// Asset(s) (`assets`) have been destroyed on the `origin` system and equivalent assets should
|
||||||
@@ -452,6 +454,7 @@ pub enum Instruction<Call> {
|
|||||||
/// Kind: *Trusted Indication*.
|
/// Kind: *Trusted Indication*.
|
||||||
///
|
///
|
||||||
/// Errors:
|
/// Errors:
|
||||||
|
#[builder(loads_holding)]
|
||||||
ReceiveTeleportedAsset(MultiAssets),
|
ReceiveTeleportedAsset(MultiAssets),
|
||||||
|
|
||||||
/// Respond with information that the local system is expecting.
|
/// Respond with information that the local system is expecting.
|
||||||
@@ -776,6 +779,7 @@ pub enum Instruction<Call> {
|
|||||||
/// Kind: *Command*
|
/// Kind: *Command*
|
||||||
///
|
///
|
||||||
/// Errors:
|
/// Errors:
|
||||||
|
#[builder(loads_holding)]
|
||||||
ClaimAsset { assets: MultiAssets, ticket: MultiLocation },
|
ClaimAsset { assets: MultiAssets, ticket: MultiLocation },
|
||||||
|
|
||||||
/// Always throws an error of type `Trap`.
|
/// Always throws an error of type `Trap`.
|
||||||
|
|||||||
@@ -649,23 +649,4 @@ 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: Different builder pattern constructors for XCM
|
||||||
|
|
||||||
|
doc:
|
||||||
|
- audience: Core Dev
|
||||||
|
description: |
|
||||||
|
The `builder()` constructor for XCM programs now only allows building messages that pay for fees,
|
||||||
|
i.e. messages that would pass the `AllowTopLevelPaidExecutionFrom` barrier.
|
||||||
|
Another constructor, `builder_unpaid()` requires an explicit `UnpaidExecution` instruction before
|
||||||
|
anything else.
|
||||||
|
For building messages without any restriction, `builder_unsafe` can be used.
|
||||||
|
This has been named like that since in general the other two should be used instead, but it's okay
|
||||||
|
to use it for teaching purposes or for experimenting.
|
||||||
|
|
||||||
|
migrations:
|
||||||
|
db: []
|
||||||
|
|
||||||
|
runtime: []
|
||||||
|
|
||||||
|
crates: []
|
||||||
|
|
||||||
|
host_functions: []
|
||||||
Reference in New Issue
Block a user