mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +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",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"staging-xcm",
|
||||
"syn 2.0.38",
|
||||
"trybuild",
|
||||
]
|
||||
|
||||
@@ -18,3 +18,4 @@ Inflector = "0.11.4"
|
||||
|
||||
[dev-dependencies]
|
||||
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
|
||||
|
||||
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,
|
||||
Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Fields, Ident, Lit, Meta, MetaNameValue,
|
||||
Result, Variant,
|
||||
};
|
||||
|
||||
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(),
|
||||
pub fn derive(input: DeriveInput) -> Result<TokenStream2> {
|
||||
let data_enum = match &input.data {
|
||||
Data::Enum(data_enum) => data_enum,
|
||||
_ => return Err(Error::new_spanned(&input, "Expected the `Instruction` enum")),
|
||||
};
|
||||
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! {
|
||||
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> {
|
||||
pub fn builder() -> XcmBuilder<Call> {
|
||||
XcmBuilder::<Call>(Vec::new())
|
||||
pub fn builder() -> XcmBuilder<Call, PaymentRequired> {
|
||||
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_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 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 docs = get_doc_comments(&variant);
|
||||
let method = match &variant.fields {
|
||||
Fields::Unit => {
|
||||
quote! {
|
||||
pub fn #method_name(mut self) -> Self {
|
||||
self.0.push(#name::<Call>::#variant_name);
|
||||
self.instructions.push(#name::<Call>::#variant_name);
|
||||
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();
|
||||
quote! {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
quote! {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -103,13 +130,209 @@ fn generate_methods_for_enum(name: syn::Ident, data_enum: &syn::DataEnum) -> Tok
|
||||
}
|
||||
});
|
||||
let output = quote! {
|
||||
impl<Call> XcmBuilder<Call> {
|
||||
impl<Call> XcmBuilder<Call, AnythingGoes> {
|
||||
#(#methods)*
|
||||
|
||||
pub fn build(self) -> Xcm<Call> {
|
||||
Xcm(self.0)
|
||||
Xcm(self.instructions)
|
||||
}
|
||||
}
|
||||
};
|
||||
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.
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
mod builder_pattern;
|
||||
mod v2;
|
||||
@@ -56,7 +57,10 @@ pub fn impl_conversion_functions_for_junctions_v3(input: TokenStream) -> TokenSt
|
||||
/// .buy_execution(fees, weight_limit)
|
||||
/// .deposit_asset(assets, beneficiary)
|
||||
/// .build();
|
||||
#[proc_macro_derive(Builder)]
|
||||
#[proc_macro_derive(Builder, attributes(builder))]
|
||||
pub fn derive_builder(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
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");
|
||||
|
||||
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
|
||||
--> tests/ui/builder_pattern.rs:23:1
|
||||
--> tests/ui/builder_pattern/wrong_target.rs:23:1
|
||||
|
|
||||
23 | struct SomeStruct;
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
@@ -426,6 +426,7 @@ pub enum Instruction<Call> {
|
||||
/// Kind: *Command*.
|
||||
///
|
||||
/// Errors:
|
||||
#[builder(loads_holding)]
|
||||
WithdrawAsset(MultiAssets),
|
||||
|
||||
/// 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*.
|
||||
///
|
||||
/// Errors:
|
||||
#[builder(loads_holding)]
|
||||
ReserveAssetDeposited(MultiAssets),
|
||||
|
||||
/// 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*.
|
||||
///
|
||||
/// Errors:
|
||||
#[builder(loads_holding)]
|
||||
ReceiveTeleportedAsset(MultiAssets),
|
||||
|
||||
/// Respond with information that the local system is expecting.
|
||||
@@ -776,6 +779,7 @@ pub enum Instruction<Call> {
|
||||
/// Kind: *Command*
|
||||
///
|
||||
/// Errors:
|
||||
#[builder(loads_holding)]
|
||||
ClaimAsset { assets: MultiAssets, ticket: MultiLocation },
|
||||
|
||||
/// 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