Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Foreign assets creation via token reserve #3104

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e12f440
add basics for foreign asset creation via token reserve WIP
gonzamontiel Dec 9, 2024
31616cd
create new extrinsic for the new way of creating foreign assets
gonzamontiel Dec 12, 2024
a50448e
ensure the modifier is the owner of the asset (migration needed)
gonzamontiel Dec 12, 2024
942bf9c
add default weights
gonzamontiel Dec 12, 2024
793236a
format
gonzamontiel Dec 12, 2024
0c891f5
Merge remote-tracking branch 'origin/master' into gonza/foreign-asset…
gonzamontiel Dec 27, 2024
d3cedc2
simplify logic, rollback to granular origins per operation
gonzamontiel Jan 2, 2025
abb9290
remove unused extrinsic
gonzamontiel Jan 3, 2025
f7b300e
improve readability
gonzamontiel Jan 3, 2025
4283fd4
make ForeignAssetCreationDeposit dynamic
gonzamontiel Jan 3, 2025
6938955
Merge branch 'master' into gonza/foreign-assets-via-token-lock
gonzamontiel Jan 6, 2025
a75ef40
remove unused block number
gonzamontiel Jan 7, 2025
c365093
fix rust tests
gonzamontiel Jan 7, 2025
2e396b4
fix moonbase xcm config
gonzamontiel Jan 7, 2025
0f5c5d1
fmt
gonzamontiel Jan 7, 2025
e02f62a
Merge branch 'master' into gonza/foreign-assets-via-token-lock
gonzamontiel Jan 7, 2025
a79f5d7
fix manager origin for moonriver
gonzamontiel Jan 7, 2025
dd6e3f6
tidy
gonzamontiel Jan 7, 2025
8cf4b8a
change back MaxFreezes to 0 as not needed anymore
gonzamontiel Jan 7, 2025
07c6cab
fix foreign assets rust tests
gonzamontiel Jan 7, 2025
ef4856c
make helper use correct call for creating foreign assets
gonzamontiel Jan 7, 2025
9bafa10
reformat test
gonzamontiel Jan 7, 2025
80d5ecb
fix benchmarks
gonzamontiel Jan 8, 2025
a9216fc
format
gonzamontiel Jan 8, 2025
2ae2dd4
Update test/helpers/assets.ts
gonzamontiel Jan 8, 2025
77ecd67
fix call
gonzamontiel Jan 8, 2025
aaab255
add UNITS multiplier
gonzamontiel Jan 8, 2025
a0b0e64
Merge branch 'master' into gonza/foreign-assets-via-token-lock
gonzamontiel Jan 8, 2025
1424ca3
rollback sudo
gonzamontiel Jan 8, 2025
2dfd940
expect balance diff
gonzamontiel Jan 8, 2025
c543215
remove unused weights
gonzamontiel Jan 8, 2025
f1dba45
lint
gonzamontiel Jan 8, 2025
a68bb12
remove typeinfo bound as already computed
gonzamontiel Jan 10, 2025
251497f
Add custom origin that ensures only XCM origins, which contain a cert…
gonzamontiel Jan 13, 2025
5e62ad1
modify foreign assets origin to use ForeignAssetOwnerOrigin
gonzamontiel Jan 13, 2025
8709f57
remove Root as possible origin
gonzamontiel Jan 14, 2025
75d0acb
get account from ensure_origin
gonzamontiel Jan 14, 2025
c24aa67
format
gonzamontiel Jan 14, 2025
9a07cf7
copy
gonzamontiel Jan 14, 2025
e749a81
Merge remote-tracking branch 'origin/master' into gonza/foreign-asset…
gonzamontiel Jan 14, 2025
4fd2e86
update mock
gonzamontiel Jan 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions pallets/moonbeam-foreign-assets/src/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Config>(string: &'static str, n: u32, balance: u32) -> T::AccountId {
const SEED: u32 = 0;
let user = account(string, n, SEED);
let _ = <T as pallet::Config>::Currency::make_free_balance_be(&user, balance.into());
let _ = <T as pallet::Config>::Currency::issue(balance.into());
user
}
fn create_n_foreign_asset<T: Config>(n: u32) -> DispatchResult {
let user: T::AccountId = create_funded_user::<T>("user", n, 100);
for i in 1..=n {
Pallet::<T>::create_foreign_asset(
RawOrigin::Root.into(),
RawOrigin::Signed(user.clone()).into(),
i as u128,
location_of(i),
18,
Expand All @@ -53,7 +62,7 @@ benchmarks! {
create_foreign_asset {
create_n_foreign_asset::<T>(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::<T>("user", 1, 100)), asset_id, Location::parent(), 18, str_to_bv("MT"), str_to_bv("Mytoken"))
verify {
assert_eq!(
Pallet::<T>::assets_by_id(asset_id),
Expand Down
121 changes: 107 additions & 14 deletions pallets/moonbeam-foreign-assets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ pub enum AssetStatus {
#[pallet]
pub mod pallet {
use super::*;
use frame_support::traits::{Currency, EnsureOriginWithArg, 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;
Expand All @@ -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 {
gonzamontiel marked this conversation as resolved.
Show resolved Hide resolved
// Convert AccountId to H160
type AccountIdToH160: Convert<Self::AccountId, H160>;

Expand All @@ -132,22 +133,38 @@ pub mod pallet {
type EvmRunner: Runner<Self>;

/// Origin that is allowed to create a new foreign assets
type ForeignAssetCreatorOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type ForeignAssetCreatorOrigin: EnsureOriginWithArg<
Self::RuntimeOrigin,
Location,
Success = Self::AccountId,
>;

/// Origin that is allowed to freeze all tokens of a foreign asset
type ForeignAssetFreezerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type ForeignAssetFreezerOrigin: EnsureOriginWithArg<
Self::RuntimeOrigin,
Location,
Success = Self::AccountId,
>;

/// Origin that is allowed to modify asset information for foreign assets
type ForeignAssetModifierOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type ForeignAssetModifierOrigin: EnsureOriginWithArg<
Self::RuntimeOrigin,
Location,
Success = Self::AccountId,
>;

/// Origin that is allowed to unfreeze all tokens of a foreign asset that was previously
/// frozen
type ForeignAssetUnfreezerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type ForeignAssetUnfreezerOrigin: EnsureOriginWithArg<
Self::RuntimeOrigin,
Location,
Success = Self::AccountId,
>;

/// Hook to be called when new foreign asset is registered.
type OnForeignAssetCreated: ForeignAssetCreatedHook<Location>;

/// Maximum nulmbers of differnt foreign assets
/// Maximum numbers of differnt foreign assets
type MaxForeignAssets: Get<u32>;

/// The overarching event type.
Expand All @@ -158,8 +175,27 @@ pub mod pallet {

// Convert XCM Location to H160
type XcmLocationToH160: ConvertLocation<H160>;

/// Amount of tokens required to lock for creating a new foreign asset
type ForeignAssetCreationDeposit: Get<BalanceOf<Self>>;

/// 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<Self::AccountId>;
}

type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

pub type AssetBalance = U256;
pub type AssetId = u128;

Expand All @@ -177,6 +213,9 @@ pub mod pallet {
EvmCallPauseFail,
EvmCallUnpauseFail,
EvmInternalError,
/// Account has insufficient balance for locking
InsufficientBalance,
OriginIsNotAssetCreator,
InvalidSymbol,
InvalidTokenName,
LocationAlreadyExists,
Expand All @@ -191,6 +230,7 @@ pub mod pallet {
contract_address: H160,
asset_id: AssetId,
xcm_location: Location,
deposit: Option<BalanceOf<T>>,
},
/// Changed the xcm type mapping for a given asset id
ForeignAssetXcmLocationChanged {
Expand All @@ -207,6 +247,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.
Expand All @@ -225,6 +269,24 @@ pub mod pallet {
pub type AssetsByLocation<T: Config> =
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<T: Config> =
StorageMap<_, Blake2_128Concat, AssetId, AssetCreationDetails<T>>;

#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub enum AssetOwner<T: Config> {
Governance,
Account(T::AccountId),
}

#[derive(Clone, Decode, Encode, Eq, PartialEq, Debug, TypeInfo, MaxEncodedLen)]
pub struct AssetCreationDetails<T: Config> {
pub owner: AssetOwner<T>,
pub deposit: Option<BalanceOf<T>>,
}

impl<T: Config> Pallet<T> {
/// The account ID of this pallet
#[inline]
Expand All @@ -242,6 +304,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,
Expand Down Expand Up @@ -274,18 +338,25 @@ pub mod pallet {
let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;

let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
let owner = AssetOwner::<T>::Governance;

// Insert the association assetId->foreigAsset
// Insert the association foreigAsset->assetId
AssetsById::<T>::insert(&asset_id, &xcm_location);
AssetsByLocation::<T>::insert(&xcm_location, (asset_id, AssetStatus::Active));

T::OnForeignAssetCreated::on_asset_created(&xcm_location, &asset_id);
AssetsCreationDetails::<T>::insert(
&asset_id,
AssetCreationDetails {
owner,
deposit: None,
},
);
Comment on lines +347 to +353
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How important is this storage for assets that were created with governance? Asking because the assets were already migrated on stagenet, and I do not see it being breaking if we don't fix the storage there.

Seems only important for assets created by normal accounts.


Self::deposit_event(Event::ForeignAssetCreated {
contract_address,
asset_id,
xcm_location,
deposit: None,
});
Ok(())
}
Expand Down Expand Up @@ -355,7 +426,8 @@ pub mod pallet {
symbol: BoundedVec<u8, ConstU32<256>>,
name: BoundedVec<u8, ConstU32<256>>,
) -> DispatchResult {
T::ForeignAssetCreatorOrigin::ensure_origin(origin)?;
let owner_account =
T::ForeignAssetCreatorOrigin::ensure_origin(origin.clone(), &xcm_location)?;

// Ensure such an assetId does not exist
ensure!(
Expand All @@ -380,20 +452,34 @@ pub mod pallet {

let symbol = core::str::from_utf8(&symbol).map_err(|_| Error::<T>::InvalidSymbol)?;
let name = core::str::from_utf8(&name).map_err(|_| Error::<T>::InvalidTokenName)?;

let contract_address = EvmCaller::<T>::erc20_create(asset_id, decimals, symbol, name)?;
let deposit = T::ForeignAssetCreationDeposit::get();
let owner = AssetOwner::<T>::Account(owner_account.clone());

// Insert the association assetId->foreigAsset
// Insert the association foreigAsset->assetId
AssetsById::<T>::insert(&asset_id, &xcm_location);
AssetsByLocation::<T>::insert(&xcm_location, (asset_id, AssetStatus::Active));

// Reserve _deposit_ amount of funds from the caller
<T as Config>::Currency::reserve(&owner_account, deposit)?;

// Insert the amount that is reserved from the user
AssetsCreationDetails::<T>::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(())
}
Expand All @@ -408,7 +494,8 @@ 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::ForeignAssetModifierOrigin::ensure_origin(origin, &new_xcm_location)?;

let previous_location =
AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
Expand Down Expand Up @@ -441,11 +528,15 @@ pub mod pallet {
asset_id: AssetId,
allow_xcm_deposit: bool,
) -> DispatchResult {
T::ForeignAssetFreezerOrigin::ensure_origin(origin)?;
ensure_signed(origin.clone())?;

let xcm_location =
AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;

// Ensures that the origin is an XCM location that owns the asset
// represented by the assets xcm location
T::ForeignAssetFreezerOrigin::ensure_origin(origin, &xcm_location)?;

let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;

Expand Down Expand Up @@ -475,10 +566,12 @@ pub mod pallet {
#[pallet::call_index(3)]
#[pallet::weight(<T as Config>::WeightInfo::unfreeze_foreign_asset())]
pub fn unfreeze_foreign_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
T::ForeignAssetUnfreezerOrigin::ensure_origin(origin)?;
ensure_signed(origin.clone())?;

let xcm_location =
AssetsById::<T>::get(&asset_id).ok_or(Error::<T>::AssetDoesNotExist)?;
// Ensures that the origin is an XCM location that contains the asset
T::ForeignAssetUnfreezerOrigin::ensure_origin(origin, &xcm_location)?;

let (_asset_id, asset_status) = AssetsByLocation::<T>::get(&xcm_location)
.ok_or(Error::<T>::CorruptedStorageOrphanLocation)?;
Expand Down
40 changes: 33 additions & 7 deletions pallets/moonbeam-foreign-assets/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,19 @@
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;
use xcm::latest::Location;

pub type Balance = u128;

pub type BlockNumber = u32;
type AccountId = MockAccount;
type Block = frame_system::mocking::MockBlock<Test>;

Expand Down Expand Up @@ -176,19 +177,39 @@ impl sp_runtime::traits::Convert<AccountId, H160> for AccountIdToH160 {
}
}

pub struct ForeignAssetMockOrigin;
impl EnsureOriginWithArg<RuntimeOrigin, Location> for ForeignAssetMockOrigin {
type Success = AccountId;

fn try_origin(
_: RuntimeOrigin,
_: &Location,
) -> core::result::Result<Self::Success, RuntimeOrigin> {
Ok(Alice.into())
}
}

parameter_types! {
pub const ForeignAssetCreationDeposit: u128 = 1;
}

impl crate::Config for Test {
type AccountIdToH160 = AccountIdToH160;
type AssetIdFilter = Everything;
type EvmRunner = pallet_evm::runner::stack::Runner<Self>;
type ForeignAssetCreatorOrigin = EnsureRoot<AccountId>;
type ForeignAssetFreezerOrigin = EnsureRoot<AccountId>;
type ForeignAssetModifierOrigin = EnsureRoot<AccountId>;
type ForeignAssetUnfreezerOrigin = EnsureRoot<AccountId>;
type ForeignAssetCreatorOrigin = EnsureSigned<AccountId>;
type ForeignAssetFreezerOrigin = EnsureSigned<AccountId>;
type ForeignAssetModifierOrigin = EnsureSigned<AccountId>;
type ForeignAssetUnfreezerOrigin = EnsureSigned<AccountId>;
type OnForeignAssetCreated = NoteDownHook<Location>;
type MaxForeignAssets = ConstU32<3>;
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type XcmLocationToH160 = ();
type ForeignAssetCreationDeposit = ForeignAssetCreationDeposit;
type Balance = Balance;

type Currency = Balances;
}

pub(crate) struct ExtBuilder {
Expand Down Expand Up @@ -217,6 +238,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<super::Event<Test>> {
Expand Down
Loading
Loading