difftreelog
fix(rmrk) move rmrk specific types to rmrk-traits
in: master
19 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6338,6 +6338,7 @@
"pallet-nonfungible",
"pallet-structure",
"parity-scale-codec 3.1.2",
+ "rmrk-traits",
"scale-info",
"sp-core",
"sp-runtime",
@@ -6357,6 +6358,7 @@
"pallet-nonfungible",
"pallet-rmrk-core",
"parity-scale-codec 3.1.2",
+ "rmrk-traits",
"scale-info",
"sp-core",
"sp-runtime",
@@ -8999,15 +9001,24 @@
version = "0.0.1"
dependencies = [
"parity-scale-codec 2.3.1",
+ "rmrk-traits",
"serde",
"sp-api",
"sp-core",
"sp-runtime",
"sp-std",
- "up-data-structs",
]
[[package]]
+name = "rmrk-traits"
+version = "0.1.0"
+dependencies = [
+ "parity-scale-codec 3.1.2",
+ "scale-info",
+ "serde",
+]
+
+[[package]]
name = "rocksdb"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -12723,6 +12734,7 @@
"frame-system",
"pallet-evm",
"parity-scale-codec 3.1.2",
+ "rmrk-traits",
"scale-info",
"serde",
"sp-core",
pallets/proxy-rmrk-core/Cargo.tomldiffbeforeafterboth--- a/pallets/proxy-rmrk-core/Cargo.toml
+++ b/pallets/proxy-rmrk-core/Cargo.toml
@@ -22,6 +22,7 @@
up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.22" }
frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
+rmrk-traits = { default-features = false, path = "../../primitives/rmrk-traits" }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
derivative = { version = "2.2.0", features = ["use_core"] }
@@ -33,6 +34,7 @@
"sp-runtime/std",
"sp-std/std",
"up-data-structs/std",
+ "rmrk-traits/std",
"pallet-common/std",
"pallet-nonfungible/std",
"pallet-structure/std",
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 CannotAcceptNonOwnedNft,136 CannotRejectNonOwnedNft,137 ResourceNotPending,138 }139140 #[pallet::call]141 impl<T: Config> Pallet<T> {142 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143 #[transactional]144 pub fn create_collection(145 origin: OriginFor<T>,146 metadata: RmrkString,147 max: Option<u32>,148 symbol: RmrkCollectionSymbol,149 ) -> DispatchResult {150 let sender = ensure_signed(origin)?;151152 let limits = CollectionLimits {153 owner_can_transfer: Some(false),154 token_limit: max,155 ..Default::default()156 };157158 let data = CreateCollectionData {159 limits: Some(limits),160 token_prefix: symbol161 .into_inner()162 .try_into()163 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164 permissions: Some(CollectionPermissions {165 nesting: Some(NestingRule::Owner),166 ..Default::default()167 }),168 ..Default::default()169 };170171 let unique_collection_id = Self::init_collection(172 T::CrossAccountId::from_sub(sender.clone()),173 data,174 [175 Self::rmrk_property(Metadata, &metadata)?,176 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177 ]178 .into_iter(),179 )?;180 let rmrk_collection_id = <CollectionIndex<T>>::get();181182 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185 <CollectionIndex<T>>::mutate(|n| *n += 1);186187 Self::deposit_event(Event::CollectionCreated {188 issuer: sender,189 collection_id: rmrk_collection_id,190 });191192 Ok(())193 }194195 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196 #[transactional]197 pub fn destroy_collection(198 origin: OriginFor<T>,199 collection_id: RmrkCollectionId,200 ) -> DispatchResult {201 let sender = ensure_signed(origin)?;202 let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204 let collection = Self::get_typed_nft_collection(205 Self::unique_collection_id(collection_id)?,206 misc::CollectionType::Regular,207 )?;208209 <PalletNft<T>>::destroy_collection(collection, &cross_sender)210 .map_err(Self::map_unique_err_to_proxy)?;211212 Self::deposit_event(Event::CollectionDestroyed {213 issuer: sender,214 collection_id,215 });216217 Ok(())218 }219220 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221 #[transactional]222 pub fn change_collection_issuer(223 origin: OriginFor<T>,224 collection_id: RmrkCollectionId,225 new_issuer: <T::Lookup as StaticLookup>::Source,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228229 let new_issuer = T::Lookup::lookup(new_issuer)?;230231 Self::change_collection_owner(232 Self::unique_collection_id(collection_id)?,233 misc::CollectionType::Regular,234 sender.clone(),235 new_issuer.clone(),236 )?;237238 Self::deposit_event(Event::IssuerChanged {239 old_issuer: sender,240 new_issuer,241 collection_id,242 });243244 Ok(())245 }246247 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248 #[transactional]249 pub fn lock_collection(250 origin: OriginFor<T>,251 collection_id: RmrkCollectionId,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254 let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256 let collection = Self::get_typed_nft_collection(257 Self::unique_collection_id(collection_id)?,258 misc::CollectionType::Regular,259 )?;260261 Self::check_collection_owner(&collection, &cross_sender)?;262263 let token_count = collection.total_supply();264265 let mut collection = collection.into_inner();266 collection.limits.token_limit = Some(token_count);267 collection.save()?;268269 Self::deposit_event(Event::CollectionLocked {270 issuer: sender,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn mint_nft(280 origin: OriginFor<T>,281 owner: T::AccountId,282 collection_id: RmrkCollectionId,283 recipient: Option<T::AccountId>,284 royalty_amount: Option<Permill>,285 metadata: RmrkString,286 transferable: bool,287 ) -> DispatchResult {288 let sender = ensure_signed(origin)?;289 let sender = T::CrossAccountId::from_sub(sender);290 let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292 let royalty_info = royalty_amount.map(|amount| rmrk::RoyaltyInfo {293 recipient: recipient.unwrap_or_else(|| owner.clone()),294 amount,295 });296297 let collection = Self::get_typed_nft_collection(298 Self::unique_collection_id(collection_id)?,299 misc::CollectionType::Regular,300 )?;301302 let nft_id = Self::create_nft(303 &sender,304 &cross_owner,305 &collection,306 [307 Self::rmrk_property(TokenType, &NftType::Regular)?,308 Self::rmrk_property(Transferable, &transferable)?,309 Self::rmrk_property(PendingNftAccept, &false)?,310 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311 Self::rmrk_property(Metadata, &metadata)?,312 Self::rmrk_property(Equipped, &false)?,313 Self::rmrk_property(314 ResourceCollection,315 &Self::init_collection(316 sender.clone(),317 CreateCollectionData {318 ..Default::default()319 },320 [Self::rmrk_property(321 CollectionType,322 &misc::CollectionType::Resource,323 )?]324 .into_iter(),325 )?,326 )?, // todo possibly add limits to the collection if rmrk warrants them327 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328 ]329 .into_iter(),330 )331 .map_err(|err| match err {332 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333 err => Self::map_unique_err_to_proxy(err),334 })?;335336 Self::deposit_event(Event::NftMinted {337 owner,338 collection_id,339 nft_id: nft_id.0,340 });341342 Ok(())343 }344345 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346 #[transactional]347 pub fn burn_nft(348 origin: OriginFor<T>,349 collection_id: RmrkCollectionId,350 nft_id: RmrkNftId,351 ) -> DispatchResult {352 let sender = ensure_signed(origin)?;353 let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355 Self::destroy_nft(356 cross_sender,357 Self::unique_collection_id(collection_id)?,358 nft_id.into(),359 )360 .map_err(Self::map_unique_err_to_proxy)?;361362 Self::deposit_event(Event::NFTBurned {363 owner: sender,364 nft_id,365 });366367 Ok(())368 }369370 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371 #[transactional]372 pub fn send(373 origin: OriginFor<T>,374 rmrk_collection_id: RmrkCollectionId,375 rmrk_nft_id: RmrkNftId,376 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377 ) -> DispatchResult {378 let sender = ensure_signed(origin.clone())?;379 let cross_sender = T::CrossAccountId::from_sub(sender);380381 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382 let nft_id = rmrk_nft_id.into();383384 let token_data =385 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387 let from = token_data.owner;388389 let collection =390 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392 ensure!(393 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394 <Error<T>>::NonTransferable395 );396397 ensure!(398 !Self::get_nft_property_decoded(399 collection_id,400 nft_id,401 RmrkProperty::PendingNftAccept402 )?,403 <Error<T>>::NoPermission404 );405406 let target_owner;407408 match new_owner {409 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {410 target_owner = T::CrossAccountId::from_sub(account_id);411 }412 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(413 target_collection_id,414 target_nft_id,415 ) => {416 let target_collection_id = Self::unique_collection_id(target_collection_id)?;417418 let target_nft_budget = budget::Value::new(NESTING_BUDGET);419420 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(421 target_collection_id,422 target_nft_id.into(),423 Some((collection_id, nft_id)),424 &target_nft_budget,425 )426 .map_err(Self::map_unique_err_to_proxy)?;427428 let is_approval_required = cross_sender != target_nft_owner;429430 if is_approval_required {431 target_owner = target_nft_owner;432433 <PalletNft<T>>::set_scoped_token_property(434 collection.id,435 nft_id,436 PropertyScope::Rmrk,437 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,438 )?;439 } else {440 target_owner = T::CrossTokenAddressMapping::token_to_address(441 target_collection_id,442 target_nft_id.into(),443 );444 }445 }446 }447448 let src_nft_budget = budget::Value::new(NESTING_BUDGET);449450 <PalletNft<T>>::transfer_from(451 &collection,452 &cross_sender,453 &from,454 &target_owner,455 nft_id,456 &src_nft_budget,457 )458 .map_err(Self::map_unique_err_to_proxy)?;459460 Ok(())461 }462463 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]464 #[transactional]465 pub fn accept_nft(466 origin: OriginFor<T>,467 rmrk_collection_id: RmrkCollectionId,468 rmrk_nft_id: RmrkNftId,469 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,470 ) -> DispatchResult {471 let sender = ensure_signed(origin.clone())?;472 let cross_sender = T::CrossAccountId::from_sub(sender);473474 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;475 let nft_id = rmrk_nft_id.into();476477 let collection =478 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;479480 let new_owner = match new_owner {481 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {482 T::CrossAccountId::from_sub(account_id)483 }484 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(485 target_collection_id,486 target_nft_id,487 ) => {488 let target_collection_id = Self::unique_collection_id(target_collection_id)?;489490 T::CrossTokenAddressMapping::token_to_address(491 target_collection_id,492 TokenId(target_nft_id),493 )494 }495 };496497 let budget = budget::Value::new(NESTING_BUDGET);498499 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)500 .map_err(|err| {501 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {502 <Error<T>>::CannotAcceptNonOwnedNft.into()503 } else {504 Self::map_unique_err_to_proxy(err)505 }506 })?;507508 <PalletNft<T>>::set_scoped_token_property(509 collection.id,510 nft_id,511 PropertyScope::Rmrk,512 Self::rmrk_property(PendingNftAccept, &false)?,513 )?;514515 Ok(())516 }517518 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]519 #[transactional]520 pub fn reject_nft(521 origin: OriginFor<T>,522 collection_id: RmrkCollectionId,523 nft_id: RmrkNftId,524 ) -> DispatchResult {525 let sender = ensure_signed(origin)?;526 let cross_sender = T::CrossAccountId::from_sub(sender);527528 let collection_id = Self::unique_collection_id(collection_id)?;529 let nft_id = nft_id.into();530531 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {532 if err == <CommonError<T>>::NoPermission.into()533 || err == <CommonError<T>>::ApprovedValueTooLow.into()534 {535 <Error<T>>::CannotRejectNonOwnedNft.into()536 } else {537 Self::map_unique_err_to_proxy(err)538 }539 })?;540541 Ok(())542 }543544 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]545 #[transactional]546 pub fn accept_resource(547 origin: OriginFor<T>,548 rmrk_collection_id: RmrkCollectionId,549 rmrk_nft_id: RmrkNftId,550 rmrk_resource_id: RmrkResourceId,551 ) -> DispatchResult {552 let sender = ensure_signed(origin)?;553 let cross_sender = T::CrossAccountId::from_sub(sender);554555 let collection_id = Self::unique_collection_id(rmrk_collection_id)556 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;557558 let nft_id = rmrk_nft_id.into();559 let resource_id = rmrk_resource_id.into();560561 let budget = budget::Value::new(NESTING_BUDGET);562563 let nft_owner =564 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)565 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567 let resource_collection_id: CollectionId =568 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)569 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;570571 let is_pending: bool = Self::get_nft_property_decoded(572 resource_collection_id,573 resource_id,574 PendingResourceAccept,575 )576 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;577578 ensure!(is_pending, <Error<T>>::ResourceNotPending);579580 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);581582 <PalletNft<T>>::set_scoped_token_property(583 resource_collection_id,584 rmrk_resource_id.into(),585 PropertyScope::Rmrk,586 Self::rmrk_property(PendingResourceAccept, &false)?,587 )?;588589 Ok(())590 }591592 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593 #[transactional]594 pub fn accept_resource_removal(595 origin: OriginFor<T>,596 rmrk_collection_id: RmrkCollectionId,597 rmrk_nft_id: RmrkNftId,598 rmrk_resource_id: RmrkResourceId,599 ) -> DispatchResult {600 let sender = ensure_signed(origin)?;601 let cross_sender = T::CrossAccountId::from_sub(sender);602603 let collection_id = Self::unique_collection_id(rmrk_collection_id)604 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;605606 let nft_id = rmrk_nft_id.into();607 let resource_id = rmrk_resource_id.into();608609 let budget = budget::Value::new(NESTING_BUDGET);610611 let nft_owner =612 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)613 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;614615 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);616617 let resource_collection_id: CollectionId =618 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)619 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;620621 let is_pending: bool = Self::get_nft_property_decoded(622 resource_collection_id,623 resource_id,624 PendingResourceRemoval,625 )626 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;627628 ensure!(is_pending, <Error<T>>::ResourceNotPending);629630 let resource_collection = Self::get_typed_nft_collection(631 resource_collection_id,632 misc::CollectionType::Resource,633 )?;634635 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())636 .map_err(Self::map_unique_err_to_proxy)?;637638 Ok(())639 }640641 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]642 #[transactional]643 pub fn set_property(644 origin: OriginFor<T>,645 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,646 maybe_nft_id: Option<RmrkNftId>,647 key: RmrkKeyString,648 value: RmrkValueString,649 ) -> DispatchResult {650 let sender = ensure_signed(origin)?;651 let sender = T::CrossAccountId::from_sub(sender);652653 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654 let budget = budget::Value::new(NESTING_BUDGET);655656 match maybe_nft_id {657 Some(nft_id) => {658 let token_id: TokenId = nft_id.into();659660 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;661 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;662663 <PalletNft<T>>::set_scoped_token_property(664 collection_id,665 token_id,666 PropertyScope::Rmrk,667 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,668 )?;669 }670 None => {671 let collection = Self::get_typed_nft_collection(672 collection_id,673 misc::CollectionType::Regular,674 )?;675676 Self::check_collection_owner(&collection, &sender)?;677678 <PalletCommon<T>>::set_scoped_collection_property(679 collection_id,680 PropertyScope::Rmrk,681 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,682 )?;683 }684 }685686 Self::deposit_event(Event::PropertySet {687 collection_id: rmrk_collection_id,688 maybe_nft_id,689 key,690 value,691 });692693 Ok(())694 }695696 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]697 #[transactional]698 pub fn add_basic_resource(699 origin: OriginFor<T>,700 collection_id: RmrkCollectionId,701 nft_id: RmrkNftId,702 resource: RmrkBasicResource,703 ) -> DispatchResult {704 let sender = ensure_signed(origin.clone())?;705706 let resource_id = Self::resource_add(707 sender,708 Self::unique_collection_id(collection_id)?,709 nft_id.into(),710 [711 Self::rmrk_property(TokenType, &NftType::Resource)?,712 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,713 Self::rmrk_property(Src, &resource.src)?,714 Self::rmrk_property(Metadata, &resource.metadata)?,715 Self::rmrk_property(License, &resource.license)?,716 Self::rmrk_property(Thumb, &resource.thumb)?,717 ]718 .into_iter(),719 )?;720721 Self::deposit_event(Event::ResourceAdded {722 nft_id,723 resource_id,724 });725 Ok(())726 }727728 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]729 #[transactional]730 pub fn add_composable_resource(731 origin: OriginFor<T>,732 collection_id: RmrkCollectionId,733 nft_id: RmrkNftId,734 _resource_id: RmrkBoundedResource,735 resource: RmrkComposableResource,736 ) -> DispatchResult {737 let sender = ensure_signed(origin.clone())?;738739 let resource_id = Self::resource_add(740 sender,741 Self::unique_collection_id(collection_id)?,742 nft_id.into(),743 [744 Self::rmrk_property(TokenType, &NftType::Resource)?,745 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,746 Self::rmrk_property(Parts, &resource.parts)?,747 Self::rmrk_property(Base, &resource.base)?,748 Self::rmrk_property(Src, &resource.src)?,749 Self::rmrk_property(Metadata, &resource.metadata)?,750 Self::rmrk_property(License, &resource.license)?,751 Self::rmrk_property(Thumb, &resource.thumb)?,752 ]753 .into_iter(),754 )?;755756 Self::deposit_event(Event::ResourceAdded {757 nft_id,758 resource_id,759 });760 Ok(())761 }762763 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]764 #[transactional]765 pub fn add_slot_resource(766 origin: OriginFor<T>,767 collection_id: RmrkCollectionId,768 nft_id: RmrkNftId,769 resource: RmrkSlotResource,770 ) -> DispatchResult {771 let sender = ensure_signed(origin.clone())?;772773 let resource_id = Self::resource_add(774 sender,775 Self::unique_collection_id(collection_id)?,776 nft_id.into(),777 [778 Self::rmrk_property(TokenType, &NftType::Resource)?,779 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,780 Self::rmrk_property(Base, &resource.base)?,781 Self::rmrk_property(Src, &resource.src)?,782 Self::rmrk_property(Metadata, &resource.metadata)?,783 Self::rmrk_property(Slot, &resource.slot)?,784 Self::rmrk_property(License, &resource.license)?,785 Self::rmrk_property(Thumb, &resource.thumb)?,786 ]787 .into_iter(),788 )?;789790 Self::deposit_event(Event::ResourceAdded {791 nft_id,792 resource_id,793 });794 Ok(())795 }796797 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]798 #[transactional]799 pub fn remove_resource(800 origin: OriginFor<T>,801 collection_id: RmrkCollectionId,802 nft_id: RmrkNftId,803 resource_id: RmrkResourceId,804 ) -> DispatchResult {805 let sender = ensure_signed(origin.clone())?;806807 Self::resource_remove(808 sender,809 Self::unique_collection_id(collection_id)?,810 nft_id.into(),811 resource_id.into(),812 )?;813814 Self::deposit_event(Event::ResourceRemoval {815 nft_id,816 resource_id,817 });818 Ok(())819 }820 }821}822823impl<T: Config> Pallet<T> {824 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {825 let key = rmrk_key.to_key::<T>()?;826827 let scoped_key = PropertyScope::Rmrk828 .apply(key)829 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;830831 Ok(scoped_key)832 }833834 // todo think about renaming these835 pub fn rmrk_property<E: Encode>(836 rmrk_key: RmrkProperty,837 value: &E,838 ) -> Result<Property, DispatchError> {839 let key = rmrk_key.to_key::<T>()?;840841 let value = value842 .encode()843 .try_into()844 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;845846 let property = Property { key, value };847848 Ok(property)849 }850851 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {852 vec.decode()853 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())854 }855856 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>857 where858 BoundedVec<u8, S>: TryFrom<Vec<u8>>,859 {860 vec.rebind()861 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())862 }863864 fn init_collection(865 sender: T::CrossAccountId,866 data: CreateCollectionData<T::AccountId>,867 properties: impl Iterator<Item = Property>,868 ) -> Result<CollectionId, DispatchError> {869 let collection_id = <PalletNft<T>>::init_collection(sender, data);870871 if let Err(DispatchError::Arithmetic(_)) = &collection_id {872 return Err(<Error<T>>::NoAvailableCollectionId.into());873 }874875 <PalletCommon<T>>::set_scoped_collection_properties(876 collection_id?,877 PropertyScope::Rmrk,878 properties,879 )?;880881 collection_id882 }883884 pub fn create_nft(885 sender: &T::CrossAccountId,886 owner: &T::CrossAccountId,887 collection: &NonfungibleHandle<T>,888 properties: impl Iterator<Item = Property>,889 ) -> Result<TokenId, DispatchError> {890 let data = CreateNftExData {891 properties: BoundedVec::default(),892 owner: owner.clone(),893 };894895 let budget = budget::Value::new(NESTING_BUDGET);896897 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;898899 let nft_id = <PalletNft<T>>::current_token_id(collection.id);900901 <PalletNft<T>>::set_scoped_token_properties(902 collection.id,903 nft_id,904 PropertyScope::Rmrk,905 properties,906 )?;907908 Ok(nft_id)909 }910911 fn destroy_nft(912 sender: T::CrossAccountId,913 collection_id: CollectionId,914 token_id: TokenId,915 ) -> DispatchResult {916 let collection =917 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;918919 let token_data =920 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;921922 let from = token_data.owner;923924 let budget = budget::Value::new(NESTING_BUDGET);925926 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)927 }928929 fn resource_add(930 sender: T::AccountId,931 collection_id: CollectionId,932 token_id: TokenId,933 resource_properties: impl Iterator<Item = Property>,934 ) -> Result<RmrkResourceId, DispatchError> {935 let collection =936 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;937 ensure!(collection.owner == sender, Error::<T>::NoPermission);938939 let sender = T::CrossAccountId::from_sub(sender);940 let budget = budget::Value::new(NESTING_BUDGET);941942 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)943 .map_err(Self::map_unique_err_to_proxy)?;944945 let pending = sender != nft_owner;946947 let resource_collection_id: CollectionId =948 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;949 let resource_collection =950 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;951952 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them953954 let resource_id = Self::create_nft(955 &sender,956 &nft_owner,957 &resource_collection,958 resource_properties.chain(959 [960 Self::rmrk_property(PendingResourceAccept, &pending)?,961 Self::rmrk_property(PendingResourceRemoval, &false)?,962 ]963 .into_iter(),964 ),965 )966 .map_err(|err| match err {967 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),968 err => Self::map_unique_err_to_proxy(err),969 })?;970971 Ok(resource_id.0)972 }973974 fn resource_remove(975 sender: T::AccountId,976 collection_id: CollectionId,977 nft_id: TokenId,978 resource_id: TokenId,979 ) -> DispatchResult {980 let collection =981 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;982 ensure!(collection.owner == sender, Error::<T>::NoPermission);983984 let resource_collection_id: CollectionId =985 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;986 let resource_collection =987 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;988 ensure!(989 <PalletNft<T>>::token_exists(&resource_collection, resource_id),990 Error::<T>::ResourceDoesntExist991 );992993 let budget = up_data_structs::budget::Value::new(10);994 let topmost_owner =995 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;996997 let sender = T::CrossAccountId::from_sub(sender);998 if topmost_owner == sender {999 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1000 .map_err(Self::map_unique_err_to_proxy)?;1001 } else {1002 <PalletNft<T>>::set_scoped_token_property(1003 resource_collection_id,1004 resource_id,1005 PropertyScope::Rmrk,1006 Self::rmrk_property(PendingResourceRemoval, &true)?,1007 )?;1008 }10091010 Ok(())1011 }10121013 fn change_collection_owner(1014 collection_id: CollectionId,1015 collection_type: misc::CollectionType,1016 sender: T::AccountId,1017 new_owner: T::AccountId,1018 ) -> DispatchResult {1019 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1020 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10211022 let mut collection = collection.into_inner();10231024 collection.owner = new_owner;1025 collection.save()1026 }10271028 fn check_collection_owner(1029 collection: &NonfungibleHandle<T>,1030 account: &T::CrossAccountId,1031 ) -> DispatchResult {1032 collection1033 .check_is_owner(account)1034 .map_err(Self::map_unique_err_to_proxy)1035 }10361037 pub fn last_collection_idx() -> RmrkCollectionId {1038 <CollectionIndex<T>>::get()1039 }10401041 pub fn unique_collection_id(1042 rmrk_collection_id: RmrkCollectionId,1043 ) -> Result<CollectionId, DispatchError> {1044 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1045 .map_err(|_| <Error<T>>::CollectionUnknown.into())1046 }10471048 pub fn rmrk_collection_id(1049 unique_collection_id: CollectionId,1050 ) -> Result<RmrkCollectionId, DispatchError> {1051 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1052 .map_err(|_| <Error<T>>::CollectionUnknown.into())1053 }10541055 pub fn get_nft_collection(1056 collection_id: CollectionId,1057 ) -> Result<NonfungibleHandle<T>, DispatchError> {1058 let collection = <CollectionHandle<T>>::try_get(collection_id)1059 .map_err(|_| <Error<T>>::CollectionUnknown)?;10601061 match collection.mode {1062 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1063 _ => Err(<Error<T>>::CollectionUnknown.into()),1064 }1065 }10661067 pub fn collection_exists(collection_id: CollectionId) -> bool {1068 <CollectionHandle<T>>::try_get(collection_id).is_ok()1069 }10701071 pub fn get_collection_property(1072 collection_id: CollectionId,1073 key: RmrkProperty,1074 ) -> Result<PropertyValue, DispatchError> {1075 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1076 .get(&Self::rmrk_property_key(key)?)1077 .ok_or(<Error<T>>::CollectionUnknown)?1078 .clone();10791080 Ok(collection_property)1081 }10821083 pub fn get_collection_property_decoded<V: Decode>(1084 collection_id: CollectionId,1085 key: RmrkProperty,1086 ) -> Result<V, DispatchError> {1087 Self::decode_property(Self::get_collection_property(collection_id, key)?)1088 }10891090 pub fn get_collection_type(1091 collection_id: CollectionId,1092 ) -> Result<misc::CollectionType, DispatchError> {1093 Self::get_collection_property_decoded(collection_id, CollectionType)1094 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1095 }10961097 pub fn ensure_collection_type(1098 collection_id: CollectionId,1099 collection_type: misc::CollectionType,1100 ) -> DispatchResult {1101 let actual_type = Self::get_collection_type(collection_id)?;1102 ensure!(1103 actual_type == collection_type,1104 <CommonError<T>>::NoPermission1105 );11061107 Ok(())1108 }11091110 pub fn get_typed_nft_collection(1111 collection_id: CollectionId,1112 collection_type: misc::CollectionType,1113 ) -> Result<NonfungibleHandle<T>, DispatchError> {1114 Self::ensure_collection_type(collection_id, collection_type)?;11151116 Self::get_nft_collection(collection_id)1117 }11181119 pub fn get_typed_nft_collection_mapped(1120 rmrk_collection_id: RmrkCollectionId,1121 collection_type: misc::CollectionType,1122 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1123 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;11241125 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11261127 Ok((collection, unique_collection_id))1128 }11291130 pub fn get_nft_property(1131 collection_id: CollectionId,1132 nft_id: TokenId,1133 key: RmrkProperty,1134 ) -> Result<PropertyValue, DispatchError> {1135 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1136 .get(&Self::rmrk_property_key(key)?)1137 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1138 .clone();11391140 Ok(nft_property)1141 }11421143 pub fn get_nft_property_decoded<V: Decode>(1144 collection_id: CollectionId,1145 nft_id: TokenId,1146 key: RmrkProperty,1147 ) -> Result<V, DispatchError> {1148 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1149 }11501151 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1152 <TokenData<T>>::contains_key((collection_id, nft_id))1153 }11541155 pub fn get_nft_type(1156 collection_id: CollectionId,1157 token_id: TokenId,1158 ) -> Result<NftType, DispatchError> {1159 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1160 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1161 }11621163 pub fn ensure_nft_type(1164 collection_id: CollectionId,1165 token_id: TokenId,1166 nft_type: NftType,1167 ) -> DispatchResult {1168 let actual_type = Self::get_nft_type(collection_id, token_id)?;1169 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11701171 Ok(())1172 }11731174 pub fn ensure_nft_owner(1175 collection_id: CollectionId,1176 token_id: TokenId,1177 possible_owner: &T::CrossAccountId,1178 nesting_budget: &dyn budget::Budget,1179 ) -> DispatchResult {1180 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1181 possible_owner.clone(),1182 collection_id,1183 token_id,1184 None,1185 nesting_budget,1186 )?;11871188 ensure!(is_owned, <Error<T>>::NoPermission);11891190 Ok(())1191 }11921193 pub fn filter_user_properties<Key, Value, R, Mapper>(1194 collection_id: CollectionId,1195 token_id: Option<TokenId>,1196 filter_keys: Option<Vec<RmrkPropertyKey>>,1197 mapper: Mapper,1198 ) -> Result<Vec<R>, DispatchError>1199 where1200 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1201 Value: Decode + Default,1202 Mapper: Fn(Key, Value) -> R,1203 {1204 filter_keys1205 .map(|keys| {1206 let properties = keys1207 .into_iter()1208 .filter_map(|key| {1209 let key: Key = key.try_into().ok()?;12101211 let value = match token_id {1212 Some(token_id) => Self::get_nft_property_decoded(1213 collection_id,1214 token_id,1215 UserProperty(key.as_ref()),1216 ),1217 None => Self::get_collection_property_decoded(1218 collection_id,1219 UserProperty(key.as_ref()),1220 ),1221 }1222 .ok()?;12231224 Some(mapper(key, value))1225 })1226 .collect();12271228 Ok(properties)1229 })1230 .unwrap_or_else(|| {1231 let properties =1232 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12331234 Ok(properties)1235 })1236 }12371238 pub fn iterate_user_properties<Key, Value, R, Mapper>(1239 collection_id: CollectionId,1240 token_id: Option<TokenId>,1241 mapper: Mapper,1242 ) -> Result<impl Iterator<Item = R>, DispatchError>1243 where1244 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1245 Value: Decode + Default,1246 Mapper: Fn(Key, Value) -> R,1247 {1248 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12491250 let properties = match token_id {1251 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1252 None => <PalletCommon<T>>::collection_properties(collection_id),1253 };12541255 let properties = properties.into_iter().filter_map(move |(key, value)| {1256 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12571258 let key: Key = key.to_vec().try_into().ok()?;1259 let value: Value = value.decode().ok()?;12601261 Some(mapper(key, value))1262 });12631264 Ok(properties)1265 }12661267 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1268 map_unique_err_to_proxy! {1269 match err {1270 CommonError::NoPermission => NoPermission,1271 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1272 CommonError::PublicMintingNotAllowed => NoPermission,1273 CommonError::TokenNotFound => NoAvailableNftId,1274 CommonError::ApprovedValueTooLow => NoPermission,1275 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1276 StructureError::TokenNotFound => NoAvailableNftId,1277 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1278 }1279 }1280 }1281}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{pallet_prelude::*, transactional, BoundedVec, dispatch::DispatchResult};20use frame_system::{pallet_prelude::*, ensure_signed};21use sp_runtime::{DispatchError, Permill, traits::StaticLookup};22use sp_std::vec::Vec;23use up_data_structs::{*, mapping::TokenAddressMapping};24use pallet_common::{25 Pallet as PalletCommon, Error as CommonError, CollectionHandle, CommonCollectionOperations,26};27use pallet_nonfungible::{Pallet as PalletNft, NonfungibleHandle, TokenData};28use pallet_structure::{Pallet as PalletStructure, Error as StructureError};29use pallet_evm::account::CrossAccountId;30use core::convert::AsRef;3132pub use pallet::*;3334pub mod misc;35pub mod property;3637use misc::*;38pub use property::*;3940use RmrkProperty::*;4142const NESTING_BUDGET: u32 = 5;4344#[frame_support::pallet]45pub mod pallet {46 use super::*;47 use pallet_evm::account;4849 #[pallet::config]50 pub trait Config:51 frame_system::Config + pallet_common::Config + pallet_nonfungible::Config + account::Config52 {53 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;54 }5556 #[pallet::storage]57 #[pallet::getter(fn collection_index)]58 pub type CollectionIndex<T: Config> = StorageValue<_, RmrkCollectionId, ValueQuery>;5960 #[pallet::storage]61 pub type UniqueCollectionId<T: Config> =62 StorageMap<_, Twox64Concat, RmrkCollectionId, CollectionId, ValueQuery>;6364 #[pallet::storage]65 pub type RmrkInernalCollectionId<T: Config> =66 StorageMap<_, Twox64Concat, CollectionId, RmrkCollectionId, ValueQuery>;6768 #[pallet::pallet]69 #[pallet::generate_store(pub(super) trait Store)]70 pub struct Pallet<T>(_);7172 #[pallet::event]73 #[pallet::generate_deposit(pub(super) fn deposit_event)]74 pub enum Event<T: Config> {75 CollectionCreated {76 issuer: T::AccountId,77 collection_id: RmrkCollectionId,78 },79 CollectionDestroyed {80 issuer: T::AccountId,81 collection_id: RmrkCollectionId,82 },83 IssuerChanged {84 old_issuer: T::AccountId,85 new_issuer: T::AccountId,86 collection_id: RmrkCollectionId,87 },88 CollectionLocked {89 issuer: T::AccountId,90 collection_id: RmrkCollectionId,91 },92 NftMinted {93 owner: T::AccountId,94 collection_id: RmrkCollectionId,95 nft_id: RmrkNftId,96 },97 NFTBurned {98 owner: T::AccountId,99 nft_id: RmrkNftId,100 },101 PropertySet {102 collection_id: RmrkCollectionId,103 maybe_nft_id: Option<RmrkNftId>,104 key: RmrkKeyString,105 value: RmrkValueString,106 },107 ResourceAdded {108 nft_id: RmrkNftId,109 resource_id: RmrkResourceId,110 },111 ResourceRemoval {112 nft_id: RmrkNftId,113 resource_id: RmrkResourceId,114 },115 }116117 #[pallet::error]118 pub enum Error<T> {119 /* Unique-specific events */120 CorruptedCollectionType,121 NftTypeEncodeError,122 RmrkPropertyKeyIsTooLong,123 RmrkPropertyValueIsTooLong,124125 /* RMRK compatible events */126 CollectionNotEmpty,127 NoAvailableCollectionId,128 NoAvailableNftId,129 CollectionUnknown,130 NoPermission,131 NonTransferable,132 CollectionFullOrLocked,133 ResourceDoesntExist,134 CannotSendToDescendentOrSelf,135 CannotAcceptNonOwnedNft,136 CannotRejectNonOwnedNft,137 ResourceNotPending,138 }139140 #[pallet::call]141 impl<T: Config> Pallet<T> {142 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]143 #[transactional]144 pub fn create_collection(145 origin: OriginFor<T>,146 metadata: RmrkString,147 max: Option<u32>,148 symbol: RmrkCollectionSymbol,149 ) -> DispatchResult {150 let sender = ensure_signed(origin)?;151152 let limits = CollectionLimits {153 owner_can_transfer: Some(false),154 token_limit: max,155 ..Default::default()156 };157158 let data = CreateCollectionData {159 limits: Some(limits),160 token_prefix: symbol161 .into_inner()162 .try_into()163 .map_err(|_| <CommonError<T>>::CollectionTokenPrefixLimitExceeded)?,164 permissions: Some(CollectionPermissions {165 nesting: Some(NestingRule::Owner),166 ..Default::default()167 }),168 ..Default::default()169 };170171 let unique_collection_id = Self::init_collection(172 T::CrossAccountId::from_sub(sender.clone()),173 data,174 [175 Self::rmrk_property(Metadata, &metadata)?,176 Self::rmrk_property(CollectionType, &misc::CollectionType::Regular)?,177 ]178 .into_iter(),179 )?;180 let rmrk_collection_id = <CollectionIndex<T>>::get();181182 <UniqueCollectionId<T>>::insert(rmrk_collection_id, unique_collection_id);183 <RmrkInernalCollectionId<T>>::insert(unique_collection_id, rmrk_collection_id);184185 <CollectionIndex<T>>::mutate(|n| *n += 1);186187 Self::deposit_event(Event::CollectionCreated {188 issuer: sender,189 collection_id: rmrk_collection_id,190 });191192 Ok(())193 }194195 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]196 #[transactional]197 pub fn destroy_collection(198 origin: OriginFor<T>,199 collection_id: RmrkCollectionId,200 ) -> DispatchResult {201 let sender = ensure_signed(origin)?;202 let cross_sender = T::CrossAccountId::from_sub(sender.clone());203204 let collection = Self::get_typed_nft_collection(205 Self::unique_collection_id(collection_id)?,206 misc::CollectionType::Regular,207 )?;208209 <PalletNft<T>>::destroy_collection(collection, &cross_sender)210 .map_err(Self::map_unique_err_to_proxy)?;211212 Self::deposit_event(Event::CollectionDestroyed {213 issuer: sender,214 collection_id,215 });216217 Ok(())218 }219220 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]221 #[transactional]222 pub fn change_collection_issuer(223 origin: OriginFor<T>,224 collection_id: RmrkCollectionId,225 new_issuer: <T::Lookup as StaticLookup>::Source,226 ) -> DispatchResult {227 let sender = ensure_signed(origin)?;228229 let new_issuer = T::Lookup::lookup(new_issuer)?;230231 Self::change_collection_owner(232 Self::unique_collection_id(collection_id)?,233 misc::CollectionType::Regular,234 sender.clone(),235 new_issuer.clone(),236 )?;237238 Self::deposit_event(Event::IssuerChanged {239 old_issuer: sender,240 new_issuer,241 collection_id,242 });243244 Ok(())245 }246247 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]248 #[transactional]249 pub fn lock_collection(250 origin: OriginFor<T>,251 collection_id: RmrkCollectionId,252 ) -> DispatchResult {253 let sender = ensure_signed(origin)?;254 let cross_sender = T::CrossAccountId::from_sub(sender.clone());255256 let collection = Self::get_typed_nft_collection(257 Self::unique_collection_id(collection_id)?,258 misc::CollectionType::Regular,259 )?;260261 Self::check_collection_owner(&collection, &cross_sender)?;262263 let token_count = collection.total_supply();264265 let mut collection = collection.into_inner();266 collection.limits.token_limit = Some(token_count);267 collection.save()?;268269 Self::deposit_event(Event::CollectionLocked {270 issuer: sender,271 collection_id,272 });273274 Ok(())275 }276277 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]278 #[transactional]279 pub fn mint_nft(280 origin: OriginFor<T>,281 owner: T::AccountId,282 collection_id: RmrkCollectionId,283 recipient: Option<T::AccountId>,284 royalty_amount: Option<Permill>,285 metadata: RmrkString,286 transferable: bool,287 ) -> DispatchResult {288 let sender = ensure_signed(origin)?;289 let sender = T::CrossAccountId::from_sub(sender);290 let cross_owner = T::CrossAccountId::from_sub(owner.clone());291292 let royalty_info = royalty_amount.map(|amount| rmrk_traits::RoyaltyInfo {293 recipient: recipient.unwrap_or_else(|| owner.clone()),294 amount,295 });296297 let collection = Self::get_typed_nft_collection(298 Self::unique_collection_id(collection_id)?,299 misc::CollectionType::Regular,300 )?;301302 let nft_id = Self::create_nft(303 &sender,304 &cross_owner,305 &collection,306 [307 Self::rmrk_property(TokenType, &NftType::Regular)?,308 Self::rmrk_property(Transferable, &transferable)?,309 Self::rmrk_property(PendingNftAccept, &false)?,310 Self::rmrk_property(RoyaltyInfo, &royalty_info)?,311 Self::rmrk_property(Metadata, &metadata)?,312 Self::rmrk_property(Equipped, &false)?,313 Self::rmrk_property(314 ResourceCollection,315 &Self::init_collection(316 sender.clone(),317 CreateCollectionData {318 ..Default::default()319 },320 [Self::rmrk_property(321 CollectionType,322 &misc::CollectionType::Resource,323 )?]324 .into_iter(),325 )?,326 )?, // todo possibly add limits to the collection if rmrk warrants them327 Self::rmrk_property(ResourcePriorities, &<Vec<u8>>::new())?,328 ]329 .into_iter(),330 )331 .map_err(|err| match err {332 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),333 err => Self::map_unique_err_to_proxy(err),334 })?;335336 Self::deposit_event(Event::NftMinted {337 owner,338 collection_id,339 nft_id: nft_id.0,340 });341342 Ok(())343 }344345 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]346 #[transactional]347 pub fn burn_nft(348 origin: OriginFor<T>,349 collection_id: RmrkCollectionId,350 nft_id: RmrkNftId,351 ) -> DispatchResult {352 let sender = ensure_signed(origin)?;353 let cross_sender = T::CrossAccountId::from_sub(sender.clone());354355 Self::destroy_nft(356 cross_sender,357 Self::unique_collection_id(collection_id)?,358 nft_id.into(),359 )360 .map_err(Self::map_unique_err_to_proxy)?;361362 Self::deposit_event(Event::NFTBurned {363 owner: sender,364 nft_id,365 });366367 Ok(())368 }369370 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]371 #[transactional]372 pub fn send(373 origin: OriginFor<T>,374 rmrk_collection_id: RmrkCollectionId,375 rmrk_nft_id: RmrkNftId,376 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,377 ) -> DispatchResult {378 let sender = ensure_signed(origin.clone())?;379 let cross_sender = T::CrossAccountId::from_sub(sender);380381 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;382 let nft_id = rmrk_nft_id.into();383384 let token_data =385 <TokenData<T>>::get((collection_id, nft_id)).ok_or(<Error<T>>::NoAvailableNftId)?;386387 let from = token_data.owner;388389 let collection =390 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;391392 ensure!(393 Self::get_nft_property_decoded(collection_id, nft_id, RmrkProperty::Transferable)?,394 <Error<T>>::NonTransferable395 );396397 ensure!(398 !Self::get_nft_property_decoded(399 collection_id,400 nft_id,401 RmrkProperty::PendingNftAccept402 )?,403 <Error<T>>::NoPermission404 );405406 let target_owner;407408 match new_owner {409 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {410 target_owner = T::CrossAccountId::from_sub(account_id);411 }412 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(413 target_collection_id,414 target_nft_id,415 ) => {416 let target_collection_id = Self::unique_collection_id(target_collection_id)?;417418 let target_nft_budget = budget::Value::new(NESTING_BUDGET);419420 let target_nft_owner = <PalletStructure<T>>::get_checked_indirect_owner(421 target_collection_id,422 target_nft_id.into(),423 Some((collection_id, nft_id)),424 &target_nft_budget,425 )426 .map_err(Self::map_unique_err_to_proxy)?;427428 let is_approval_required = cross_sender != target_nft_owner;429430 if is_approval_required {431 target_owner = target_nft_owner;432433 <PalletNft<T>>::set_scoped_token_property(434 collection.id,435 nft_id,436 PropertyScope::Rmrk,437 Self::rmrk_property(PendingNftAccept, &is_approval_required)?,438 )?;439 } else {440 target_owner = T::CrossTokenAddressMapping::token_to_address(441 target_collection_id,442 target_nft_id.into(),443 );444 }445 }446 }447448 let src_nft_budget = budget::Value::new(NESTING_BUDGET);449450 <PalletNft<T>>::transfer_from(451 &collection,452 &cross_sender,453 &from,454 &target_owner,455 nft_id,456 &src_nft_budget,457 )458 .map_err(Self::map_unique_err_to_proxy)?;459460 Ok(())461 }462463 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]464 #[transactional]465 pub fn accept_nft(466 origin: OriginFor<T>,467 rmrk_collection_id: RmrkCollectionId,468 rmrk_nft_id: RmrkNftId,469 new_owner: RmrkAccountIdOrCollectionNftTuple<T::AccountId>,470 ) -> DispatchResult {471 let sender = ensure_signed(origin.clone())?;472 let cross_sender = T::CrossAccountId::from_sub(sender);473474 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;475 let nft_id = rmrk_nft_id.into();476477 let collection =478 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;479480 let new_owner = match new_owner {481 RmrkAccountIdOrCollectionNftTuple::AccountId(account_id) => {482 T::CrossAccountId::from_sub(account_id)483 }484 RmrkAccountIdOrCollectionNftTuple::CollectionAndNftTuple(485 target_collection_id,486 target_nft_id,487 ) => {488 let target_collection_id = Self::unique_collection_id(target_collection_id)?;489490 T::CrossTokenAddressMapping::token_to_address(491 target_collection_id,492 TokenId(target_nft_id),493 )494 }495 };496497 let budget = budget::Value::new(NESTING_BUDGET);498499 <PalletNft<T>>::transfer(&collection, &cross_sender, &new_owner, nft_id, &budget)500 .map_err(|err| {501 if err == <CommonError<T>>::OnlyOwnerAllowedToNest.into() {502 <Error<T>>::CannotAcceptNonOwnedNft.into()503 } else {504 Self::map_unique_err_to_proxy(err)505 }506 })?;507508 <PalletNft<T>>::set_scoped_token_property(509 collection.id,510 nft_id,511 PropertyScope::Rmrk,512 Self::rmrk_property(PendingNftAccept, &false)?,513 )?;514515 Ok(())516 }517518 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]519 #[transactional]520 pub fn reject_nft(521 origin: OriginFor<T>,522 collection_id: RmrkCollectionId,523 nft_id: RmrkNftId,524 ) -> DispatchResult {525 let sender = ensure_signed(origin)?;526 let cross_sender = T::CrossAccountId::from_sub(sender);527528 let collection_id = Self::unique_collection_id(collection_id)?;529 let nft_id = nft_id.into();530531 Self::destroy_nft(cross_sender, collection_id, nft_id).map_err(|err| {532 if err == <CommonError<T>>::NoPermission.into()533 || err == <CommonError<T>>::ApprovedValueTooLow.into()534 {535 <Error<T>>::CannotRejectNonOwnedNft.into()536 } else {537 Self::map_unique_err_to_proxy(err)538 }539 })?;540541 Ok(())542 }543544 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]545 #[transactional]546 pub fn accept_resource(547 origin: OriginFor<T>,548 rmrk_collection_id: RmrkCollectionId,549 rmrk_nft_id: RmrkNftId,550 rmrk_resource_id: RmrkResourceId,551 ) -> DispatchResult {552 let sender = ensure_signed(origin)?;553 let cross_sender = T::CrossAccountId::from_sub(sender);554555 let collection_id = Self::unique_collection_id(rmrk_collection_id)556 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;557558 let nft_id = rmrk_nft_id.into();559 let resource_id = rmrk_resource_id.into();560561 let budget = budget::Value::new(NESTING_BUDGET);562563 let nft_owner =564 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)565 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;566567 let resource_collection_id: CollectionId =568 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)569 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;570571 let is_pending: bool = Self::get_nft_property_decoded(572 resource_collection_id,573 resource_id,574 PendingResourceAccept,575 )576 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;577578 ensure!(is_pending, <Error<T>>::ResourceNotPending);579580 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);581582 <PalletNft<T>>::set_scoped_token_property(583 resource_collection_id,584 rmrk_resource_id.into(),585 PropertyScope::Rmrk,586 Self::rmrk_property(PendingResourceAccept, &false)?,587 )?;588589 Ok(())590 }591592 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]593 #[transactional]594 pub fn accept_resource_removal(595 origin: OriginFor<T>,596 rmrk_collection_id: RmrkCollectionId,597 rmrk_nft_id: RmrkNftId,598 rmrk_resource_id: RmrkResourceId,599 ) -> DispatchResult {600 let sender = ensure_signed(origin)?;601 let cross_sender = T::CrossAccountId::from_sub(sender);602603 let collection_id = Self::unique_collection_id(rmrk_collection_id)604 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;605606 let nft_id = rmrk_nft_id.into();607 let resource_id = rmrk_resource_id.into();608609 let budget = budget::Value::new(NESTING_BUDGET);610611 let nft_owner =612 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)613 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;614615 ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);616617 let resource_collection_id: CollectionId =618 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)619 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;620621 let is_pending: bool = Self::get_nft_property_decoded(622 resource_collection_id,623 resource_id,624 PendingResourceRemoval,625 )626 .map_err(|_| <Error<T>>::ResourceDoesntExist)?;627628 ensure!(is_pending, <Error<T>>::ResourceNotPending);629630 let resource_collection = Self::get_typed_nft_collection(631 resource_collection_id,632 misc::CollectionType::Resource,633 )?;634635 <PalletNft<T>>::burn(&resource_collection, &cross_sender, rmrk_resource_id.into())636 .map_err(Self::map_unique_err_to_proxy)?;637638 Ok(())639 }640641 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]642 #[transactional]643 pub fn set_property(644 origin: OriginFor<T>,645 #[pallet::compact] rmrk_collection_id: RmrkCollectionId,646 maybe_nft_id: Option<RmrkNftId>,647 key: RmrkKeyString,648 value: RmrkValueString,649 ) -> DispatchResult {650 let sender = ensure_signed(origin)?;651 let sender = T::CrossAccountId::from_sub(sender);652653 let collection_id = Self::unique_collection_id(rmrk_collection_id)?;654 let budget = budget::Value::new(NESTING_BUDGET);655656 match maybe_nft_id {657 Some(nft_id) => {658 let token_id: TokenId = nft_id.into();659660 Self::ensure_nft_owner(collection_id, token_id, &sender, &budget)?;661 Self::ensure_nft_type(collection_id, token_id, NftType::Regular)?;662663 <PalletNft<T>>::set_scoped_token_property(664 collection_id,665 token_id,666 PropertyScope::Rmrk,667 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,668 )?;669 }670 None => {671 let collection = Self::get_typed_nft_collection(672 collection_id,673 misc::CollectionType::Regular,674 )?;675676 Self::check_collection_owner(&collection, &sender)?;677678 <PalletCommon<T>>::set_scoped_collection_property(679 collection_id,680 PropertyScope::Rmrk,681 Self::rmrk_property(UserProperty(key.as_slice()), &value)?,682 )?;683 }684 }685686 Self::deposit_event(Event::PropertySet {687 collection_id: rmrk_collection_id,688 maybe_nft_id,689 key,690 value,691 });692693 Ok(())694 }695696 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]697 #[transactional]698 pub fn add_basic_resource(699 origin: OriginFor<T>,700 collection_id: RmrkCollectionId,701 nft_id: RmrkNftId,702 resource: RmrkBasicResource,703 ) -> DispatchResult {704 let sender = ensure_signed(origin.clone())?;705706 let resource_id = Self::resource_add(707 sender,708 Self::unique_collection_id(collection_id)?,709 nft_id.into(),710 [711 Self::rmrk_property(TokenType, &NftType::Resource)?,712 Self::rmrk_property(ResourceType, &misc::ResourceType::Basic)?,713 Self::rmrk_property(Src, &resource.src)?,714 Self::rmrk_property(Metadata, &resource.metadata)?,715 Self::rmrk_property(License, &resource.license)?,716 Self::rmrk_property(Thumb, &resource.thumb)?,717 ]718 .into_iter(),719 )?;720721 Self::deposit_event(Event::ResourceAdded {722 nft_id,723 resource_id,724 });725 Ok(())726 }727728 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]729 #[transactional]730 pub fn add_composable_resource(731 origin: OriginFor<T>,732 collection_id: RmrkCollectionId,733 nft_id: RmrkNftId,734 _resource_id: RmrkBoundedResource,735 resource: RmrkComposableResource,736 ) -> DispatchResult {737 let sender = ensure_signed(origin.clone())?;738739 let resource_id = Self::resource_add(740 sender,741 Self::unique_collection_id(collection_id)?,742 nft_id.into(),743 [744 Self::rmrk_property(TokenType, &NftType::Resource)?,745 Self::rmrk_property(ResourceType, &misc::ResourceType::Composable)?,746 Self::rmrk_property(Parts, &resource.parts)?,747 Self::rmrk_property(Base, &resource.base)?,748 Self::rmrk_property(Src, &resource.src)?,749 Self::rmrk_property(Metadata, &resource.metadata)?,750 Self::rmrk_property(License, &resource.license)?,751 Self::rmrk_property(Thumb, &resource.thumb)?,752 ]753 .into_iter(),754 )?;755756 Self::deposit_event(Event::ResourceAdded {757 nft_id,758 resource_id,759 });760 Ok(())761 }762763 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]764 #[transactional]765 pub fn add_slot_resource(766 origin: OriginFor<T>,767 collection_id: RmrkCollectionId,768 nft_id: RmrkNftId,769 resource: RmrkSlotResource,770 ) -> DispatchResult {771 let sender = ensure_signed(origin.clone())?;772773 let resource_id = Self::resource_add(774 sender,775 Self::unique_collection_id(collection_id)?,776 nft_id.into(),777 [778 Self::rmrk_property(TokenType, &NftType::Resource)?,779 Self::rmrk_property(ResourceType, &misc::ResourceType::Slot)?,780 Self::rmrk_property(Base, &resource.base)?,781 Self::rmrk_property(Src, &resource.src)?,782 Self::rmrk_property(Metadata, &resource.metadata)?,783 Self::rmrk_property(Slot, &resource.slot)?,784 Self::rmrk_property(License, &resource.license)?,785 Self::rmrk_property(Thumb, &resource.thumb)?,786 ]787 .into_iter(),788 )?;789790 Self::deposit_event(Event::ResourceAdded {791 nft_id,792 resource_id,793 });794 Ok(())795 }796797 #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]798 #[transactional]799 pub fn remove_resource(800 origin: OriginFor<T>,801 collection_id: RmrkCollectionId,802 nft_id: RmrkNftId,803 resource_id: RmrkResourceId,804 ) -> DispatchResult {805 let sender = ensure_signed(origin.clone())?;806807 Self::resource_remove(808 sender,809 Self::unique_collection_id(collection_id)?,810 nft_id.into(),811 resource_id.into(),812 )?;813814 Self::deposit_event(Event::ResourceRemoval {815 nft_id,816 resource_id,817 });818 Ok(())819 }820 }821}822823impl<T: Config> Pallet<T> {824 pub fn rmrk_property_key(rmrk_key: RmrkProperty) -> Result<PropertyKey, DispatchError> {825 let key = rmrk_key.to_key::<T>()?;826827 let scoped_key = PropertyScope::Rmrk828 .apply(key)829 .map_err(|_| <Error<T>>::RmrkPropertyKeyIsTooLong)?;830831 Ok(scoped_key)832 }833834 // todo think about renaming these835 pub fn rmrk_property<E: Encode>(836 rmrk_key: RmrkProperty,837 value: &E,838 ) -> Result<Property, DispatchError> {839 let key = rmrk_key.to_key::<T>()?;840841 let value = value842 .encode()843 .try_into()844 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong)?;845846 let property = Property { key, value };847848 Ok(property)849 }850851 pub fn decode_property<D: Decode>(vec: PropertyValue) -> Result<D, DispatchError> {852 vec.decode()853 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())854 }855856 pub fn rebind<L, S>(vec: &BoundedVec<u8, L>) -> Result<BoundedVec<u8, S>, DispatchError>857 where858 BoundedVec<u8, S>: TryFrom<Vec<u8>>,859 {860 vec.rebind()861 .map_err(|_| <Error<T>>::RmrkPropertyValueIsTooLong.into())862 }863864 fn init_collection(865 sender: T::CrossAccountId,866 data: CreateCollectionData<T::AccountId>,867 properties: impl Iterator<Item = Property>,868 ) -> Result<CollectionId, DispatchError> {869 let collection_id = <PalletNft<T>>::init_collection(sender, data);870871 if let Err(DispatchError::Arithmetic(_)) = &collection_id {872 return Err(<Error<T>>::NoAvailableCollectionId.into());873 }874875 <PalletCommon<T>>::set_scoped_collection_properties(876 collection_id?,877 PropertyScope::Rmrk,878 properties,879 )?;880881 collection_id882 }883884 pub fn create_nft(885 sender: &T::CrossAccountId,886 owner: &T::CrossAccountId,887 collection: &NonfungibleHandle<T>,888 properties: impl Iterator<Item = Property>,889 ) -> Result<TokenId, DispatchError> {890 let data = CreateNftExData {891 properties: BoundedVec::default(),892 owner: owner.clone(),893 };894895 let budget = budget::Value::new(NESTING_BUDGET);896897 <PalletNft<T>>::create_item(collection, sender, data, &budget)?;898899 let nft_id = <PalletNft<T>>::current_token_id(collection.id);900901 <PalletNft<T>>::set_scoped_token_properties(902 collection.id,903 nft_id,904 PropertyScope::Rmrk,905 properties,906 )?;907908 Ok(nft_id)909 }910911 fn destroy_nft(912 sender: T::CrossAccountId,913 collection_id: CollectionId,914 token_id: TokenId,915 ) -> DispatchResult {916 let collection =917 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;918919 let token_data =920 <TokenData<T>>::get((collection_id, token_id)).ok_or(<Error<T>>::NoAvailableNftId)?;921922 let from = token_data.owner;923924 let budget = budget::Value::new(NESTING_BUDGET);925926 <PalletNft<T>>::burn_from(&collection, &sender, &from, token_id, &budget)927 }928929 fn resource_add(930 sender: T::AccountId,931 collection_id: CollectionId,932 token_id: TokenId,933 resource_properties: impl Iterator<Item = Property>,934 ) -> Result<RmrkResourceId, DispatchError> {935 let collection =936 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;937 ensure!(collection.owner == sender, Error::<T>::NoPermission);938939 let sender = T::CrossAccountId::from_sub(sender);940 let budget = budget::Value::new(NESTING_BUDGET);941942 let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, token_id, &budget)943 .map_err(Self::map_unique_err_to_proxy)?;944945 let pending = sender != nft_owner;946947 let resource_collection_id: CollectionId =948 Self::get_nft_property_decoded(collection_id, token_id, ResourceCollection)?;949 let resource_collection =950 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;951952 // todo probably add extra connections to bases, slots, etc., when RMRK starts to use them953954 let resource_id = Self::create_nft(955 &sender,956 &nft_owner,957 &resource_collection,958 resource_properties.chain(959 [960 Self::rmrk_property(PendingResourceAccept, &pending)?,961 Self::rmrk_property(PendingResourceRemoval, &false)?,962 ]963 .into_iter(),964 ),965 )966 .map_err(|err| match err {967 DispatchError::Arithmetic(_) => <Error<T>>::NoAvailableNftId.into(),968 err => Self::map_unique_err_to_proxy(err),969 })?;970971 Ok(resource_id.0)972 }973974 fn resource_remove(975 sender: T::AccountId,976 collection_id: CollectionId,977 nft_id: TokenId,978 resource_id: TokenId,979 ) -> DispatchResult {980 let collection =981 Self::get_typed_nft_collection(collection_id, misc::CollectionType::Regular)?;982 ensure!(collection.owner == sender, Error::<T>::NoPermission);983984 let resource_collection_id: CollectionId =985 Self::get_nft_property_decoded(collection_id, nft_id, ResourceCollection)?;986 let resource_collection =987 Self::get_typed_nft_collection(resource_collection_id, misc::CollectionType::Resource)?;988 ensure!(989 <PalletNft<T>>::token_exists(&resource_collection, resource_id),990 Error::<T>::ResourceDoesntExist991 );992993 let budget = up_data_structs::budget::Value::new(10);994 let topmost_owner =995 <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;996997 let sender = T::CrossAccountId::from_sub(sender);998 if topmost_owner == sender {999 <PalletNft<T>>::burn(&resource_collection, &sender, resource_id)1000 .map_err(Self::map_unique_err_to_proxy)?;1001 } else {1002 <PalletNft<T>>::set_scoped_token_property(1003 resource_collection_id,1004 resource_id,1005 PropertyScope::Rmrk,1006 Self::rmrk_property(PendingResourceRemoval, &true)?,1007 )?;1008 }10091010 Ok(())1011 }10121013 fn change_collection_owner(1014 collection_id: CollectionId,1015 collection_type: misc::CollectionType,1016 sender: T::AccountId,1017 new_owner: T::AccountId,1018 ) -> DispatchResult {1019 let collection = Self::get_typed_nft_collection(collection_id, collection_type)?;1020 Self::check_collection_owner(&collection, &T::CrossAccountId::from_sub(sender))?;10211022 let mut collection = collection.into_inner();10231024 collection.owner = new_owner;1025 collection.save()1026 }10271028 fn check_collection_owner(1029 collection: &NonfungibleHandle<T>,1030 account: &T::CrossAccountId,1031 ) -> DispatchResult {1032 collection1033 .check_is_owner(account)1034 .map_err(Self::map_unique_err_to_proxy)1035 }10361037 pub fn last_collection_idx() -> RmrkCollectionId {1038 <CollectionIndex<T>>::get()1039 }10401041 pub fn unique_collection_id(1042 rmrk_collection_id: RmrkCollectionId,1043 ) -> Result<CollectionId, DispatchError> {1044 <UniqueCollectionId<T>>::try_get(rmrk_collection_id)1045 .map_err(|_| <Error<T>>::CollectionUnknown.into())1046 }10471048 pub fn rmrk_collection_id(1049 unique_collection_id: CollectionId,1050 ) -> Result<RmrkCollectionId, DispatchError> {1051 <RmrkInernalCollectionId<T>>::try_get(unique_collection_id)1052 .map_err(|_| <Error<T>>::CollectionUnknown.into())1053 }10541055 pub fn get_nft_collection(1056 collection_id: CollectionId,1057 ) -> Result<NonfungibleHandle<T>, DispatchError> {1058 let collection = <CollectionHandle<T>>::try_get(collection_id)1059 .map_err(|_| <Error<T>>::CollectionUnknown)?;10601061 match collection.mode {1062 CollectionMode::NFT => Ok(NonfungibleHandle::cast(collection)),1063 _ => Err(<Error<T>>::CollectionUnknown.into()),1064 }1065 }10661067 pub fn collection_exists(collection_id: CollectionId) -> bool {1068 <CollectionHandle<T>>::try_get(collection_id).is_ok()1069 }10701071 pub fn get_collection_property(1072 collection_id: CollectionId,1073 key: RmrkProperty,1074 ) -> Result<PropertyValue, DispatchError> {1075 let collection_property = <PalletCommon<T>>::collection_properties(collection_id)1076 .get(&Self::rmrk_property_key(key)?)1077 .ok_or(<Error<T>>::CollectionUnknown)?1078 .clone();10791080 Ok(collection_property)1081 }10821083 pub fn get_collection_property_decoded<V: Decode>(1084 collection_id: CollectionId,1085 key: RmrkProperty,1086 ) -> Result<V, DispatchError> {1087 Self::decode_property(Self::get_collection_property(collection_id, key)?)1088 }10891090 pub fn get_collection_type(1091 collection_id: CollectionId,1092 ) -> Result<misc::CollectionType, DispatchError> {1093 Self::get_collection_property_decoded(collection_id, CollectionType)1094 .map_err(|_| <Error<T>>::CorruptedCollectionType.into())1095 }10961097 pub fn ensure_collection_type(1098 collection_id: CollectionId,1099 collection_type: misc::CollectionType,1100 ) -> DispatchResult {1101 let actual_type = Self::get_collection_type(collection_id)?;1102 ensure!(1103 actual_type == collection_type,1104 <CommonError<T>>::NoPermission1105 );11061107 Ok(())1108 }11091110 pub fn get_typed_nft_collection(1111 collection_id: CollectionId,1112 collection_type: misc::CollectionType,1113 ) -> Result<NonfungibleHandle<T>, DispatchError> {1114 Self::ensure_collection_type(collection_id, collection_type)?;11151116 Self::get_nft_collection(collection_id)1117 }11181119 pub fn get_typed_nft_collection_mapped(1120 rmrk_collection_id: RmrkCollectionId,1121 collection_type: misc::CollectionType,1122 ) -> Result<(NonfungibleHandle<T>, CollectionId), DispatchError> {1123 let unique_collection_id = Self::unique_collection_id(rmrk_collection_id)?;11241125 let collection = Self::get_typed_nft_collection(unique_collection_id, collection_type)?;11261127 Ok((collection, unique_collection_id))1128 }11291130 pub fn get_nft_property(1131 collection_id: CollectionId,1132 nft_id: TokenId,1133 key: RmrkProperty,1134 ) -> Result<PropertyValue, DispatchError> {1135 let nft_property = <PalletNft<T>>::token_properties((collection_id, nft_id))1136 .get(&Self::rmrk_property_key(key)?)1137 .ok_or(<Error<T>>::NoAvailableNftId)? // todo replace with better error?1138 .clone();11391140 Ok(nft_property)1141 }11421143 pub fn get_nft_property_decoded<V: Decode>(1144 collection_id: CollectionId,1145 nft_id: TokenId,1146 key: RmrkProperty,1147 ) -> Result<V, DispatchError> {1148 Self::decode_property(Self::get_nft_property(collection_id, nft_id, key)?)1149 }11501151 pub fn nft_exists(collection_id: CollectionId, nft_id: TokenId) -> bool {1152 <TokenData<T>>::contains_key((collection_id, nft_id))1153 }11541155 pub fn get_nft_type(1156 collection_id: CollectionId,1157 token_id: TokenId,1158 ) -> Result<NftType, DispatchError> {1159 Self::get_nft_property_decoded(collection_id, token_id, TokenType)1160 .map_err(|_| <Error<T>>::NftTypeEncodeError.into())1161 }11621163 pub fn ensure_nft_type(1164 collection_id: CollectionId,1165 token_id: TokenId,1166 nft_type: NftType,1167 ) -> DispatchResult {1168 let actual_type = Self::get_nft_type(collection_id, token_id)?;1169 ensure!(actual_type == nft_type, <Error<T>>::NoPermission);11701171 Ok(())1172 }11731174 pub fn ensure_nft_owner(1175 collection_id: CollectionId,1176 token_id: TokenId,1177 possible_owner: &T::CrossAccountId,1178 nesting_budget: &dyn budget::Budget,1179 ) -> DispatchResult {1180 let is_owned = <PalletStructure<T>>::check_indirectly_owned(1181 possible_owner.clone(),1182 collection_id,1183 token_id,1184 None,1185 nesting_budget,1186 )?;11871188 ensure!(is_owned, <Error<T>>::NoPermission);11891190 Ok(())1191 }11921193 pub fn filter_user_properties<Key, Value, R, Mapper>(1194 collection_id: CollectionId,1195 token_id: Option<TokenId>,1196 filter_keys: Option<Vec<RmrkPropertyKey>>,1197 mapper: Mapper,1198 ) -> Result<Vec<R>, DispatchError>1199 where1200 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1201 Value: Decode + Default,1202 Mapper: Fn(Key, Value) -> R,1203 {1204 filter_keys1205 .map(|keys| {1206 let properties = keys1207 .into_iter()1208 .filter_map(|key| {1209 let key: Key = key.try_into().ok()?;12101211 let value = match token_id {1212 Some(token_id) => Self::get_nft_property_decoded(1213 collection_id,1214 token_id,1215 UserProperty(key.as_ref()),1216 ),1217 None => Self::get_collection_property_decoded(1218 collection_id,1219 UserProperty(key.as_ref()),1220 ),1221 }1222 .ok()?;12231224 Some(mapper(key, value))1225 })1226 .collect();12271228 Ok(properties)1229 })1230 .unwrap_or_else(|| {1231 let properties =1232 Self::iterate_user_properties(collection_id, token_id, mapper)?.collect();12331234 Ok(properties)1235 })1236 }12371238 pub fn iterate_user_properties<Key, Value, R, Mapper>(1239 collection_id: CollectionId,1240 token_id: Option<TokenId>,1241 mapper: Mapper,1242 ) -> Result<impl Iterator<Item = R>, DispatchError>1243 where1244 Key: TryFrom<RmrkPropertyKey> + AsRef<[u8]>,1245 Value: Decode + Default,1246 Mapper: Fn(Key, Value) -> R,1247 {1248 let key_prefix = Self::rmrk_property_key(UserProperty(b""))?;12491250 let properties = match token_id {1251 Some(token_id) => <PalletNft<T>>::token_properties((collection_id, token_id)),1252 None => <PalletCommon<T>>::collection_properties(collection_id),1253 };12541255 let properties = properties.into_iter().filter_map(move |(key, value)| {1256 let key = key.as_slice().strip_prefix(key_prefix.as_slice())?;12571258 let key: Key = key.to_vec().try_into().ok()?;1259 let value: Value = value.decode().ok()?;12601261 Some(mapper(key, value))1262 });12631264 Ok(properties)1265 }12661267 fn map_unique_err_to_proxy(err: DispatchError) -> DispatchError {1268 map_unique_err_to_proxy! {1269 match err {1270 CommonError::NoPermission => NoPermission,1271 CommonError::CollectionTokenLimitExceeded => CollectionFullOrLocked,1272 CommonError::PublicMintingNotAllowed => NoPermission,1273 CommonError::TokenNotFound => NoAvailableNftId,1274 CommonError::ApprovedValueTooLow => NoPermission,1275 CommonError::CantDestroyNotEmptyCollection => CollectionNotEmpty,1276 StructureError::TokenNotFound => NoAvailableNftId,1277 StructureError::OuroborosDetected => CannotSendToDescendentOrSelf,1278 }1279 }1280 }1281}pallets/proxy-rmrk-equip/Cargo.tomldiffbeforeafterboth--- a/pallets/proxy-rmrk-equip/Cargo.toml
+++ b/pallets/proxy-rmrk-equip/Cargo.toml
@@ -21,6 +21,7 @@
up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.22" }
frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.22" }
+rmrk-traits = { default-features = false, path = "../../primitives/rmrk-traits" }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
pallet-rmrk-core = { default-features = false, path = "../proxy-rmrk-core" }
@@ -32,6 +33,7 @@
"sp-runtime/std",
"sp-std/std",
"up-data-structs/std",
+ "rmrk-traits/std",
"pallet-common/std",
"pallet-nonfungible/std",
"pallet-rmrk-core/std",
primitives/data-structs/Cargo.tomldiffbeforeafterboth--- a/primitives/data-structs/Cargo.toml
+++ b/primitives/data-structs/Cargo.toml
@@ -26,6 +26,7 @@
derivative = { version = "2.2.0", features = ["use_core"] }
struct-versioning = { path = "../../crates/struct-versioning" }
pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.22" }
+rmrk-traits = { default-features = false, path = "../rmrk-traits" }
[features]
default = ["std"]
@@ -39,7 +40,8 @@
"sp-core/std",
"sp-std/std",
"pallet-evm/std",
+ "rmrk-traits/std",
]
serde1 = ["serde/alloc"]
limit-testing = []
-runtime-benchmarks = []
\ No newline at end of file
+runtime-benchmarks = []
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -36,14 +36,12 @@
use derivative::Derivative;
use scale_info::TypeInfo;
-pub mod rmrk;
-
// RMRK
-use rmrk::{
+use rmrk_traits::{
CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme, ThemeProperty,
ResourceTypes, BasicResource, ComposableResource, SlotResource,
};
-pub use rmrk::{
+pub use rmrk_traits::{
primitives::{
CollectionId as RmrkCollectionId, NftId as RmrkNftId, BaseId as RmrkBaseId,
PartId as RmrkPartId, ResourceId as RmrkResourceId,
primitives/data-structs/src/rmrk.rsdiffbeforeafterboth--- a/primitives/data-structs/src/rmrk.rs
+++ /dev/null
@@ -1,435 +0,0 @@
-use codec::{Decode, Encode, MaxEncodedLen};
-use scale_info::TypeInfo;
-
-#[cfg(feature = "std")]
-use serde::Serialize;
-
-use primitives::*;
-
-pub mod primitives {
- pub type CollectionId = u32;
- pub type ResourceId = u32;
- pub type NftId = u32;
- pub type BaseId = u32;
- pub type SlotId = u32;
- pub type PartId = u32;
- pub type ZIndex = u32;
-}
-
-#[cfg(feature = "std")]
-mod serialize {
- use core::convert::AsRef;
- use serde::ser::{self, Serialize};
-
- pub mod vec {
- use super::*;
-
- pub fn serialize<D, V, C>(value: &C, serializer: D) -> Result<D::Ok, D::Error>
- where
- D: ser::Serializer,
- V: Serialize,
- C: AsRef<[V]>,
- {
- value.as_ref().serialize(serializer)
- }
- }
-
- pub mod opt_vec {
- use super::*;
-
- pub fn serialize<D, V, C>(value: &Option<C>, serializer: D) -> Result<D::Ok, D::Error>
- where
- D: ser::Serializer,
- V: Serialize,
- C: AsRef<[V]>,
- {
- match value {
- Some(value) => super::vec::serialize(value, serializer),
- None => serializer.serialize_none(),
- }
- }
- }
-}
-
-/// Collection info.
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- AccountId: Serialize,
- BoundedString: AsRef<[u8]>,
- BoundedSymbol: AsRef<[u8]>
- "#)
-)]
-pub struct CollectionInfo<BoundedString, BoundedSymbol, AccountId> {
- /// Current bidder and bid price.
- pub issuer: AccountId,
-
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub metadata: BoundedString,
- pub max: Option<u32>,
-
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub symbol: BoundedSymbol,
- pub nfts_count: u32,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-pub enum AccountIdOrCollectionNftTuple<AccountId> {
- AccountId(AccountId),
- CollectionAndNftTuple(CollectionId, NftId),
-}
-
-/// Royalty information (recipient and amount)
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
-pub struct RoyaltyInfo<AccountId, RoyaltyAmount> {
- /// Recipient (AccountId) of the royalty
- pub recipient: AccountId,
- /// Amount (Permill) of the royalty
- pub amount: RoyaltyAmount,
-}
-
-/// Nft info.
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- AccountId: Serialize,
- RoyaltyAmount: Serialize,
- BoundedString: AsRef<[u8]>
- "#)
-)]
-pub struct NftInfo<AccountId, RoyaltyAmount, BoundedString> {
- /// The owner of the NFT, can be either an Account or a tuple (CollectionId, NftId)
- pub owner: AccountIdOrCollectionNftTuple<AccountId>,
- /// Royalty (optional)
- pub royalty: Option<RoyaltyInfo<AccountId, RoyaltyAmount>>,
-
- /// Arbitrary data about an instance, e.g. IPFS hash
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub metadata: BoundedString,
-
- /// Equipped state
- pub equipped: bool,
- /// Pending state (if sent to NFT)
- pub pending: bool,
-}
-
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
-pub struct NftChild {
- pub collection_id: CollectionId,
- pub nft_id: NftId,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, PartialEq, TypeInfo)]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- BoundedKey: AsRef<[u8]>,
- BoundedValue: AsRef<[u8]>
- "#)
-)]
-pub struct PropertyInfo<BoundedKey, BoundedValue> {
- /// Key of the property
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub key: BoundedKey,
-
- /// Value of the property
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub value: BoundedValue,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
-pub struct BasicResource<BoundedString> {
- /// If the resource is Media, the base property is absent. Media src should be a URI like an
- /// IPFS hash.
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub src: Option<BoundedString>,
-
- /// Reference to IPFS location of metadata
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub metadata: Option<BoundedString>,
-
- /// Optional location or identier of license
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub license: Option<BoundedString>,
-
- /// If the resource has the thumb property, this will be a URI to a thumbnail of the given
- /// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
- /// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
- /// another bird, showing the full render of one bird inside the other's inventory might be a
- /// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
- /// image that is lighter and faster to load but representative of this resource.
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub thumb: Option<BoundedString>,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- BoundedString: AsRef<[u8]>,
- BoundedParts: AsRef<[PartId]>
- "#)
-)]
-pub struct ComposableResource<BoundedString, BoundedParts> {
- /// If a resource is composed, it will have an array of parts that compose it
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub parts: BoundedParts,
-
- /// A Base is uniquely identified by the combination of the word `base`, its minting block
- /// number, and user provided symbol during Base creation, glued by dashes `-`, e.g.
- /// base-4477293-kanaria_superbird.
- pub base: BaseId,
-
- /// If the resource is Media, the base property is absent. Media src should be a URI like an
- /// IPFS hash.
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub src: Option<BoundedString>,
-
- /// Reference to IPFS location of metadata
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub metadata: Option<BoundedString>,
-
- /// If the resource has the slot property, it was designed to fit into a specific Base's slot.
- /// The baseslot will be composed of two dot-delimited values, like so:
- /// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is
- /// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird
-
- /// Optional location or identier of license
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub license: Option<BoundedString>,
-
- /// If the resource has the thumb property, this will be a URI to a thumbnail of the given
- /// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
- /// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
- /// another bird, showing the full render of one bird inside the other's inventory might be a
- /// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
- /// image that is lighter and faster to load but representative of this resource.
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub thumb: Option<BoundedString>,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
-pub struct SlotResource<BoundedString> {
- /// A Base is uniquely identified by the combination of the word `base`, its minting block
- /// number, and user provided symbol during Base creation, glued by dashes `-`, e.g.
- /// base-4477293-kanaria_superbird.
- pub base: BaseId,
-
- /// If the resource is Media, the base property is absent. Media src should be a URI like an
- /// IPFS hash.
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub src: Option<BoundedString>,
-
- /// Reference to IPFS location of metadata
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub metadata: Option<BoundedString>,
-
- /// If the resource has the slot property, it was designed to fit into a specific Base's slot.
- /// The baseslot will be composed of two dot-delimited values, like so:
- /// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is
- /// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird
- pub slot: SlotId,
-
- /// The license field, if present, should contain a link to a license (IPFS or static HTTP
- /// url), or an identifier, like RMRK_nocopy or ipfs://ipfs/someHashOfLicense.
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub license: Option<BoundedString>,
-
- /// If the resource has the thumb property, this will be a URI to a thumbnail of the given
- /// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
- /// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
- /// another bird, showing the full render of one bird inside the other's inventory might be a
- /// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
- /// image that is lighter and faster to load but representative of this resource.
- #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
- pub thumb: Option<BoundedString>,
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- BoundedString: AsRef<[u8]>,
- BoundedParts: AsRef<[PartId]>
- "#)
-)]
-pub enum ResourceTypes<BoundedString, BoundedParts> {
- Basic(BasicResource<BoundedString>),
- Composable(ComposableResource<BoundedString, BoundedParts>),
- Slot(SlotResource<BoundedString>),
-}
-
-#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- BoundedString: AsRef<[u8]>,
- BoundedParts: AsRef<[PartId]>
- "#)
-)]
-pub struct ResourceInfo<BoundedString, BoundedParts> {
- /// id is a 5-character string of reasonable uniqueness.
- /// The combination of base ID and resource id should be unique across the entire RMRK
- /// ecosystem which
- //#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub id: ResourceId,
-
- /// Resource
- pub resource: ResourceTypes<BoundedString, BoundedParts>,
-
- /// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted
- pub pending: bool,
-
- /// If resource removal request is sent by non-rootowned NFT, pending will be true and need to be accepted
- pub pending_removal: bool,
-}
-
-#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- AccountId: Serialize,
- BoundedString: AsRef<[u8]>
- "#)
-)]
-pub struct BaseInfo<AccountId, BoundedString> {
- /// Original creator of the Base
- pub issuer: AccountId,
-
- /// Specifies how an NFT should be rendered, ie "svg"
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub base_type: BoundedString,
-
- /// User provided symbol during Base creation
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub symbol: BoundedString,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
-#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
-pub struct FixedPart<BoundedString> {
- pub id: PartId,
- pub z: ZIndex,
-
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub src: BoundedString,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
-#[cfg_attr(
- feature = "std",
- serde(bound = "BoundedCollectionList: AsRef<[CollectionId]>")
-)]
-pub enum EquippableList<BoundedCollectionList> {
- All,
- Empty,
- Custom(#[cfg_attr(feature = "std", serde(with = "serialize::vec"))] BoundedCollectionList),
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- BoundedString: AsRef<[u8]>,
- BoundedCollectionList: AsRef<[CollectionId]>
- "#)
-)]
-pub struct SlotPart<BoundedString, BoundedCollectionList> {
- pub id: PartId,
- pub equippable: EquippableList<BoundedCollectionList>,
-
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub src: BoundedString,
-
- pub z: ZIndex,
-}
-
-#[cfg_attr(feature = "std", derive(Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- BoundedString: AsRef<[u8]>,
- BoundedCollectionList: AsRef<[CollectionId]>
- "#)
-)]
-pub enum PartType<BoundedString, BoundedCollectionList> {
- FixedPart(FixedPart<BoundedString>),
- SlotPart(SlotPart<BoundedString, BoundedCollectionList>),
-}
-
-impl<BoundedString, BoundedCollectionList> PartType<BoundedString, BoundedCollectionList> {
- pub fn id(&self) -> PartId {
- match self {
- Self::FixedPart(part) => part.id,
- Self::SlotPart(part) => part.id,
- }
- }
-
- pub fn src(&self) -> &BoundedString {
- match self {
- Self::FixedPart(part) => &part.src,
- Self::SlotPart(part) => &part.src,
- }
- }
-
- pub fn z_index(&self) -> ZIndex {
- match self {
- Self::FixedPart(part) => part.z,
- Self::SlotPart(part) => part.z,
- }
- }
-}
-
-#[cfg_attr(feature = "std", derive(Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)]
-#[cfg_attr(
- feature = "std",
- serde(bound = r#"
- BoundedString: AsRef<[u8]>,
- PropertyList: AsRef<[ThemeProperty<BoundedString>]>,
- "#)
-)]
-pub struct Theme<BoundedString, PropertyList> {
- /// Name of the theme
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub name: BoundedString,
-
- /// Theme properties
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub properties: PropertyList,
- /// Inheritability
- pub inherit: bool,
-}
-
-#[cfg_attr(feature = "std", derive(Eq, Serialize))]
-#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)]
-#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
-pub struct ThemeProperty<BoundedString> {
- /// Key of the property
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub key: BoundedString,
-
- /// Value of the property
- #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
- pub value: BoundedString,
-}
primitives/rmrk-rpc/Cargo.tomldiffbeforeafterboth--- a/primitives/rmrk-rpc/Cargo.toml
+++ b/primitives/rmrk-rpc/Cargo.toml
@@ -11,7 +11,7 @@
sp-api = { default-features = false, git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.22' }
sp-runtime = { default-features = false, git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.22' }
serde = { version = "1.0.130", default-features = false, features = ["derive"] }
-up-data-structs = { default-features = false, path = '../data-structs' }
+rmrk-traits = { default-features = false, path = "../rmrk-traits" }
[features]
default = ["std"]
@@ -22,5 +22,5 @@
"sp-api/std",
"sp-runtime/std",
"serde/std",
- "up-data-structs/std",
+ "rmrk-traits/std",
]
primitives/rmrk-rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rmrk-rpc/src/lib.rs
+++ b/primitives/rmrk-rpc/src/lib.rs
@@ -3,7 +3,7 @@
use sp_api::{Encode, Decode};
use sp_std::vec::Vec;
use sp_runtime::DispatchError;
-use up_data_structs::rmrk::{primitives::*, NftChild};
+use rmrk_traits::{primitives::*, NftChild};
pub type Result<T> = core::result::Result<T, DispatchError>;
primitives/rmrk-traits/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "rmrk-traits"
+authors = ["Unique Network <support@uniquenetwork.io>"]
+description = "RMRK proxy data structs definitions"
+edition = "2021"
+license = 'GPLv3'
+homepage = "https://unique.network"
+repository = 'https://github.com/UniqueNetwork/unique-chain'
+version = '0.1.0'
+
+[dependencies]
+scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
+codec = { package = "parity-scale-codec", version = "3.1.2", default-features = false, features = ["derive"] }
+serde = { version = "1.0.130", features = ["derive"], default-features = false, optional = true }
+
+[features]
+default = ["std"]
+std = [
+ "serde1",
+ "serde/std",
+ "codec/std",
+]
+serde1 = ["serde/alloc"]
primitives/rmrk-traits/src/base.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/base.rs
@@ -0,0 +1,30 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ AccountId: Serialize,
+ BoundedString: AsRef<[u8]>
+ "#)
+)]
+pub struct BaseInfo<AccountId, BoundedString> {
+ /// Original creator of the Base
+ pub issuer: AccountId,
+
+ /// Specifies how an NFT should be rendered, ie "svg"
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub base_type: BoundedString,
+
+ /// User provided symbol during Base creation
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub symbol: BoundedString,
+}
primitives/rmrk-traits/src/collection.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/collection.rs
@@ -0,0 +1,31 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ AccountId: Serialize,
+ BoundedString: AsRef<[u8]>,
+ BoundedSymbol: AsRef<[u8]>
+ "#)
+)]
+pub struct CollectionInfo<BoundedString, BoundedSymbol, AccountId> {
+ /// Current bidder and bid price.
+ pub issuer: AccountId,
+
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub metadata: BoundedString,
+ pub max: Option<u32>,
+
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub symbol: BoundedSymbol,
+ pub nfts_count: u32,
+}
primitives/rmrk-traits/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/lib.rs
@@ -0,0 +1,31 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+pub mod base;
+pub mod collection;
+pub mod nft;
+pub mod part;
+pub mod property;
+pub mod resource;
+pub mod theme;
+
+#[cfg(feature = "std")]
+mod serialize;
+
+pub use base::BaseInfo;
+pub use part::{EquippableList, FixedPart, PartType, SlotPart};
+pub use theme::{Theme, ThemeProperty};
+pub use collection::CollectionInfo;
+pub use nft::{AccountIdOrCollectionNftTuple, NftInfo, RoyaltyInfo, NftChild};
+pub use property::PropertyInfo;
+pub use resource::{
+ BasicResource, ComposableResource, ResourceInfo, ResourceTypes, SlotResource,
+};
+pub mod primitives {
+ pub type CollectionId = u32;
+ pub type ResourceId = u32;
+ pub type NftId = u32;
+ pub type BaseId = u32;
+ pub type SlotId = u32;
+ pub type PartId = u32;
+ pub type ZIndex = u32;
+}
primitives/rmrk-traits/src/nft.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/nft.rs
@@ -0,0 +1,61 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+use crate::primitives::*;
+
+#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+pub enum AccountIdOrCollectionNftTuple<AccountId> {
+ AccountId(AccountId),
+ CollectionAndNftTuple(CollectionId, NftId),
+}
+
+/// Royalty information (recipient and amount)
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
+pub struct RoyaltyInfo<AccountId, RoyaltyAmount> {
+ /// Recipient (AccountId) of the royalty
+ pub recipient: AccountId,
+ /// Amount (Permill) of the royalty
+ pub amount: RoyaltyAmount,
+}
+
+/// Nft info.
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ AccountId: Serialize,
+ RoyaltyAmount: Serialize,
+ BoundedString: AsRef<[u8]>
+ "#)
+)]
+pub struct NftInfo<AccountId, RoyaltyAmount, BoundedString> {
+ /// The owner of the NFT, can be either an Account or a tuple (CollectionId, NftId)
+ pub owner: AccountIdOrCollectionNftTuple<AccountId>,
+ /// Royalty (optional)
+ pub royalty: Option<RoyaltyInfo<AccountId, RoyaltyAmount>>,
+
+ /// Arbitrary data about an instance, e.g. IPFS hash
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub metadata: BoundedString,
+
+ /// Equipped state
+ pub equipped: bool,
+ /// Pending state (if sent to NFT)
+ pub pending: bool,
+}
+
+#[cfg_attr(feature = "std", derive(PartialEq, Eq, Serialize))]
+#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
+pub struct NftChild {
+ pub collection_id: CollectionId,
+ pub nft_id: NftId,
+}
primitives/rmrk-traits/src/part.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/part.rs
@@ -0,0 +1,89 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+use crate::primitives::*;
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
+#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
+pub struct FixedPart<BoundedString> {
+ pub id: PartId,
+ pub z: ZIndex,
+
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub src: BoundedString,
+}
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = "BoundedCollectionList: AsRef<[CollectionId]>")
+)]
+pub enum EquippableList<BoundedCollectionList> {
+ All,
+ Empty,
+ Custom(#[cfg_attr(feature = "std", serde(with = "serialize::vec"))] BoundedCollectionList),
+}
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ BoundedString: AsRef<[u8]>,
+ BoundedCollectionList: AsRef<[CollectionId]>
+ "#)
+)]
+pub struct SlotPart<BoundedString, BoundedCollectionList> {
+ pub id: PartId,
+ pub equippable: EquippableList<BoundedCollectionList>,
+
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub src: BoundedString,
+
+ pub z: ZIndex,
+}
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq, Eq, MaxEncodedLen)]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ BoundedString: AsRef<[u8]>,
+ BoundedCollectionList: AsRef<[CollectionId]>
+ "#)
+)]
+pub enum PartType<BoundedString, BoundedCollectionList> {
+ FixedPart(FixedPart<BoundedString>),
+ SlotPart(SlotPart<BoundedString, BoundedCollectionList>),
+}
+
+impl<BoundedString, BoundedCollectionList> PartType<BoundedString, BoundedCollectionList> {
+ pub fn id(&self) -> PartId {
+ match self {
+ Self::FixedPart(part) => part.id,
+ Self::SlotPart(part) => part.id,
+ }
+ }
+
+ pub fn src(&self) -> &BoundedString {
+ match self {
+ Self::FixedPart(part) => &part.src,
+ Self::SlotPart(part) => &part.src,
+ }
+ }
+
+ pub fn z_index(&self) -> ZIndex {
+ match self {
+ Self::FixedPart(part) => part.z,
+ Self::SlotPart(part) => part.z,
+ }
+ }
+}
primitives/rmrk-traits/src/property.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/property.rs
@@ -0,0 +1,27 @@
+use codec::{Decode, Encode};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[derive(Encode, Decode, PartialEq, TypeInfo)]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ BoundedKey: AsRef<[u8]>,
+ BoundedValue: AsRef<[u8]>
+ "#)
+)]
+pub struct PropertyInfo<BoundedKey, BoundedValue> {
+ /// Key of the property
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub key: BoundedKey,
+
+ /// Value of the property
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub value: BoundedValue,
+}
primitives/rmrk-traits/src/resource.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/resource.rs
@@ -0,0 +1,164 @@
+use codec::{Decode, Encode, MaxEncodedLen};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+use crate::primitives::*;
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
+pub struct BasicResource<BoundedString> {
+ /// If the resource is Media, the base property is absent. Media src should be a URI like an
+ /// IPFS hash.
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub src: Option<BoundedString>,
+
+ /// Reference to IPFS location of metadata
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub metadata: Option<BoundedString>,
+
+ /// Optional location or identier of license
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub license: Option<BoundedString>,
+
+ /// If the resource has the thumb property, this will be a URI to a thumbnail of the given
+ /// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
+ /// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
+ /// another bird, showing the full render of one bird inside the other's inventory might be a
+ /// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
+ /// image that is lighter and faster to load but representative of this resource.
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub thumb: Option<BoundedString>,
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ BoundedString: AsRef<[u8]>,
+ BoundedParts: AsRef<[PartId]>
+ "#)
+)]
+pub struct ComposableResource<BoundedString, BoundedParts> {
+ /// If a resource is composed, it will have an array of parts that compose it
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub parts: BoundedParts,
+
+ /// A Base is uniquely identified by the combination of the word `base`, its minting block
+ /// number, and user provided symbol during Base creation, glued by dashes `-`, e.g.
+ /// base-4477293-kanaria_superbird.
+ pub base: BaseId,
+
+ /// If the resource is Media, the base property is absent. Media src should be a URI like an
+ /// IPFS hash.
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub src: Option<BoundedString>,
+
+ /// Reference to IPFS location of metadata
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub metadata: Option<BoundedString>,
+
+ /// If the resource has the slot property, it was designed to fit into a specific Base's slot.
+ /// The baseslot will be composed of two dot-delimited values, like so:
+ /// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is
+ /// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird
+
+ /// Optional location or identier of license
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub license: Option<BoundedString>,
+
+ /// If the resource has the thumb property, this will be a URI to a thumbnail of the given
+ /// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
+ /// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
+ /// another bird, showing the full render of one bird inside the other's inventory might be a
+ /// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
+ /// image that is lighter and faster to load but representative of this resource.
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub thumb: Option<BoundedString>,
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
+pub struct SlotResource<BoundedString> {
+ /// A Base is uniquely identified by the combination of the word `base`, its minting block
+ /// number, and user provided symbol during Base creation, glued by dashes `-`, e.g.
+ /// base-4477293-kanaria_superbird.
+ pub base: BaseId,
+
+ /// If the resource is Media, the base property is absent. Media src should be a URI like an
+ /// IPFS hash.
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub src: Option<BoundedString>,
+
+ /// Reference to IPFS location of metadata
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub metadata: Option<BoundedString>,
+
+ /// If the resource has the slot property, it was designed to fit into a specific Base's slot.
+ /// The baseslot will be composed of two dot-delimited values, like so:
+ /// "base-4477293-kanaria_superbird.machine_gun_scope". This means: "This resource is
+ /// compatible with the machine_gun_scope slot of base base-4477293-kanaria_superbird
+ pub slot: SlotId,
+
+ /// The license field, if present, should contain a link to a license (IPFS or static HTTP
+ /// url), or an identifier, like RMRK_nocopy or ipfs://ipfs/someHashOfLicense.
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub license: Option<BoundedString>,
+
+ /// If the resource has the thumb property, this will be a URI to a thumbnail of the given
+ /// resource. For example, if we have a composable NFT like a Kanaria bird, the resource is
+ /// complex and too detailed to show in a search-results page or a list. Also, if a bird owns
+ /// another bird, showing the full render of one bird inside the other's inventory might be a
+ /// bit of a strain on the browser. For this reason, the thumb value can contain a URI to an
+ /// image that is lighter and faster to load but representative of this resource.
+ #[cfg_attr(feature = "std", serde(with = "serialize::opt_vec"))]
+ pub thumb: Option<BoundedString>,
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ BoundedString: AsRef<[u8]>,
+ BoundedParts: AsRef<[PartId]>
+ "#)
+)]
+pub enum ResourceTypes<BoundedString, BoundedParts> {
+ Basic(BasicResource<BoundedString>),
+ Composable(ComposableResource<BoundedString, BoundedParts>),
+ Slot(SlotResource<BoundedString>),
+}
+
+#[derive(Encode, Decode, Eq, PartialEq, Clone, Debug, TypeInfo, MaxEncodedLen)]
+#[cfg_attr(feature = "std", derive(Serialize))]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ BoundedString: AsRef<[u8]>,
+ BoundedParts: AsRef<[PartId]>
+ "#)
+)]
+pub struct ResourceInfo<BoundedString, BoundedParts> {
+ /// id is a 5-character string of reasonable uniqueness.
+ /// The combination of base ID and resource id should be unique across the entire RMRK
+ /// ecosystem which
+ //#[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub id: ResourceId,
+
+ /// Resource
+ pub resource: ResourceTypes<BoundedString, BoundedParts>,
+
+ /// If resource is sent to non-rootowned NFT, pending will be false and need to be accepted
+ pub pending: bool,
+
+ /// If resource removal request is sent by non-rootowned NFT, pending will be true and need to be accepted
+ pub pending_removal: bool,
+}
primitives/rmrk-traits/src/serialize.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/serialize.rs
@@ -0,0 +1,31 @@
+use core::convert::AsRef;
+use serde::ser::{self, Serialize};
+
+pub mod vec {
+ use super::*;
+
+ pub fn serialize<D, V, C>(value: &C, serializer: D) -> Result<D::Ok, D::Error>
+ where
+ D: ser::Serializer,
+ V: Serialize,
+ C: AsRef<[V]>,
+ {
+ value.as_ref().serialize(serializer)
+ }
+}
+
+pub mod opt_vec {
+ use super::*;
+
+ pub fn serialize<D, V, C>(value: &Option<C>, serializer: D) -> Result<D::Ok, D::Error>
+ where
+ D: ser::Serializer,
+ V: Serialize,
+ C: AsRef<[V]>,
+ {
+ match value {
+ Some(value) => super::vec::serialize(value, serializer),
+ None => serializer.serialize_none(),
+ }
+ }
+}
primitives/rmrk-traits/src/theme.rsdiffbeforeafterboth--- /dev/null
+++ b/primitives/rmrk-traits/src/theme.rs
@@ -0,0 +1,42 @@
+use codec::{Decode, Encode};
+use scale_info::TypeInfo;
+
+#[cfg(feature = "std")]
+use serde::Serialize;
+
+#[cfg(feature = "std")]
+use crate::serialize;
+
+#[cfg_attr(feature = "std", derive(Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)]
+#[cfg_attr(
+ feature = "std",
+ serde(bound = r#"
+ BoundedString: AsRef<[u8]>,
+ PropertyList: AsRef<[ThemeProperty<BoundedString>]>,
+ "#)
+)]
+pub struct Theme<BoundedString, PropertyList> {
+ /// Name of the theme
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub name: BoundedString,
+
+ /// Theme properties
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub properties: PropertyList,
+ /// Inheritability
+ pub inherit: bool,
+}
+
+#[cfg_attr(feature = "std", derive(Eq, Serialize))]
+#[derive(Encode, Decode, Debug, TypeInfo, Clone, PartialEq)]
+#[cfg_attr(feature = "std", serde(bound = "BoundedString: AsRef<[u8]>"))]
+pub struct ThemeProperty<BoundedString> {
+ /// Key of the property
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub key: BoundedString,
+
+ /// Value of the property
+ #[cfg_attr(feature = "std", serde(with = "serialize::vec"))]
+ pub value: BoundedString,
+}