Files
pezkuwi-runtime-templates/evm-template/runtime/src/configs/asset_config.rs
T

420 lines
14 KiB
Rust

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<AssetIdParameter> pezpallet_assets::BenchmarkHelper<AssetIdParameter> for BenchmarkHelper
where
AssetIdParameter: From<u128>,
{
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<xcm::v4::Location> for AssetType {
fn from(location: xcm::v4::Location) -> Self {
Self::Xcm(location)
}
}
#[cfg(feature = "runtime-benchmarks")]
fn convert_v3_to_v4(v3: MultiLocation) -> Option<xcm::v4::Location> {
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<MultiLocation> 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<Location> for AssetType {
type Error = ();
fn try_from(location: Location) -> Result<Self, Self::Error> {
Ok(Self::Xcm(location.try_into()?))
}
}
impl From<AssetType> for Option<xcm::v4::Location> {
fn from(val: AssetType) -> Self {
match val {
AssetType::Xcm(location) => Some(location),
}
}
}
// Implementation on how to retrieve the AssetId from an AssetType
impl From<AssetType> 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(<Runtime as frame_system::Config>::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<u8>,
pub symbol: Vec<u8>,
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<Runtime> 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::<Runtime>::start_destroy { id: asset.into() })
.get_dispatch_info()
.call_weight
}
}
/// This trait ensure we can convert AccountIds to AssetIds.
pub trait AccountIdAssetIdConversion<Account, AssetId> {
// Get assetId and prefix from account
fn account_to_asset_id(account: Account) -> Option<(Vec<u8>, 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<AccountId, AssetId> 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<u8>, 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 _ = <Registrar as AssetRegistrar<Runtime>>::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 = <Registrar as AssetRegistrar<Runtime>>::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 = <Registrar as AssetRegistrar<Runtime>>::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<xcm::v4::Location> = 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);
}
}
}
}
}