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:
Francisco Aguirre
2023-11-21 16:09:40 +01:00
committed by GitHub
parent b25d29a502
commit b3841b6b71
25 changed files with 625 additions and 54 deletions
Generated
+1
View File
@@ -21512,6 +21512,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"staging-xcm",
"syn 2.0.38",
"trybuild",
]
+1
View File
@@ -18,3 +18,4 @@ Inflector = "0.11.4"
[dev-dependencies]
trybuild = { version = "1.0.74", features = ["diff"] }
xcm = { package = "staging-xcm", path = ".." }
+255 -32
View File
@@ -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()
}
+5 -1
View File
@@ -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 },
])
);
}
+1 -1
View File
@@ -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,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;
| ^^^^^^^^^^^^^^^^^^
+4
View File
@@ -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 },
])
);
}
}
+24
View File
@@ -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: []