diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b302bbc90..7ccbb5d6b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -39,7 +39,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2021-06-17 + toolchain: nightly-2021-11-07 components: rustfmt target: wasm32-unknown-unknown default: true diff --git a/.github/workflows/master.yml.disabled b/.github/workflows/master.yml.disabled index 925e4ef1a..9c2687c65 100644 --- a/.github/workflows/master.yml.disabled +++ b/.github/workflows/master.yml.disabled @@ -16,7 +16,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2021-01-07 + toolchain: nightly-2021-11-07 override: true default: true - name: Install cargo-unleash diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32cb4bc11..d1dd8e300 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly-2021-06-17 + toolchain: nightly-2021-11-07 components: rustfmt target: wasm32-unknown-unknown override: true diff --git a/Cargo.dev.toml b/Cargo.dev.toml index 61f16a5c0..53fd7b3a0 100644 --- a/Cargo.dev.toml +++ b/Cargo.dev.toml @@ -1,5 +1,3 @@ -cargo-features = ["resolver"] - [workspace] members = [ "auction", @@ -25,6 +23,7 @@ members = [ "weight-gen", "weight-meter", ] + resolver = "2" [profile.dev] diff --git a/auction/Cargo.toml b/auction/Cargo.toml index 23d0bd27a..090cbc1da 100644 --- a/auction/Cargo.toml +++ b/auction/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Acala Developers"] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/authority/Cargo.toml b/authority/Cargo.toml index 51fdc98f3..80a34b039 100644 --- a/authority/Cargo.toml +++ b/authority/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Acala Developers"] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/bencher/Cargo.toml b/bencher/Cargo.toml index 7f5b600e3..d1c89673f 100644 --- a/bencher/Cargo.toml +++ b/bencher/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] paste = "1.0" diff --git a/bencher/src/build_wasm/prerequisites.rs b/bencher/src/build_wasm/prerequisites.rs index fc09c4d0a..3074480df 100644 --- a/bencher/src/build_wasm/prerequisites.rs +++ b/bencher/src/build_wasm/prerequisites.rs @@ -199,7 +199,7 @@ fn create_check_toolchain_project(project_dir: &Path) { [package] name = "wasm-test" version = "1.0.0" - edition = "2018" + edition = "2021" build = "build.rs" [lib] diff --git a/bencher/src/build_wasm/wasm_project.rs b/bencher/src/build_wasm/wasm_project.rs index c28d9099a..0468eed10 100644 --- a/bencher/src/build_wasm/wasm_project.rs +++ b/bencher/src/build_wasm/wasm_project.rs @@ -288,8 +288,7 @@ fn create_project_cargo_toml( let mut package = Table::new(); package.insert("name".into(), format!("{}-wasm", crate_name).into()); package.insert("version".into(), "1.0.0".into()); - package.insert("edition".into(), "2018".into()); - package.insert("resolver".into(), "2".into()); + package.insert("edition".into(), "2021".into()); wasm_workspace_toml.insert("package".into(), package.into()); diff --git a/benchmarking/Cargo.toml b/benchmarking/Cargo.toml index d5b533daf..80c6ed17e 100644 --- a/benchmarking/Cargo.toml +++ b/benchmarking/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] serde = { version = "1.0.124", optional = true } diff --git a/benchmarking/src/lib.rs b/benchmarking/src/lib.rs index bef64a653..5d147af06 100644 --- a/benchmarking/src/lib.rs +++ b/benchmarking/src/lib.rs @@ -657,7 +657,6 @@ macro_rules! benchmark_backend { // Every variant must implement [`BenchmarkingSetup`]. // // ```nocompile -// // struct Transfer; // impl BenchmarkingSetup for Transfer { ... } // diff --git a/build-script-utils/Cargo.toml b/build-script-utils/Cargo.toml index c5d311634..8d4becf31 100644 --- a/build-script-utils/Cargo.toml +++ b/build-script-utils/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Parity Technologies ", "Laminar Developers "] -edition = "2018" +edition = "2021" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/currencies/Cargo.toml b/currencies/Cargo.toml index a5b01edbf..cb2605d49 100644 --- a/currencies/Cargo.toml +++ b/currencies/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/gradually-update/Cargo.toml b/gradually-update/Cargo.toml index c1727bbf7..55f61a97c 100644 --- a/gradually-update/Cargo.toml +++ b/gradually-update/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/nft/Cargo.toml b/nft/Cargo.toml index 7c1ca69ef..bce773e2c 100644 --- a/nft/Cargo.toml +++ b/nft/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Acala Developers"] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/oracle/Cargo.toml b/oracle/Cargo.toml index 269bc260e..d7b14758d 100644 --- a/oracle/Cargo.toml +++ b/oracle/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/oracle/rpc/Cargo.toml b/oracle/rpc/Cargo.toml index 43e94f21f..77697f125 100644 --- a/oracle/rpc/Cargo.toml +++ b/oracle/rpc/Cargo.toml @@ -2,7 +2,7 @@ name = "orml-oracle-rpc" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" license = "Apache-2.0" description = "RPC module for orml-oracle." diff --git a/oracle/rpc/runtime-api/Cargo.toml b/oracle/rpc/runtime-api/Cargo.toml index ff7b53b85..4d4803521 100644 --- a/oracle/rpc/runtime-api/Cargo.toml +++ b/oracle/rpc/runtime-api/Cargo.toml @@ -2,7 +2,7 @@ name = "orml-oracle-rpc-runtime-api" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" license = "Apache-2.0" description = "Runtime API module for orml-oracle-rpc." diff --git a/rewards/Cargo.toml b/rewards/Cargo.toml index 69bc2fd13..957664962 100644 --- a/rewards/Cargo.toml +++ b/rewards/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Acala Developers"] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/tokens/Cargo.toml b/tokens/Cargo.toml index f217417e3..a7b4c36e0 100644 --- a/tokens/Cargo.toml +++ b/tokens/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/traits/Cargo.toml b/traits/Cargo.toml index fe1beac1c..8179913fa 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/unknown-tokens/Cargo.toml b/unknown-tokens/Cargo.toml index 030ccdc79..305f2c929 100644 --- a/unknown-tokens/Cargo.toml +++ b/unknown-tokens/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Acala Developers"] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml index 21ae7056f..02f3e83f8 100644 --- a/utilities/Cargo.toml +++ b/utilities/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/vesting/Cargo.toml b/vesting/Cargo.toml index 8f6dfac7f..e2027d965 100644 --- a/vesting/Cargo.toml +++ b/vesting/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/weight-gen/Cargo.toml b/weight-gen/Cargo.toml index f5d4fa5b1..abf2ad4bb 100644 --- a/weight-gen/Cargo.toml +++ b/weight-gen/Cargo.toml @@ -4,7 +4,7 @@ description = "CLI for generating weight from bencher output" license = "Apache-2.0" version = "0.4.1-dev" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [dependencies] serde = { version = "1.0.119", features = ['derive'] } diff --git a/weight-meter/Cargo.toml b/weight-meter/Cargo.toml index 84c30e88a..0fbd07b9e 100644 --- a/weight-meter/Cargo.toml +++ b/weight-meter/Cargo.toml @@ -3,7 +3,7 @@ name = "orml-weight-meter" version = "0.4.1-dev" license = "Apache-2.0" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/weight-meter/weight-meter-procedural/Cargo.toml b/weight-meter/weight-meter-procedural/Cargo.toml index 7447cb69e..45d5687ec 100644 --- a/weight-meter/weight-meter-procedural/Cargo.toml +++ b/weight-meter/weight-meter-procedural/Cargo.toml @@ -3,7 +3,7 @@ name = "weight-meter-procedural" version = "0.1.0" license = "Apache-2.0" authors = ["Laminar Developers "] -edition = "2018" +edition = "2021" [lib] proc-macro = true diff --git a/xcm-support/Cargo.toml b/xcm-support/Cargo.toml index ab2c62c98..8604dfc6b 100644 --- a/xcm-support/Cargo.toml +++ b/xcm-support/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Acala Developers"] -edition = "2018" +edition = "2021" [dependencies] codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } diff --git a/xcm/Cargo.toml b/xcm/Cargo.toml index 828a7f3aa..3a19a2280 100644 --- a/xcm/Cargo.toml +++ b/xcm/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Acala Developers"] -edition = "2018" +edition = "2021" [dependencies] codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } diff --git a/xcm/src/lib.rs b/xcm/src/lib.rs index b886094ba..1334ef9ea 100644 --- a/xcm/src/lib.rs +++ b/xcm/src/lib.rs @@ -5,9 +5,11 @@ use frame_support::{pallet_prelude::*, traits::EnsureOrigin}; use frame_system::pallet_prelude::*; -use sp_std::boxed::Box; - -use xcm::latest::prelude::*; +use sp_std::{ + boxed::Box, + convert::{TryFrom, TryInto}, +}; +use xcm::{latest::prelude::*, VersionedMultiLocation, VersionedXcm}; pub use module::*; @@ -43,6 +45,9 @@ pub mod module { /// The message and destination was recognized as being reachable but /// the operation could not be completed. SendFailure, + /// The version of the `Versioned` value used is not able to be + /// interpreted. + BadVersion, } #[pallet::call] @@ -51,15 +56,18 @@ pub mod module { #[pallet::weight(100_000_000)] pub fn send_as_sovereign( origin: OriginFor, - dest: Box, - message: Box>, + dest: Box, + message: Box>, ) -> DispatchResult { let _ = T::SovereignOrigin::ensure_origin(origin)?; - pallet_xcm::Pallet::::send_xcm(Here, *dest.clone(), *message.clone()).map_err(|e| match e { + let dest = MultiLocation::try_from(*dest).map_err(|()| Error::::BadVersion)?; + let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?; + + pallet_xcm::Pallet::::send_xcm(Here, dest.clone(), message.clone()).map_err(|e| match e { SendError::CannotReachDestination(..) => Error::::Unreachable, _ => Error::::SendFailure, })?; - Self::deposit_event(Event::Sent(*dest, *message)); + Self::deposit_event(Event::Sent(dest, message)); Ok(()) } } diff --git a/xtokens/Cargo.toml b/xtokens/Cargo.toml index 9d1246ace..823602d39 100644 --- a/xtokens/Cargo.toml +++ b/xtokens/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/open-web3-stack/open-runtime-module-library/tre license = "Apache-2.0" version = "0.4.1-dev" authors = ["Acala Developers"] -edition = "2018" +edition = "2021" [dependencies] scale-info = { version = "1.0", default-features = false, features = ["derive"] } diff --git a/xtokens/src/lib.rs b/xtokens/src/lib.rs index b75dbf8cf..f511be7e5 100644 --- a/xtokens/src/lib.rs +++ b/xtokens/src/lib.rs @@ -104,8 +104,12 @@ pub mod module { pub enum Event { /// Transferred. \[sender, currency_id, amount, dest\] Transferred(T::AccountId, T::CurrencyId, T::Balance, MultiLocation), + /// Transferred with fee. \[sender, currency_id, amount, fee, dest\] + TransferredWithFee(T::AccountId, T::CurrencyId, T::Balance, T::Balance, MultiLocation), /// Transferred `MultiAsset`. \[sender, asset, dest\] TransferredMultiAsset(T::AccountId, MultiAsset, MultiLocation), + /// Transferred `MultiAsset` with fee. \[sender, asset, fee, dest\] + TransferredMultiAssetWithFee(T::AccountId, MultiAsset, MultiAsset, MultiLocation), } #[pallet::error] @@ -134,6 +138,11 @@ pub mod module { /// The version of the `Versioned` value used is not able to be /// interpreted. BadVersion, + /// The fee MultiAsset is of different type than the asset to transfer. + DistincAssetAndFeeId, + /// The fee amount was zero when the fee specification extrinsic is + /// being used. + FeeCannotBeZero, } #[pallet::hooks] @@ -195,6 +204,89 @@ pub mod module { let dest: MultiLocation = (*dest).try_into().map_err(|()| Error::::BadVersion)?; Self::do_transfer_multiasset(who, asset, dest, dest_weight, true) } + + /// Transfer native currencies specifying the fee and amount as + /// separate. + /// + /// `dest_weight` is the weight for XCM execution on the dest chain, and + /// it would be charged from the transferred assets. If set below + /// requirements, the execution may fail and assets wouldn't be + /// received. + /// + /// `fee` is the amount to be spent to pay for execution in destination + /// chain. Both fee and amount will be subtracted form the callers + /// balance. + /// + /// If `fee` is not high enough to cover for the execution costs in the + /// destination chain, then the assets will be trapped in the + /// destination chain + /// + /// It's a no-op if any error on local XCM execution or message sending. + /// Note sending assets out per se doesn't guarantee they would be + /// received. Receiving depends on if the XCM message could be delivered + /// by the network, and if the receiving chain would handle + /// messages correctly. + #[pallet::weight(Pallet::::weight_of_transfer(currency_id.clone(), *amount, dest))] + #[transactional] + pub fn transfer_with_fee( + origin: OriginFor, + currency_id: T::CurrencyId, + amount: T::Balance, + fee: T::Balance, + dest: Box, + dest_weight: Weight, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let dest: MultiLocation = (*dest).try_into().map_err(|()| Error::::BadVersion)?; + // Zero fee is an error + if fee.is_zero() { + return Err(Error::::FeeCannotBeZero.into()); + } + + Self::do_transfer_with_fee(who, currency_id, amount, fee, dest, dest_weight) + } + + /// Transfer `MultiAsset` specifying the fee and amount as separate. + /// + /// `dest_weight` is the weight for XCM execution on the dest chain, and + /// it would be charged from the transferred assets. If set below + /// requirements, the execution may fail and assets wouldn't be + /// received. + /// + /// `fee` is the multiasset to be spent to pay for execution in + /// destination chain. Both fee and amount will be subtracted form the + /// callers balance For now we only accept fee and asset having the same + /// `MultiLocation` id. + /// + /// If `fee` is not high enough to cover for the execution costs in the + /// destination chain, then the assets will be trapped in the + /// destination chain + /// + /// It's a no-op if any error on local XCM execution or message sending. + /// Note sending assets out per se doesn't guarantee they would be + /// received. Receiving depends on if the XCM message could be delivered + /// by the network, and if the receiving chain would handle + /// messages correctly. + #[pallet::weight(Pallet::::weight_of_transfer_multiasset(asset, dest))] + #[transactional] + pub fn transfer_multiasset_with_fee( + origin: OriginFor, + asset: Box, + fee: Box, + dest: Box, + dest_weight: Weight, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let asset: MultiAsset = (*asset).try_into().map_err(|()| Error::::BadVersion)?; + let fee: MultiAsset = (*fee).try_into().map_err(|()| Error::::BadVersion)?; + let dest: MultiLocation = (*dest).try_into().map_err(|()| Error::::BadVersion)?; + // Zero fee is an error + if fungible_amount(&fee).is_zero() { + return Err(Error::::FeeCannotBeZero.into()); + } + + Self::do_transfer_multiasset_with_fee(who, asset, fee, dest, dest_weight, true) + } } impl Pallet { @@ -215,6 +307,25 @@ pub mod module { Ok(()) } + fn do_transfer_with_fee( + who: T::AccountId, + currency_id: T::CurrencyId, + amount: T::Balance, + fee: T::Balance, + dest: MultiLocation, + dest_weight: Weight, + ) -> DispatchResult { + let location: MultiLocation = T::CurrencyIdConvert::convert(currency_id.clone()) + .ok_or(Error::::NotCrossChainTransferableCurrency)?; + + let asset = (location.clone(), amount.into()).into(); + let fee_asset: MultiAsset = (location, fee.into()).into(); + Self::do_transfer_multiasset_with_fee(who.clone(), asset, fee_asset, dest.clone(), dest_weight, false)?; + + Self::deposit_event(Event::::TransferredWithFee(who, currency_id, amount, fee, dest)); + Ok(()) + } + fn do_transfer_multiasset( who: T::AccountId, asset: MultiAsset, @@ -254,6 +365,65 @@ pub mod module { Ok(()) } + fn do_transfer_multiasset_with_fee( + who: T::AccountId, + asset: MultiAsset, + fee: MultiAsset, + dest: MultiLocation, + dest_weight: Weight, + deposit_event: bool, + ) -> DispatchResult { + if !asset.is_fungible(None) || !fee.is_fungible(None) { + return Err(Error::::NotFungible.into()); + } + + if fungible_amount(&asset).is_zero() { + return Ok(()); + } + + // For now fee and asset id should be identical + // We can relax this assumption in the future + ensure!(fee.id == asset.id, Error::::DistincAssetAndFeeId); + + let (transfer_kind, dest, reserve, recipient) = Self::transfer_kind(&asset, &dest)?; + let mut msg = match transfer_kind { + SelfReserveAsset => Self::transfer_self_reserve_asset_with_fee( + asset.clone(), + fee.clone(), + dest.clone(), + recipient, + dest_weight, + )?, + ToReserve => Self::transfer_to_reserve_with_fee( + asset.clone(), + fee.clone(), + dest.clone(), + recipient, + dest_weight, + )?, + ToNonReserve => Self::transfer_to_non_reserve_with_fee( + asset.clone(), + fee.clone(), + reserve, + dest.clone(), + recipient, + dest_weight, + )?, + }; + + let origin_location = T::AccountIdToMultiLocation::convert(who.clone()); + let weight = T::Weigher::weight(&mut msg).map_err(|()| Error::::UnweighableMessage)?; + T::XcmExecutor::execute_xcm_in_credit(origin_location, msg, weight, weight) + .ensure_complete() + .map_err(|_| Error::::XcmExecutionFailed)?; + + if deposit_event { + Self::deposit_event(Event::::TransferredMultiAssetWithFee(who, asset, fee, dest)); + } + + Ok(()) + } + fn transfer_self_reserve_asset( asset: MultiAsset, dest: MultiLocation, @@ -274,6 +444,27 @@ pub mod module { ])) } + fn transfer_self_reserve_asset_with_fee( + asset: MultiAsset, + fee: MultiAsset, + dest: MultiLocation, + recipient: MultiLocation, + dest_weight: Weight, + ) -> Result, DispatchError> { + Ok(Xcm(vec![ + WithdrawAsset(vec![asset, fee.clone()].into()), + DepositReserveAsset { + assets: All.into(), + max_assets: 1, + dest: dest.clone(), + xcm: Xcm(vec![ + Self::buy_execution(fee, &dest, dest_weight)?, + Self::deposit_asset(recipient), + ]), + }, + ])) + } + fn transfer_to_reserve( asset: MultiAsset, reserve: MultiLocation, @@ -293,6 +484,26 @@ pub mod module { ])) } + fn transfer_to_reserve_with_fee( + asset: MultiAsset, + fee: MultiAsset, + reserve: MultiLocation, + recipient: MultiLocation, + dest_weight: Weight, + ) -> Result, DispatchError> { + Ok(Xcm(vec![ + WithdrawAsset(vec![asset, fee.clone()].into()), + InitiateReserveWithdraw { + assets: All.into(), + reserve: reserve.clone(), + xcm: Xcm(vec![ + Self::buy_execution(fee, &reserve, dest_weight)?, + Self::deposit_asset(recipient), + ]), + }, + ])) + } + fn transfer_to_non_reserve( asset: MultiAsset, reserve: MultiLocation, @@ -334,6 +545,48 @@ pub mod module { ])) } + fn transfer_to_non_reserve_with_fee( + asset: MultiAsset, + fee: MultiAsset, + reserve: MultiLocation, + dest: MultiLocation, + recipient: MultiLocation, + dest_weight: Weight, + ) -> Result, DispatchError> { + let mut reanchored_dest = dest.clone(); + if reserve == MultiLocation::parent() { + match dest { + MultiLocation { + parents, + interior: X1(Parachain(id)), + } if parents == 1 => { + reanchored_dest = Parachain(id).into(); + } + _ => {} + } + } + + Ok(Xcm(vec![ + WithdrawAsset(vec![asset, fee.clone()].into()), + InitiateReserveWithdraw { + assets: All.into(), + reserve: reserve.clone(), + xcm: Xcm(vec![ + Self::buy_execution(half(&fee), &reserve, dest_weight)?, + DepositReserveAsset { + assets: All.into(), + max_assets: 1, + dest: reanchored_dest, + xcm: Xcm(vec![ + Self::buy_execution(half(&fee), &dest, dest_weight)?, + Self::deposit_asset(recipient), + ]), + }, + ]), + }, + ])) + } + fn deposit_asset(recipient: MultiLocation) -> Instruction<()> { DepositAsset { assets: All.into(), diff --git a/xtokens/src/mock/para.rs b/xtokens/src/mock/para.rs index 0c67e974a..087217078 100644 --- a/xtokens/src/mock/para.rs +++ b/xtokens/src/mock/para.rs @@ -198,8 +198,8 @@ impl Config for XcmConfig { type Weigher = FixedWeightBounds; type Trader = AllTokensAreCreatedEqualToWeight; type ResponseHandler = (); - type AssetTrap = (); - type AssetClaims = (); + type AssetTrap = PolkadotXcm; + type AssetClaims = PolkadotXcm; type SubscriptionService = PolkadotXcm; } diff --git a/xtokens/src/tests.rs b/xtokens/src/tests.rs index ba2540dfd..9bd661014 100644 --- a/xtokens/src/tests.rs +++ b/xtokens/src/tests.rs @@ -77,6 +77,42 @@ fn send_relay_chain_asset_to_relay_chain() { }); } +#[test] +fn send_relay_chain_asset_to_relay_chain_with_fee() { + TestNet::reset(); + + Relay::execute_with(|| { + let _ = RelayBalances::deposit_creating(¶_a_account(), 1_000); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer_with_fee( + Some(ALICE).into(), + CurrencyId::R, + 450, + 50, + Box::new( + MultiLocation::new( + 1, + X1(Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }) + ) + .into() + ), + 40, + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &ALICE), 500); + }); + + // It should use 40 for weight, so 460 should reach destination + Relay::execute_with(|| { + assert_eq!(RelayBalances::free_balance(¶_a_account()), 500); + assert_eq!(RelayBalances::free_balance(&BOB), 460); + }); +} + #[test] fn cannot_lost_fund_on_send_failed() { TestNet::reset(); @@ -149,6 +185,50 @@ fn send_relay_chain_asset_to_sibling() { }); } +#[test] +fn send_relay_chain_asset_to_sibling_with_fee() { + TestNet::reset(); + + Relay::execute_with(|| { + let _ = RelayBalances::deposit_creating(¶_a_account(), 1000); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer_with_fee( + Some(ALICE).into(), + CurrencyId::R, + 410, + 90, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 40, + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &ALICE), 500); + }); + + // It should use 40 weight + Relay::execute_with(|| { + assert_eq!(RelayBalances::free_balance(¶_a_account()), 500); + assert_eq!(RelayBalances::free_balance(¶_b_account()), 460); + }); + + // It should use another 40 weight in paraB + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::R, &BOB), 420); + }); +} + #[test] fn send_sibling_asset_to_reserve_sibling() { TestNet::reset(); @@ -189,6 +269,48 @@ fn send_sibling_asset_to_reserve_sibling() { }); } +#[test] +fn send_sibling_asset_to_reserve_sibling_with_fee() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &ALICE, 1_000)); + }); + + ParaB::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &sibling_a_account(), 1_000)); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer_with_fee( + Some(ALICE).into(), + CurrencyId::B, + 450, + 50, + Box::new( + ( + Parent, + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + }, + ) + .into() + ), + 40, + )); + + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &ALICE), 500); + }); + + // It should use 40 for weight, so 460 should reach destination + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &sibling_a_account()), 500); + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &BOB), 460); + }); +} + #[test] fn send_sibling_asset_to_non_reserve_sibling() { TestNet::reset(); @@ -235,6 +357,55 @@ fn send_sibling_asset_to_non_reserve_sibling() { }); } +#[test] +fn send_sibling_asset_to_non_reserve_sibling_with_fee() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &ALICE, 1_000)); + }); + + ParaB::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::B, &sibling_a_account(), 1_000)); + }); + + ParaA::execute_with(|| { + assert_ok!(ParaXTokens::transfer_with_fee( + Some(ALICE).into(), + CurrencyId::B, + 410, + 90, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(3), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 40 + )); + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &ALICE), 500); + }); + + // Should use only 40 weight + // check reserve accounts + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &sibling_a_account()), 500); + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &sibling_c_account()), 460); + }); + + // Should use 40 additional weight + ParaC::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::B, &BOB), 420); + }); +} + #[test] fn send_self_parachain_asset_to_sibling() { TestNet::reset(); @@ -271,6 +442,44 @@ fn send_self_parachain_asset_to_sibling() { }); } +#[test] +fn send_self_parachain_asset_to_sibling_with_fee() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::A, &ALICE, 1_000)); + + assert_ok!(ParaXTokens::transfer_with_fee( + Some(ALICE).into(), + CurrencyId::A, + 450, + 50, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 40, + )); + + assert_eq!(ParaTokens::free_balance(CurrencyId::A, &ALICE), 500); + assert_eq!(ParaTokens::free_balance(CurrencyId::A, &sibling_b_account()), 500); + }); + + // It should use 40 for weight, so 460 should reach destination + ParaB::execute_with(|| { + assert_eq!(ParaTokens::free_balance(CurrencyId::A, &BOB), 460); + }); +} + #[test] fn transfer_no_reserve_assets_fails() { TestNet::reset(); @@ -369,8 +578,8 @@ fn send_as_sovereign() { let assets: MultiAsset = (Here, 1_000_000_000_000).into(); assert_ok!(para::OrmlXcm::send_as_sovereign( para::Origin::root(), - Box::new(MultiLocation::parent()), - Box::new(Xcm(vec![ + Box::new(Parent.into()), + Box::new(VersionedXcm::from(Xcm(vec![ WithdrawAsset(assets.clone().into()), BuyExecution { fees: assets, @@ -381,7 +590,7 @@ fn send_as_sovereign() { require_weight_at_most: 1_000_000_000, call: call.encode().into(), } - ])) + ]))) )); }); @@ -412,8 +621,8 @@ fn send_as_sovereign_fails_if_bad_origin() { assert_err!( para::OrmlXcm::send_as_sovereign( para::Origin::signed(ALICE), - Box::new(MultiLocation::parent()), - Box::new(Xcm(vec![ + Box::new(Parent.into()), + Box::new(VersionedXcm::from(Xcm(vec![ WithdrawAsset(assets.clone().into()), BuyExecution { fees: assets, @@ -424,7 +633,7 @@ fn send_as_sovereign_fails_if_bad_origin() { require_weight_at_most: 1_000_000_000, call: call.encode().into(), } - ])) + ]))) ), DispatchError::BadOrigin, ); @@ -448,3 +657,79 @@ fn call_size_limit() { If the limit is too strong, maybe consider increasing the limit", ); } + +#[test] +fn send_with_zero_fee_should_yield_an_error() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::A, &ALICE, 1_000)); + + // Transferring with zero fee should fail + assert_noop!( + ParaXTokens::transfer_with_fee( + Some(ALICE).into(), + CurrencyId::A, + 450, + 0, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 40, + ), + Error::::FeeCannotBeZero + ); + }); +} + +#[test] +fn send_with_insufficient_fee_traps_assets() { + TestNet::reset(); + + ParaA::execute_with(|| { + assert_ok!(ParaTokens::deposit(CurrencyId::A, &ALICE, 1_000)); + + // ParaB charges 40, but we specify 30 as fee. Assets will be trapped + // Call succedes in paraA + assert_ok!(ParaXTokens::transfer_with_fee( + Some(ALICE).into(), + CurrencyId::A, + 450, + 30, + Box::new( + MultiLocation::new( + 1, + X2( + Parachain(2), + Junction::AccountId32 { + network: NetworkId::Any, + id: BOB.into(), + } + ) + ) + .into() + ), + 40, + )); + }); + + // In paraB, assets have been trapped due to he failed execution + ParaB::execute_with(|| { + assert!(para::System::events().iter().any(|r| { + matches!( + r.event, + para::Event::PolkadotXcm(pallet_xcm::Event::::AssetsTrapped(_, _, _)) + ) + })); + }) +}