use pezframe_support::{dispatch::GetDispatchInfo, weights::Weight}; use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode}; use scale_info::TypeInfo; use pezsp_core::{H160, H256}; use pezsp_runtime::traits::Hash as THash; use pezsp_std::{ convert::{From, Into}, prelude::*, }; use xcm::latest::Location; #[cfg(feature = "runtime-benchmarks")] use xcm::v3::MultiLocation; use crate::{ types::{AccountId, AssetId, Balance}, AssetManager, Assets, Runtime, RuntimeCall, RuntimeOrigin, }; // Required for runtime benchmarks pezpallet_assets::runtime_benchmarks_enabled! { pub struct BenchmarkHelper; impl pezpallet_assets::BenchmarkHelper for BenchmarkHelper where AssetIdParameter: From, { fn create_asset_id_parameter(id: u32) -> AssetIdParameter { (id as u128).into() } } } // Our AssetType. For now we only handle Xcm Assets #[derive( Clone, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, DecodeWithMemTracking, TypeInfo, )] pub enum AssetType { Xcm(xcm::v4::Location), } impl Default for AssetType { fn default() -> Self { Self::Xcm(xcm::v4::Location::here()) } } impl From for AssetType { fn from(location: xcm::v4::Location) -> Self { Self::Xcm(location) } } #[cfg(feature = "runtime-benchmarks")] fn convert_v3_to_v4(v3: MultiLocation) -> Option { Some(xcm::v4::Location { parents: v3.parents, interior: xcm::v4::Junctions::try_from(v3.interior).ok()?, // Returns None if conversion fails }) } #[cfg(feature = "runtime-benchmarks")] impl From for AssetType { fn from(value: MultiLocation) -> Self { Self::Xcm(convert_v3_to_v4(value).unwrap_or(xcm::v4::Location::default())) } } // This can be removed once we fully adopt xcm::v4 everywhere impl TryFrom for AssetType { type Error = (); fn try_from(location: Location) -> Result { Ok(Self::Xcm(location.try_into()?)) } } impl From for Option { fn from(val: AssetType) -> Self { match val { AssetType::Xcm(location) => Some(location), } } } // Implementation on how to retrieve the AssetId from an AssetType impl From for AssetId { fn from(asset: AssetType) -> AssetId { match asset { AssetType::Xcm(id) => { let mut result: [u8; 16] = [0u8; 16]; let hash: H256 = id.using_encoded(::Hashing::hash); result.copy_from_slice(&hash.as_fixed_bytes()[0..16]); u128::from_le_bytes(result) } } } } #[derive( Clone, Default, Eq, Debug, PartialEq, Ord, PartialOrd, Encode, Decode, DecodeWithMemTracking, TypeInfo, )] pub struct AssetRegistrarMetadata { pub name: Vec, pub symbol: Vec, pub decimals: u8, pub is_frozen: bool, } // We instruct how to register the Assets // In this case, we tell it to Create an Asset in pezpallet-assets pub struct AssetRegistrar; use pezframe_support::{pezpallet_prelude::DispatchResult, transactional}; impl pezpallet_asset_manager::AssetRegistrar for AssetRegistrar { #[transactional] fn create_foreign_asset( asset: AssetId, min_balance: Balance, metadata: AssetRegistrarMetadata, is_sufficient: bool, ) -> DispatchResult { Assets::force_create( RuntimeOrigin::root(), asset.into(), AssetManager::account_id(), is_sufficient, min_balance, )?; // Lastly, the metadata Assets::force_set_metadata( RuntimeOrigin::root(), asset.into(), metadata.name, metadata.symbol, metadata.decimals, metadata.is_frozen, ) } #[transactional] fn destroy_foreign_asset(asset: AssetId) -> DispatchResult { // Mark the asset as destroying Assets::start_destroy(RuntimeOrigin::root(), asset.into()) } fn destroy_asset_dispatch_info_weight(asset: AssetId) -> Weight { // For us both of them (Foreign and Local) have the same annotated weight for a given // witness // We need to take the dispatch info from the destroy call, which is already annotated in // the assets pezpallet // This is the dispatch info of destroy RuntimeCall::Assets(pezpallet_assets::Call::::start_destroy { id: asset.into() }) .get_dispatch_info() .call_weight } } /// This trait ensure we can convert AccountIds to AssetIds. pub trait AccountIdAssetIdConversion { // Get assetId and prefix from account fn account_to_asset_id(account: Account) -> Option<(Vec, AssetId)>; // Get AccountId from AssetId and prefix fn asset_id_to_account(prefix: &[u8], asset_id: AssetId) -> Account; } const FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; // Instruct how to go from an H160 to an AssetID // We just take the lowest 128 bits impl AccountIdAssetIdConversion for Runtime { /// The way to convert an account to assetId is by ensuring that the prefix is 0XFFFFFFFF /// and by taking the lowest 128 bits as the assetId fn account_to_asset_id(account: AccountId) -> Option<(Vec, AssetId)> { let h160_account: H160 = account.into(); let mut data = [0u8; 16]; let (prefix_part, id_part) = h160_account.as_fixed_bytes().split_at(4); if prefix_part == FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX { data.copy_from_slice(id_part); let asset_id: AssetId = u128::from_be_bytes(data); Some((prefix_part.to_vec(), asset_id)) } else { None } } // The opposite conversion fn asset_id_to_account(prefix: &[u8], asset_id: AssetId) -> AccountId { let mut data = [0u8; 20]; data[0..4].copy_from_slice(prefix); data[4..20].copy_from_slice(&asset_id.to_be_bytes()); AccountId::from(data) } } #[cfg(test)] mod tests { mod asset_registrar { use pezpallet_asset_manager::AssetRegistrar; use pezsp_io::TestExternalities; use crate::{ configs::{AssetRegistrar as Registrar, AssetRegistrarMetadata}, types::AssetId, AssetManager, Assets, Runtime, RuntimeOrigin, }; #[test] fn test_destroy_asset_dispatch_info_weight() { let asset_id: AssetId = 1; let _ = >::destroy_asset_dispatch_info_weight( asset_id, ); } #[test] fn test_destroy_foreign_asset() { TestExternalities::default().execute_with(|| { let asset_id: AssetId = 1; let _ = Assets::force_create( RuntimeOrigin::root(), asset_id.into(), AssetManager::account_id(), true, 1, ); let res = >::destroy_foreign_asset(asset_id); assert!(res.is_ok()); }); } #[test] fn test_create_foreign_asset() { TestExternalities::default().execute_with(|| { let asset_id = 1; let res = >::create_foreign_asset( asset_id, 1, AssetRegistrarMetadata { name: vec![0, 1, 2, 3], symbol: vec![4, 5, 6, 7], decimals: 6, is_frozen: false, }, false, ); assert!(res.is_ok()); }); } } mod account_asset_id_conversion { use core::str::FromStr; use fp_account::AccountId20; use crate::{ configs::{ asset_config::FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, AccountIdAssetIdConversion, }, types::AssetId, AccountId, Runtime, }; #[test] fn test_account_to_asset_id_success() { let expected_asset_id: AssetId = 1; let mut data = [0u8; 32]; data[0..4].copy_from_slice(FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX); data[4..20].copy_from_slice(&expected_asset_id.to_be_bytes()); let account_id = AccountId::from(data); let (prefix, asset_id) = Runtime::account_to_asset_id(account_id) .expect("Account to asset id conversion failed"); assert_eq!(prefix, FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX); assert_eq!(asset_id, expected_asset_id); } #[test] fn test_account_to_asset_id_error() { let expected_asset_id: AssetId = 1; let mut data = [0u8; 32]; data[0..3].copy_from_slice(&FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX[0..3]); data[3..4].copy_from_slice(&[0]); data[4..20].copy_from_slice(&expected_asset_id.to_be_bytes()); let account_id = AccountId::from(data); let res = Runtime::account_to_asset_id(account_id); assert_eq!(res, None); } #[test] fn test_asset_id_to_account() { let expected = AccountId20::from_str("0xFFFFFFFF00000000000000000000000000000001").unwrap(); let asset_id = 1; let result = Runtime::asset_id_to_account(FOREIGN_ASSET_PRECOMPILE_ADDRESS_PREFIX, asset_id); assert_eq!(result, expected); } } mod asset_type { use crate::{configs::AssetType, types::AssetId}; #[test] fn test_asset_type_default() { let default_asset_type = AssetType::default(); match default_asset_type { AssetType::Xcm(location) => { assert_eq!(location, xcm::v4::Location::here()); } } } #[cfg(feature = "runtime-benchmarks")] #[test] fn test_asset_type_from_location_v3() { let location = xcm::v3::MultiLocation { parents: 1, interior: xcm::v3::Junctions::Here }; // Use the From implementation directly let asset_type = AssetType::from(location); assert!(matches!(asset_type, AssetType::Xcm(_))); } #[test] fn test_asset_type_try_from_location_v4() { let location = xcm::v4::Location { parents: 1, interior: xcm::v4::Junctions::Here }; let asset_type = AssetType::from(location.clone()); match asset_type { AssetType::Xcm(loc) => assert_eq!(loc, location), } } #[test] fn test_asset_type_into_location() { let location = xcm::v4::Location::here(); let asset_type = AssetType::Xcm(location.clone()); let result: Option = asset_type.into(); assert_eq!(result, Some(location)); } #[test] fn test_asset_type_into_asset_id() { let location = xcm::v4::Location { parents: 1, interior: xcm::v4::Junctions::Here }; let asset_type = AssetType::Xcm(location); let asset_id: AssetId = asset_type.into(); assert!(asset_id > 0); } #[cfg(feature = "runtime-benchmarks")] #[test] fn test_convert_v3_to_v4() { use std::sync::Arc; use xcm::v3::{Junction as JunctionV3, Junctions as JunctionsV3, MultiLocation}; use crate::configs::convert_v3_to_v4; // Test with Here junctions let v3_location = MultiLocation { parents: 1, interior: JunctionsV3::Here }; let v4_location = convert_v3_to_v4(v3_location).unwrap(); assert_eq!(v4_location.parents, 1); assert_eq!(v4_location.interior, xcm::v4::Junctions::Here); // Test with X1 junction let v3_location = MultiLocation { parents: 0, interior: JunctionsV3::X1(JunctionV3::TeyrChain(1000)), }; let v4_location = convert_v3_to_v4(v3_location).unwrap(); assert_eq!(v4_location.parents, 0); // Create X1 junction with Arc let junction_array = Arc::new([xcm::v4::Junction::TeyrChain(1000)]); let expected_interior = xcm::v4::Junctions::X1(junction_array); assert_eq!(v4_location.interior, expected_interior); } #[cfg(feature = "runtime-benchmarks")] #[test] fn test_from_multilocation_for_asset_type() { use std::sync::Arc; use xcm::v3::{Junction as JunctionV3, Junctions as JunctionsV3, MultiLocation}; // Test with valid conversion let v3_location = MultiLocation { parents: 1, interior: JunctionsV3::Here }; let asset_type = AssetType::from(v3_location); assert!(matches!(asset_type, AssetType::Xcm(_))); // Test with more complex location let v3_location = MultiLocation { parents: 0, interior: JunctionsV3::X1(JunctionV3::TeyrChain(1000)), }; let asset_type = AssetType::from(v3_location); match asset_type { AssetType::Xcm(location) => { assert_eq!(location.parents, 0); // Create X1 junction with Arc let junction_array = Arc::new([xcm::v4::Junction::TeyrChain(1000)]); let expected_interior = xcm::v4::Junctions::X1(junction_array); assert_eq!(location.interior, expected_interior); } } } } }