--- /dev/null +++ b/pallets/rmrk-proxy/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-rmrk-proxy" +version = "0.1.0" +license = "GPLv3" +edition = "2021" + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = '3.1.2' + +[dependencies] +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" } +pallet-common = { default-features = false, path = '../common' } +pallet-nonfungible = { default-features = false, path = "../../pallets/nonfungible" } +# pallet-structure = { default-features = false, path = '../structure' } +up-data-structs = { default-features = false, path = '../../primitives/data-structs' } +# evm-coder = { default-features = false, path = '../../crates/evm-coder' } +# pallet-evm-coder-substrate = { default-features = false, path = '../evm-coder-substrate' } +pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.21-logs" } +frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.21" } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "up-data-structs/std", + "pallet-common/std", + "pallet-nonfungible/std", + "pallet-evm/std", + # "pallet-structure/std", + # "evm-coder/std", + # "pallet-evm-coder-substrate/std", + 'frame-benchmarking/std', +] +runtime-benchmarks = [ + 'frame-benchmarking', + 'frame-support/runtime-benchmarks', + 'frame-system/runtime-benchmarks', +] --- /dev/null +++ b/pallets/rmrk-proxy/src/lib.rs @@ -0,0 +1,229 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{pallet_prelude::*, transactional, BoundedVec, traits::ConstU32, dispatch::DispatchResult}; +use frame_system::{pallet_prelude::*, ensure_signed}; +use sp_runtime::DispatchError; +use up_data_structs::*; +use pallet_common::{Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations}; +use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle}; +use pallet_evm::account::CrossAccountId; + +pub use pallet::*; + +pub mod misc; + +use misc::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use pallet_evm::account; + + #[pallet::config] + pub trait Config: frame_system::Config + + pallet_common::Config + + pallet_nonfungible::Config + + account::Config { + type Event: From> + IsType<::Event>; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + CollectionCreated { + issuer: T::AccountId, + collection_id: CollectionId, + }, + CollectionDestroyed { + issuer: T::AccountId, + collection_id: CollectionId, + }, + CollectionLocked { + issuer: T::AccountId, + collection_id: CollectionId, + }, + } + + #[pallet::error] + pub enum Error { + CorruptedCollectionType, + NotRmrkCollection, + CollectionNotEmpty, + NoAvailableCollectionId, + CollectionUnknown, + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] + #[transactional] + pub fn create_collection( + origin: OriginFor, + metadata: PropertyValue, + max: Option, + symbol: BoundedVec>, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let limits = max.map(|max| CollectionLimits { + token_limit: Some(max), + ..Default::default() + }); + + let data = CreateCollectionData { + limits, + token_prefix: symbol, + ..Default::default() + }; + + let collection_id_res = >::init_collection(sender.clone(), data); + + if let Err(DispatchError::Arithmetic(_)) = &collection_id_res { + return Err(>::NoAvailableCollectionId.into()); + } + + let collection_id = collection_id_res?; + + let collection = Self::get_nft_collection(collection_id)?.into_inner(); + + >::set_scoped_collection_properties( + &collection, + PropertyScope::Rmrk, + [ + rmrk_property!(Metadata, metadata), + rmrk_property!(CollectionType, CollectionType::Regular), + ].into_iter() + )?; + + Self::deposit_event(Event::CollectionCreated { issuer: sender, collection_id }); + + Ok(()) + } + + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] + #[transactional] + pub fn destroy_collection( + origin: OriginFor, + collection_id: CollectionId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let cross_sender = T::CrossAccountId::from_sub(sender.clone()); + + let collection = Self::get_nft_collection(collection_id)?; + + Self::check_collection_type(collection_id, CollectionType::Regular)?; + + ensure!(collection.total_supply() == 0, >::CollectionNotEmpty); + + >::destroy_collection(collection, &cross_sender)?; + + Self::deposit_event(Event::CollectionDestroyed { issuer: sender, collection_id }); + + Ok(()) + } + + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] + #[transactional] + pub fn change_collection_issuer( + origin: OriginFor, + collection_id: CollectionId, + new_issuer: T::AccountId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + Self::change_collection_owner( + collection_id, + CollectionType::Regular, + sender, + new_issuer + )?; + + Ok(()) + } + + #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))] + #[transactional] + pub fn lock_collection( + origin: OriginFor, + collection_id: CollectionId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let cross_sender = T::CrossAccountId::from_sub(sender.clone()); + + let collection = Self::get_nft_collection(collection_id)?; + collection.check_is_owner(&cross_sender)?; + + let token_count = collection.total_supply(); + + let mut collection = collection.into_inner(); + collection.limits.token_limit = Some(token_count); + collection.save()?; + + Self::deposit_event(Event::CollectionLocked { issuer: sender, collection_id }); + + Ok(()) + } + } +} + +impl Pallet { + fn change_collection_owner( + collection_id: CollectionId, + collection_type: CollectionType, + sender: T::AccountId, + new_owner: T::AccountId, + ) -> DispatchResult { + let mut collection = Self::get_nft_collection(collection_id)?.into_inner(); + collection.check_is_owner(&T::CrossAccountId::from_sub(sender))?; + + Self::check_collection_type(collection_id, collection_type)?; + + collection.owner = new_owner; + collection.save() + } + + fn get_nft_collection(collection_id: CollectionId) -> Result, DispatchError> { + let collection = >::try_get(collection_id) + .map_err(|_| >::CollectionUnknown)? + .into_nft_collection()?; + + Ok(collection) + } + + fn get_collection_type(collection_id: CollectionId) -> Result { + let collection_type: CollectionType = >::collection_properties(collection_id) + .get(&rmrk_property!(CollectionType)) + .ok_or(>::NotRmrkCollection)? + .try_into() + .map_err(>::from)?; + + Ok(collection_type) + } + + fn check_collection_type(collection_id: CollectionId, collection_type: CollectionType) -> DispatchResult { + let actual_type = Self::get_collection_type(collection_id)?; + ensure!(actual_type == collection_type, >::NoPermission); + + Ok(()) + } +} --- /dev/null +++ b/pallets/rmrk-proxy/src/misc.rs @@ -0,0 +1,93 @@ +use super::*; +use codec::{Encode, Decode}; +use pallet_nonfungible::NonfungibleHandle; + +#[macro_export] +macro_rules! rmrk_property { + ($key:ident, $value:expr) => { + Property { + key: rmrk_property!($key), + value: $value.into() + } + }; + + ($key:ident) => { + RmrkProperty::$key.to_key() + }; +} + +macro_rules! impl_rmrk_value { + ($enum_name:path, decode_error: $error:ident) => { + impl Into for $enum_name { + fn into(self) -> PropertyValue { + self.encode().try_into().unwrap() + } + } + + impl TryFrom<&PropertyValue> for $enum_name { + type Error = MiscError; + + fn try_from(value: &PropertyValue) -> Result { + let mut value = value.as_slice(); + + <$enum_name>::decode(&mut value) + .map_err(|_| MiscError::$error) + } + } + + }; +} + +pub enum MiscError { + CorruptedCollectionType, +} + +impl From for Error { + fn from(error: MiscError) -> Self { + match error { + MiscError::CorruptedCollectionType => Self::CorruptedCollectionType, + } + } +} + +pub trait IntoNftCollection { + fn into_nft_collection(self) -> Result, Error>; +} + +impl IntoNftCollection for CollectionHandle { + fn into_nft_collection(self) -> Result, Error> { + match self.mode { + CollectionMode::NFT => Ok(NonfungibleHandle::cast(self)), + _ => Err(>::NotRmrkCollection) + } + } +} + +pub enum RmrkProperty { + Metadata, + CollectionType, +} + +impl RmrkProperty { + pub fn to_key(self) -> PropertyKey { + let key = |str_key: &str| { + PropertyKey::try_from( + str_key.bytes().collect::>() + ).unwrap() + }; + + match self { + Self::Metadata => key("metadata"), + Self::CollectionType => key("collection-type"), + } + } +} + +#[derive(Encode, Decode, PartialEq, Eq)] +pub enum CollectionType { + Regular, + Resource, + Base, +} + +impl_rmrk_value!(CollectionType, decode_error: CorruptedCollectionType);