diff --git a/pallets/moonbeam-foreign-assets/src/benchmarks.rs b/pallets/moonbeam-foreign-assets/src/benchmarks.rs index fa4dd3601d..92d8d1f894 100644 --- a/pallets/moonbeam-foreign-assets/src/benchmarks.rs +++ b/pallets/moonbeam-foreign-assets/src/benchmarks.rs @@ -16,18 +16,27 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{AssetStatus, Call, Config, Pallet}; -use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; +use crate::{pallet, AssetStatus, Call, Config, Pallet}; +use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; use frame_support::pallet_prelude::*; +use frame_support::traits::Currency; use frame_system::RawOrigin; use sp_runtime::traits::ConstU32; use sp_runtime::BoundedVec; use xcm::latest::prelude::*; +fn create_funded_user(string: &'static str, n: u32, balance: u32) -> T::AccountId { + const SEED: u32 = 0; + let user = account(string, n, SEED); + let _ = ::Currency::make_free_balance_be(&user, balance.into()); + let _ = ::Currency::issue(balance.into()); + user +} fn create_n_foreign_asset(n: u32) -> DispatchResult { + let user: T::AccountId = create_funded_user::("user", n, 100); for i in 1..=n { Pallet::::create_foreign_asset( - RawOrigin::Root.into(), + RawOrigin::Signed(user.clone()).into(), i as u128, location_of(i), 18, @@ -53,7 +62,7 @@ benchmarks! { create_foreign_asset { create_n_foreign_asset::(T::MaxForeignAssets::get().saturating_sub(1))?; let asset_id = T::MaxForeignAssets::get() as u128; - }: _(RawOrigin::Root, asset_id, Location::parent(), 18, str_to_bv("MT"), str_to_bv("Mytoken")) + }: _(RawOrigin::Signed(create_funded_user::("user", 1, 100)), asset_id, Location::parent(), 18, str_to_bv("MT"), str_to_bv("Mytoken")) verify { assert_eq!( Pallet::::assets_by_id(asset_id), diff --git a/pallets/moonbeam-foreign-assets/src/lib.rs b/pallets/moonbeam-foreign-assets/src/lib.rs index 1d92d8facd..c54cbc00bd 100644 --- a/pallets/moonbeam-foreign-assets/src/lib.rs +++ b/pallets/moonbeam-foreign-assets/src/lib.rs @@ -107,8 +107,9 @@ pub enum AssetStatus { #[pallet] pub mod pallet { use super::*; + use frame_support::traits::{Currency, ReservableCurrency}; use pallet_evm::{GasWeightMapping, Runner}; - use sp_runtime::traits::{AccountIdConversion, Convert}; + use sp_runtime::traits::{AccountIdConversion, AtLeast32BitUnsigned, Convert}; use xcm_executor::traits::ConvertLocation; use xcm_executor::traits::Error as MatchError; use xcm_executor::AssetsInHolding; @@ -121,7 +122,7 @@ pub mod pallet { pub const PALLET_ID: frame_support::PalletId = frame_support::PalletId(*b"forgasst"); #[pallet::config] - pub trait Config: frame_system::Config + pallet_evm::Config { + pub trait Config: frame_system::Config + pallet_evm::Config + scale_info::TypeInfo { // Convert AccountId to H160 type AccountIdToH160: Convert; @@ -131,23 +132,13 @@ pub mod pallet { /// EVM runner type EvmRunner: Runner; - /// Origin that is allowed to create a new foreign assets - type ForeignAssetCreatorOrigin: EnsureOrigin; - - /// Origin that is allowed to freeze all tokens of a foreign asset - type ForeignAssetFreezerOrigin: EnsureOrigin; - - /// Origin that is allowed to modify asset information for foreign assets - type ForeignAssetModifierOrigin: EnsureOrigin; - - /// Origin that is allowed to unfreeze all tokens of a foreign asset that was previously - /// frozen - type ForeignAssetUnfreezerOrigin: EnsureOrigin; + /// Origin that is allowed to create new foreign assets + type EnsureXcmLocation: EnsureXcmLocation; /// Hook to be called when new foreign asset is registered. type OnForeignAssetCreated: ForeignAssetCreatedHook; - /// Maximum nulmbers of differnt foreign assets + /// Maximum numbers of differnt foreign assets type MaxForeignAssets: Get; /// The overarching event type. @@ -158,8 +149,27 @@ pub mod pallet { // Convert XCM Location to H160 type XcmLocationToH160: ConvertLocation; + + /// Amount of tokens required to lock for creating a new foreign asset + type ForeignAssetCreationDeposit: Get>; + + /// The balance type for locking funds + type Balance: Member + + Parameter + + AtLeast32BitUnsigned + + Default + + Copy + + MaybeSerializeDeserialize + + MaxEncodedLen + + TypeInfo; + + /// The currency type for locking funds + type Currency: ReservableCurrency; } + type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + pub type AssetBalance = U256; pub type AssetId = u128; @@ -177,6 +187,10 @@ pub mod pallet { EvmCallPauseFail, EvmCallUnpauseFail, EvmInternalError, + /// Account has insufficient balance for locking + InsufficientBalance, + OriginIsNotAssetCreator, + CannotConvertLocationToAccount, InvalidSymbol, InvalidTokenName, LocationAlreadyExists, @@ -191,6 +205,7 @@ pub mod pallet { contract_address: H160, asset_id: AssetId, xcm_location: Location, + deposit: Option>, }, /// Changed the xcm type mapping for a given asset id ForeignAssetXcmLocationChanged { @@ -207,6 +222,10 @@ pub mod pallet { asset_id: AssetId, xcm_location: Location, }, + /// Tokens have been locked for asset creation + TokensLocked(T::AccountId, AssetId, AssetBalance), + /// Lock verification failed + LockVerificationFailed(T::AccountId, AssetId), } /// Mapping from an asset id to a Foreign asset type. @@ -225,6 +244,18 @@ pub mod pallet { pub type AssetsByLocation = StorageMap<_, Blake2_128Concat, Location, (AssetId, AssetStatus)>; + /// Mapping from an asset id to its creation details + #[pallet::storage] + #[pallet::getter(fn assets_creation_details)] + pub type AssetsCreationDetails = + StorageMap<_, Blake2_128Concat, AssetId, AssetCreationDetails>; + + #[derive(Clone, Decode, Encode, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen)] + pub struct AssetCreationDetails { + pub owner: T::AccountId, + pub deposit: Option>, + } + impl Pallet { /// The account ID of this pallet #[inline] @@ -242,6 +273,8 @@ pub mod pallet { H160(buffer) } + /// This method only exists for migration purposes and will be deleted once the + /// foreign assets migration is finished. pub fn register_foreign_asset( asset_id: AssetId, xcm_location: Location, @@ -274,18 +307,26 @@ pub mod pallet { let name = core::str::from_utf8(&name).map_err(|_| Error::::InvalidTokenName)?; let contract_address = EvmCaller::::erc20_create(asset_id, decimals, symbol, name)?; + let owner = T::EnsureXcmLocation::account_for_location(&xcm_location) + .ok_or(Error::::CannotConvertLocationToAccount)?; // Insert the association assetId->foreigAsset // Insert the association foreigAsset->assetId AssetsById::::insert(&asset_id, &xcm_location); AssetsByLocation::::insert(&xcm_location, (asset_id, AssetStatus::Active)); - - T::OnForeignAssetCreated::on_asset_created(&xcm_location, &asset_id); + AssetsCreationDetails::::insert( + &asset_id, + AssetCreationDetails { + owner, + deposit: None, + }, + ); Self::deposit_event(Event::ForeignAssetCreated { contract_address, asset_id, xcm_location, + deposit: None, }); Ok(()) } @@ -355,7 +396,8 @@ pub mod pallet { symbol: BoundedVec>, name: BoundedVec>, ) -> DispatchResult { - T::ForeignAssetCreatorOrigin::ensure_origin(origin)?; + let owner_account = + T::EnsureXcmLocation::ensure_xcm_origin(origin.clone(), Some(&xcm_location))?; // Ensure such an assetId does not exist ensure!( @@ -380,20 +422,34 @@ pub mod pallet { let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::::InvalidSymbol)?; let name = core::str::from_utf8(&name).map_err(|_| Error::::InvalidTokenName)?; - let contract_address = EvmCaller::::erc20_create(asset_id, decimals, symbol, name)?; + let deposit = T::ForeignAssetCreationDeposit::get(); + let owner = owner_account.clone(); // Insert the association assetId->foreigAsset // Insert the association foreigAsset->assetId AssetsById::::insert(&asset_id, &xcm_location); AssetsByLocation::::insert(&xcm_location, (asset_id, AssetStatus::Active)); + // Reserve _deposit_ amount of funds from the caller + ::Currency::reserve(&owner_account, deposit)?; + + // Insert the amount that is reserved from the user + AssetsCreationDetails::::insert( + &asset_id, + AssetCreationDetails { + owner, + deposit: Some(deposit), + }, + ); + T::OnForeignAssetCreated::on_asset_created(&xcm_location, &asset_id); Self::deposit_event(Event::ForeignAssetCreated { contract_address, asset_id, xcm_location, + deposit: Some(deposit), }); Ok(()) } @@ -408,11 +464,14 @@ pub mod pallet { asset_id: AssetId, new_xcm_location: Location, ) -> DispatchResult { - T::ForeignAssetModifierOrigin::ensure_origin(origin)?; + // Ensures that the origin is an XCM location that contains the asset + T::EnsureXcmLocation::ensure_xcm_origin(origin.clone(), Some(&new_xcm_location))?; let previous_location = AssetsById::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; + T::EnsureXcmLocation::ensure_xcm_origin(origin, Some(&previous_location))?; + ensure!( !AssetsByLocation::::contains_key(&new_xcm_location), Error::::LocationAlreadyExists @@ -441,11 +500,16 @@ pub mod pallet { asset_id: AssetId, allow_xcm_deposit: bool, ) -> DispatchResult { - T::ForeignAssetFreezerOrigin::ensure_origin(origin)?; + // Ensure that the origin is coming from an XCM. + T::EnsureXcmLocation::ensure_xcm_origin(origin.clone(), None)?; let xcm_location = AssetsById::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; + // Ensures that the origin is an XCM location that owns the asset + // represented by the assets xcm location + T::EnsureXcmLocation::ensure_xcm_origin(origin, Some(&xcm_location))?; + let (_asset_id, asset_status) = AssetsByLocation::::get(&xcm_location) .ok_or(Error::::CorruptedStorageOrphanLocation)?; @@ -475,10 +539,12 @@ pub mod pallet { #[pallet::call_index(3)] #[pallet::weight(::WeightInfo::unfreeze_foreign_asset())] pub fn unfreeze_foreign_asset(origin: OriginFor, asset_id: AssetId) -> DispatchResult { - T::ForeignAssetUnfreezerOrigin::ensure_origin(origin)?; + T::EnsureXcmLocation::ensure_xcm_origin(origin.clone(), None)?; let xcm_location = AssetsById::::get(&asset_id).ok_or(Error::::AssetDoesNotExist)?; + // Ensures that the origin is an XCM location that contains the asset + T::EnsureXcmLocation::ensure_xcm_origin(origin, Some(&xcm_location))?; let (_asset_id, asset_status) = AssetsByLocation::::get(&xcm_location) .ok_or(Error::::CorruptedStorageOrphanLocation)?; @@ -596,4 +662,13 @@ pub mod pallet { AssetsById::::get(asset_id) } } + + /// A trait to ensure that the origin is an XCM location that contains the asset + pub trait EnsureXcmLocation { + fn ensure_xcm_origin( + origin: T::RuntimeOrigin, + location: Option<&Location>, + ) -> Result; + fn account_for_location(location: &Location) -> Option; + } } diff --git a/pallets/moonbeam-foreign-assets/src/mock.rs b/pallets/moonbeam-foreign-assets/src/mock.rs index 7f96e1194a..7a82cb57fb 100644 --- a/pallets/moonbeam-foreign-assets/src/mock.rs +++ b/pallets/moonbeam-foreign-assets/src/mock.rs @@ -17,11 +17,11 @@ use super::*; use crate as pallet_moonbeam_foreign_assets; -use frame_support::traits::Everything; +use frame_support::traits::{EnsureOriginWithArg, Everything}; use frame_support::{construct_runtime, pallet_prelude::*, parameter_types}; -use frame_system::EnsureRoot; +use frame_system::EnsureSigned; use pallet_evm::{FrameSystemAccountProvider, SubstrateBlockHashMapping}; -use precompile_utils::testing::MockAccount; +use precompile_utils::testing::{Alice, MockAccount}; use sp_core::{H256, U256}; use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use sp_runtime::BuildStorage; @@ -29,6 +29,7 @@ use xcm::latest::Location; pub type Balance = u128; +pub type BlockNumber = u32; type AccountId = MockAccount; type Block = frame_system::mocking::MockBlock; @@ -176,19 +177,51 @@ impl sp_runtime::traits::Convert for AccountIdToH160 { } } +pub struct ForeignAssetMockOrigin; +impl EnsureOriginWithArg for ForeignAssetMockOrigin { + type Success = AccountId; + + fn try_origin( + _: RuntimeOrigin, + _: &Location, + ) -> core::result::Result { + Ok(Alice.into()) + } +} + +parameter_types! { + pub const ForeignAssetCreationDeposit: u128 = 1; +} + +pub struct ForeignAssetsEnsureXCM; + +impl EnsureXcmLocation for ForeignAssetsEnsureXCM { + fn ensure_xcm_origin( + origin: RuntimeOrigin, + location: Option<&Location>, + ) -> Result { + ensure_signed(origin).map_err(|_| DispatchError::BadOrigin) + } + + fn account_for_location(location: &Location) -> Option { + Some(Alice.into()) + } +} + impl crate::Config for Test { type AccountIdToH160 = AccountIdToH160; type AssetIdFilter = Everything; type EvmRunner = pallet_evm::runner::stack::Runner; - type ForeignAssetCreatorOrigin = EnsureRoot; - type ForeignAssetFreezerOrigin = EnsureRoot; - type ForeignAssetModifierOrigin = EnsureRoot; - type ForeignAssetUnfreezerOrigin = EnsureRoot; + type EnsureXcmLocation = ForeignAssetsEnsureXCM; type OnForeignAssetCreated = NoteDownHook; type MaxForeignAssets = ConstU32<3>; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type XcmLocationToH160 = (); + type ForeignAssetCreationDeposit = ForeignAssetCreationDeposit; + type Balance = Balance; + + type Currency = Balances; } pub(crate) struct ExtBuilder { @@ -217,6 +250,11 @@ impl ExtBuilder { ext.execute_with(|| System::set_block_number(1)); ext } + + pub fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } } pub(crate) fn events() -> Vec> { diff --git a/pallets/moonbeam-foreign-assets/src/tests.rs b/pallets/moonbeam-foreign-assets/src/tests.rs index ee7689f17b..68f9a6d62b 100644 --- a/pallets/moonbeam-foreign-assets/src/tests.rs +++ b/pallets/moonbeam-foreign-assets/src/tests.rs @@ -18,7 +18,7 @@ use crate::*; use mock::*; use frame_support::{assert_noop, assert_ok}; -use precompile_utils::testing::Bob; +use precompile_utils::testing::{Alice, Bob}; use xcm::latest::prelude::*; fn encode_ticker(str_: &str) -> BoundedVec> { @@ -31,181 +31,193 @@ fn encode_token_name(str_: &str) -> BoundedVec> { #[test] fn create_foreign_and_freeze_unfreeze() { - ExtBuilder::default().build().execute_with(|| { - // create foreign asset - assert_ok!(EvmForeignAssets::create_foreign_asset( - RuntimeOrigin::root(), - 1, - Location::parent(), - 18, - encode_ticker("MTT"), - encode_token_name("Mytoken"), - )); - - assert_eq!(EvmForeignAssets::assets_by_id(1), Some(Location::parent())); - assert_eq!( - EvmForeignAssets::assets_by_location(Location::parent()), - Some((1, AssetStatus::Active)), - ); - expect_events(vec![crate::Event::ForeignAssetCreated { - contract_address: H160([ - 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - ]), - asset_id: 1, - xcm_location: Location::parent(), - }]); - - let (xcm_location, asset_id): (Location, u128) = get_asset_created_hook_invocation() - .expect("Decoding of invocation data should not fail"); - assert_eq!(xcm_location, Location::parent()); - assert_eq!(asset_id, 1u128); - - // Check storage - assert_eq!(EvmForeignAssets::assets_by_id(&1), Some(Location::parent())); - assert_eq!( - EvmForeignAssets::assets_by_location(&Location::parent()), - Some((1, AssetStatus::Active)) - ); + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1_000)]) + .build() + .execute_with(|| { + // create foreign asset + assert_ok!(EvmForeignAssets::create_foreign_asset( + RuntimeOrigin::from(Some(Alice.into())), + 1, + Location::parent(), + 18, + encode_ticker("MTT"), + encode_token_name("Mytoken"), + )); - // Unfreeze should return AssetNotFrozen error - assert_noop!( - EvmForeignAssets::unfreeze_foreign_asset(RuntimeOrigin::root(), 1), - Error::::AssetNotFrozen - ); + assert_eq!(EvmForeignAssets::assets_by_id(1), Some(Location::parent())); + assert_eq!( + EvmForeignAssets::assets_by_location(Location::parent()), + Some((1, AssetStatus::Active)), + ); + assert_eq!( + EvmForeignAssets::assets_by_location(Location::parent()), + Some((1, AssetStatus::Active)), + ); + assert_eq!( + EvmForeignAssets::assets_creation_details(&1), + Some(AssetCreationDetails { + owner: Alice.into(), + deposit: Some(ForeignAssetCreationDeposit::get()) + }) + ); - // Freeze should work - assert_ok!(EvmForeignAssets::freeze_foreign_asset( - RuntimeOrigin::root(), - 1, - true - ),); - assert_eq!( - EvmForeignAssets::assets_by_location(&Location::parent()), - Some((1, AssetStatus::FrozenXcmDepositAllowed)) - ); + expect_events(vec![crate::Event::ForeignAssetCreated { + contract_address: H160([ + 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]), + asset_id: 1, + xcm_location: Location::parent(), + deposit: Some(ForeignAssetCreationDeposit::get()), + }]); - // Should not be able to freeze an asset already frozen - assert_noop!( - EvmForeignAssets::freeze_foreign_asset(RuntimeOrigin::root(), 1, true), - Error::::AssetAlreadyFrozen - ); + let (xcm_location, asset_id): (Location, u128) = get_asset_created_hook_invocation() + .expect("Decoding of invocation data should not fail"); + assert_eq!(xcm_location, Location::parent()); + assert_eq!(asset_id, 1u128); - // Unfreeze should work - assert_ok!(EvmForeignAssets::unfreeze_foreign_asset( - RuntimeOrigin::root(), - 1 - ),); - assert_eq!( - EvmForeignAssets::assets_by_location(&Location::parent()), - Some((1, AssetStatus::Active)) - ); - }); + // Check storage + assert_eq!(EvmForeignAssets::assets_by_id(&1), Some(Location::parent())); + assert_eq!( + EvmForeignAssets::assets_by_location(&Location::parent()), + Some((1, AssetStatus::Active)) + ); + assert_eq!( + EvmForeignAssets::assets_creation_details(&1), + Some(AssetCreationDetails { + owner: Alice.into(), + deposit: Some(1) + }) + ); + + // Unfreeze should return AssetNotFrozen error + assert_noop!( + EvmForeignAssets::unfreeze_foreign_asset(RuntimeOrigin::signed(Alice.into()), 1), + Error::::AssetNotFrozen + ); + + // Freeze should work + assert_ok!(EvmForeignAssets::freeze_foreign_asset( + RuntimeOrigin::signed(Alice.into()), + 1, + true + ),); + assert_eq!( + EvmForeignAssets::assets_by_location(&Location::parent()), + Some((1, AssetStatus::FrozenXcmDepositAllowed)) + ); + + // Should not be able to freeze an asset already frozen + assert_noop!( + EvmForeignAssets::freeze_foreign_asset( + RuntimeOrigin::signed(Alice.into()), + 1, + true + ), + Error::::AssetAlreadyFrozen + ); + + // Unfreeze should work + assert_ok!(EvmForeignAssets::unfreeze_foreign_asset( + RuntimeOrigin::signed(Alice.into()), + 1 + ),); + assert_eq!( + EvmForeignAssets::assets_by_location(&Location::parent()), + Some((1, AssetStatus::Active)) + ); + }); } #[test] fn test_asset_exists_error() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EvmForeignAssets::create_foreign_asset( - RuntimeOrigin::root(), - 1, - Location::parent(), - 18, - encode_ticker("MTT"), - encode_token_name("Mytoken"), - )); - assert_eq!( - EvmForeignAssets::assets_by_id(1).unwrap(), - Location::parent() - ); - assert_noop!( - EvmForeignAssets::create_foreign_asset( - RuntimeOrigin::root(), + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1_000)]) + .build() + .execute_with(|| { + assert_ok!(EvmForeignAssets::create_foreign_asset( + RuntimeOrigin::signed(Alice.into()), 1, Location::parent(), 18, encode_ticker("MTT"), encode_token_name("Mytoken"), - ), - Error::::AssetAlreadyExists - ); - }); + )); + assert_eq!( + EvmForeignAssets::assets_by_id(1).unwrap(), + Location::parent() + ); + assert_noop!( + EvmForeignAssets::create_foreign_asset( + RuntimeOrigin::signed(Alice.into()), + 1, + Location::parent(), + 18, + encode_ticker("MTT"), + encode_token_name("Mytoken"), + ), + Error::::AssetAlreadyExists + ); + }); } #[test] -fn test_regular_user_cannot_call_extrinsics() { - ExtBuilder::default().build().execute_with(|| { - assert_noop!( - EvmForeignAssets::create_foreign_asset( - RuntimeOrigin::signed(Bob.into()), +fn test_root_can_change_foreign_asset_for_asset_id() { + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1_000)]) + .build() + .execute_with(|| { + assert_ok!(EvmForeignAssets::create_foreign_asset( + RuntimeOrigin::signed(Alice.into()), 1, Location::parent(), 18, encode_ticker("MTT"), encode_token_name("Mytoken"), - ), - sp_runtime::DispatchError::BadOrigin - ); + )); - assert_noop!( - EvmForeignAssets::change_xcm_location( - RuntimeOrigin::signed(Bob.into()), + assert_ok!(EvmForeignAssets::change_xcm_location( + RuntimeOrigin::signed(Alice.into()), 1, - Location::parent() - ), - sp_runtime::DispatchError::BadOrigin - ); - }); -} + Location::here() + )); -#[test] -fn test_root_can_change_foreign_asset_for_asset_id() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(EvmForeignAssets::create_foreign_asset( - RuntimeOrigin::root(), - 1, - Location::parent(), - 18, - encode_ticker("MTT"), - encode_token_name("Mytoken"), - )); - - assert_ok!(EvmForeignAssets::change_xcm_location( - RuntimeOrigin::root(), - 1, - Location::here() - )); - - // New associations are stablished - assert_eq!(EvmForeignAssets::assets_by_id(1).unwrap(), Location::here()); - assert_eq!( - EvmForeignAssets::assets_by_location(Location::here()).unwrap(), - (1, AssetStatus::Active), - ); + // New associations are stablished + assert_eq!(EvmForeignAssets::assets_by_id(1).unwrap(), Location::here()); + assert_eq!( + EvmForeignAssets::assets_by_location(Location::here()).unwrap(), + (1, AssetStatus::Active), + ); - // Old ones are deleted - assert!(EvmForeignAssets::assets_by_location(Location::parent()).is_none()); + // Old ones are deleted + assert!(EvmForeignAssets::assets_by_location(Location::parent()).is_none()); - expect_events(vec![ - crate::Event::ForeignAssetCreated { - contract_address: H160([ - 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - ]), - asset_id: 1, - xcm_location: Location::parent(), - }, - crate::Event::ForeignAssetXcmLocationChanged { - asset_id: 1, - new_xcm_location: Location::here(), - }, - ]) - }); + expect_events(vec![ + crate::Event::ForeignAssetCreated { + contract_address: H160([ + 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]), + asset_id: 1, + xcm_location: Location::parent(), + deposit: Some(ForeignAssetCreationDeposit::get()), + }, + crate::Event::ForeignAssetXcmLocationChanged { + asset_id: 1, + new_xcm_location: Location::here(), + }, + ]) + }); } #[test] fn test_asset_id_non_existent_error() { ExtBuilder::default().build().execute_with(|| { assert_noop!( - EvmForeignAssets::change_xcm_location(RuntimeOrigin::root(), 1, Location::parent()), + EvmForeignAssets::change_xcm_location( + RuntimeOrigin::from(Some(Alice.into())), + 1, + Location::parent() + ), Error::::AssetDoesNotExist ); }); @@ -213,42 +225,49 @@ fn test_asset_id_non_existent_error() { #[test] fn test_location_already_exist_error() { - ExtBuilder::default().build().execute_with(|| { - // Setup: create a first foreign asset taht we will try to override - assert_ok!(EvmForeignAssets::create_foreign_asset( - RuntimeOrigin::root(), - 1, - Location::parent(), - 18, - encode_ticker("MTT"), - encode_token_name("Mytoken"), - )); - - assert_noop!( - EvmForeignAssets::create_foreign_asset( - RuntimeOrigin::root(), - 2, + ExtBuilder::default() + .with_balances(vec![(Alice.into(), 1_000)]) + .build() + .execute_with(|| { + // Setup: create a first foreign asset taht we will try to override + assert_ok!(EvmForeignAssets::create_foreign_asset( + RuntimeOrigin::from(Some(Alice.into())), + 1, Location::parent(), 18, encode_ticker("MTT"), encode_token_name("Mytoken"), - ), - Error::::LocationAlreadyExists - ); + )); - // Setup: create a second foreign asset that will try to override the first one - assert_ok!(EvmForeignAssets::create_foreign_asset( - RuntimeOrigin::root(), - 2, - Location::new(2, *&[]), - 18, - encode_ticker("MTT"), - encode_token_name("Mytoken"), - )); + assert_noop!( + EvmForeignAssets::create_foreign_asset( + RuntimeOrigin::from(Some(Alice.into())), + 2, + Location::parent(), + 18, + encode_ticker("MTT"), + encode_token_name("Mytoken"), + ), + Error::::LocationAlreadyExists + ); - assert_noop!( - EvmForeignAssets::change_xcm_location(RuntimeOrigin::root(), 2, Location::parent()), - Error::::LocationAlreadyExists - ); - }); + // Setup: create a second foreign asset that will try to override the first one + assert_ok!(EvmForeignAssets::create_foreign_asset( + RuntimeOrigin::from(Some(Alice.into())), + 2, + Location::new(2, *&[]), + 18, + encode_ticker("MTT"), + encode_token_name("Mytoken"), + )); + + assert_noop!( + EvmForeignAssets::change_xcm_location( + RuntimeOrigin::from(Some(Alice.into())), + 2, + Location::parent() + ), + Error::::LocationAlreadyExists + ); + }); } diff --git a/pallets/moonbeam-foreign-assets/src/weights.rs b/pallets/moonbeam-foreign-assets/src/weights.rs index 8b270fa1c5..c2797d494b 100644 --- a/pallets/moonbeam-foreign-assets/src/weights.rs +++ b/pallets/moonbeam-foreign-assets/src/weights.rs @@ -75,6 +75,8 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + + /// Storage: `ForeignAssetsCreator::AssetIdToForeignAsset` (r:1 w:1) /// Proof: `ForeignAssetsCreator::AssetIdToForeignAsset` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ForeignAssetsCreator::ForeignAssetToAssetId` (r:0 w:2) diff --git a/pallets/moonbeam-lazy-migrations/src/mock.rs b/pallets/moonbeam-lazy-migrations/src/mock.rs index 6aecf8164c..29a4711625 100644 --- a/pallets/moonbeam-lazy-migrations/src/mock.rs +++ b/pallets/moonbeam-lazy-migrations/src/mock.rs @@ -18,13 +18,14 @@ use super::*; use crate as pallet_moonbeam_lazy_migrations; -use frame_support::traits::AsEnsureOriginWithArg; +use frame_support::traits::{AsEnsureOriginWithArg, ConstU128}; use frame_support::weights::constants::RocksDbWeight; use frame_support::{construct_runtime, parameter_types, traits::Everything, weights::Weight}; use frame_system::{EnsureRoot, EnsureSigned}; use pallet_asset_manager::AssetRegistrar; use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, FrameSystemAccountProvider}; -use precompile_utils::testing::MockAccount; +use pallet_moonbeam_foreign_assets::EnsureXcmLocation; +use precompile_utils::testing::{Alice, MockAccount}; use sp_core::{ConstU32, H160, H256, U256}; use sp_runtime::{ traits::{BlakeTwo256, Hash, IdentityLookup}, @@ -288,19 +289,34 @@ impl sp_runtime::traits::Convert for AccountIdToH160 { } } +pub struct ForeignAssetsEnsureXCM; + +impl EnsureXcmLocation for ForeignAssetsEnsureXCM { + fn ensure_xcm_origin( + origin: RuntimeOrigin, + location: Option<&Location>, + ) -> Result { + ensure_signed(origin).map_err(|_| DispatchError::BadOrigin) + } + + fn account_for_location(location: &Location) -> Option { + Some(Alice.into()) + } +} + impl pallet_moonbeam_foreign_assets::Config for Test { type AccountIdToH160 = AccountIdToH160; type AssetIdFilter = Everything; type EvmRunner = pallet_evm::runner::stack::Runner; - type ForeignAssetCreatorOrigin = EnsureRoot; - type ForeignAssetFreezerOrigin = EnsureRoot; - type ForeignAssetModifierOrigin = EnsureRoot; - type ForeignAssetUnfreezerOrigin = EnsureRoot; + type EnsureXcmLocation = ForeignAssetsEnsureXCM; type OnForeignAssetCreated = (); type MaxForeignAssets = ConstU32<3>; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type XcmLocationToH160 = (); + type ForeignAssetCreationDeposit = ConstU128<1>; + type Balance = Balance; + type Currency = Balances; } impl Config for Test { diff --git a/runtime/moonbase/src/foreign_origin.rs b/runtime/moonbase/src/foreign_origin.rs new file mode 100644 index 0000000000..562b879f10 --- /dev/null +++ b/runtime/moonbase/src/foreign_origin.rs @@ -0,0 +1,48 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use crate::xcm_config::LocationToAccountId; +use crate::{Runtime, RuntimeOrigin}; +use frame_support::ensure; +use frame_support::traits::{EnsureOrigin, Everything}; +use moonbeam_core_primitives::AccountId; +use pallet_moonbeam_foreign_assets::EnsureXcmLocation; +use sp_runtime::DispatchError; +use xcm::latest::Location; +use xcm_executor::traits::ConvertLocation; + +pub struct ForeignAssetsEnsureXcmLocation; + +impl EnsureXcmLocation for ForeignAssetsEnsureXcmLocation { + fn ensure_xcm_origin( + o: RuntimeOrigin, + location: Option<&Location>, + ) -> Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone()) + .map_err(|_| DispatchError::BadOrigin)?; + if let Some(location) = location { + ensure!( + location.starts_with(&origin_location), + DispatchError::BadOrigin + ); + } + Self::account_for_location(&origin_location).ok_or(DispatchError::BadOrigin) + } + + fn account_for_location(location: &Location) -> Option { + LocationToAccountId::convert_location(location) + } +} diff --git a/runtime/moonbase/src/lib.rs b/runtime/moonbase/src/lib.rs index 5cd1c82cc1..516f684adb 100644 --- a/runtime/moonbase/src/lib.rs +++ b/runtime/moonbase/src/lib.rs @@ -128,7 +128,9 @@ pub use sp_runtime::BuildStorage; pub type Precompiles = MoonbasePrecompiles; +pub mod foreign_origin; mod weights; + pub(crate) use weights as moonbase_weights; /// UNIT, the native token, uses 18 decimals of precision. diff --git a/runtime/moonbase/src/runtime_params.rs b/runtime/moonbase/src/runtime_params.rs index 2ac2a86141..8d4dfc36af 100644 --- a/runtime/moonbase/src/runtime_params.rs +++ b/runtime/moonbase/src/runtime_params.rs @@ -42,6 +42,13 @@ pub mod dynamic_params { { 1_000 * currency::UNIT * currency::SUPPLY_FACTOR }, > = BoundedU128::const_new::<{ 1 * currency::UNIT * currency::SUPPLY_FACTOR }>(); } + + #[dynamic_pallet_params] + #[codec(index = 2)] + pub mod xcm_config { + #[codec(index = 0)] + pub static ForeignAssetCreationDeposit: u128 = 100 * currency::UNIT; + } } expose_u128_get!( diff --git a/runtime/moonbase/src/xcm_config.rs b/runtime/moonbase/src/xcm_config.rs index a2f25db09c..46106b5cee 100644 --- a/runtime/moonbase/src/xcm_config.rs +++ b/runtime/moonbase/src/xcm_config.rs @@ -17,13 +17,13 @@ //! XCM configuration for Moonbase. //! -use super::moonbase_weights; use super::{ governance, AccountId, AssetId, AssetManager, Balance, Balances, EmergencyParaXcm, Erc20XcmBridge, EvmForeignAssets, MaintenanceMode, MessageQueue, ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, }; +use super::{moonbase_weights, runtime_params}; use crate::OpenTechCommitteeInstance; use moonkit_xcm_primitives::AccountIdAssetIdConversion; use sp_runtime::{ @@ -67,14 +67,14 @@ use xcm_primitives::{ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; +use crate::foreign_origin::ForeignAssetsEnsureXcmLocation; +use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; use sp_core::Get; use sp_std::{ convert::{From, Into, TryFrom}, prelude::*, }; -use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; - parameter_types! { // The network Id of the relay pub const RelayNetwork: NetworkId = NetworkId::Westend; @@ -98,8 +98,9 @@ parameter_types! { } /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used -/// when determining ownership of accounts for asset transacting and when attempting to use XCM -/// `Transact` in order to determine the dispatch Origin. +/// when determining ownership of accounts for asset transacting, when attempting to use XCM +/// `Transact` in order to determine the dispatch Origin, and when validating foreign assets +/// creation and ownership through the moonbeam_foreign_assets pallet. pub type LocationToAccountId = ( // The parent (Relay-chain) origin converts to the default `AccountId`. ParentIsPreset, @@ -695,27 +696,26 @@ impl frame_support::traits::Contains for EvmForeignAssetIdFilter { } } -pub type ForeignAssetManagerOrigin = EitherOfDiverse< - EnsureRoot, - EitherOfDiverse< - pallet_collective::EnsureProportionMoreThan, - governance::custom_origins::FastGeneralAdmin, - >, ->; +parameter_types! { + /// Balance in the native currency that will be reserved from the user + /// to create a new foreign asset + pub ForeignAssetCreationDeposit: u128 = + runtime_params::dynamic_params::xcm_config::ForeignAssetCreationDeposit::get(); +} impl pallet_moonbeam_foreign_assets::Config for Runtime { type AccountIdToH160 = AccountIdToH160; type AssetIdFilter = EvmForeignAssetIdFilter; type EvmRunner = EvmRunnerPrecompileOrEthXcm; - type ForeignAssetCreatorOrigin = ForeignAssetManagerOrigin; - type ForeignAssetFreezerOrigin = ForeignAssetManagerOrigin; - type ForeignAssetModifierOrigin = ForeignAssetManagerOrigin; - type ForeignAssetUnfreezerOrigin = ForeignAssetManagerOrigin; + type EnsureXcmLocation = ForeignAssetsEnsureXcmLocation; type OnForeignAssetCreated = (); type MaxForeignAssets = ConstU32<256>; type RuntimeEvent = RuntimeEvent; type WeightInfo = moonbase_weights::pallet_moonbeam_foreign_assets::WeightInfo; type XcmLocationToH160 = LocationToH160; + type ForeignAssetCreationDeposit = ForeignAssetCreationDeposit; + type Balance = Balance; + type Currency = Balances; } pub struct AssetFeesFilter; diff --git a/runtime/moonbase/tests/common/mod.rs b/runtime/moonbase/tests/common/mod.rs index 794988584f..0dac91c5ae 100644 --- a/runtime/moonbase/tests/common/mod.rs +++ b/runtime/moonbase/tests/common/mod.rs @@ -32,10 +32,13 @@ use polkadot_parachain::primitives::HeadData; use sp_consensus_slots::Slot; use sp_core::{Encode, H160}; use sp_runtime::{traits::Dispatchable, BuildStorage, Digest, DigestItem, Perbill, Percent}; +use xcm_executor::traits::ConvertLocation; use std::collections::BTreeMap; use fp_rpc::ConvertTransaction; +use moonbase_runtime::xcm_config::LocationToAccountId; +use moonbase_runtime::{currency, RuntimeOrigin}; use pallet_transaction_payment::Multiplier; pub fn existential_deposit() -> u128 { @@ -226,8 +229,16 @@ impl ExtBuilder { .build_storage() .unwrap(); + let mut balances_with_xcm_accounts = self.balances.clone(); + for xcm_asset_initialization in self.xcm_assets.iter() { + let account = + LocationToAccountId::convert_location(&xcm_asset_initialization.xcm_location) + .expect("Could not convert location to account id"); + let balance = 200 * UNIT; + balances_with_xcm_accounts.push((account, balance)); + } pallet_balances::GenesisConfig:: { - balances: self.balances, + balances: balances_with_xcm_accounts, } .assimilate_storage(&mut t) .unwrap(); @@ -293,10 +304,12 @@ impl ExtBuilder { // If any xcm assets specified, we register them here for xcm_asset_initialization in xcm_assets { let asset_id = xcm_asset_initialization.asset_id; - EvmForeignAssets::create_foreign_asset( - root_origin(), + let asset_location = xcm_asset_initialization.xcm_location; + let origin: RuntimeOrigin = pallet_xcm::Origin::Xcm(asset_location.clone()).into(); + assert_ok!(EvmForeignAssets::create_foreign_asset( + origin, asset_id, - xcm_asset_initialization.xcm_location, + asset_location, xcm_asset_initialization.decimals, xcm_asset_initialization .symbol @@ -310,8 +323,7 @@ impl ExtBuilder { .to_vec() .try_into() .expect("too long"), - ) - .expect("fail to create foreign asset"); + ),); for (account, balance) in xcm_asset_initialization.balances { if EvmForeignAssets::mint_into(asset_id, account, balance.into()).is_err() { diff --git a/runtime/moonbase/tests/integration_test.rs b/runtime/moonbase/tests/integration_test.rs index b7f6e3fb16..366bb5b4dd 100644 --- a/runtime/moonbase/tests/integration_test.rs +++ b/runtime/moonbase/tests/integration_test.rs @@ -37,30 +37,11 @@ use frame_support::{ StorageHasher, Twox128, }; use moonbase_runtime::{ - //asset_config::ForeignAssetInstance, - xcm_config::SelfReserve, - AccountId, - AssetId, - Balances, - CrowdloanRewards, - EvmForeignAssets, - Executive, - OpenTechCommitteeCollective, - ParachainStaking, - PolkadotXcm, - Precompiles, - Runtime, - RuntimeBlockWeights, - RuntimeCall, - RuntimeEvent, - System, - TransactionPayment, - TransactionPaymentAsGasPrice, - Treasury, - TreasuryCouncilCollective, - XcmTransactor, - FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, - WEEKS, + xcm_config::SelfReserve, AccountId, AssetId, Balances, CrowdloanRewards, EvmForeignAssets, + Executive, OpenTechCommitteeCollective, ParachainStaking, PolkadotXcm, Precompiles, Runtime, + RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, System, TransactionPayment, + TransactionPaymentAsGasPrice, Treasury, TreasuryCouncilCollective, XcmTransactor, + FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, WEEKS, }; use polkadot_parachain::primitives::Sibling; use precompile_utils::testing::MockHandle; @@ -79,6 +60,7 @@ use nimbus_primitives::NimbusId; use pallet_evm::PrecompileSet; //use pallet_evm_precompileset_assets_erc20::{SELECTOR_LOG_APPROVAL, SELECTOR_LOG_TRANSFER}; use moonbase_runtime::runtime_params::dynamic_params; +use moonbase_runtime::xcm_config::LocationToAccountId; use pallet_moonbeam_foreign_assets::AssetStatus; use pallet_transaction_payment::Multiplier; use pallet_xcm_transactor::{Currency, CurrencyPayment, HrmpOperation, TransactWeights}; @@ -1268,48 +1250,50 @@ fn update_reward_address_via_precompile() { #[test] fn create_and_manipulate_foreign_asset() { - ExtBuilder::default().build().execute_with(|| { - let source_location = xcm::v4::Location::parent(); - - // Create foreign asset - assert_ok!(EvmForeignAssets::create_foreign_asset( - moonbase_runtime::RuntimeOrigin::root(), - 1, - source_location.clone(), - 12, - bounded_vec![b'M', b'T'], - bounded_vec![b'M', b'y', b'T', b'o', b'k'], - )); - assert_eq!( - EvmForeignAssets::assets_by_id(1), - Some(source_location.clone()) - ); - assert_eq!( - EvmForeignAssets::assets_by_location(&source_location), - Some((1, AssetStatus::Active)) - ); + let source_location = xcm::v4::Location::parent(); + let account = + LocationToAccountId::convert_location(&source_location).expect("Cannot get account"); + let origin: RuntimeOrigin = pallet_xcm::Origin::Xcm(source_location.clone()).into(); + ExtBuilder::default() + .with_balances(vec![(account, 1_000 * UNIT)]) + .build() + .execute_with(|| { + // Create foreign asset + assert_ok!(EvmForeignAssets::create_foreign_asset( + origin.clone(), + 1, + source_location.clone(), + 12, + bounded_vec![b'M', b'T'], + bounded_vec![b'M', b'y', b'T', b'o', b'k'], + )); + assert_eq!( + EvmForeignAssets::assets_by_id(1), + Some(source_location.clone()) + ); + assert_eq!( + EvmForeignAssets::assets_by_location(&source_location), + Some((1, AssetStatus::Active)) + ); - // Freeze foreign asset - assert_ok!(EvmForeignAssets::freeze_foreign_asset( - moonbase_runtime::RuntimeOrigin::root(), - 1, - true - )); - assert_eq!( - EvmForeignAssets::assets_by_location(&source_location), - Some((1, AssetStatus::FrozenXcmDepositAllowed)) - ); + // Freeze foreign asset + assert_ok!(EvmForeignAssets::freeze_foreign_asset( + origin.clone(), + 1, + true + )); + assert_eq!( + EvmForeignAssets::assets_by_location(&source_location), + Some((1, AssetStatus::FrozenXcmDepositAllowed)) + ); - // Unfreeze foreign asset - assert_ok!(EvmForeignAssets::unfreeze_foreign_asset( - moonbase_runtime::RuntimeOrigin::root(), - 1, - )); - assert_eq!( - EvmForeignAssets::assets_by_location(&source_location), - Some((1, AssetStatus::Active)) - ); - }); + // Unfreeze foreign asset + assert_ok!(EvmForeignAssets::unfreeze_foreign_asset(origin, 1,)); + assert_eq!( + EvmForeignAssets::assets_by_location(&source_location), + Some((1, AssetStatus::Active)) + ); + }); } // The precoompile asset-erc20 is deprecated and not used anymore for new evm foreign assets diff --git a/runtime/moonbeam/src/foreign_origin.rs b/runtime/moonbeam/src/foreign_origin.rs new file mode 100644 index 0000000000..55df40de12 --- /dev/null +++ b/runtime/moonbeam/src/foreign_origin.rs @@ -0,0 +1,50 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use crate::xcm_config::LocationToAccountId; +use crate::{Runtime, RuntimeOrigin}; +use frame_support::ensure; +use frame_support::traits::{EnsureOrigin, Everything}; +use moonbeam_core_primitives::AccountId; +use pallet_moonbeam_foreign_assets::EnsureXcmLocation; +use sp_runtime::DispatchError; +use xcm::latest::Location; +use xcm_executor::traits::ConvertLocation; + +// `EnsureOriginWithArg` impl for `ForeignAssetOwnerOrigin` which allows only XCM origins +// which are locations containing the class location. +pub struct ForeignAssetsEnsureXcmLocation; + +impl EnsureXcmLocation for ForeignAssetsEnsureXcmLocation { + fn ensure_xcm_origin( + o: RuntimeOrigin, + location: Option<&Location>, + ) -> Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone()) + .map_err(|_| DispatchError::BadOrigin)?; + if let Some(location) = location { + ensure!( + location.starts_with(&origin_location), + DispatchError::BadOrigin + ); + } + Self::account_for_location(&origin_location).ok_or(DispatchError::BadOrigin) + } + + fn account_for_location(location: &Location) -> Option { + LocationToAccountId::convert_location(location) + } +} diff --git a/runtime/moonbeam/src/lib.rs b/runtime/moonbeam/src/lib.rs index 9190d2804a..362c91d6ce 100644 --- a/runtime/moonbeam/src/lib.rs +++ b/runtime/moonbeam/src/lib.rs @@ -119,6 +119,7 @@ pub use sp_runtime::BuildStorage; pub type Precompiles = MoonbeamPrecompiles; pub mod asset_config; +mod foreign_origin; pub mod governance; pub mod runtime_params; mod weights; diff --git a/runtime/moonbeam/src/runtime_params.rs b/runtime/moonbeam/src/runtime_params.rs index 7c37dbaf81..75cae97bb8 100644 --- a/runtime/moonbeam/src/runtime_params.rs +++ b/runtime/moonbeam/src/runtime_params.rs @@ -42,6 +42,13 @@ pub mod dynamic_params { { 1_000 * currency::GLMR * currency::SUPPLY_FACTOR }, > = BoundedU128::const_new::<{ 1 * currency::GLMR * currency::SUPPLY_FACTOR }>(); } + + #[dynamic_pallet_params] + #[codec(index = 2)] + pub mod xcm_config { + #[codec(index = 0)] + pub static ForeignAssetCreationDeposit: u128 = 10_000 * currency::GLMR; + } } expose_u128_get!( diff --git a/runtime/moonbeam/src/xcm_config.rs b/runtime/moonbeam/src/xcm_config.rs index 506f91d5ff..5bcf9c7543 100644 --- a/runtime/moonbeam/src/xcm_config.rs +++ b/runtime/moonbeam/src/xcm_config.rs @@ -18,10 +18,10 @@ //! use super::{ - governance, AccountId, AssetId, AssetManager, Balance, Balances, EmergencyParaXcm, - Erc20XcmBridge, EvmForeignAssets, MaintenanceMode, MessageQueue, OpenTechCommitteeInstance, - ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, - RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, + governance, runtime_params, AccountId, AssetId, AssetManager, Balance, Balances, + EmergencyParaXcm, Erc20XcmBridge, EvmForeignAssets, MaintenanceMode, MessageQueue, + OpenTechCommitteeInstance, ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, + RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, }; use super::moonbeam_weights; @@ -66,14 +66,14 @@ use xcm_primitives::{ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; +use crate::foreign_origin::ForeignAssetsEnsureXcmLocation; +use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; use sp_core::Get; use sp_std::{ convert::{From, Into, TryFrom}, prelude::*, }; -use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; - parameter_types! { // The network Id of the relay pub const RelayNetwork: NetworkId = NetworkId::Polkadot; @@ -681,27 +681,26 @@ impl frame_support::traits::Contains for EvmForeignAssetIdFilter { } } -pub type ForeignAssetManagerOrigin = EitherOfDiverse< - EnsureRoot, - EitherOfDiverse< - pallet_collective::EnsureProportionMoreThan, - governance::custom_origins::FastGeneralAdmin, - >, ->; +parameter_types! { + /// Balance in the native currency that will be reserved from the user + /// to create a new foreign asset + pub ForeignAssetCreationDeposit: u128 = + runtime_params::dynamic_params::xcm_config::ForeignAssetCreationDeposit::get(); +} impl pallet_moonbeam_foreign_assets::Config for Runtime { type AccountIdToH160 = AccountIdToH160; type AssetIdFilter = EvmForeignAssetIdFilter; type EvmRunner = EvmRunnerPrecompileOrEthXcm; - type ForeignAssetCreatorOrigin = ForeignAssetManagerOrigin; - type ForeignAssetFreezerOrigin = ForeignAssetManagerOrigin; - type ForeignAssetModifierOrigin = ForeignAssetManagerOrigin; - type ForeignAssetUnfreezerOrigin = ForeignAssetManagerOrigin; + type EnsureXcmLocation = ForeignAssetsEnsureXcmLocation; type OnForeignAssetCreated = (); type MaxForeignAssets = ConstU32<256>; type RuntimeEvent = RuntimeEvent; type WeightInfo = moonbeam_weights::pallet_moonbeam_foreign_assets::WeightInfo; type XcmLocationToH160 = LocationToH160; + type ForeignAssetCreationDeposit = ForeignAssetCreationDeposit; + type Balance = Balance; + type Currency = Balances; } pub struct AssetFeesFilter; diff --git a/runtime/moonriver/src/foreign_origin.rs b/runtime/moonriver/src/foreign_origin.rs new file mode 100644 index 0000000000..55df40de12 --- /dev/null +++ b/runtime/moonriver/src/foreign_origin.rs @@ -0,0 +1,50 @@ +// Copyright 2019-2022 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use crate::xcm_config::LocationToAccountId; +use crate::{Runtime, RuntimeOrigin}; +use frame_support::ensure; +use frame_support::traits::{EnsureOrigin, Everything}; +use moonbeam_core_primitives::AccountId; +use pallet_moonbeam_foreign_assets::EnsureXcmLocation; +use sp_runtime::DispatchError; +use xcm::latest::Location; +use xcm_executor::traits::ConvertLocation; + +// `EnsureOriginWithArg` impl for `ForeignAssetOwnerOrigin` which allows only XCM origins +// which are locations containing the class location. +pub struct ForeignAssetsEnsureXcmLocation; + +impl EnsureXcmLocation for ForeignAssetsEnsureXcmLocation { + fn ensure_xcm_origin( + o: RuntimeOrigin, + location: Option<&Location>, + ) -> Result { + let origin_location = pallet_xcm::EnsureXcm::::try_origin(o.clone()) + .map_err(|_| DispatchError::BadOrigin)?; + if let Some(location) = location { + ensure!( + location.starts_with(&origin_location), + DispatchError::BadOrigin + ); + } + Self::account_for_location(&origin_location).ok_or(DispatchError::BadOrigin) + } + + fn account_for_location(location: &Location) -> Option { + LocationToAccountId::convert_location(location) + } +} diff --git a/runtime/moonriver/src/lib.rs b/runtime/moonriver/src/lib.rs index e8c82f3377..92f8ad6e4b 100644 --- a/runtime/moonriver/src/lib.rs +++ b/runtime/moonriver/src/lib.rs @@ -125,6 +125,7 @@ pub mod governance; pub mod runtime_params; pub mod xcm_config; +mod foreign_origin; mod migrations; mod precompiles; mod weights; diff --git a/runtime/moonriver/src/runtime_params.rs b/runtime/moonriver/src/runtime_params.rs index 054a89ed26..e57f5f9cc6 100644 --- a/runtime/moonriver/src/runtime_params.rs +++ b/runtime/moonriver/src/runtime_params.rs @@ -42,6 +42,13 @@ pub mod dynamic_params { { 1_000 * currency::MOVR * currency::SUPPLY_FACTOR }, > = BoundedU128::const_new::<{ 1 * currency::MOVR * currency::SUPPLY_FACTOR }>(); } + + #[dynamic_pallet_params] + #[codec(index = 2)] + pub mod xcm_config { + #[codec(index = 0)] + pub static ForeignAssetCreationDeposit: u128 = 100 * currency::MOVR; + } } expose_u128_get!( diff --git a/runtime/moonriver/src/xcm_config.rs b/runtime/moonriver/src/xcm_config.rs index 1519d25203..75e13b48d7 100644 --- a/runtime/moonriver/src/xcm_config.rs +++ b/runtime/moonriver/src/xcm_config.rs @@ -18,10 +18,10 @@ //! use super::{ - governance, AccountId, AssetId, AssetManager, Balance, Balances, EmergencyParaXcm, - Erc20XcmBridge, EvmForeignAssets, MaintenanceMode, MessageQueue, OpenTechCommitteeInstance, - ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, RuntimeBlockWeights, - RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, + governance, runtime_params, AccountId, AssetId, AssetManager, Balance, Balances, + EmergencyParaXcm, Erc20XcmBridge, EvmForeignAssets, MaintenanceMode, MessageQueue, + OpenTechCommitteeInstance, ParachainInfo, ParachainSystem, Perbill, PolkadotXcm, Runtime, + RuntimeBlockWeights, RuntimeCall, RuntimeEvent, RuntimeOrigin, Treasury, XcmpQueue, }; use super::moonriver_weights; @@ -66,14 +66,14 @@ use xcm_primitives::{ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; +use crate::foreign_origin::ForeignAssetsEnsureXcmLocation; +use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; use sp_core::Get; use sp_std::{ convert::{From, Into, TryFrom}, prelude::*, }; -use crate::governance::referenda::{FastGeneralAdminOrRoot, GeneralAdminOrRoot}; - parameter_types! { // The network Id of the relay pub const RelayNetwork: NetworkId = NetworkId::Kusama; @@ -694,27 +694,26 @@ impl frame_support::traits::Contains for EvmForeignAssetIdFilter { } } -pub type ForeignAssetManagerOrigin = EitherOfDiverse< - EnsureRoot, - EitherOfDiverse< - pallet_collective::EnsureProportionMoreThan, - governance::custom_origins::FastGeneralAdmin, - >, ->; +parameter_types! { + /// Balance in the native currency that will be reserved from the user + /// to create a new foreign asset + pub ForeignAssetCreationDeposit: u128 = + runtime_params::dynamic_params::xcm_config::ForeignAssetCreationDeposit::get(); +} impl pallet_moonbeam_foreign_assets::Config for Runtime { type AccountIdToH160 = AccountIdToH160; type AssetIdFilter = EvmForeignAssetIdFilter; type EvmRunner = EvmRunnerPrecompileOrEthXcm; - type ForeignAssetCreatorOrigin = ForeignAssetManagerOrigin; - type ForeignAssetFreezerOrigin = ForeignAssetManagerOrigin; - type ForeignAssetModifierOrigin = ForeignAssetManagerOrigin; - type ForeignAssetUnfreezerOrigin = ForeignAssetManagerOrigin; + type EnsureXcmLocation = ForeignAssetsEnsureXcmLocation; type OnForeignAssetCreated = (); type MaxForeignAssets = ConstU32<256>; type RuntimeEvent = RuntimeEvent; type WeightInfo = moonriver_weights::pallet_moonbeam_foreign_assets::WeightInfo; type XcmLocationToH160 = LocationToH160; + type ForeignAssetCreationDeposit = ForeignAssetCreationDeposit; + type Currency = Balances; + type Balance = Balance; } pub struct AssetFeesFilter; diff --git a/test/helpers/assets.ts b/test/helpers/assets.ts index 4ca79762a1..c1bff9be8c 100644 --- a/test/helpers/assets.ts +++ b/test/helpers/assets.ts @@ -399,15 +399,12 @@ export async function registerForeignAsset( // Sanitize Xcm Location const xcmLoc = patchLocationV4recursively(xcmLocation); - + const api = context.polkadotJs(); const { result } = await context.createBlock( - context - .polkadotJs() - .tx.sudo.sudo( - context - .polkadotJs() - .tx.evmForeignAssets.createForeignAsset(assetId, xcmLoc, decimals, symbol, name) - ) + api.tx.sudo.sudoAs( + alith.address, + api.tx.evmForeignAssets.createForeignAsset(assetId, xcmLoc, decimals, symbol, name) + ) ); // Fetch the relevant event diff --git a/test/suites/dev/moonbase/test-assets/test-foreign-assets-change-xcm-location.ts b/test/suites/dev/moonbase/test-assets/test-foreign-assets-change-xcm-location.ts index 0fa325ce36..e083f4a120 100644 --- a/test/suites/dev/moonbase/test-assets/test-foreign-assets-change-xcm-location.ts +++ b/test/suites/dev/moonbase/test-assets/test-foreign-assets-change-xcm-location.ts @@ -26,7 +26,9 @@ describeSuite({ ); assetId = registeredAssetId; - await verifyLatestBlockFees(context); + // Expect a balance diff after creating an asset due to the deposit + const assetCreationDeposit = 100; + await verifyLatestBlockFees(context, BigInt(assetCreationDeposit)); }); it({