From 9687841309fa1113efa4075977fa7399bd8c5a24 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Tue, 11 Aug 2020 11:43:05 +1200 Subject: [PATCH 01/14] update for rococo --- benchmarking/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/benchmarking/src/lib.rs b/benchmarking/src/lib.rs index 09d9606c2..b8f624f0a 100644 --- a/benchmarking/src/lib.rs +++ b/benchmarking/src/lib.rs @@ -10,7 +10,6 @@ mod tests; pub use frame_benchmarking::Analysis; pub use frame_benchmarking::{ benchmarking, BenchmarkBatch, BenchmarkParameter, BenchmarkResults, Benchmarking, BenchmarkingSetup, - BenchmarkingSetupInstance, }; pub use frame_support; pub use paste; From 75234730161c875d55636576214cf34349d31c80 Mon Sep 17 00:00:00 2001 From: Shaopeng Wang Date: Tue, 11 Aug 2020 13:53:59 +1200 Subject: [PATCH 02/14] Update benchmarking. --- benchmarking/src/lib.rs | 898 ++++++++++++++++------------------------ 1 file changed, 350 insertions(+), 548 deletions(-) diff --git a/benchmarking/src/lib.rs b/benchmarking/src/lib.rs index b8f624f0a..8907897a7 100644 --- a/benchmarking/src/lib.rs +++ b/benchmarking/src/lib.rs @@ -3,31 +3,28 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(feature = "std")] mod tests; -#[cfg(feature = "std")] -pub use frame_benchmarking::Analysis; pub use frame_benchmarking::{ benchmarking, BenchmarkBatch, BenchmarkParameter, BenchmarkResults, Benchmarking, BenchmarkingSetup, }; -pub use frame_support; -pub use paste; +#[cfg(feature = "std")] +pub use frame_benchmarking::{Analysis, BenchmarkSelector}; #[doc(hidden)] pub use sp_io::storage::root as storage_root; -pub use sp_runtime::traits::{Dispatchable, One, Zero}; +pub use sp_runtime::traits::Zero; +pub use frame_support; +pub use paste; /// Construct pallet benchmarks for weighing dispatchables. /// -/// Works around the idea of complexity parameters, named by a single letter -/// (which is usually upper cased in complexity notation but is lower-cased for -/// use in this macro). +/// Works around the idea of complexity parameters, named by a single letter (which is usually +/// upper cased in complexity notation but is lower-cased for use in this macro). /// -/// Complexity parameters ("parameters") have a range which is a `u32` pair. -/// Every time a benchmark is prepared and run, this parameter takes a concrete -/// value within the range. There is an associated instancing block, which is a -/// single expression that is evaluated during preparation. It may use `?` -/// (`i.e. `return Err(...)`) to bail with a string error. Here's a +/// Complexity parameters ("parameters") have a range which is a `u32` pair. Every time a benchmark +/// is prepared and run, this parameter takes a concrete value within the range. There is an +/// associated instancing block, which is a single expression that is evaluated during +/// preparation. It may use `?` (`i.e. `return Err(...)`) to bail with a string error. Here's a /// few examples: /// /// ```ignore @@ -44,32 +41,29 @@ pub use sp_runtime::traits::{Dispatchable, One, Zero}; /// } /// ``` /// -/// Note that due to parsing restrictions, if the `from` expression is not a -/// single token (i.e. a literal or constant), then it must be parenthesised. +/// Note that due to parsing restrictions, if the `from` expression is not a single token (i.e. a +/// literal or constant), then it must be parenthesised. /// -/// The macro allows for a number of "arms", each representing an individual -/// benchmark. Using the simple syntax, the associated dispatchable function -/// maps 1:1 with the benchmark and the name of the benchmark is the same as -/// that of the associated function. However, extended syntax allows -/// for arbitrary expresions to be evaluated in a benchmark (including for -/// example, `on_initialize`). +/// The macro allows for a number of "arms", each representing an individual benchmark. Using the +/// simple syntax, the associated dispatchable function maps 1:1 with the benchmark and the name of +/// the benchmark is the same as that of the associated function. However, extended syntax allows +/// for arbitrary expresions to be evaluated in a benchmark (including for example, +/// `on_initialize`). /// -/// The macro allows for common parameters whose ranges and instancing -/// expressions may be drawn upon (or not) by each arm. Syntax is available to -/// allow for only the range to be drawn upon if desired, allowing an -/// alternative instancing expression to be given. +/// The macro allows for common parameters whose ranges and instancing expressions may be drawn upon +/// (or not) by each arm. Syntax is available to allow for only the range to be drawn upon if +/// desired, allowing an alternative instancing expression to be given. /// -/// Note that the ranges are *inclusive* on both sides. This is in contrast to -/// ranges in Rust which are left-inclusive right-exclusive. +/// Note that the ranges are *inclusive* on both sides. This is in contrast to ranges in Rust which +/// are left-inclusive right-exclusive. /// -/// Each arm may also have a block of code which is run prior to any instancing -/// and a block of code which is run afterwards. All code blocks may draw upon -/// the specific value of each parameter at any time. Local variables are shared -/// between the two pre- and post- code blocks, but do not leak from the -/// interior of any instancing expressions. +/// Each arm may also have a block of code which is run prior to any instancing and a block of code +/// which is run afterwards. All code blocks may draw upon the specific value of each parameter +/// at any time. Local variables are shared between the two pre- and post- code blocks, but do not +/// leak from the interior of any instancing expressions. /// -/// Any common parameters that are unused in an arm do not have their instancing -/// expressions evaluated. +/// Any common parameters that are unused in an arm do not have their instancing expressions +/// evaluated. /// /// Example: /// ```ignore @@ -82,11 +76,6 @@ pub use sp_runtime::traits::{Dispatchable, One, Zero}; /// // The constructed runtime struct, and the pallet to benchmark. /// { MyRuntime, my_pallet } /// -/// // common parameter; just one for this example. -/// // will be `1`, `MAX_LENGTH` or any value inbetween -/// _ { -/// let l in 1 .. MAX_LENGTH => initialize_l(l); -/// } /// /// // first dispatchable: foo; this is a user dispatchable and operates on a `u8` vector of /// // size `l`, which we allow to be initialized as usual. @@ -129,28 +118,26 @@ pub use sp_runtime::traits::{Dispatchable, One, Zero}; /// } /// ``` /// -/// Test functions are automatically generated for each benchmark and are -/// accessible to you when you run `cargo test`. All tests are named -/// `test_benchmark_`, expect you to pass them the Runtime -/// Trait, and run them in a test externalities environment. The test function -/// runs your benchmark just like a regular benchmark, but only testing at the -/// lowest and highest values for each component. The function will return -/// `Ok(())` if the benchmarks return no errors. +/// Test functions are automatically generated for each benchmark and are accessible to you when you +/// run `cargo test`. All tests are named `test_benchmark_`, expect you to pass them +/// the Runtime Trait, and run them in a test externalities environment. The test function runs your +/// benchmark just like a regular benchmark, but only testing at the lowest and highest values for +/// each component. The function will return `Ok(())` if the benchmarks return no errors. /// -/// You can optionally add a `verify` code block at the end of a benchmark to -/// test any final state of your benchmark in a unit test. For example: +/// You can optionally add a `verify` code block at the end of a benchmark to test any final state +/// of your benchmark in a unit test. For example: /// /// ```ignore /// sort_vector { -/// let x in 1 .. 10000; -/// let mut m = Vec::::new(); -/// for i in (0..x).rev() { -/// m.push(i); -/// } +/// let x in 1 .. 10000; +/// let mut m = Vec::::new(); +/// for i in (0..x).rev() { +/// m.push(i); +/// } /// }: { -/// m.sort(); +/// m.sort(); /// } verify { -/// ensure!(m[0] == 0, "You forgot to sort!") +/// ensure!(m[0] == 0, "You forgot to sort!") /// } /// ``` /// @@ -181,11 +168,12 @@ macro_rules! runtime_benchmarks { $( $rest:tt )* ) => { $crate::benchmarks_iter!( - NO_INSTANCE + { } $runtime $pallet { $( { $common , $common_from , $common_to , $common_instancer } )* } ( ) + ( ) $( $rest )* ); } @@ -204,11 +192,12 @@ macro_rules! runtime_benchmarks_instance { $( $rest:tt )* ) => { $crate::benchmarks_iter!( - $instance + { I } $runtime $pallet { $( { $common , $common_from , $common_to , $common_instancer } )* } ( ) + ( ) $( $rest )* ); } @@ -217,75 +206,79 @@ macro_rules! runtime_benchmarks_instance { #[macro_export] #[doc(hidden)] macro_rules! benchmarks_iter { - // mutation arm: + // detect and extract extra tag: ( - $instance:ident + { $( $instance:ident )? } $runtime:ident $pallet:ident { $( $common:tt )* } - ( $( $names:ident )* ) - $name:ident { $( $code:tt )* }: _ ( $origin:expr $( , $arg:expr )* ) - verify $postcode:block + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + #[extra] + $name:ident $( $rest:tt )* ) => { $crate::benchmarks_iter! { - $instance + { $( $instance)? } $runtime $pallet { $( $common )* } ( $( $names )* ) - $name { $( $code )* }: $name ( $origin $( , $arg )* ) - verify $postcode + ( $( $names_extra )* $name ) + $name $( $rest )* } }; - // no instance mutation arm: + // mutation arm: ( - NO_INSTANCE + { $( $instance:ident )? } $runtime:ident $pallet:ident { $( $common:tt )* } - ( $( $names:ident )* ) - $name:ident { $( $code:tt )* }: $dispatch:ident ( $origin:expr $( , $arg:expr )* ) + ( $( $names:tt )* ) // This contains $( $( { $instance } )? $name:ident )* + ( $( $names_extra:tt )* ) + $name:ident { $( $code:tt )* }: _ ( $origin:expr $( , $arg:expr )* ) verify $postcode:block $( $rest:tt )* ) => { $crate::benchmarks_iter! { - NO_INSTANCE + { $( $instance)? } $runtime $pallet { $( $common )* } ( $( $names )* ) - $name { $( $code )* }: { - < - $pallet::Call<$runtime> as $crate::frame_support::traits::UnfilteredDispatchable - >::dispatch_bypass_filter($pallet::Call::<$runtime>::$dispatch($($arg),*), $origin.into())?; - } + ( $( $names_extra )* ) + $name { $( $code )* }: $name ( $origin $( , $arg )* ) verify $postcode $( $rest )* } }; - // instance mutation arm: + // mutation arm: ( - $instance:ident + { $( $instance:ident )? } $runtime:ident $pallet:ident { $( $common:tt )* } - ( $( $names:ident )* ) + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) $name:ident { $( $code:tt )* }: $dispatch:ident ( $origin:expr $( , $arg:expr )* ) verify $postcode:block $( $rest:tt )* ) => { $crate::benchmarks_iter! { - $instance + { $( $instance)? } $runtime $pallet { $( $common )* } ( $( $names )* ) + ( $( $names_extra )* ) $name { $( $code )* }: { + ( $( $names_extra )* ) < - $pallet::Call<$runtime, $instance> as $crate::frame_support::traits::UnfilteredDispatchable - >::dispatch_bypass_filter($pallet::Call::<$runtime, $instance>::$dispatch($($arg),*), $origin.into())?; + $pallet::Call as $crate::frame_support::traits::UnfilteredDispatchable + >::dispatch_bypass_filter( + $pallet::Call::::$dispatch($($arg),*), $origin.into() + )?; } verify $postcode $( $rest )* @@ -293,20 +286,21 @@ macro_rules! benchmarks_iter { }; // iteration arm: ( - $instance:ident + { $( $instance:ident )? } $runtime:ident $pallet:ident { $( $common:tt )* } - ( $( $names:ident )* ) + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) $name:ident { $( $code:tt )* }: $eval:block verify $postcode:block $( $rest:tt )* ) => { $crate::benchmark_backend! { - $instance + { $( $instance)? } + $name $runtime $pallet - $name { $( $common )* } { } { $eval } @@ -315,38 +309,63 @@ macro_rules! benchmarks_iter { } #[cfg(test)] - $crate::impl_benchmark_test!($instance $runtime $pallet $name); + $crate::impl_benchmark_test!( + $runtime + $pallet + { $( $instance)? } + $name + ); $crate::benchmarks_iter!( - $instance + { $( $instance)? } $runtime $pallet { $( $common )* } - ( $( $names )* $name ) + ( $( $names )* { $( $instance )? } $name ) + ( $( $names_extra )* ) $( $rest )* ); }; // iteration-exit arm - ( $instance:ident $runtime:ident $pallet:ident { $( $common:tt )* } ( $( $names:ident )* ) ) => { - $crate::selected_benchmark!( $instance $runtime $pallet $( $names ),* ); - $crate::impl_benchmark!( $instance $runtime $pallet $( $names ),* ); + ( + { $( $instance:ident )? } + $runtime:ident + $pallet:ident + { $( $common:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ) => { + $crate::selected_benchmark!( + $runtime + $pallet + { $( $instance)? } + $( $names )* + ); + $crate::impl_benchmark!( + { $( $where_clause )* } + { $( $instance)? } + ( $( $names )* ) + ( $( $names_extra ),* ) + ); }; // add verify block to _() format ( - $instance:ident + { $( $instance:ident )? } $runtime:ident $pallet:ident { $( $common:tt )* } - ( $( $names:ident )* ) + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) $name:ident { $( $code:tt )* }: _ ( $origin:expr $( , $arg:expr )* ) $( $rest:tt )* ) => { $crate::benchmarks_iter! { - $instance + { $( $instance)? } $runtime $pallet { $( $common )* } ( $( $names )* ) + ( $( $names_extra )* ) $name { $( $code )* }: _ ( $origin $( , $arg )* ) verify { } $( $rest )* @@ -354,20 +373,22 @@ macro_rules! benchmarks_iter { }; // add verify block to name() format ( - $instance:ident + { $( $instance:ident )? } $runtime:ident $pallet:ident { $( $common:tt )* } - ( $( $names:ident )* ) + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) $name:ident { $( $code:tt )* }: $dispatch:ident ( $origin:expr $( , $arg:expr )* ) $( $rest:tt )* ) => { $crate::benchmarks_iter! { - $instance + { $( $instance)? } $runtime $pallet { $( $common )* } ( $( $names )* ) + ( $( $names_extra )* ) $name { $( $code )* }: $dispatch ( $origin $( , $arg )* ) verify { } $( $rest )* @@ -375,20 +396,22 @@ macro_rules! benchmarks_iter { }; // add verify block to {} format ( - $instance:ident + { $( $instance:ident )? } $runtime:ident $pallet:ident { $( $common:tt )* } - ( $( $names:ident )* ) + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) $name:ident { $( $code:tt )* }: $eval:block $( $rest:tt )* ) => { $crate::benchmarks_iter!( - $instance + { $( $instance)? } $runtime $pallet { $( $common )* } ( $( $names )* ) + ( $( $names_extra )* ) $name { $( $code )* }: $eval verify { } $( $rest )* @@ -400,7 +423,7 @@ macro_rules! benchmarks_iter { #[doc(hidden)] macro_rules! benchmark_backend { // parsing arms - ($instance:ident $runtime:ident $pallet:ident $name:ident { + ( { $( $instance:ident )? } $name:ident $runtime:ident $pallet:ident { $( $common:tt )* } { $( PRE { $( $pre_parsed:tt )* } )* @@ -409,13 +432,13 @@ macro_rules! benchmark_backend { $( $rest:tt )* } $postcode:block) => { $crate::benchmark_backend! { - $instance $runtime $pallet $name { $( $common )* } { + { $( $instance)? } $name $runtime $pallet { $( $common )* } { $( PRE { $( $pre_parsed )* } )* PRE { $pre_id , $pre_ty , $pre_ex } } { $eval } { $( $rest )* } $postcode } }; - ($instance:ident $runtime:ident $pallet:ident $name:ident { + ( { $( $instance:ident )? } $name:ident $runtime:ident $pallet:ident { $( $common:tt )* } { $( $parsed:tt )* @@ -424,14 +447,14 @@ macro_rules! benchmark_backend { $( $rest:tt )* } $postcode:block) => { $crate::benchmark_backend! { - $instance $runtime $pallet $name { $( $common )* } { + { $( $instance)? } $name $runtime $pallet { $( $common )* } { $( $parsed )* PARAM { $param , $param_from , $param_to , $param_instancer } } { $eval } { $( $rest )* } $postcode } }; // mutation arm to look after defaulting to a common param - ($instance:ident $runtime:ident $pallet:ident $name:ident { + ( { $( $instance:ident )? } $name:ident $runtime:ident $pallet:ident { $( { $common:ident , $common_from:tt , $common_to:expr , $common_instancer:expr } )* } { $( $parsed:tt )* @@ -440,7 +463,7 @@ macro_rules! benchmark_backend { $( $rest:tt )* } $postcode:block) => { $crate::benchmark_backend! { - $instance $runtime $pallet $name { + { $( $instance)? } $name $runtime $pallet { $( { $common , $common_from , $common_to , $common_instancer } )* } { $( $parsed )* @@ -454,7 +477,7 @@ macro_rules! benchmark_backend { } }; // mutation arm to look after defaulting only the range to common param - ($instance:ident $runtime:ident $pallet:ident $name:ident { + ( { $( $instance:ident )? } $name:ident $runtime:ident $pallet:ident { $( { $common:ident , $common_from:tt , $common_to:expr , $common_instancer:expr } )* } { $( $parsed:tt )* @@ -463,7 +486,7 @@ macro_rules! benchmark_backend { $( $rest:tt )* } $postcode:block) => { $crate::benchmark_backend! { - $instance $runtime $pallet $name { + { $( $instance)? } $name $runtime $pallet { $( { $common , $common_from , $common_to , $common_instancer } )* } { $( $parsed )* @@ -477,7 +500,7 @@ macro_rules! benchmark_backend { } }; // mutation arm to look after a single tt for param_from. - ($instance:ident $runtime:ident $pallet:ident $name:ident { + ( { $( $instance:ident )? } $name:ident $runtime:ident $pallet:ident { $( $common:tt )* } { $( $parsed:tt )* @@ -486,14 +509,15 @@ macro_rules! benchmark_backend { $( $rest:tt )* } $postcode:block) => { $crate::benchmark_backend! { - $instance $runtime $pallet $name { $( $common )* } { $( $parsed )* } { $eval } { + { $( $instance)? } + $name $runtime $pallet { $( $common )* } { $( $parsed )* } { $eval } { let $param in ( $param_from ) .. $param_to => $param_instancer; $( $rest )* } $postcode } }; // mutation arm to look after the default tail of `=> ()` - ($instance:ident $runtime:ident $pallet:ident $name:ident { + ( { $( $instance:ident )? } $name:ident $runtime:ident $pallet:ident { $( $common:tt )* } { $( $parsed:tt )* @@ -502,14 +526,15 @@ macro_rules! benchmark_backend { $( $rest:tt )* } $postcode:block) => { $crate::benchmark_backend! { - $instance $runtime $pallet $name { $( $common )* } { $( $parsed )* } { $eval } { + { $( $instance)? } + $name $runtime $pallet { $( $common )* } { $( $parsed )* } { $eval } { let $param in $param_from .. $param_to => (); $( $rest )* } $postcode } }; // mutation arm to look after `let _ =` - ($instance:ident $runtime:ident $pallet:ident $name:ident { + ( { $( $instance:ident )? } $name:ident $runtime:ident $pallet:ident { $( $common:tt )* } { $( $parsed:tt )* @@ -518,14 +543,15 @@ macro_rules! benchmark_backend { $( $rest:tt )* } $postcode:block) => { $crate::benchmark_backend! { - $instance $runtime $pallet $name { $( $common )* } { $( $parsed )* } { $eval } { + { $( $instance)? } + $name $runtime $pallet { $( $common )* } { $( $parsed )* } { $eval } { let $pre_id : _ = $pre_ex; $( $rest )* } $postcode } }; - // no instance actioning arm - (NO_INSTANCE $runtime:ident $pallet:ident $name:ident { + // actioning arm + ( { $( $instance:ident )? } $name:ident $runtime:ident $pallet:ident { $( { $common:ident , $common_from:tt , $common_to:expr , $common_instancer:expr } )* } { $( PRE { $pre_id:tt , $pre_ty:ty , $pre_ex:expr } )* @@ -534,7 +560,7 @@ macro_rules! benchmark_backend { #[allow(non_camel_case_types)] struct $name; #[allow(unused_variables)] - impl $crate::BenchmarkingSetup<$runtime> for $name { + impl<$( <$instance>, I: Instance)? > $crate::BenchmarkingSetup<$runtime $(, $instance)?> for $name { fn components(&self) -> Vec<($crate::BenchmarkParameter, u32, u32)> { vec! [ $( @@ -586,68 +612,6 @@ macro_rules! benchmark_backend { } } }; - // instance actioning arm - ($instance:ident $runtime:ident $pallet:ident $name:ident { - $( { $common:ident , $common_from:tt , $common_to:expr , $common_instancer:expr } )* - } { - $( PRE { $pre_id:tt , $pre_ty:ty , $pre_ex:expr } )* - $( PARAM { $param:ident , $param_from:expr , $param_to:expr , $param_instancer:expr } )* - } { $eval:block } { $( $post:tt )* } $postcode:block) => { - #[allow(non_camel_case_types)] - struct $name; - #[allow(unused_variables)] - impl $crate::BenchmarkingSetupInstance<$runtime, $instance> for $name { - fn components(&self) -> Vec<($crate::BenchmarkParameter, u32, u32)> { - vec! [ - $( - ($crate::BenchmarkParameter::$param, $param_from, $param_to) - ),* - ] - } - - fn instance(&self, components: &[($crate::BenchmarkParameter, u32)]) - -> Result Result<(), &'static str>>, &'static str> - { - $( - let $common = $common_from; - )* - $( - // Prepare instance - let $param = components.iter() - .find(|&c| c.0 == $crate::BenchmarkParameter::$param) - .unwrap().1; - )* - $( - let $pre_id : $pre_ty = $pre_ex; - )* - $( $param_instancer ; )* - $( $post )* - - Ok(Box::new(move || -> Result<(), &'static str> { $eval; Ok(()) })) - } - - fn verify(&self, components: &[($crate::BenchmarkParameter, u32)]) - -> Result Result<(), &'static str>>, &'static str> - { - $( - let $common = $common_from; - )* - $( - // Prepare instance - let $param = components.iter() - .find(|&c| c.0 == $crate::BenchmarkParameter::$param) - .unwrap().1; - )* - $( - let $pre_id : $pre_ty = $pre_ex; - )* - $( $param_instancer ; )* - $( $post )* - - Ok(Box::new(move || -> Result<(), &'static str> { $eval; $postcode; Ok(()) })) - } - } - } } // Creates a `SelectedBenchmark` enum implementing `BenchmarkingSetup`. @@ -655,20 +619,22 @@ macro_rules! benchmark_backend { // Every variant must implement [`BenchmarkingSetup`]. // // ```nocompile -// +// // struct Transfer; // impl BenchmarkingSetup for Transfer { ... } // // struct SetBalance; // impl BenchmarkingSetup for SetBalance { ... } // -// selected_benchmark!(Transfer, SetBalance); +// selected_benchmark!({} Transfer {} SetBalance); // ``` #[macro_export] #[doc(hidden)] macro_rules! selected_benchmark { ( - NO_INSTANCE $runtime:ident $pallet:ident $( $bench:ident ),* + $runtime:ident $pallet:ident + { $( $instance:ident )? } + $( { $( $bench_inst:ident )? } $bench:ident )* ) => { // The list of available benchmarks for this pallet. #[allow(non_camel_case_types)] @@ -677,10 +643,14 @@ macro_rules! selected_benchmark { } // Allow us to select a benchmark from the list of available benchmarks. - impl $crate::BenchmarkingSetup<$runtime> for SelectedBenchmark { + impl<$( <$instance>, I: Instance)? > $crate::BenchmarkingSetup<$runtime $(, $instance)?> for SelectedBenchmark { fn components(&self) -> Vec<($crate::BenchmarkParameter, u32, u32)> { match self { - $( Self::$bench => <$bench as $crate::BenchmarkingSetup<$runtime>>::components(&$bench), )* + $( + Self::$bench => < + $bench as $crate::BenchmarkingSetup<$runtime $(, $bench_inst)? > + >::components(&$bench), + )* } } @@ -688,7 +658,11 @@ macro_rules! selected_benchmark { -> Result Result<(), &'static str>>, &'static str> { match self { - $( Self::$bench => <$bench as $crate::BenchmarkingSetup<$runtime>>::instance(&$bench, components), )* + $( + Self::$bench => < + $bench as $crate::BenchmarkingSetup<$runtime $(, $bench_inst)? > + >::instance(&$bench, components), + )* } } @@ -696,200 +670,38 @@ macro_rules! selected_benchmark { -> Result Result<(), &'static str>>, &'static str> { match self { - $( Self::$bench => <$bench as $crate::BenchmarkingSetup<$runtime>>::verify(&$bench, components), )* + $( + Self::$bench => < + $bench as $crate::BenchmarkingSetup<$runtime $(, $bench_inst)? > + >::verify(&$bench, components), + )* } } } }; - ( - $instance:ident $runtime:ident $pallet:ident $( $bench:ident ),* - ) => { - // The list of available benchmarks for this pallet. - #[allow(non_camel_case_types)] - enum SelectedBenchmark { - $( $bench, )* - } - - // Allow us to select a benchmark from the list of available benchmarks. - impl $crate::BenchmarkingSetupInstance<$runtime, $instance> for SelectedBenchmark { - fn components(&self) -> Vec<($crate::BenchmarkParameter, u32, u32)> { - match self { - $( Self::$bench => <$bench as $crate::BenchmarkingSetupInstance<$runtime, $instance>>::components(&$bench), )* - } - } - - fn instance(&self, components: &[($crate::BenchmarkParameter, u32)]) - -> Result Result<(), &'static str>>, &'static str> - { - match self { - $( Self::$bench => <$bench as $crate::BenchmarkingSetupInstance<$runtime, $instance>>::instance(&$bench, components), )* - } - } - - fn verify(&self, components: &[($crate::BenchmarkParameter, u32)]) - -> Result Result<(), &'static str>>, &'static str> - { - match self { - $( Self::$bench => <$bench as $crate::BenchmarkingSetupInstance<$runtime, $instance>>::verify(&$bench, components), )* - } - } - } - } } #[macro_export] #[doc(hidden)] macro_rules! impl_benchmark { ( - NO_INSTANCE $runtime:ident $pallet:ident $( $name:ident ),* + $runtime:ident $pallet:ident + { $( $instance:ident )? } + ( $( { $( $name_inst:ident )? } $name:ident )* ) + ( $( $name_extra:ident ),* ) ) => { #[cfg(feature="runtime-benchmarks")] pub struct Benchmark; #[cfg(feature="runtime-benchmarks")] impl $crate::Benchmarking<$crate::BenchmarkResults> for Benchmark { - fn benchmarks() -> Vec<&'static [u8]> { - vec![ $( stringify!($name).as_ref() ),* ] - } - - fn run_benchmark( - extrinsic: &[u8], - lowest_range_values: &[u32], - highest_range_values: &[u32], - steps: &[u32], - repeat: u32, - whitelist: &[Vec] - ) -> Result, &'static str> { - // Map the input to the selected benchmark. - let extrinsic = sp_std::str::from_utf8(extrinsic) - .map_err(|_| "`extrinsic` is not a valid utf8 string!")?; - let selected_benchmark = match extrinsic { - $( stringify!($name) => SelectedBenchmark::$name, )* - _ => return Err("Could not find extrinsic."), - }; - - $crate::benchmarking::set_whitelist(whitelist.to_vec()); - - // Warm up the DB - $crate::benchmarking::commit_db(); - $crate::benchmarking::wipe_db(); - - let components = >::components(&selected_benchmark); - let mut results: Vec<$crate::BenchmarkResults> = Vec::new(); - - // Default number of steps for a component. - let mut prev_steps = 10; - - // Select the component we will be benchmarking. Each component will be benchmarked. - for (idx, (name, low, high)) in components.iter().enumerate() { - // Get the number of steps for this component. - let steps = steps.get(idx).cloned().unwrap_or(prev_steps); - prev_steps = steps; - - // Skip this loop if steps is zero - if steps == 0 { continue } - - let lowest = lowest_range_values.get(idx).cloned().unwrap_or(*low); - let highest = highest_range_values.get(idx).cloned().unwrap_or(*high); - - let diff = highest - lowest; - - // Create up to `STEPS` steps for that component between high and low. - let step_size = (diff / steps).max(1); - let num_of_steps = diff / step_size + 1; - - for s in 0..num_of_steps { - // This is the value we will be testing for component `name` - let component_value = lowest + step_size * s; - - // Select the max value for all the other components. - let c: Vec<($crate::BenchmarkParameter, u32)> = components.iter() - .enumerate() - .map(|(idx, (n, _, h))| - if n == name { - (*n, component_value) - } else { - (*n, *highest_range_values.get(idx).unwrap_or(h)) - } - ) - .collect(); - - // Run the benchmark `repeat` times. - for _ in 0..repeat { - // Set up the externalities environment for the setup we want to - // benchmark. - let closure_to_benchmark = < - SelectedBenchmark as $crate::BenchmarkingSetup<$runtime> - >::instance(&selected_benchmark, &c)?; - - // Set the block number to at least 1 so events are deposited. - if $crate::Zero::is_zero(&frame_system::Module::<$runtime>::block_number()) { - frame_system::Module::<$runtime>::set_block_number(1u8.into()); - } - - // Commit the externalities to the database, flushing the DB cache. - // This will enable worst case scenario for reading from the database. - $crate::benchmarking::commit_db(); - - $crate::benchmarking::reset_read_write_count(); - - // Time the extrinsic logic. - frame_support::debug::trace!( - target: "benchmark", - "Start Benchmark: {:?} {:?}", name, component_value - ); - - let start_extrinsic = $crate::benchmarking::current_time(); - closure_to_benchmark()?; - let finish_extrinsic = $crate::benchmarking::current_time(); - let elapsed_extrinsic = finish_extrinsic - start_extrinsic; - - $crate::benchmarking::commit_db(); - frame_support::debug::trace!( - target: "benchmark", - "End Benchmark: {} ns", elapsed_extrinsic - ); - let read_write_count = $crate::benchmarking::read_write_count(); - frame_support::debug::trace!( - target: "benchmark", - "Read/Write Count {:?}", read_write_count - ); - - // Time the storage root recalculation. - let start_storage_root = $crate::benchmarking::current_time(); - $crate::storage_root(); - let finish_storage_root = $crate::benchmarking::current_time(); - let elapsed_storage_root = finish_storage_root - start_storage_root; - - results.push($crate::BenchmarkResults { - components: c.clone(), - extrinsic_time: elapsed_extrinsic, - storage_root_time: elapsed_storage_root, - reads: read_write_count.0, - repeat_reads: read_write_count.1, - writes: read_write_count.2, - repeat_writes: read_write_count.3, - }); - - // Wipe the DB back to the genesis state. - $crate::benchmarking::wipe_db(); - } - } + fn benchmarks(extra: bool) -> Vec<&'static [u8]> { + let mut all = vec![ $( stringify!($name).as_ref() ),* ]; + if !extra { + let extra = [ $( stringify!($name_extra).as_ref() ),* ]; + all.retain(|x| !extra.contains(x)); } - return Ok(results); - } - } - }; - ( - $instance:ident $runtime:ident $pallet:ident $( $name:ident ),* - ) => { - #[cfg(feature="runtime-benchmarks")] - pub struct Benchmark; - - #[cfg(feature="runtime-benchmarks")] - impl $crate::Benchmarking<$crate::BenchmarkResults> for Benchmark { - fn benchmarks() -> Vec<&'static [u8]> { - vec![ $( stringify!($name).as_ref() ),* ] + all } fn run_benchmark( @@ -908,6 +720,7 @@ macro_rules! impl_benchmark { _ => return Err("Could not find extrinsic."), }; + // Add whitelist to DB $crate::benchmarking::set_whitelist(whitelist.to_vec()); // Warm up the DB @@ -915,112 +728,128 @@ macro_rules! impl_benchmark { $crate::benchmarking::wipe_db(); let components = < - SelectedBenchmark as $crate::BenchmarkingSetupInstance<$runtime, $instance> + SelectedBenchmark as $crate::BenchmarkingSetup<$runtime $(, $instance)?> >::components(&selected_benchmark); let mut results: Vec<$crate::BenchmarkResults> = Vec::new(); // Default number of steps for a component. let mut prev_steps = 10; - // Select the component we will be benchmarking. Each component will be benchmarked. - for (idx, (name, low, high)) in components.iter().enumerate() { - // Get the number of steps for this component. - let steps = steps.get(idx).cloned().unwrap_or(prev_steps); - prev_steps = steps; - - // Skip this loop if steps is zero - if steps == 0 { continue } - - let lowest = lowest_range_values.get(idx).cloned().unwrap_or(*low); - let highest = highest_range_values.get(idx).cloned().unwrap_or(*high); - - let diff = highest - lowest; - - // Create up to `STEPS` steps for that component between high and low. - let step_size = (diff / steps).max(1); - let num_of_steps = diff / step_size + 1; - - for s in 0..num_of_steps { - // This is the value we will be testing for component `name` - let component_value = lowest + step_size * s; - - // Select the max value for all the other components. - let c: Vec<($crate::BenchmarkParameter, u32)> = components.iter() - .enumerate() - .map(|(idx, (n, _, h))| - if n == name { - (*n, component_value) - } else { - (*n, *highest_range_values.get(idx).unwrap_or(h)) - } - ) - .collect(); - - // Run the benchmark `repeat` times. - for _ in 0..repeat { - // Set up the externalities environment for the setup we want to benchmark. - let closure_to_benchmark = < - SelectedBenchmark as $crate::BenchmarkingSetupInstance<$runtime, $instance> - >::instance(&selected_benchmark, &c)?; - - // Set the block number to at least 1 so events are deposited. - if $crate::Zero::is_zero(&frame_system::Module::<$runtime>::block_number()) { - frame_system::Module::<$runtime>::set_block_number($crate::One::one()); - } + let repeat_benchmark = | + repeat: u32, + c: Vec<($crate::BenchmarkParameter, u32)>, + results: &mut Vec<$crate::BenchmarkResults>, + | -> Result<(), &'static str> { + // Run the benchmark `repeat` times. + for _ in 0..repeat { + // Set up the externalities environment for the setup we want to + // benchmark. + let closure_to_benchmark = < + SelectedBenchmark as $crate::BenchmarkingSetup<$runtime $(, $instance)?> + >::instance(&selected_benchmark, &c)?; - // Commit the externalities to the database, flushing the DB cache. - // This will enable worst case scenario for reading from the database. - $crate::benchmarking::commit_db(); - - // Reset the read/write counter so we don't count operations in the setup process. - $crate::benchmarking::reset_read_write_count(); - - // Time the extrinsic logic. - frame_support::debug::trace!( - target: "benchmark", - "Start Benchmark: {:?} {:?}", name, component_value - ); - let start_extrinsic = $crate::benchmarking::current_time(); - closure_to_benchmark()?; - let finish_extrinsic = $crate::benchmarking::current_time(); - let elapsed_extrinsic = finish_extrinsic - start_extrinsic; - - $crate::benchmarking::commit_db(); - frame_support::debug::trace!( - target: "benchmark", - "End Benchmark: {} ns", elapsed_extrinsic - ); - let read_write_count = $crate::benchmarking::read_write_count(); - frame_support::debug::trace!( - target: "benchmark", - "Read/Write Count {:?}", read_write_count - ); + // Set the block number to at least 1 so events are deposited. + if $crate::Zero::is_zero(&frame_system::Module::::block_number()) { + frame_system::Module::::set_block_number(1.into()); + } - // Time the storage root recalculation. - let start_storage_root = $crate::benchmarking::current_time(); - $crate::storage_root(); - let finish_storage_root = $crate::benchmarking::current_time(); - let elapsed_storage_root = finish_storage_root - start_storage_root; + // Commit the externalities to the database, flushing the DB cache. + // This will enable worst case scenario for reading from the database. + $crate::benchmarking::commit_db(); + + // Reset the read/write counter so we don't count operations in the setup process. + $crate::benchmarking::reset_read_write_count(); + + // Time the extrinsic logic. + frame_support::debug::trace!( + target: "benchmark", + "Start Benchmark: {:?}", c + ); + + let start_extrinsic = $crate::benchmarking::current_time(); + closure_to_benchmark()?; + let finish_extrinsic = $crate::benchmarking::current_time(); + let elapsed_extrinsic = finish_extrinsic - start_extrinsic; + // Commit the changes to get proper write count + $crate::benchmarking::commit_db(); + frame_support::debug::trace!( + target: "benchmark", + "End Benchmark: {} ns", elapsed_extrinsic + ); + let read_write_count = $crate::benchmarking::read_write_count(); + frame_support::debug::trace!( + target: "benchmark", + "Read/Write Count {:?}", read_write_count + ); + + // Time the storage root recalculation. + let start_storage_root = $crate::benchmarking::current_time(); + $crate::storage_root(); + let finish_storage_root = $crate::benchmarking::current_time(); + let elapsed_storage_root = finish_storage_root - start_storage_root; + + results.push($crate::BenchmarkResults { + components: c.clone(), + extrinsic_time: elapsed_extrinsic, + storage_root_time: elapsed_storage_root, + reads: read_write_count.0, + repeat_reads: read_write_count.1, + writes: read_write_count.2, + repeat_writes: read_write_count.3, + }); + + // Wipe the DB back to the genesis state. + $crate::benchmarking::wipe_db(); + } - results.push($crate::BenchmarkResults { - components: c.clone(), - extrinsic_time: elapsed_extrinsic, - storage_root_time: elapsed_storage_root, - reads: read_write_count.0, - repeat_reads: read_write_count.1, - writes: read_write_count.2, - repeat_writes: read_write_count.3, - }); + Ok(()) + }; - // Wipe the DB back to the genesis state. - $crate::benchmarking::wipe_db(); + if components.is_empty() { + repeat_benchmark(repeat, Default::default(), &mut results)?; + } else { + // Select the component we will be benchmarking. Each component will be benchmarked. + for (idx, (name, low, high)) in components.iter().enumerate() { + // Get the number of steps for this component. + let steps = steps.get(idx).cloned().unwrap_or(prev_steps); + prev_steps = steps; + + // Skip this loop if steps is zero + if steps == 0 { continue } + + let lowest = lowest_range_values.get(idx).cloned().unwrap_or(*low); + let highest = highest_range_values.get(idx).cloned().unwrap_or(*high); + + let diff = highest - lowest; + + // Create up to `STEPS` steps for that component between high and low. + let step_size = (diff / steps).max(1); + let num_of_steps = diff / step_size + 1; + + for s in 0..num_of_steps { + // This is the value we will be testing for component `name` + let component_value = lowest + step_size * s; + + // Select the max value for all the other components. + let c: Vec<($crate::BenchmarkParameter, u32)> = components.iter() + .enumerate() + .map(|(idx, (n, _, h))| + if n == name { + (*n, component_value) + } else { + (*n, *highest_range_values.get(idx).unwrap_or(h)) + } + ) + .collect(); + + repeat_benchmark(repeat, c, &mut results)?; } } } return Ok(results); } } - } + }; } // This creates a unit test for one benchmark of the main benchmark macro. @@ -1030,9 +859,8 @@ macro_rules! impl_benchmark { #[doc(hidden)] macro_rules! impl_benchmark_test { ( - NO_INSTANCE - $runtime:ident - $pallet:ident + $runtime:ident $pallet:ident + { $( $instance:ident )? } $name:ident ) => { $crate::paste::item! { @@ -1040,93 +868,51 @@ macro_rules! impl_benchmark_test { { let selected_benchmark = SelectedBenchmark::$name; let components = < - SelectedBenchmark as $crate::BenchmarkingSetup<$runtime> + SelectedBenchmark as $crate::BenchmarkingSetup >::components(&selected_benchmark); - // assert!( - // components.len() != 0, - // "You need to add components to your benchmark!", - // ); - for (_, (name, low, high)) in components.iter().enumerate() { - // Test only the low and high value, assuming values in the middle won't break - for component_value in vec![low, high] { - // Select the max value for all the other components. - let c: Vec<($crate::BenchmarkParameter, u32)> = components.iter() - .enumerate() - .map(|(_, (n, _, h))| - if n == name { - (*n, *component_value) - } else { - (*n, *h) - } - ) - .collect(); - - // Set up the verification state - let closure_to_verify = < - SelectedBenchmark as $crate::BenchmarkingSetup<$runtime> - >::verify(&selected_benchmark, &c)?; - - // Set the block number to at least 1 so events are deposited. - if $crate::Zero::is_zero(&frame_system::Module::<$runtime>::block_number()) { - frame_system::Module::<$runtime>::set_block_number($crate::One::one()); - } - - // Run verification - closure_to_verify()?; - - // Reset the state - $crate::benchmarking::wipe_db(); + let execute_benchmark = | + c: Vec<($crate::BenchmarkParameter, u32)> + | -> Result<(), &'static str> { + // Set up the verification state + let closure_to_verify = < + SelectedBenchmark as $crate::BenchmarkingSetup + >::verify(&selected_benchmark, &c)?; + + // Set the block number to at least 1 so events are deposited. + if $crate::Zero::is_zero(&frame_system::Module::<$runtime>::block_number()) { + frame_system::Module::<$runtime>::set_block_number(1.into()); } - } - Ok(()) - } - } - }; - ( - $instance:ident - $runtime:ident - $pallet:ident - $name:ident - ) => { - $crate::paste::item! { - fn [] () -> Result<(), &'static str> - { - let selected_benchmark = SelectedBenchmark::$name; - let components = < - SelectedBenchmark as $crate::BenchmarkingSetupInstance<$runtime, $instance> - >::components(&selected_benchmark); - - for (_, (name, low, high)) in components.iter().enumerate() { - // Test only the low and high value, assuming values in the middle won't break - for component_value in vec![low, high] { - // Select the max value for all the other components. - let c: Vec<($crate::BenchmarkParameter, u32)> = components.iter() - .enumerate() - .map(|(_, (n, _, h))| - if n == name { - (*n, *component_value) - } else { - (*n, *h) - } - ) - .collect(); - // Set up the verification state - let closure_to_verify = < - SelectedBenchmark as $crate::BenchmarkingSetupInstance<$runtime, $instance> - >::verify(&selected_benchmark, &c)?; + // Run verification + closure_to_verify()?; - // Set the block number to at least 1 so events are deposited. - if $crate::Zero::is_zero(&frame_system::Module::<$runtime>::block_number()) { - frame_system::Module::<$runtime>::set_block_number($crate::One::one()); - } + // Reset the state + $crate::benchmarking::wipe_db(); - // Run verification - closure_to_verify()?; + Ok(()) + }; - // Reset the state - $crate::benchmarking::wipe_db(); + if components.is_empty() { + execute_benchmark(Default::default())?; + } else { + for (_, (name, low, high)) in components.iter().enumerate() { + // Test only the low and high value, assuming values in the middle won't break + for component_value in vec![low, high] { + // Select the max value for all the other components. + let c: Vec<($crate::BenchmarkParameter, u32)> = components.iter() + .enumerate() + .map(|(_, (n, _, h))| + if n == name { + (*n, *component_value) + } else { + (*n, *h) + } + ) + .collect(); + + execute_benchmark(c)?; + } } } Ok(()) @@ -1135,38 +921,54 @@ macro_rules! impl_benchmark_test { }; } + /// This macro adds pallet benchmarks to a `Vec` object. /// /// First create an object that holds in the input parameters for the benchmark: /// /// ```ignore -/// let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat); +/// let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat, &whitelist); /// ``` /// +/// The `whitelist` is a `Vec>` of storage keys that you would like to skip for DB tracking. For example: +/// +/// ```ignore +/// let whitelist: Vec> = vec![ +/// // Block Number +/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec(), +/// // Total Issuance +/// hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec(), +/// // Execution Phase +/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec(), +/// // Event Count +/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec(), +/// ]; +/// /// Then define a mutable local variable to hold your `BenchmarkBatch` object: /// /// ```ignore /// let mut batches = Vec::::new(); /// ```` /// -/// Then add the pallets you want to benchmark to this object, including the -/// string you want to use target a particular pallet: +/// Then add the pallets you want to benchmark to this object, using their crate name and generated +/// module struct: /// /// ```ignore -/// add_benchmark!(params, batches, b"balances", Balances); -/// add_benchmark!(params, batches, b"identity", Identity); -/// add_benchmark!(params, batches, b"session", SessionBench::); +/// add_benchmark!(params, batches, pallet_balances, Balances); +/// add_benchmark!(params, batches, pallet_session, SessionBench::); +/// add_benchmark!(params, batches, frame_system, SystemBench::); /// ... /// ``` /// /// At the end of `dispatch_benchmark`, you should return this batches object. #[macro_export] macro_rules! add_benchmark { - ( $params:ident, $batches:ident, $name:literal, $( $location:tt )* ) => ( - let (pallet, benchmark, lowest_range_values, highest_range_values, steps, repeat, whitelist) = $params; - if &pallet[..] == &$name[..] || &pallet[..] == &b"*"[..] { + ( $params:ident, $batches:ident, $name:ident, $( $location:tt )* ) => ( + let name_string = stringify!($name).as_bytes(); + let (pallet, benchmark, lowest_range_values, highest_range_values, steps, repeat, whitelist, extra) = $params; + if &pallet[..] == &name_string[..] || &pallet[..] == &b"*"[..] { if &pallet[..] == &b"*"[..] || &benchmark[..] == &b"*"[..] { - for benchmark in $( $location )*::Benchmark::benchmarks().into_iter() { + for benchmark in $( $location )*::Benchmark::benchmarks(extra).into_iter() { $batches.push($crate::BenchmarkBatch { results: $( $location )*::Benchmark::run_benchmark( benchmark, @@ -1176,7 +978,7 @@ macro_rules! add_benchmark { repeat, whitelist, )?, - pallet: $name.to_vec(), + pallet: name_string.to_vec(), benchmark: benchmark.to_vec(), }); } @@ -1190,7 +992,7 @@ macro_rules! add_benchmark { repeat, whitelist, )?, - pallet: $name.to_vec(), + pallet: name_string.to_vec(), benchmark: benchmark.clone(), }); } From 1067fc84ddd63ad228b9c6c6831ee8a8d52529ce Mon Sep 17 00:00:00 2001 From: Shaopeng Wang Date: Tue, 11 Aug 2020 13:56:08 +1200 Subject: [PATCH 03/14] fmt --- benchmarking/src/lib.rs | 85 ++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/benchmarking/src/lib.rs b/benchmarking/src/lib.rs index 8907897a7..cf6912ff0 100644 --- a/benchmarking/src/lib.rs +++ b/benchmarking/src/lib.rs @@ -10,21 +10,23 @@ pub use frame_benchmarking::{ }; #[cfg(feature = "std")] pub use frame_benchmarking::{Analysis, BenchmarkSelector}; +pub use frame_support; +pub use paste; #[doc(hidden)] pub use sp_io::storage::root as storage_root; pub use sp_runtime::traits::Zero; -pub use frame_support; -pub use paste; /// Construct pallet benchmarks for weighing dispatchables. /// -/// Works around the idea of complexity parameters, named by a single letter (which is usually -/// upper cased in complexity notation but is lower-cased for use in this macro). +/// Works around the idea of complexity parameters, named by a single letter +/// (which is usually upper cased in complexity notation but is lower-cased for +/// use in this macro). /// -/// Complexity parameters ("parameters") have a range which is a `u32` pair. Every time a benchmark -/// is prepared and run, this parameter takes a concrete value within the range. There is an -/// associated instancing block, which is a single expression that is evaluated during -/// preparation. It may use `?` (`i.e. `return Err(...)`) to bail with a string error. Here's a +/// Complexity parameters ("parameters") have a range which is a `u32` pair. +/// Every time a benchmark is prepared and run, this parameter takes a concrete +/// value within the range. There is an associated instancing block, which is a +/// single expression that is evaluated during preparation. It may use `?` +/// (`i.e. `return Err(...)`) to bail with a string error. Here's a /// few examples: /// /// ```ignore @@ -41,29 +43,32 @@ pub use paste; /// } /// ``` /// -/// Note that due to parsing restrictions, if the `from` expression is not a single token (i.e. a -/// literal or constant), then it must be parenthesised. +/// Note that due to parsing restrictions, if the `from` expression is not a +/// single token (i.e. a literal or constant), then it must be parenthesised. /// -/// The macro allows for a number of "arms", each representing an individual benchmark. Using the -/// simple syntax, the associated dispatchable function maps 1:1 with the benchmark and the name of -/// the benchmark is the same as that of the associated function. However, extended syntax allows -/// for arbitrary expresions to be evaluated in a benchmark (including for example, -/// `on_initialize`). +/// The macro allows for a number of "arms", each representing an individual +/// benchmark. Using the simple syntax, the associated dispatchable function +/// maps 1:1 with the benchmark and the name of the benchmark is the same as +/// that of the associated function. However, extended syntax allows +/// for arbitrary expresions to be evaluated in a benchmark (including for +/// example, `on_initialize`). /// -/// The macro allows for common parameters whose ranges and instancing expressions may be drawn upon -/// (or not) by each arm. Syntax is available to allow for only the range to be drawn upon if -/// desired, allowing an alternative instancing expression to be given. +/// The macro allows for common parameters whose ranges and instancing +/// expressions may be drawn upon (or not) by each arm. Syntax is available to +/// allow for only the range to be drawn upon if desired, allowing an +/// alternative instancing expression to be given. /// -/// Note that the ranges are *inclusive* on both sides. This is in contrast to ranges in Rust which -/// are left-inclusive right-exclusive. +/// Note that the ranges are *inclusive* on both sides. This is in contrast to +/// ranges in Rust which are left-inclusive right-exclusive. /// -/// Each arm may also have a block of code which is run prior to any instancing and a block of code -/// which is run afterwards. All code blocks may draw upon the specific value of each parameter -/// at any time. Local variables are shared between the two pre- and post- code blocks, but do not -/// leak from the interior of any instancing expressions. +/// Each arm may also have a block of code which is run prior to any instancing +/// and a block of code which is run afterwards. All code blocks may draw upon +/// the specific value of each parameter at any time. Local variables are shared +/// between the two pre- and post- code blocks, but do not leak from the +/// interior of any instancing expressions. /// -/// Any common parameters that are unused in an arm do not have their instancing expressions -/// evaluated. +/// Any common parameters that are unused in an arm do not have their instancing +/// expressions evaluated. /// /// Example: /// ```ignore @@ -118,14 +123,16 @@ pub use paste; /// } /// ``` /// -/// Test functions are automatically generated for each benchmark and are accessible to you when you -/// run `cargo test`. All tests are named `test_benchmark_`, expect you to pass them -/// the Runtime Trait, and run them in a test externalities environment. The test function runs your -/// benchmark just like a regular benchmark, but only testing at the lowest and highest values for -/// each component. The function will return `Ok(())` if the benchmarks return no errors. +/// Test functions are automatically generated for each benchmark and are +/// accessible to you when you run `cargo test`. All tests are named +/// `test_benchmark_`, expect you to pass them the Runtime +/// Trait, and run them in a test externalities environment. The test function +/// runs your benchmark just like a regular benchmark, but only testing at the +/// lowest and highest values for each component. The function will return +/// `Ok(())` if the benchmarks return no errors. /// -/// You can optionally add a `verify` code block at the end of a benchmark to test any final state -/// of your benchmark in a unit test. For example: +/// You can optionally add a `verify` code block at the end of a benchmark to +/// test any final state of your benchmark in a unit test. For example: /// /// ```ignore /// sort_vector { @@ -619,7 +626,7 @@ macro_rules! benchmark_backend { // Every variant must implement [`BenchmarkingSetup`]. // // ```nocompile -// +// // struct Transfer; // impl BenchmarkingSetup for Transfer { ... } // @@ -921,7 +928,6 @@ macro_rules! impl_benchmark_test { }; } - /// This macro adds pallet benchmarks to a `Vec` object. /// /// First create an object that holds in the input parameters for the benchmark: @@ -930,7 +936,8 @@ macro_rules! impl_benchmark_test { /// let params = (&pallet, &benchmark, &lowest_range_values, &highest_range_values, &steps, repeat, &whitelist); /// ``` /// -/// The `whitelist` is a `Vec>` of storage keys that you would like to skip for DB tracking. For example: +/// The `whitelist` is a `Vec>` of storage keys that you would like to +/// skip for DB tracking. For example: /// /// ```ignore /// let whitelist: Vec> = vec![ @@ -945,21 +952,19 @@ macro_rules! impl_benchmark_test { /// ]; /// /// Then define a mutable local variable to hold your `BenchmarkBatch` object: -/// /// ```ignore /// let mut batches = Vec::::new(); /// ```` -/// +/// /// Then add the pallets you want to benchmark to this object, using their crate name and generated /// module struct: -/// /// ```ignore /// add_benchmark!(params, batches, pallet_balances, Balances); /// add_benchmark!(params, batches, pallet_session, SessionBench::); /// add_benchmark!(params, batches, frame_system, SystemBench::); /// ... /// ``` -/// +/// /// At the end of `dispatch_benchmark`, you should return this batches object. #[macro_export] macro_rules! add_benchmark { From 8d34af4903b18d48c5942776186a0ee5698f8574 Mon Sep 17 00:00:00 2001 From: Shaopeng Wang Date: Tue, 11 Aug 2020 14:17:32 +1200 Subject: [PATCH 04/14] fix benchmarking macro --- benchmarking/src/lib.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/benchmarking/src/lib.rs b/benchmarking/src/lib.rs index cf6912ff0..83d23de17 100644 --- a/benchmarking/src/lib.rs +++ b/benchmarking/src/lib.rs @@ -280,11 +280,11 @@ macro_rules! benchmarks_iter { ( $( $names )* ) ( $( $names_extra )* ) $name { $( $code )* }: { - ( $( $names_extra )* ) < - $pallet::Call as $crate::frame_support::traits::UnfilteredDispatchable - >::dispatch_bypass_filter( - $pallet::Call::::$dispatch($($arg),*), $origin.into() + $pallet::Call<$runtime $(, $instance)? > as $crate::frame_support::traits::UnfilteredDispatchable + > + ::dispatch_bypass_filter( + $pallet::Call::<$runtime $(, $instance)? >::$dispatch($($arg),*), $origin.into() )?; } verify $postcode @@ -349,7 +349,8 @@ macro_rules! benchmarks_iter { $( $names )* ); $crate::impl_benchmark!( - { $( $where_clause )* } + $runtime + $pallet { $( $instance)? } ( $( $names )* ) ( $( $names_extra ),* ) @@ -757,7 +758,7 @@ macro_rules! impl_benchmark { // Set the block number to at least 1 so events are deposited. if $crate::Zero::is_zero(&frame_system::Module::::block_number()) { - frame_system::Module::::set_block_number(1.into()); + frame_system::Module::::set_block_number(1u8.into()); } // Commit the externalities to the database, flushing the DB cache. @@ -875,7 +876,7 @@ macro_rules! impl_benchmark_test { { let selected_benchmark = SelectedBenchmark::$name; let components = < - SelectedBenchmark as $crate::BenchmarkingSetup + SelectedBenchmark as $crate::BenchmarkingSetup<$runtime, _> >::components(&selected_benchmark); let execute_benchmark = | @@ -883,12 +884,12 @@ macro_rules! impl_benchmark_test { | -> Result<(), &'static str> { // Set up the verification state let closure_to_verify = < - SelectedBenchmark as $crate::BenchmarkingSetup + SelectedBenchmark as $crate::BenchmarkingSetup<$runtime, _> >::verify(&selected_benchmark, &c)?; // Set the block number to at least 1 so events are deposited. if $crate::Zero::is_zero(&frame_system::Module::<$runtime>::block_number()) { - frame_system::Module::<$runtime>::set_block_number(1.into()); + frame_system::Module::<$runtime>::set_block_number(1u8.into()); } // Run verification From 3023e32b700edc739c97d3efaa1d240c8d8e71d0 Mon Sep 17 00:00:00 2001 From: Shaopeng Wang Date: Tue, 11 Aug 2020 14:29:48 +1200 Subject: [PATCH 05/14] make clippy happy --- benchmarking/src/lib.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/benchmarking/src/lib.rs b/benchmarking/src/lib.rs index 83d23de17..4f6d942b2 100644 --- a/benchmarking/src/lib.rs +++ b/benchmarking/src/lib.rs @@ -136,15 +136,15 @@ pub use sp_runtime::traits::Zero; /// /// ```ignore /// sort_vector { -/// let x in 1 .. 10000; -/// let mut m = Vec::::new(); -/// for i in (0..x).rev() { -/// m.push(i); -/// } +/// let x in 1 .. 10000; +/// let mut m = Vec::::new(); +/// for i in (0..x).rev() { +/// m.push(i); +/// } /// }: { -/// m.sort(); +/// m.sort(); /// } verify { -/// ensure!(m[0] == 0, "You forgot to sort!") +/// ensure!(m[0] == 0, "You forgot to sort!") /// } /// ``` /// @@ -942,14 +942,14 @@ macro_rules! impl_benchmark_test { /// /// ```ignore /// let whitelist: Vec> = vec![ -/// // Block Number -/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec(), -/// // Total Issuance -/// hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec(), -/// // Execution Phase -/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec(), -/// // Event Count -/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec(), +/// // Block Number +/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec(), +/// // Total Issuance +/// hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec(), +/// // Execution Phase +/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec(), +/// // Event Count +/// hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec(), /// ]; /// /// Then define a mutable local variable to hold your `BenchmarkBatch` object: From 13e8e49224712962d8abd08b5fe99fc6e4f6142b Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Wed, 12 Aug 2020 17:45:37 +1200 Subject: [PATCH 06/14] add xtokens --- Cargo.dev.toml | 1 + xtokens/Cargo.toml | 40 ++++++++++++++++ xtokens/README.md | 5 ++ xtokens/src/lib.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 xtokens/Cargo.toml create mode 100644 xtokens/README.md create mode 100644 xtokens/src/lib.rs diff --git a/Cargo.dev.toml b/Cargo.dev.toml index 76d67fe61..dc29db2bf 100644 --- a/Cargo.dev.toml +++ b/Cargo.dev.toml @@ -10,4 +10,5 @@ members = [ "traits", "utilities", "vesting", + "xtokens", ] diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml new file mode 100644 index 000000000..3831a7b92 --- /dev/null +++ b/xtokens/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "orml-xtokens" +description = "Crosschain token transfer" +repository = "https://github.com/open-web3-stack/open-runtime-module-library/tree/master/tokens" +license = "Apache-2.0" +version = "0.1.3-dev" +authors = ["Laminar Developers "] +edition = "2018" + +[dependencies] +serde = { version = "1.0.111", optional = true } +codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false } +sp-runtime = { version = "2.0.0-rc5", default-features = false } +sp-io = { version = "2.0.0-rc5", default-features = false } +sp-std = { version = "2.0.0-rc5", default-features = false } + +frame-support = { version = "2.0.0-rc5", default-features = false } +frame-system = { version = "2.0.0-rc5", default-features = false } + +orml-traits = { path = "../traits", version = "0.1.3-dev", default-features = false } + +cumulus-primitives = { git = "https://github.com/paritytech/cumulus", branch = "bkchr-next-key" } + +[dev-dependencies] +sp-core = { version = "2.0.0-rc5", default-features = false } + +clear_on_drop = { version = "0.2.4", features = ["no_cc"] } # https://github.com/paritytech/substrate/issues/4179 + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", +] diff --git a/xtokens/README.md b/xtokens/README.md new file mode 100644 index 000000000..d54bf65a9 --- /dev/null +++ b/xtokens/README.md @@ -0,0 +1,5 @@ +# XTokens Module + +## Overview + +Provide basic cross-chain token transfer functionality. diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs new file mode 100644 index 000000000..6091c0a01 --- /dev/null +++ b/xtokens/src/lib.rs @@ -0,0 +1,113 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use frame_support::{decl_error, decl_event, decl_module, decl_storage, Parameter}; +use frame_system::ensure_signed; +use orml_traits::MultiCurrency; +use sp_runtime::traits::{AtLeast32Bit, CheckedSub, MaybeSerializeDeserialize, Member}; +use sp_std::{ + convert::{TryFrom, TryInto}, + prelude::*, +}; + +use cumulus_primitives::{ + xcmp::{XCMPMessageHandler, XCMPMessageSender}, + ParaId, +}; + +#[derive(Encode, Decode)] +pub enum XCMPMessage { + /// Transfer tokens to the given account from the Parachain account. + Transfer(Vec, Balance, AccountId), +} + +pub trait Trait: frame_system::Trait { + type Event: From> + Into<::Event>; + + /// The balance type + type Balance: Parameter + Member + AtLeast32Bit + Default + Copy + MaybeSerializeDeserialize; + + /// The currency ID type + type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + Into> + TryFrom>; + + type Currency: MultiCurrency; + + type XCMPMessageSender: XCMPMessageSender>; +} + +decl_storage! { + trait Store for Module as Tokens { + UnknownBalances: double_map hasher(blake2_128_concat) T::AccountId, hasher(blake2_128_concat) Vec => T::Balance; + } +} + +decl_event!( + pub enum Event where + ::AccountId, + ::Balance + { + TransferToParachain(ParaId, Vec, AccountId, Balance), + ReceivedTransferFromParachain(ParaId, Vec, AccountId, Balance), + } +); + +decl_error! { + /// Error for token module. + pub enum Error for Module { + TooLow + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + type Error = Error; + + fn deposit_event() = default; + + #[weight = 10] + fn transfer_to_parachain(origin, currency_id: T::CurrencyId, para_id: ParaId, dest: T::AccountId, amount: T::Balance) { + let who = ensure_signed(origin)?; + + let _ = T::Currency::withdraw(currency_id, &who, amount)?; + + let asset_id: Vec = currency_id.into(); + + T::XCMPMessageSender::send_xcmp_message(para_id, &XCMPMessage::Transfer(asset_id.clone(), amount, dest.clone())).expect("should not fail"); + + Self::deposit_event(Event::::TransferToParachain(para_id, asset_id, dest, amount)); + } + + #[weight = 10] + fn transfer_unknown_asset_to_parachain(origin, id: Vec, para_id: ParaId, dest: T::AccountId, amount: T::Balance) { + let who = ensure_signed(origin)?; + + UnknownBalances::::try_mutate(who, &id, |total| total.checked_sub(&amount).ok_or(Error::::TooLow))?; + + T::XCMPMessageSender::send_xcmp_message(para_id, &XCMPMessage::Transfer(id.clone(), amount, dest.clone())).expect("should not fail"); + + Self::deposit_event(Event::::TransferToParachain(para_id, id, dest, amount)); + } + } +} + +impl XCMPMessageHandler> for Module { + fn handle_xcmp_message(src: ParaId, msg: &XCMPMessage) { + match msg { + XCMPMessage::Transfer(asset_id, amount, dest) => { + match asset_id.clone().try_into() { + Ok(currency_id) => { + T::Currency::deposit(currency_id, dest, *amount).expect("should not fail"); + } + _ => UnknownBalances::::mutate(dest, asset_id, |total| *total += *amount), + } + + Self::deposit_event(Event::::ReceivedTransferFromParachain( + src, + asset_id.clone(), + dest.clone(), + *amount, + )); + } + } + } +} From 74407eb7dff7a8396af0124ff692d8e9a4087465 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Wed, 12 Aug 2020 19:51:34 +1200 Subject: [PATCH 07/14] fix build --- xtokens/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml index 3831a7b92..5c2bd658e 100644 --- a/xtokens/Cargo.toml +++ b/xtokens/Cargo.toml @@ -19,7 +19,7 @@ frame-system = { version = "2.0.0-rc5", default-features = false } orml-traits = { path = "../traits", version = "0.1.3-dev", default-features = false } -cumulus-primitives = { git = "https://github.com/paritytech/cumulus", branch = "bkchr-next-key" } +cumulus-primitives = { git = "https://github.com/paritytech/cumulus", branch = "bkchr-next-key", default-features = false } [dev-dependencies] sp-core = { version = "2.0.0-rc5", default-features = false } @@ -37,4 +37,5 @@ std = [ "frame-support/std", "frame-system/std", "orml-traits/std", + "cumulus-primitives/std", ] From e40692915556582de45679434ed470f6ddd760de Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Fri, 14 Aug 2020 11:45:22 +1200 Subject: [PATCH 08/14] relay chain token transfer --- xtokens/Cargo.toml | 4 +++- xtokens/src/lib.rs | 55 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml index 5c2bd658e..91c11bfca 100644 --- a/xtokens/Cargo.toml +++ b/xtokens/Cargo.toml @@ -19,7 +19,8 @@ frame-system = { version = "2.0.0-rc5", default-features = false } orml-traits = { path = "../traits", version = "0.1.3-dev", default-features = false } -cumulus-primitives = { git = "https://github.com/paritytech/cumulus", branch = "bkchr-next-key", default-features = false } +cumulus-primitives = { git = "https://github.com/paritytech/cumulus", default-features = false } +cumulus-upward-message = { git = "https://github.com/paritytech/cumulus", default-features = false } [dev-dependencies] sp-core = { version = "2.0.0-rc5", default-features = false } @@ -38,4 +39,5 @@ std = [ "frame-system/std", "orml-traits/std", "cumulus-primitives/std", + "cumulus-upward-message/std", ] diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 6091c0a01..4772b2acc 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -4,17 +4,20 @@ use codec::{Decode, Encode}; use frame_support::{decl_error, decl_event, decl_module, decl_storage, Parameter}; use frame_system::ensure_signed; use orml_traits::MultiCurrency; -use sp_runtime::traits::{AtLeast32Bit, CheckedSub, MaybeSerializeDeserialize, Member}; +use sp_runtime::traits::{AtLeast32Bit, CheckedSub, Convert, MaybeSerializeDeserialize, Member}; use sp_std::{ convert::{TryFrom, TryInto}, prelude::*, }; use cumulus_primitives::{ + relay_chain::{Balance as RelayChainBalance, DownwardMessage}, xcmp::{XCMPMessageHandler, XCMPMessageSender}, - ParaId, + ParaId, UpwardMessageOrigin, UpwardMessageSender, }; +use cumulus_upward_message::BalancesMessage; + #[derive(Encode, Decode)] pub enum XCMPMessage { /// Transfer tokens to the given account from the Parachain account. @@ -30,9 +33,20 @@ pub trait Trait: frame_system::Trait { /// The currency ID type type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + Into> + TryFrom>; + type RelayChainCurrencyId: Get; + + type FromRelayChainBalance: Convert; + type ToRelayChainBalance: Convert; + type Currency: MultiCurrency; type XCMPMessageSender: XCMPMessageSender>; + + /// The sender of upward messages. + type UpwardMessageSender: UpwardMessageSender; + + /// The upward message type used by the Parachain runtime. + type UpwardMessage: codec::Codec + BalancesMessage>; } decl_storage! { @@ -48,6 +62,8 @@ decl_event!( { TransferToParachain(ParaId, Vec, AccountId, Balance), ReceivedTransferFromParachain(ParaId, Vec, AccountId, Balance), + TransferToRelayChain(AccountId, Balance), + ReceivedTransferFromRelayChain(AccountId, Balance), } ); @@ -68,7 +84,7 @@ decl_module! { fn transfer_to_parachain(origin, currency_id: T::CurrencyId, para_id: ParaId, dest: T::AccountId, amount: T::Balance) { let who = ensure_signed(origin)?; - let _ = T::Currency::withdraw(currency_id, &who, amount)?; + T::Currency::withdraw(currency_id, &who, amount)?; let asset_id: Vec = currency_id.into(); @@ -77,6 +93,17 @@ decl_module! { Self::deposit_event(Event::::TransferToParachain(para_id, asset_id, dest, amount)); } + fn transfer_to_relay_chain(origin, dest: T::AccountId, amount: T::Balance) { + let who = ensure_signed(origin)?; + + T::Currency::withdraw(T::RelayChainCurrencyId::Get(), &who, amount)?; + + let msg = T::UpwardMessage::transfer(dest.clone(), T::ToRelayChainBalance::convert(amount)); + T::UpwardMessageSender::send_upward_message(&msg, UpwardMessageOrigin::Signed).expect("should not fail"); + + Self::deposit_event(Event::::TransferToRelayChain(dest, amount)); + } + #[weight = 10] fn transfer_unknown_asset_to_parachain(origin, id: Vec, para_id: ParaId, dest: T::AccountId, amount: T::Balance) { let who = ensure_signed(origin)?; @@ -90,6 +117,28 @@ decl_module! { } } +/// This is a hack to convert from one generic type to another where we are sure +/// that both are the same type/use the same encoding. +fn convert_hack(input: &impl Encode) -> O { + input.using_encoded(|e| Decode::decode(&mut &e[..]).expect("Must be compatible; qed")) +} + +impl DownwardMessageHandler for Module { + fn handle_downward_message(msg: &DownwardMessage) { + match msg { + DownwardMessage::TransferInto(dest, amount, _) => { + let dest = convert_hack(&dest); + let amount = T::FromRelayChainBalance::convert(amount); + + let _ = T::Currency::deposit(T::RelayChainCurrencyId::get() & dest, amount.clone()); + + Self::deposit_event(Event::::ReceivedTransferFromRelayChain(dest, amount)); + } + _ => {} + } + } +} + impl XCMPMessageHandler> for Module { fn handle_xcmp_message(src: ParaId, msg: &XCMPMessage) { match msg { From a51218b2859442d4c93dcbfa3328d4e36fb67123 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Fri, 14 Aug 2020 17:27:22 +1200 Subject: [PATCH 09/14] fix --- xtokens/src/lib.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index 4772b2acc..bf2d9092b 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -1,7 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; -use frame_support::{decl_error, decl_event, decl_module, decl_storage, Parameter}; +use frame_support::{decl_error, decl_event, decl_module, decl_storage, traits::Get, Parameter}; use frame_system::ensure_signed; use orml_traits::MultiCurrency; use sp_runtime::traits::{AtLeast32Bit, CheckedSub, Convert, MaybeSerializeDeserialize, Member}; @@ -13,7 +13,7 @@ use sp_std::{ use cumulus_primitives::{ relay_chain::{Balance as RelayChainBalance, DownwardMessage}, xcmp::{XCMPMessageHandler, XCMPMessageSender}, - ParaId, UpwardMessageOrigin, UpwardMessageSender, + DownwardMessageHandler, ParaId, UpwardMessageOrigin, UpwardMessageSender, }; use cumulus_upward_message::BalancesMessage; @@ -46,7 +46,7 @@ pub trait Trait: frame_system::Trait { type UpwardMessageSender: UpwardMessageSender; /// The upward message type used by the Parachain runtime. - type UpwardMessage: codec::Codec + BalancesMessage>; + type UpwardMessage: codec::Codec + BalancesMessage; } decl_storage! { @@ -93,12 +93,13 @@ decl_module! { Self::deposit_event(Event::::TransferToParachain(para_id, asset_id, dest, amount)); } + #[weight = 10] fn transfer_to_relay_chain(origin, dest: T::AccountId, amount: T::Balance) { let who = ensure_signed(origin)?; - T::Currency::withdraw(T::RelayChainCurrencyId::Get(), &who, amount)?; + T::Currency::withdraw(T::RelayChainCurrencyId::get(), &who, amount)?; - let msg = T::UpwardMessage::transfer(dest.clone(), T::ToRelayChainBalance::convert(amount)); + let msg = T::UpwardMessage::transfer(dest.clone(), amount); T::UpwardMessageSender::send_upward_message(&msg, UpwardMessageOrigin::Signed).expect("should not fail"); Self::deposit_event(Event::::TransferToRelayChain(dest, amount)); @@ -127,12 +128,12 @@ impl DownwardMessageHandler for Module { fn handle_downward_message(msg: &DownwardMessage) { match msg { DownwardMessage::TransferInto(dest, amount, _) => { - let dest = convert_hack(&dest); - let amount = T::FromRelayChainBalance::convert(amount); + let dest: T::AccountId = convert_hack(dest); + let amount = T::FromRelayChainBalance::convert(*amount); - let _ = T::Currency::deposit(T::RelayChainCurrencyId::get() & dest, amount.clone()); + let _ = T::Currency::deposit(T::RelayChainCurrencyId::get(), &dest, amount.clone()); - Self::deposit_event(Event::::ReceivedTransferFromRelayChain(dest, amount)); + Self::deposit_event(Event::::ReceivedTransferFromRelayChain(dest.clone(), amount)); } _ => {} } From f5d1847c8b5fb318c24f4e96aa429e6dbb5cb02a Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 17 Aug 2020 11:08:21 +1200 Subject: [PATCH 10/14] fix --- xtokens/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index bf2d9092b..cc95cb2aa 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -46,7 +46,7 @@ pub trait Trait: frame_system::Trait { type UpwardMessageSender: UpwardMessageSender; /// The upward message type used by the Parachain runtime. - type UpwardMessage: codec::Codec + BalancesMessage; + type UpwardMessage: codec::Codec + BalancesMessage; } decl_storage! { @@ -99,7 +99,7 @@ decl_module! { T::Currency::withdraw(T::RelayChainCurrencyId::get(), &who, amount)?; - let msg = T::UpwardMessage::transfer(dest.clone(), amount); + let msg = T::UpwardMessage::transfer(dest.clone(), T::ToRelayChainBalance::convert(amount)); T::UpwardMessageSender::send_upward_message(&msg, UpwardMessageOrigin::Signed).expect("should not fail"); Self::deposit_event(Event::::TransferToRelayChain(dest, amount)); From 21ed68e0975d50f3fe4d95f2d84cd9224e148bf1 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Thu, 20 Aug 2020 18:18:15 +1200 Subject: [PATCH 11/14] add docs --- xtokens/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index cc95cb2aa..e0dd7ba36 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -60,9 +60,13 @@ decl_event!( ::AccountId, ::Balance { + /// Transfer some assets to another parachain. [para_id, asset_id, dest, amount] TransferToParachain(ParaId, Vec, AccountId, Balance), + /// Received soem assets from another parachain. [para_id, asset_id, dest, amount] ReceivedTransferFromParachain(ParaId, Vec, AccountId, Balance), + /// Transfer relay chain token to relay chain. [dest, amount] TransferToRelayChain(AccountId, Balance), + /// Received relay chain token from relay chain. [dest, amount] ReceivedTransferFromRelayChain(AccountId, Balance), } ); From 07fc40af8808db8b499ebeef0d91449c7a0fe0a8 Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Fri, 21 Aug 2020 22:02:50 +1200 Subject: [PATCH 12/14] disable with_transaction for now --- utilities/src/lib.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index 9ab9ceef6..e94912e69 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -16,14 +16,15 @@ pub use ordered_set::OrderedSet; /// Transactions can be nested to any depth. Commits happen to the parent /// transaction. pub fn with_transaction_result(f: impl FnOnce() -> Result) -> Result { - with_transaction(|| { - let res = f(); - if res.is_ok() { - TransactionOutcome::Commit(res) - } else { - TransactionOutcome::Rollback(res) - } - }) + // with_transaction(|| { + // let res = f(); + // if res.is_ok() { + // TransactionOutcome::Commit(res) + // } else { + // TransactionOutcome::Rollback(res) + // } + // }) + f() } #[cfg(test)] From 6c2081fe9018c19a6c2f83ecc5735c01aae3ab3e Mon Sep 17 00:00:00 2001 From: Shaopeng Wang Date: Fri, 4 Sep 2020 14:46:45 +1200 Subject: [PATCH 13/14] Replace xtokens. --- xtokens/Cargo.toml | 8 +- xtokens/README.md | 5 - xtokens/src/lib.rs | 343 ++++++++++++++++++++++++++++++++-------- xtokens/src/mock.rs | 248 +++++++++++++++++++++++++++++ xtokens/src/tests.rs | 369 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 898 insertions(+), 75 deletions(-) delete mode 100644 xtokens/README.md create mode 100644 xtokens/src/mock.rs create mode 100644 xtokens/src/tests.rs diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml index 91c11bfca..1efed6ef3 100644 --- a/xtokens/Cargo.toml +++ b/xtokens/Cargo.toml @@ -18,14 +18,19 @@ frame-support = { version = "2.0.0-rc5", default-features = false } frame-system = { version = "2.0.0-rc5", default-features = false } orml-traits = { path = "../traits", version = "0.1.3-dev", default-features = false } +orml-utilities = { path = "../utilities", version = "0.1.3-dev", default-features = false } cumulus-primitives = { git = "https://github.com/paritytech/cumulus", default-features = false } cumulus-upward-message = { git = "https://github.com/paritytech/cumulus", default-features = false } +# TODO: switch to polkadot master branch once ready +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch", default-features = false } + [dev-dependencies] sp-core = { version = "2.0.0-rc5", default-features = false } - clear_on_drop = { version = "0.2.4", features = ["no_cc"] } # https://github.com/paritytech/substrate/issues/4179 +polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "rococo-branch" } +orml-tokens = { path = "../tokens", version = "0.1.3-dev" } [features] default = ["std"] @@ -40,4 +45,5 @@ std = [ "orml-traits/std", "cumulus-primitives/std", "cumulus-upward-message/std", + "polkadot-parachain/std", ] diff --git a/xtokens/README.md b/xtokens/README.md deleted file mode 100644 index d54bf65a9..000000000 --- a/xtokens/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# XTokens Module - -## Overview - -Provide basic cross-chain token transfer functionality. diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index e0dd7ba36..f0a897f1a 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -3,8 +3,10 @@ use codec::{Decode, Encode}; use frame_support::{decl_error, decl_event, decl_module, decl_storage, traits::Get, Parameter}; use frame_system::ensure_signed; -use orml_traits::MultiCurrency; -use sp_runtime::traits::{AtLeast32Bit, CheckedSub, Convert, MaybeSerializeDeserialize, Member}; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, CheckedSub, Convert, MaybeSerializeDeserialize, Member, Saturating}, + DispatchResult, RuntimeDebug, +}; use sp_std::{ convert::{TryFrom, TryInto}, prelude::*, @@ -15,66 +17,115 @@ use cumulus_primitives::{ xcmp::{XCMPMessageHandler, XCMPMessageSender}, DownwardMessageHandler, ParaId, UpwardMessageOrigin, UpwardMessageSender, }; - use cumulus_upward_message::BalancesMessage; +use polkadot_parachain::primitives::AccountIdConversion; + +use orml_traits::MultiCurrency; +use orml_utilities::with_transaction_result; + +mod mock; +mod tests; + +#[derive(Encode, Decode, Eq, PartialEq, Clone, Copy, RuntimeDebug)] +/// Identity of chain. +pub enum ChainId { + /// The relay chain. + RelayChain, + /// A parachain. + ParaChain(ParaId), +} -#[derive(Encode, Decode)] -pub enum XCMPMessage { - /// Transfer tokens to the given account from the Parachain account. - Transfer(Vec, Balance, AccountId), +#[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug)] +/// Identity of cross chain currency. +pub struct XCurrencyId { + /// The owner chain of the currency. For instance, the owner chain of DOT is + /// Polkadot. + pub chain_id: ChainId, + /// The identity of the currency. + pub currency_id: Vec, +} + +#[cfg(test)] +impl XCurrencyId { + pub fn new(chain_id: ChainId, currency_id: Vec) -> Self { + XCurrencyId { chain_id, currency_id } + } +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug)] +pub enum XCMPTokenMessage { + /// Token transfer. [x_currency_id, para_id, dest, amount] + Transfer(XCurrencyId, ParaId, AccountId, Balance), } pub trait Trait: frame_system::Trait { type Event: From> + Into<::Event>; - /// The balance type - type Balance: Parameter + Member + AtLeast32Bit + Default + Copy + MaybeSerializeDeserialize; + /// The balance type. + type Balance: Parameter + Member + AtLeast32BitUnsigned + Default + Copy + MaybeSerializeDeserialize; + + /// Convertor `RelayChainBalance` to `Balance`. + type FromRelayChainBalance: Convert; + + /// Convertor `Balance` to `RelayChainBalance`. + type ToRelayChainBalance: Convert; /// The currency ID type type CurrencyId: Parameter + Member + Copy + MaybeSerializeDeserialize + Ord + Into> + TryFrom>; + /// Currency Id of relay chain. type RelayChainCurrencyId: Get; - type FromRelayChainBalance: Convert; - type ToRelayChainBalance: Convert; - + /// The `MultiCurrency` impl for tokens. type Currency: MultiCurrency; - type XCMPMessageSender: XCMPMessageSender>; + /// Parachain ID. + type ParaId: Get; - /// The sender of upward messages. + /// The sender of XCMP message. + type XCMPMessageSender: XCMPMessageSender>; + + /// The sender of upward message(to relay chain). type UpwardMessageSender: UpwardMessageSender; - /// The upward message type used by the Parachain runtime. + /// The upward message type used by parachain runtime. type UpwardMessage: codec::Codec + BalancesMessage; } decl_storage! { - trait Store for Module as Tokens { - UnknownBalances: double_map hasher(blake2_128_concat) T::AccountId, hasher(blake2_128_concat) Vec => T::Balance; + trait Store for Module as XTokens { + /// Balances of currencies not known to self parachain. + pub UnknownBalances get(fn unknown_balances): double_map hasher(blake2_128_concat) T::AccountId, hasher(blake2_128_concat) Vec => T::Balance; } } -decl_event!( +decl_event! { pub enum Event where ::AccountId, - ::Balance + ::Balance, + XCurrencyId = XCurrencyId, { - /// Transfer some assets to another parachain. [para_id, asset_id, dest, amount] - TransferToParachain(ParaId, Vec, AccountId, Balance), - /// Received soem assets from another parachain. [para_id, asset_id, dest, amount] - ReceivedTransferFromParachain(ParaId, Vec, AccountId, Balance), - /// Transfer relay chain token to relay chain. [dest, amount] - TransferToRelayChain(AccountId, Balance), - /// Received relay chain token from relay chain. [dest, amount] + /// Transferred to relay chain. [src, dest, amount] + TransferredToRelayChain(AccountId, AccountId, Balance), + + /// Received transfer from relay chain. [dest, amount] ReceivedTransferFromRelayChain(AccountId, Balance), + + /// Transferred to parachain. [x_currency_id, src, para_id, dest, amount] + TransferredToParachain(XCurrencyId, AccountId, ParaId, AccountId, Balance), + + /// Received transfer from parachain. [x_currency_id, para_id, dest, amount] + ReceivedTransferFromParachain(XCurrencyId, ParaId, AccountId, Balance), } -); +} decl_error! { - /// Error for token module. + /// Error for xtokens module. pub enum Error for Module { - TooLow + /// Insufficient balance to transfer. + InsufficientBalance, + /// Invalid currency ID. + InvalidCurrencyId, } } @@ -84,41 +135,162 @@ decl_module! { fn deposit_event() = default; + /// Transfer relay chain tokens to relay chain. #[weight = 10] - fn transfer_to_parachain(origin, currency_id: T::CurrencyId, para_id: ParaId, dest: T::AccountId, amount: T::Balance) { - let who = ensure_signed(origin)?; - - T::Currency::withdraw(currency_id, &who, amount)?; - - let asset_id: Vec = currency_id.into(); - - T::XCMPMessageSender::send_xcmp_message(para_id, &XCMPMessage::Transfer(asset_id.clone(), amount, dest.clone())).expect("should not fail"); - - Self::deposit_event(Event::::TransferToParachain(para_id, asset_id, dest, amount)); + pub fn transfer_to_relay_chain(origin, dest: T::AccountId, amount: T::Balance) { + with_transaction_result(|| { + let who = ensure_signed(origin)?; + Self::do_transfer_to_relay_chain(&who, &dest, amount)?; + Self::deposit_event(Event::::TransferredToRelayChain(who, dest, amount)); + Ok(()) + })?; } + /// Transfer tokens to parachain. #[weight = 10] - fn transfer_to_relay_chain(origin, dest: T::AccountId, amount: T::Balance) { - let who = ensure_signed(origin)?; - - T::Currency::withdraw(T::RelayChainCurrencyId::get(), &who, amount)?; + pub fn transfer_to_parachain( + origin, + x_currency_id: XCurrencyId, + para_id: ParaId, + dest: T::AccountId, + amount: T::Balance, + ) { + with_transaction_result(|| { + let who = ensure_signed(origin)?; + + if para_id == T::ParaId::get() { + return Ok(()); + } - let msg = T::UpwardMessage::transfer(dest.clone(), T::ToRelayChainBalance::convert(amount)); - T::UpwardMessageSender::send_upward_message(&msg, UpwardMessageOrigin::Signed).expect("should not fail"); + Self::do_transfer_to_parachain(x_currency_id.clone(), &who, para_id, &dest, amount)?; + Self::deposit_event(Event::::TransferredToParachain(x_currency_id, who, para_id, dest, amount)); - Self::deposit_event(Event::::TransferToRelayChain(dest, amount)); + Ok(()) + })?; } + } +} - #[weight = 10] - fn transfer_unknown_asset_to_parachain(origin, id: Vec, para_id: ParaId, dest: T::AccountId, amount: T::Balance) { - let who = ensure_signed(origin)?; +impl Module { + fn do_transfer_to_relay_chain(who: &T::AccountId, dest: &T::AccountId, amount: T::Balance) -> DispatchResult { + T::Currency::withdraw(T::RelayChainCurrencyId::get(), who, amount)?; + let msg = T::UpwardMessage::transfer(dest.clone(), T::ToRelayChainBalance::convert(amount)); + T::UpwardMessageSender::send_upward_message(&msg, UpwardMessageOrigin::Signed).expect("Should not fail; qed"); + Ok(()) + } - UnknownBalances::::try_mutate(who, &id, |total| total.checked_sub(&amount).ok_or(Error::::TooLow))?; + fn do_transfer_to_parachain( + x_currency_id: XCurrencyId, + src: &T::AccountId, + para_id: ParaId, + dest: &T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + match x_currency_id.chain_id { + ChainId::RelayChain => { + Self::transfer_relay_chain_tokens_to_parachain(x_currency_id, src, para_id, dest, amount) + } + ChainId::ParaChain(token_owner) => { + if T::ParaId::get() == token_owner { + Self::transfer_owned_tokens_to_parachain(x_currency_id, src, para_id, dest, amount) + } else { + Self::transfer_non_owned_tokens_to_parachain(token_owner, x_currency_id, src, para_id, dest, amount) + } + } + } + } + + /// Transfer relay chain tokens to another parachain. + /// + /// 1. Withdraw `src` balance. + /// 2. Transfer in relay chain: from self parachain account to `para_id` + /// account. 3. Notify `para_id` the transfer. + fn transfer_relay_chain_tokens_to_parachain( + x_currency_id: XCurrencyId, + src: &T::AccountId, + para_id: ParaId, + dest: &T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + let para_account = para_id.into_account(); + + T::Currency::withdraw(T::RelayChainCurrencyId::get(), src, amount)?; + + let msg = T::UpwardMessage::transfer(para_account, T::ToRelayChainBalance::convert(amount)); + T::UpwardMessageSender::send_upward_message(&msg, UpwardMessageOrigin::Signed).expect("Should not fail; qed"); + + T::XCMPMessageSender::send_xcmp_message( + para_id, + &XCMPTokenMessage::Transfer(x_currency_id, para_id, dest.clone(), amount), + ) + .expect("Should not fail; qed"); + + Ok(()) + } - T::XCMPMessageSender::send_xcmp_message(para_id, &XCMPMessage::Transfer(id.clone(), amount, dest.clone())).expect("should not fail"); + /// Transfer parachain tokens "owned" by self parachain to another + /// parachain. + /// + /// 1. Transfer from `src` to `para_id` account. + /// 2. Notify `para_id` the transfer. + /// + /// NOTE - `para_id` must not be self parachain. + fn transfer_owned_tokens_to_parachain( + x_currency_id: XCurrencyId, + src: &T::AccountId, + para_id: ParaId, + dest: &T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + let para_account = para_id.into_account(); + let currency_id: T::CurrencyId = x_currency_id + .currency_id + .clone() + .try_into() + .map_err(|_| Error::::InvalidCurrencyId)?; + T::Currency::transfer(currency_id, src, ¶_account, amount)?; + + T::XCMPMessageSender::send_xcmp_message( + para_id, + &XCMPTokenMessage::Transfer(x_currency_id, para_id, dest.clone(), amount), + ) + .expect("Should not fail; qed"); + + Ok(()) + } - Self::deposit_event(Event::::TransferToParachain(para_id, id, dest, amount)); + /// Transfer parachain tokens not "owned" by self chain to another + /// parachain. + /// + /// 1. Withdraw from `src`. + /// 2. Notify token owner parachain the transfer. (Token owner chain would + /// further notify `para_id`) + fn transfer_non_owned_tokens_to_parachain( + token_owner: ParaId, + x_currency_id: XCurrencyId, + src: &T::AccountId, + para_id: ParaId, + dest: &T::AccountId, + amount: T::Balance, + ) -> DispatchResult { + if let Ok(currency_id) = x_currency_id.currency_id.clone().try_into() { + // Known currency, withdraw from src. + T::Currency::withdraw(currency_id, src, amount)?; + } else { + // Unknown currency, update balance. + UnknownBalances::::try_mutate(src, &x_currency_id.currency_id, |total| -> DispatchResult { + *total = total.checked_sub(&amount).ok_or(Error::::InsufficientBalance)?; + Ok(()) + })?; } + + T::XCMPMessageSender::send_xcmp_message( + token_owner, + &XCMPTokenMessage::Transfer(x_currency_id, para_id, dest.clone(), amount), + ) + .expect("Should not fail; qed"); + + Ok(()) } } @@ -130,34 +302,67 @@ fn convert_hack(input: &impl Encode) -> O { impl DownwardMessageHandler for Module { fn handle_downward_message(msg: &DownwardMessage) { - match msg { - DownwardMessage::TransferInto(dest, amount, _) => { - let dest: T::AccountId = convert_hack(dest); - let amount = T::FromRelayChainBalance::convert(*amount); - - let _ = T::Currency::deposit(T::RelayChainCurrencyId::get(), &dest, amount.clone()); + if let DownwardMessage::TransferInto(dest, amount, _) = msg { + let dest: T::AccountId = convert_hack(dest); + let amount: T::Balance = T::FromRelayChainBalance::convert(*amount); + // Should not fail, but if it does, there is nothing can be done. + let _ = T::Currency::deposit(T::RelayChainCurrencyId::get(), &dest, amount); - Self::deposit_event(Event::::ReceivedTransferFromRelayChain(dest.clone(), amount)); - } - _ => {} + Self::deposit_event(Event::::ReceivedTransferFromRelayChain(dest, amount)); } } } -impl XCMPMessageHandler> for Module { - fn handle_xcmp_message(src: ParaId, msg: &XCMPMessage) { +impl XCMPMessageHandler> for Module { + fn handle_xcmp_message(src: ParaId, msg: &XCMPTokenMessage) { match msg { - XCMPMessage::Transfer(asset_id, amount, dest) => { - match asset_id.clone().try_into() { - Ok(currency_id) => { - T::Currency::deposit(currency_id, dest, *amount).expect("should not fail"); + XCMPTokenMessage::Transfer(x_currency_id, para_id, dest, amount) => { + match x_currency_id.chain_id { + ChainId::RelayChain => { + // Relay chain tokens. Should not fail, but if it does, there is nothing we + // could do. + let _ = T::Currency::deposit(T::RelayChainCurrencyId::get(), &dest, *amount); + } + ChainId::ParaChain(token_owner) => { + if T::ParaId::get() == token_owner { + // Handle owned tokens: + // If `para_id` is self parachain: + // 1. Transfer from `src` para account to `dest` account. + // else (`para_id` is not self parachain): + // 1. transfer between para accounts + // 2. notify the `para_id` + let src_para_account = src.into_account(); + if *para_id == T::ParaId::get() { + if let Ok(currency_id) = x_currency_id.currency_id.clone().try_into() { + // Should not fail, but if it does, there is nothing can be done. + let _ = T::Currency::transfer(currency_id, &src_para_account, dest, *amount); + } + } else { + // Should not fail, but if it does, there is nothing can be done. + let _ = Self::transfer_owned_tokens_to_parachain( + x_currency_id.clone(), + &src_para_account, + *para_id, + dest, + *amount, + ); + } + } else if let Ok(currency_id) = x_currency_id.currency_id.clone().try_into() { + // Handle known tokens. + // Should not fail, but if it does, there is nothing can be done. + let _ = T::Currency::deposit(currency_id, dest, *amount); + } else { + // Handle unknown tokens. + UnknownBalances::::mutate(dest, x_currency_id.currency_id.clone(), |total| { + *total = total.saturating_add(*amount) + }); + } } - _ => UnknownBalances::::mutate(dest, asset_id, |total| *total += *amount), } Self::deposit_event(Event::::ReceivedTransferFromParachain( + x_currency_id.clone(), src, - asset_id.clone(), dest.clone(), *amount, )); diff --git a/xtokens/src/mock.rs b/xtokens/src/mock.rs new file mode 100644 index 000000000..6346ff13c --- /dev/null +++ b/xtokens/src/mock.rs @@ -0,0 +1,248 @@ +//! Mocks for the xtokens module. + +#![cfg(test)] + +use frame_support::{impl_outer_event, impl_outer_origin, parameter_types}; +use frame_system as system; +use serde::{Deserialize, Serialize}; +use sp_core::H256; +use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill}; +use sp_std::cell::RefCell; + +use super::*; + +type AccountId = u128; +pub type Balance = u128; + +impl_outer_origin! { + pub enum Origin for Runtime {} +} + +mod xtokens { + pub use crate::Event; +} + +impl_outer_event! { + pub enum TestEvent for Runtime { + frame_system, + orml_tokens, + xtokens, + } +} + +// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Runtime; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: u32 = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} + +impl frame_system::Trait for Runtime { + type Origin = Origin; + type Call = (); + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = TestEvent; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = (); + type BaseCallFilter = (); + type SystemWeightInfo = (); +} +pub type System = system::Module; + +#[repr(u8)] +#[derive(Encode, Decode, Serialize, Deserialize, Eq, PartialEq, Copy, Clone, RuntimeDebug, PartialOrd, Ord)] +pub enum CurrencyId { + Owned = 0, + BTC, + DOT, +} +impl Into> for CurrencyId { + fn into(self) -> Vec { + vec![self as u8] + } +} + +impl TryFrom> for CurrencyId { + type Error = (); + + fn try_from(v: Vec) -> Result { + if v.len() == 1 { + let num = v[0]; + match num { + 0 => return Ok(CurrencyId::Owned), + 1 => return Ok(CurrencyId::BTC), + 2 => return Ok(CurrencyId::DOT), + _ => return Err(()), + }; + } + Err(()) + } +} + +pub fn unknown_currency_id() -> Vec { + vec![10] +} + +impl orml_tokens::Trait for Runtime { + type Event = TestEvent; + type Balance = Balance; + type Amount = i128; + type CurrencyId = CurrencyId; + type OnReceived = (); + type WeightInfo = (); +} +pub type Tokens = orml_tokens::Module; + +parameter_types! { + pub const RelayChainCurrencyId: CurrencyId = CurrencyId::DOT; + pub MockParaId: ParaId = 0.into(); +} + +impl Trait for Runtime { + type Event = TestEvent; + type Balance = Balance; + type BalanceConvertor = BalanceConvertor; + type CurrencyId = CurrencyId; + type RelayChainCurrencyId = RelayChainCurrencyId; + type Currency = Tokens; + type ParaId = MockParaId; + type XCMPMessageSender = MockXCMPMessageSender; + type UpwardMessageSender = MockUpwardMessageSender; + type UpwardMessage = MockUpwardMessage; +} +pub type XTokens = Module; + +thread_local! { + static XCMP_MESSAGES: RefCell)>> = RefCell::new(None); +} + +pub struct MockXCMPMessageSender; +impl MockXCMPMessageSender { + pub fn msg_sent(dest: ParaId, msg: XCMPTokenMessage) -> bool { + XCMP_MESSAGES.with(|v| v.borrow().clone()) == Some((dest, msg)) + } +} +impl XCMPMessageSender> for MockXCMPMessageSender { + fn send_xcmp_message(dest: ParaId, msg: &XCMPTokenMessage) -> Result<(), ()> { + XCMP_MESSAGES.with(|v| *v.borrow_mut() = Some((dest, msg.clone()))); + Ok(()) + } +} + +#[derive(Encode, Decode, Eq, PartialEq, Clone)] +pub struct MockUpwardMessage(pub AccountId, pub Balance); +impl BalancesMessage for MockUpwardMessage { + fn transfer(dest: AccountId, amount: Balance) -> Self { + MockUpwardMessage(dest, amount) + } +} + +thread_local! { + static UPWARD_MESSAGES: RefCell> = RefCell::new(None); +} +pub struct MockUpwardMessageSender; +impl MockUpwardMessageSender { + pub fn msg_sent(msg: MockUpwardMessage) -> bool { + UPWARD_MESSAGES.with(|v| v.borrow().clone()) == Some(msg) + } +} +impl UpwardMessageSender for MockUpwardMessageSender { + fn send_upward_message(msg: &MockUpwardMessage, _origin: UpwardMessageOrigin) -> Result<(), ()> { + UPWARD_MESSAGES.with(|v| *v.borrow_mut() = Some(msg.clone())); + Ok(()) + } +} + +pub struct BalanceConvertor; +impl Convert for BalanceConvertor { + fn convert(x: u128) -> u128 { + x + } +} + +pub const ALICE: AccountId = 1; +pub const BOB: AccountId = 2; + +pub const PARA_ONE_ID: u32 = 1; + +pub fn para_one_id() -> ParaId { + PARA_ONE_ID.into() +} + +pub fn para_one_account() -> AccountId { + para_one_id().into_account() +} + +pub const PARA_TWO_ID: u32 = 2; + +pub fn para_two_id() -> ParaId { + PARA_TWO_ID.into() +} + +pub fn para_two_account() -> AccountId { + para_two_id().into_account() +} + +pub struct ExtBuilder { + endowed_accounts: Vec<(AccountId, CurrencyId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + endowed_accounts: vec![], + } + } +} + +impl ExtBuilder { + pub fn balances(mut self, endowed_accounts: Vec<(AccountId, CurrencyId, Balance)>) -> Self { + self.endowed_accounts = endowed_accounts; + self + } + + pub fn one_hundred_for_alice(self) -> Self { + self.balances(vec![ + (ALICE, CurrencyId::Owned, 100), + (ALICE, CurrencyId::DOT, 100), + (ALICE, CurrencyId::BTC, 100), + ]) + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + orml_tokens::GenesisConfig:: { + endowed_accounts: self.endowed_accounts, + } + .assimilate_storage(&mut t) + .unwrap(); + + XCMP_MESSAGES.with(|v| *v.borrow_mut() = None); + UPWARD_MESSAGES.with(|v| *v.borrow_mut() = None); + + t.into() + } +} diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs new file mode 100644 index 000000000..d923abe3d --- /dev/null +++ b/xtokens/src/tests.rs @@ -0,0 +1,369 @@ +//! Unit tests for the xtokens module. + +#![cfg(test)] + +use super::*; +use mock::*; + +use frame_support::{assert_noop, assert_ok}; + +#[test] +fn transfer_to_relay_chain_works() { + ExtBuilder::default().one_hundred_for_alice().build().execute_with(|| { + System::set_block_number(1); + + assert_ok!(XTokens::transfer_to_relay_chain(Origin::signed(ALICE), BOB, 50)); + + assert_eq!(Tokens::free_balance(CurrencyId::DOT, &ALICE), 50); + assert!(MockUpwardMessageSender::msg_sent(MockUpwardMessage(BOB, 50))); + + let event = TestEvent::xtokens(RawEvent::TransferredToRelayChain(ALICE, BOB, 50)); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn transfer_to_relay_chain_fails_if_insufficient_balance() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + XTokens::transfer_to_relay_chain(Origin::signed(ALICE), BOB, 50), + orml_tokens::Error::::BalanceTooLow + ); + }); +} + +#[test] +fn transfer_relay_chain_tokens_to_parachain_works() { + ExtBuilder::default().one_hundred_for_alice().build().execute_with(|| { + System::set_block_number(1); + + let x_currency_id = XCurrencyId::new(ChainId::RelayChain, vec![0]); + assert_ok!(XTokens::transfer_to_parachain( + Origin::signed(ALICE), + x_currency_id.clone(), + para_one_id(), + BOB, + 50 + )); + + assert_eq!(Tokens::free_balance(CurrencyId::DOT, &ALICE), 50); + assert!(MockUpwardMessageSender::msg_sent(MockUpwardMessage( + para_one_account(), + 50 + ))); + assert!(MockXCMPMessageSender::msg_sent( + para_one_id(), + XCMPTokenMessage::Transfer(x_currency_id.clone(), para_one_id(), BOB, 50) + )); + + let event = TestEvent::xtokens(RawEvent::TransferredToParachain( + x_currency_id, + ALICE, + para_one_id(), + BOB, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn transfer_relay_chain_tokens_to_parachain_fails_if_insufficient_balance() { + ExtBuilder::default().build().execute_with(|| { + let x_currency_id = XCurrencyId::new(ChainId::RelayChain, vec![0]); + assert_noop!( + XTokens::transfer_to_parachain(Origin::signed(ALICE), x_currency_id, para_one_id(), BOB, 50), + orml_tokens::Error::::BalanceTooLow + ); + }); +} + +#[test] +fn transfer_owned_tokens_to_parachain_works() { + ExtBuilder::default().one_hundred_for_alice().build().execute_with(|| { + System::set_block_number(1); + + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(MockParaId::get()), CurrencyId::Owned.into()); + assert_ok!(XTokens::transfer_to_parachain( + Origin::signed(ALICE), + x_currency_id.clone(), + para_one_id(), + BOB, + 50 + )); + + assert_eq!(Tokens::free_balance(CurrencyId::Owned, &ALICE), 50); + assert_eq!(Tokens::free_balance(CurrencyId::Owned, ¶_one_account()), 50); + assert!(MockXCMPMessageSender::msg_sent( + para_one_id(), + XCMPTokenMessage::Transfer(x_currency_id.clone(), para_one_id(), BOB, 50) + )); + + let event = TestEvent::xtokens(RawEvent::TransferredToParachain( + x_currency_id, + ALICE, + para_one_id(), + BOB, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn transfer_owned_tokens_to_parachain_fails_if_unrecognized_currency_id() { + ExtBuilder::default().one_hundred_for_alice().build().execute_with(|| { + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(MockParaId::get()), unknown_currency_id()); + assert_noop!( + XTokens::transfer_to_parachain(Origin::signed(ALICE), x_currency_id, para_one_id(), BOB, 50), + Error::::InvalidCurrencyId + ); + }); +} + +#[test] +fn transfer_owned_tokens_to_parachain_fails_if_insufficient_balance() { + ExtBuilder::default().build().execute_with(|| { + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(MockParaId::get()), CurrencyId::Owned.into()); + assert_noop!( + XTokens::transfer_to_parachain(Origin::signed(ALICE), x_currency_id, para_one_id(), BOB, 50), + orml_tokens::Error::::BalanceTooLow, + ); + }); +} + +#[test] +fn transfer_known_non_owned_tokens_to_parachain_works() { + ExtBuilder::default().one_hundred_for_alice().build().execute_with(|| { + System::set_block_number(1); + + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(para_one_id()), CurrencyId::BTC.into()); + assert_ok!(XTokens::transfer_to_parachain( + Origin::signed(ALICE), + x_currency_id.clone(), + para_two_id(), + BOB, + 50 + )); + + assert_eq!(Tokens::free_balance(CurrencyId::BTC, &ALICE), 50); + assert!(MockXCMPMessageSender::msg_sent( + para_one_id(), + XCMPTokenMessage::Transfer(x_currency_id.clone(), para_two_id(), BOB, 50) + )); + + let event = TestEvent::xtokens(RawEvent::TransferredToParachain( + x_currency_id, + ALICE, + para_two_id(), + BOB, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn transfer_known_non_owned_tokens_fails_if_insufficient_balance() { + ExtBuilder::default().build().execute_with(|| { + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(para_one_id()), CurrencyId::BTC.into()); + assert_noop!( + XTokens::transfer_to_parachain(Origin::signed(ALICE), x_currency_id, para_two_id(), BOB, 50), + orml_tokens::Error::::BalanceTooLow + ); + }); +} + +#[test] +fn transfer_unknown_non_owned_tokens_to_parachain_works() { + ExtBuilder::default().one_hundred_for_alice().build().execute_with(|| { + System::set_block_number(1); + + >::insert(ALICE, unknown_currency_id(), 100); + + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(para_one_id()), unknown_currency_id()); + assert_ok!(XTokens::transfer_to_parachain( + Origin::signed(ALICE), + x_currency_id.clone(), + para_two_id(), + BOB, + 50 + )); + + assert_eq!(XTokens::unknown_balances(ALICE, unknown_currency_id()), 50); + assert!(MockXCMPMessageSender::msg_sent( + para_one_id(), + XCMPTokenMessage::Transfer(x_currency_id.clone(), para_two_id(), BOB, 50) + )); + + let event = TestEvent::xtokens(RawEvent::TransferredToParachain( + x_currency_id, + ALICE, + para_two_id(), + BOB, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn transfer_unknown_non_owned_tokens_fails_if_insufficient_balance() { + ExtBuilder::default().build().execute_with(|| { + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(para_one_id()), unknown_currency_id()); + assert_noop!( + XTokens::transfer_to_parachain(Origin::signed(ALICE), x_currency_id, para_two_id(), BOB, 50), + Error::::InsufficientBalance + ); + }); +} + +#[test] +fn handle_downward_message_works() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + let dest: polkadot_core_primitives::AccountId = [0; 32].into(); + let msg = DownwardMessage::TransferInto(dest.clone(), 50, [0; 32]); + XTokens::handle_downward_message(&msg); + + let dest_account = convert_hack(&dest); + assert_eq!(Tokens::free_balance(CurrencyId::DOT, &dest_account), 50); + + let event = TestEvent::xtokens(RawEvent::ReceivedTransferFromRelayChain(dest_account, 50)); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn handle_xcmp_message_works_for_relay_chain_tokens() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + let x_currency_id = XCurrencyId::new(ChainId::RelayChain, vec![0]); + let msg = XCMPTokenMessage::Transfer(x_currency_id.clone(), MockParaId::get(), ALICE, 50); + XTokens::handle_xcmp_message(para_one_id(), &msg); + + assert_eq!(Tokens::free_balance(CurrencyId::DOT, &ALICE), 50); + + let event = TestEvent::xtokens(RawEvent::ReceivedTransferFromParachain( + x_currency_id, + para_one_id(), + ALICE, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn handle_xcmp_message_works_for_owned_parachain_tokens() { + // transfer from para_one to para_two + ExtBuilder::default() + .balances(vec![(para_one_account(), CurrencyId::Owned, 100)]) + .build() + .execute_with(|| { + System::set_block_number(1); + + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(MockParaId::get()), CurrencyId::Owned.into()); + let msg = XCMPTokenMessage::Transfer(x_currency_id.clone(), para_two_id(), ALICE, 50); + XTokens::handle_xcmp_message(para_one_id(), &msg); + + assert_eq!(Tokens::free_balance(CurrencyId::Owned, ¶_one_account()), 50); + assert_eq!(Tokens::free_balance(CurrencyId::Owned, ¶_two_account()), 50); + + MockXCMPMessageSender::msg_sent(para_two_id(), msg); + + let event = TestEvent::xtokens(RawEvent::ReceivedTransferFromParachain( + x_currency_id, + para_one_id(), + ALICE, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn handle_xcmp_message_works_for_owned_parachain_tokens_and_self_parachain_as_dest() { + // transfer from para_one to self parachain + ExtBuilder::default() + .balances(vec![(para_one_account(), CurrencyId::Owned, 100)]) + .build() + .execute_with(|| { + System::set_block_number(1); + + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(MockParaId::get()), CurrencyId::Owned.into()); + let msg = XCMPTokenMessage::Transfer(x_currency_id.clone(), MockParaId::get(), ALICE, 50); + XTokens::handle_xcmp_message(para_one_id(), &msg); + + assert_eq!(Tokens::free_balance(CurrencyId::Owned, ¶_one_account()), 50); + assert_eq!(Tokens::free_balance(CurrencyId::Owned, &ALICE), 50); + + let event = TestEvent::xtokens(RawEvent::ReceivedTransferFromParachain( + x_currency_id, + para_one_id(), + ALICE, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn handle_xcmp_message_works_for_owned_parachain_tokens_with_invalid_currency() { + ExtBuilder::default() + .balances(vec![(para_one_account(), CurrencyId::Owned, 100)]) + .build() + .execute_with(|| { + fn handle() -> sp_std::result::Result<(), ()> { + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(MockParaId::get()), unknown_currency_id()); + let msg = XCMPTokenMessage::Transfer(x_currency_id.clone(), MockParaId::get(), ALICE, 50); + XTokens::handle_xcmp_message(para_one_id(), &msg); + Err(()) + } + assert_noop!(handle(), ()); + }); +} + +#[test] +fn handle_xcmp_message_works_for_non_owned_known_parachain_tokens() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(para_one_id()), CurrencyId::BTC.into()); + let msg = XCMPTokenMessage::Transfer(x_currency_id.clone(), MockParaId::get(), ALICE, 50); + XTokens::handle_xcmp_message(para_one_id(), &msg); + + assert_eq!(Tokens::free_balance(CurrencyId::BTC, &ALICE), 50); + + let event = TestEvent::xtokens(RawEvent::ReceivedTransferFromParachain( + x_currency_id, + para_one_id(), + ALICE, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} + +#[test] +fn handle_xcmp_message_works_for_non_owned_unknown_parachain_tokens() { + ExtBuilder::default().build().execute_with(|| { + System::set_block_number(1); + + let x_currency_id = XCurrencyId::new(ChainId::ParaChain(para_one_id()), unknown_currency_id()); + let msg = XCMPTokenMessage::Transfer(x_currency_id.clone(), MockParaId::get(), ALICE, 50); + XTokens::handle_xcmp_message(para_one_id(), &msg); + + assert_eq!(XTokens::unknown_balances(ALICE, unknown_currency_id()), 50); + + let event = TestEvent::xtokens(RawEvent::ReceivedTransferFromParachain( + x_currency_id, + para_one_id(), + ALICE, + 50, + )); + assert!(System::events().iter().any(|record| record.event == event)); + }); +} From 347ba1a3f4828faab962aea9f992ab852d0b53b4 Mon Sep 17 00:00:00 2001 From: Shaopeng Wang Date: Fri, 18 Sep 2020 10:40:25 +1200 Subject: [PATCH 14/14] Fix xtokens mock. --- xtokens/src/mock.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xtokens/src/mock.rs b/xtokens/src/mock.rs index 6346ff13c..3383a0156 100644 --- a/xtokens/src/mock.rs +++ b/xtokens/src/mock.rs @@ -109,7 +109,6 @@ impl orml_tokens::Trait for Runtime { type Amount = i128; type CurrencyId = CurrencyId; type OnReceived = (); - type WeightInfo = (); } pub type Tokens = orml_tokens::Module; @@ -121,7 +120,8 @@ parameter_types! { impl Trait for Runtime { type Event = TestEvent; type Balance = Balance; - type BalanceConvertor = BalanceConvertor; + type ToRelayChainBalance = BalanceConvertor; + type FromRelayChainBalance = BalanceConvertor; type CurrencyId = CurrencyId; type RelayChainCurrencyId = RelayChainCurrencyId; type Currency = Tokens;