diff --git a/Cargo.lock b/Cargo.lock index 2d074a0cc..0257b1ddf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16434,4 +16434,4 @@ checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", -] +] \ No newline at end of file diff --git a/pallets/xcm-interface/src/calls.rs b/pallets/xcm-interface/src/calls.rs index 99e816841..39dc37a5f 100644 --- a/pallets/xcm-interface/src/calls.rs +++ b/pallets/xcm-interface/src/calls.rs @@ -20,7 +20,7 @@ use frame_support::sp_runtime::MultiSignature; use parity_scale_codec::{Decode, Encode}; use sp_runtime::RuntimeDebug; use sp_std::{boxed::Box, vec::Vec}; -use xcm::{VersionedAssets, VersionedLocation}; +use xcm::{v4::WeightLimit, VersionedAssets, VersionedLocation}; use crate::ChainId; @@ -39,7 +39,7 @@ pub enum StakingCall { } #[derive(Encode, Decode, RuntimeDebug, Clone)] -pub enum PolkadotXcm { +pub enum PolkadotXcmCall { #[codec(index = 2)] ReserveTransferAssets( Box, @@ -47,6 +47,14 @@ pub enum PolkadotXcm { Box, u32, ), + #[codec(index = 8)] + LimitedReserveTransferAssets( + Box, + Box, + Box, + u32, + WeightLimit, + ), } #[derive(Encode, Decode, RuntimeDebug, Clone)] @@ -55,19 +63,6 @@ pub enum SystemCall { RemarkWithEvent(Vec), } -pub mod rococo { - - pub use crate::calls::*; - - #[derive(Encode, Decode, RuntimeDebug)] - pub enum RelaychainCall { - #[codec(index = 28)] - Crowdloan(ContributeCall), - #[codec(index = 91)] - Proxy(ProxyCall), - } -} - pub mod kusama { pub use crate::calls::*; @@ -79,10 +74,15 @@ pub mod kusama { #[codec(index = 30)] Proxy(ProxyCall), } + + #[derive(Encode, Decode, RuntimeDebug)] + pub enum AssetHubCall { + #[codec(index = 31)] + PolkadotXcm(PolkadotXcmCall), + } } pub mod polkadot { - pub use crate::calls::*; #[derive(Encode, Decode, RuntimeDebug)] @@ -92,6 +92,12 @@ pub mod polkadot { #[codec(index = 29)] Proxy(ProxyCall), } + + #[derive(Encode, Decode, RuntimeDebug)] + pub enum AssetHubCall { + #[codec(index = 31)] + PolkadotXcm(PolkadotXcmCall), + } } #[derive(Encode, Decode, RuntimeDebug)] diff --git a/pallets/xcm-interface/src/lib.rs b/pallets/xcm-interface/src/lib.rs index e7ea013fc..f56f617ee 100644 --- a/pallets/xcm-interface/src/lib.rs +++ b/pallets/xcm-interface/src/lib.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . #![cfg_attr(not(feature = "std"), no_std)] +#![allow(unused_imports)] pub mod calls; pub mod traits; @@ -25,20 +26,19 @@ use bifrost_primitives::{traits::XcmDestWeightAndFeeHandler, CurrencyIdMapping, pub use calls::*; use orml_traits::MultiCurrency; pub use pallet::*; +use sp_runtime::traits::UniqueSaturatedInto; pub use traits::{ChainId, MessageId, Nonce, SalpHelper}; macro_rules! use_relay { ({ $( $code:tt )* }) => { if T::RelayNetwork::get() == NetworkId::Polkadot { use polkadot::RelaychainCall; + use polkadot::AssetHubCall; $( $code )* } else if T::RelayNetwork::get() == NetworkId::Kusama { use kusama::RelaychainCall; - - $( $code )* - } else if T::RelayNetwork::get() == NetworkId::Rococo { - use rococo::RelaychainCall; + use kusama::AssetHubCall; $( $code )* } else { @@ -135,6 +135,7 @@ pub mod pallet { pub enum Event { XcmDestWeightAndFeeUpdated(XcmOperationType, CurrencyIdOf, Weight, BalanceOf), TransferredStatemineMultiAsset(AccountIdOf, BalanceOf), + TransferredEthereumAssets(AccountIdOf, sp_core::H160, BalanceOf), } /// The current storage version, we set to 2 our new version(after migrate stroage @@ -278,6 +279,76 @@ pub mod pallet { Ok(()) } + + #[pallet::call_index(2)] + #[pallet::weight({2_000_000_000})] + pub fn transfer_ethereum_assets( + origin: OriginFor, + currency_id: CurrencyIdOf, + amount: BalanceOf, + to: sp_core::H160, + ) -> DispatchResult { + let who = ensure_signed(origin.clone())?; + let asset_location = + T::CurrencyIdConvert::get_location(currency_id).ok_or(Error::::FailToConvert)?; + + let asset: Asset = Asset { + id: AssetId(asset_location), + fun: Fungible(UniqueSaturatedInto::::unique_saturated_into(amount)), + }; + + let (require_weight_at_most, xcm_fee) = + Self::xcm_dest_weight_and_fee(currency_id, XcmOperationType::EthereumTransfer) + .ok_or(Error::::OperationWeightAndFeeNotExist)?; + + let fee: Asset = Asset { + id: AssetId(Location::parent()), + fun: Fungible(UniqueSaturatedInto::::unique_saturated_into(xcm_fee)), + }; + + T::MultiCurrency::withdraw(currency_id, &who, amount)?; + + let remote_call: DoubleEncoded<()> = use_relay!({ + AssetHubCall::PolkadotXcm(PolkadotXcmCall::LimitedReserveTransferAssets( + Box::new(Location::new(2, [GlobalConsensus(Ethereum { chain_id: 1 })]).into()), + Box::new( + Location::new( + 0, + [AccountKey20 { network: None, key: to.to_fixed_bytes() }], + ) + .into(), + ), + Box::new(asset.into()), + 0, + Unlimited, + )) + .encode() + .into() + }); + + let remote_xcm = Xcm(vec![ + WithdrawAsset(fee.clone().into()), + BuyExecution { fees: fee.clone(), weight_limit: Unlimited }, + Transact { + origin_kind: OriginKind::SovereignAccount, + require_weight_at_most, + call: remote_call, + }, + DepositAsset { + assets: All.into(), + beneficiary: Location::new(1, [Parachain(T::ParachainId::get().into())]), + }, + ]); + let (ticket, _) = ::XcmRouter::validate( + &mut Some(Location::new(1, [Parachain(parachains::Statemine::ID)])), + &mut Some(remote_xcm), + ) + .map_err(|_| Error::::UnweighableMessage)?; + ::XcmRouter::deliver(ticket) + .map_err(|_| Error::::XcmExecutionFailed)?; + Self::deposit_event(Event::::TransferredEthereumAssets(who, to, amount)); + Ok(()) + } } impl XcmHelper, BalanceOf> for Pallet { diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 9e675a505..be5155042 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -329,6 +329,7 @@ pub enum XcmOperationType { RemoveVote, Any, SupplementaryFee, + EthereumTransfer, } pub struct ExtraFeeInfo { diff --git a/runtime/bifrost-kusama/src/xcm_config.rs b/runtime/bifrost-kusama/src/xcm_config.rs index edecccb34..bf9e45f8d 100644 --- a/runtime/bifrost-kusama/src/xcm_config.rs +++ b/runtime/bifrost-kusama/src/xcm_config.rs @@ -629,6 +629,38 @@ impl Contains for SafeCallFilter { } } +/// Asset filter that allows all assets from a certain location matching asset id. +pub struct AssetPrefixFrom(PhantomData<(Prefix, Origin)>); +impl ContainsPair for AssetPrefixFrom +where + Prefix: Get, + Origin: Get, +{ + fn contains(asset: &Asset, origin: &Location) -> bool { + let loc = Origin::get(); + &loc == origin && + matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) } + if asset_loc.starts_with(&Prefix::get())) + } +} + +/// Asset filter that allows native/relay asset if coming from a certain location. +pub struct NativeAssetFrom(PhantomData); +impl> ContainsPair for NativeAssetFrom { + fn contains(asset: &Asset, origin: &Location) -> bool { + let loc = T::get(); + &loc == origin && + matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) } + if *asset_loc == Location::from(Parent)) + } +} + +parameter_types! { + /// Location of Asset Hub + pub AssetHubLocation: Location = (Parent, Parachain(1000)).into(); + pub EthereumLocation: Location = Location::new(2, [GlobalConsensus(Ethereum { chain_id: 1 })]); +} + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type AssetClaims = PolkadotXcm; @@ -636,7 +668,11 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = BifrostDropAssets; type Barrier = Barrier; type RuntimeCall = RuntimeCall; - type IsReserve = MultiNativeAsset; + type IsReserve = ( + NativeAssetFrom, + AssetPrefixFrom, + MultiNativeAsset, + ); type IsTeleporter = (); type UniversalLocation = UniversalLocation; type OriginConverter = XcmOriginToTransactDispatchOrigin; diff --git a/runtime/bifrost-polkadot/src/xcm_config.rs b/runtime/bifrost-polkadot/src/xcm_config.rs index 22d7e397e..dd0ee5eb2 100644 --- a/runtime/bifrost-polkadot/src/xcm_config.rs +++ b/runtime/bifrost-polkadot/src/xcm_config.rs @@ -483,6 +483,38 @@ impl Contains for SafeCallFilter { } } +/// Asset filter that allows all assets from a certain location matching asset id. +pub struct AssetPrefixFrom(PhantomData<(Prefix, Origin)>); +impl ContainsPair for AssetPrefixFrom +where + Prefix: Get, + Origin: Get, +{ + fn contains(asset: &Asset, origin: &Location) -> bool { + let loc = Origin::get(); + &loc == origin && + matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) } + if asset_loc.starts_with(&Prefix::get())) + } +} + +/// Asset filter that allows native/relay asset if coming from a certain location. +pub struct NativeAssetFrom(PhantomData); +impl> ContainsPair for NativeAssetFrom { + fn contains(asset: &Asset, origin: &Location) -> bool { + let loc = T::get(); + &loc == origin && + matches!(asset, Asset { id: AssetId(asset_loc), fun: Fungible(_a) } + if *asset_loc == Location::from(Parent)) + } +} + +parameter_types! { + /// Location of Asset Hub + pub AssetHubLocation: Location = (Parent, Parachain(1000)).into(); + pub EthereumLocation: Location = Location::new(2, [GlobalConsensus(Ethereum { chain_id: 1 })]); +} + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type AssetClaims = PolkadotXcm; @@ -490,7 +522,11 @@ impl xcm_executor::Config for XcmConfig { type AssetTrap = BifrostDropAssets; type Barrier = Barrier; type RuntimeCall = RuntimeCall; - type IsReserve = MultiNativeAsset; + type IsReserve = ( + NativeAssetFrom, + AssetPrefixFrom, + MultiNativeAsset, + ); type IsTeleporter = (); type UniversalLocation = UniversalLocation; type OriginConverter = XcmOriginToTransactDispatchOrigin;