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/benchmarking/src/lib.rs b/benchmarking/src/lib.rs index 09d9606c2..4f6d942b2 100644 --- a/benchmarking/src/lib.rs +++ b/benchmarking/src/lib.rs @@ -3,20 +3,18 @@ #![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, - BenchmarkingSetupInstance, }; +#[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::{Dispatchable, One, Zero}; +pub use sp_runtime::traits::Zero; /// Construct pallet benchmarks for weighing dispatchables. /// @@ -83,11 +81,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. @@ -182,11 +175,12 @@ macro_rules! runtime_benchmarks { $( $rest:tt )* ) => { $crate::benchmarks_iter!( - NO_INSTANCE + { } $runtime $pallet { $( { $common , $common_from , $common_to , $common_instancer } )* } ( ) + ( ) $( $rest )* ); } @@ -205,11 +199,12 @@ macro_rules! runtime_benchmarks_instance { $( $rest:tt )* ) => { $crate::benchmarks_iter!( - $instance + { I } $runtime $pallet { $( { $common , $common_from , $common_to , $common_instancer } )* } ( ) + ( ) $( $rest )* ); } @@ -218,75 +213,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 )* }: { < - $pallet::Call<$runtime, $instance> as $crate::frame_support::traits::UnfilteredDispatchable - >::dispatch_bypass_filter($pallet::Call::<$runtime, $instance>::$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 $( $rest )* @@ -294,20 +293,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 } @@ -316,38 +316,64 @@ 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!( + $runtime + $pallet + { $( $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 )* @@ -355,20 +381,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 )* @@ -376,20 +404,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 )* @@ -401,7 +431,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 )* } )* @@ -410,13 +440,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 )* @@ -425,14 +455,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 )* @@ -441,7 +471,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 )* @@ -455,7 +485,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 )* @@ -464,7 +494,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 )* @@ -478,7 +508,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 )* @@ -487,14 +517,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 )* @@ -503,14 +534,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 )* @@ -519,14 +551,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 } )* @@ -535,7 +568,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! [ $( @@ -587,68 +620,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`. @@ -663,13 +634,15 @@ macro_rules! benchmark_backend { // 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)] @@ -678,10 +651,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), + )* } } @@ -689,7 +666,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), + )* } } @@ -697,200 +678,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( @@ -909,6 +728,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 @@ -916,112 +736,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()); - } - - // 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; + 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)?; - $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(1u8.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. @@ -1031,9 +867,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! { @@ -1041,93 +876,51 @@ macro_rules! impl_benchmark_test { { let selected_benchmark = SelectedBenchmark::$name; let components = < - SelectedBenchmark as $crate::BenchmarkingSetup<$runtime> + SelectedBenchmark as $crate::BenchmarkingSetup<$runtime, _> >::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<$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(1u8.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(); + // Run verification + closure_to_verify()?; - // Set up the verification state - let closure_to_verify = < - SelectedBenchmark as $crate::BenchmarkingSetupInstance<$runtime, $instance> - >::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()); - } + // 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(()) @@ -1141,33 +934,47 @@ macro_rules! impl_benchmark_test { /// 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); /// ``` /// -/// Then define a mutable local variable to hold your `BenchmarkBatch` object: +/// 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, @@ -1177,7 +984,7 @@ macro_rules! add_benchmark { repeat, whitelist, )?, - pallet: $name.to_vec(), + pallet: name_string.to_vec(), benchmark: benchmark.to_vec(), }); } @@ -1191,7 +998,7 @@ macro_rules! add_benchmark { repeat, whitelist, )?, - pallet: $name.to_vec(), + pallet: name_string.to_vec(), benchmark: benchmark.clone(), }); } 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)] diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml new file mode 100644 index 000000000..1efed6ef3 --- /dev/null +++ b/xtokens/Cargo.toml @@ -0,0 +1,49 @@ +[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 } +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"] +std = [ + "serde", + "codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "frame-system/std", + "orml-traits/std", + "cumulus-primitives/std", + "cumulus-upward-message/std", + "polkadot-parachain/std", +] diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs new file mode 100644 index 000000000..f0a897f1a --- /dev/null +++ b/xtokens/src/lib.rs @@ -0,0 +1,372 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use frame_support::{decl_error, decl_event, decl_module, decl_storage, traits::Get, Parameter}; +use frame_system::ensure_signed; +use sp_runtime::{ + traits::{AtLeast32BitUnsigned, CheckedSub, Convert, MaybeSerializeDeserialize, Member, Saturating}, + DispatchResult, RuntimeDebug, +}; +use sp_std::{ + convert::{TryFrom, TryInto}, + prelude::*, +}; + +use cumulus_primitives::{ + relay_chain::{Balance as RelayChainBalance, DownwardMessage}, + 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, 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 + 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; + + /// The `MultiCurrency` impl for tokens. + type Currency: MultiCurrency; + + /// Parachain ID. + type ParaId: Get; + + /// 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 parachain runtime. + type UpwardMessage: codec::Codec + BalancesMessage; +} + +decl_storage! { + 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! { + pub enum Event where + ::AccountId, + ::Balance, + XCurrencyId = XCurrencyId, + { + /// 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 xtokens module. + pub enum Error for Module { + /// Insufficient balance to transfer. + InsufficientBalance, + /// Invalid currency ID. + InvalidCurrencyId, + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + type Error = Error; + + fn deposit_event() = default; + + /// Transfer relay chain tokens to relay chain. + #[weight = 10] + 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] + 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(()); + } + + 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)); + + Ok(()) + })?; + } + } +} + +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(()) + } + + 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(()) + } + + /// 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(()) + } + + /// 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(()) + } +} + +/// 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) { + 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, amount)); + } + } +} + +impl XCMPMessageHandler> for Module { + fn handle_xcmp_message(src: ParaId, msg: &XCMPTokenMessage) { + match msg { + 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) + }); + } + } + } + + Self::deposit_event(Event::::ReceivedTransferFromParachain( + x_currency_id.clone(), + src, + dest.clone(), + *amount, + )); + } + } + } +} diff --git a/xtokens/src/mock.rs b/xtokens/src/mock.rs new file mode 100644 index 000000000..3383a0156 --- /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 = (); +} +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 ToRelayChainBalance = BalanceConvertor; + type FromRelayChainBalance = 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)); + }); +}