diff --git a/Cargo.lock b/Cargo.lock index 50b84e14..b139ee54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9046,6 +9046,7 @@ dependencies = [ name = "pop-api-primitives" version = "0.0.0" dependencies = [ + "bounded-collections 0.1.9", "parity-scale-codec", "scale-info", ] diff --git a/contracts/pop-api-examples/nfts/lib.rs b/contracts/pop-api-examples/nfts/lib.rs index 28898062..02fbf986 100755 --- a/contracts/pop-api-examples/nfts/lib.rs +++ b/contracts/pop-api-examples/nfts/lib.rs @@ -5,7 +5,10 @@ use pop_api::nfts; #[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub enum ContractError { + InvalidCollection, + ItemAlreadyExists, NftsError(nfts::Error), + NotOwner, } impl From for ContractError { @@ -25,7 +28,7 @@ mod pop_api_extension_demo { impl PopApiExtensionDemo { #[ink(constructor, payable)] pub fn new() -> Self { - ink::env::debug_println!("PopApiExtensionDemo::new"); + ink::env::debug_println!("Contract::new"); Default::default() } @@ -36,21 +39,35 @@ mod pop_api_extension_demo { item_id: u32, receiver: AccountId, ) -> Result<(), ContractError> { - ink::env::debug_println!("PopApiExtensionDemo::mint_through_runtime: collection_id: {:?} \nitem_id {:?} \nreceiver: {:?}, ", collection_id, item_id, receiver); - - // simplified API call - let result = pop_api::nfts::mint(collection_id, item_id, receiver); ink::env::debug_println!( - "PopApiExtensionDemo::mint_through_runtime result: {result:?}" + "Contract::mint_through_runtime: collection_id: {:?} item_id {:?} receiver: {:?}", + collection_id, + item_id, + receiver ); - if let Err(pop_api::nfts::Error::NoConfig) = result { - ink::env::debug_println!( - "PopApiExtensionDemo::mint_through_runtime expected error received" - ); + + // Check if item already exists (demo purposes only, unnecessary as would expect check in mint call) + if pop_api::nfts::item(collection_id, item_id)?.is_some() { + return Err(ContractError::ItemAlreadyExists); + } + + // mint api + pop_api::nfts::mint(collection_id, item_id, receiver)?; + ink::env::debug_println!("Contract::mint_through_runtime: item minted successfully"); + + // check owner + match pop_api::nfts::owner(collection_id, item_id)? { + Some(owner) if owner == receiver => { + ink::env::debug_println!( + "Contract::mint_through_runtime success: minted item belongs to receiver" + ); + }, + _ => { + return Err(ContractError::NotOwner); + }, } - result?; - ink::env::debug_println!("PopApiExtensionDemo::mint_through_runtime end"); + ink::env::debug_println!("Contract::mint_through_runtime end"); Ok(()) } } diff --git a/pop-api/primitives/Cargo.toml b/pop-api/primitives/Cargo.toml index 80f0d929..ba8c6efd 100644 --- a/pop-api/primitives/Cargo.toml +++ b/pop-api/primitives/Cargo.toml @@ -6,6 +6,7 @@ version = "0.0.0" edition = "2021" [dependencies] +bounded-collections = { version = "0.1", default-features = false } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } scale-info = { version = "2.6", default-features = false, features = ["derive"], optional = true } @@ -17,6 +18,7 @@ crate-type = ["rlib"] [features] default = ["std"] std = [ + "bounded-collections/std", "scale/std", "scale-info/std", ] diff --git a/pop-api/primitives/src/lib.rs b/pop-api/primitives/src/lib.rs index b401dc5f..b3d84120 100644 --- a/pop-api/primitives/src/lib.rs +++ b/pop-api/primitives/src/lib.rs @@ -1,3 +1,18 @@ #![cfg_attr(not(feature = "std"), no_std, no_main)] +pub use bounded_collections::{BoundedBTreeMap, BoundedBTreeSet, BoundedVec, ConstU32}; +//use scale::{Decode, Encode, MaxEncodedLen}; + pub mod storage_keys; + +// /// Some way of identifying an account on the chain. +// #[derive(Encode, Decode, Debug, MaxEncodedLen)] +// pub struct AccountId([u8; 32]); +// Id used for identifying non-fungible collections. +pub type CollectionId = u32; +// Id used for identifying non-fungible items. +pub type ItemId = u32; +/// The maximum length of an attribute key. +pub type KeyLimit = ConstU32<64>; +/// The maximum approvals an item could have. +pub type ApprovalsLimit = ConstU32<20>; diff --git a/pop-api/primitives/src/storage_keys.rs b/pop-api/primitives/src/storage_keys.rs index c90851ab..dbb2b3c0 100644 --- a/pop-api/primitives/src/storage_keys.rs +++ b/pop-api/primitives/src/storage_keys.rs @@ -1,3 +1,4 @@ +use super::*; use scale::{Decode, Encode, MaxEncodedLen}; #[derive(Encode, Decode, Debug, MaxEncodedLen)] @@ -14,10 +15,20 @@ pub enum ParachainSystemKeys { // https://github.com/paritytech/polkadot-sdk/blob/master/substrate/frame/nfts/src/impl_nonfungibles.rs #[derive(Encode, Decode, Debug, MaxEncodedLen)] pub enum NftsKeys { - Owner, - CollectionOwner, - Attribute, - CustomAttribute, - SystemAttribute, - CollectionAttribute, + // Get the details of a collection. + Collection(CollectionId), + /// Get the owner of the collection, if the collection exists. + CollectionOwner(CollectionId), + // Get the details of an item. + Item(CollectionId, ItemId), + /// Get the owner of the item, if the item exists. + Owner(CollectionId, ItemId), + /// Get the attribute value of `item` of `collection` corresponding to `key`. + Attribute(CollectionId, ItemId, BoundedVec), + // /// Get the custom attribute value of `item` of `collection` corresponding to `key`. + // CustomAttribute(AccountId, CollectionId, ItemId, BoundedVec), + /// Get the system attribute value of `item` of `collection` corresponding to `key` + SystemAttribute(CollectionId, Option, BoundedVec), + /// Get the attribute value of `item` of `collection` corresponding to `key`. + CollectionAttribute(CollectionId, BoundedVec), } diff --git a/pop-api/src/lib.rs b/pop-api/src/lib.rs index b8b47587..f1ffe3f5 100644 --- a/pop-api/src/lib.rs +++ b/pop-api/src/lib.rs @@ -11,17 +11,10 @@ pub use sp_runtime::{BoundedVec, MultiAddress, MultiSignature}; use v0::RuntimeCall; pub use v0::{balances, nfts, state}; -// Id used for identifying non-fungible collections. -pub type CollectionId = u32; - -// Id used for identifying non-fungible items. -pub type ItemId = u32; - type AccountId = ::AccountId; type Balance = ::Balance; type BlockNumber = ::BlockNumber; type StringLimit = u32; -type KeyLimit = u32; type MaxTips = u32; pub type Result = core::result::Result; diff --git a/pop-api/src/v0/nfts.rs b/pop-api/src/v0/nfts.rs index ec75249e..b447aee8 100644 --- a/pop-api/src/v0/nfts.rs +++ b/pop-api/src/v0/nfts.rs @@ -1,7 +1,7 @@ use super::RuntimeCall; use crate::{PopApiError::UnknownStatusCode, *}; use ink::prelude::vec::Vec; -use primitives::MultiAddress; +use primitives::{ApprovalsLimit, BoundedBTreeMap, CollectionId, ItemId, KeyLimit, MultiAddress}; use scale::Encode; use types::*; @@ -300,6 +300,66 @@ pub fn claim_swap( }))?) } +/// Get the owner of the item, if the item exists. +pub fn owner(collection: CollectionId, item: ItemId) -> Result> { + Ok(state::read(RuntimeStateKeys::Nfts(NftsKeys::Owner(collection, item)))?) +} + +/// Get the owner of the collection, if the collection exists. +pub fn collection_owner(collection: CollectionId) -> Result> { + Ok(state::read(RuntimeStateKeys::Nfts(NftsKeys::CollectionOwner(collection)))?) +} + +/// Get the attribute value of `item` of `collection` corresponding to `key`. +pub fn attribute( + collection: CollectionId, + item: ItemId, + key: BoundedVec, +) -> Result>> { + Ok(state::read(RuntimeStateKeys::Nfts(NftsKeys::Attribute(collection, item, key)))?) +} + +// /// Get the custom attribute value of `item` of `collection` corresponding to `key`. +// pub fn custom_attribute( +// account: AccountId, +// collection: CollectionId, +// item: ItemId, +// key: BoundedVec, +// ) -> Result>> { +// Ok(state::read(RuntimeStateKeys::Nfts(NftsKeys::CustomAttribute( +// account, collection, item, key, +// )))?) +// } + +/// Get the system attribute value of `item` of `collection` corresponding to `key` if +/// `item` is `Some`. Otherwise, returns the system attribute value of `collection` +/// corresponding to `key`. +pub fn system_attribute( + collection: CollectionId, + item: Option, + key: BoundedVec, +) -> Result>> { + Ok(state::read(RuntimeStateKeys::Nfts(NftsKeys::SystemAttribute(collection, item, key)))?) +} + +/// Get the attribute value of `item` of `collection` corresponding to `key`. +pub fn collection_attribute( + collection: CollectionId, + key: BoundedVec, +) -> Result>> { + Ok(state::read(RuntimeStateKeys::Nfts(NftsKeys::CollectionAttribute(collection, key)))?) +} + +/// Get the details of a collection. +pub fn collection(collection: CollectionId) -> Result> { + Ok(state::read(RuntimeStateKeys::Nfts(NftsKeys::Collection(collection)))?) +} + +/// Get the details of an item. +pub fn item(collection: CollectionId, item: ItemId) -> Result> { + Ok(state::read(RuntimeStateKeys::Nfts(NftsKeys::Item(collection, item)))?) +} + #[derive(Encode)] pub(crate) enum NftCalls { #[codec(index = 0)] @@ -582,7 +642,10 @@ impl From for Error { // Local implementations of pallet-nfts types mod types { use super::*; - use crate::{Balance, BlockNumber, CollectionId}; + use crate::{ + primitives::{CollectionId, ItemId}, + Balance, BlockNumber, + }; use enumflags2::{bitflags, BitFlags}; use scale::{Decode, EncodeLike, MaxEncodedLen}; use scale_info::{build::Fields, meta_type, prelude::vec, Path, Type, TypeInfo, TypeParameter}; @@ -611,6 +674,24 @@ mod types { pub mint_settings: MintSettings, } + /// Information about a collection. + #[derive(Decode)] + pub struct CollectionDetails { + /// Collection's owner. + pub owner: AccountId, + /// The total balance deposited by the owner for all the storage data associated with this + /// collection. Used by `destroy`. + pub owner_deposit: Balance, + /// The total number of outstanding items of this collection. + pub items: u32, + /// The total number of outstanding item metadata of this collection. + pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + pub item_configs: u32, + /// The total number of attributes for this collection. + pub attributes: u32, + } + /// Wrapper type for `BitFlags` that implements `Codec`. pub struct CollectionSettings(pub BitFlags); @@ -633,6 +714,18 @@ mod types { DepositRequired, } + /// Information concerning the ownership of a single unique item. + #[derive(Decode)] + pub struct ItemDetails { + /// The owner of this item. + pub owner: AccountId, + /// The approved transferrer of this item, if one is set. + pub approvals: BoundedBTreeMap, ApprovalsLimit>, + /// The amount held in the pallet's default account for this item. Free-hold items will have + /// this as zero. + pub deposit: Balance, + } + /// Support for up to 64 user-enabled features on an item. #[bitflags] #[repr(u64)] diff --git a/runtime/src/assets_config.rs b/runtime/src/assets_config.rs index e5aa144e..f2acffb0 100644 --- a/runtime/src/assets_config.rs +++ b/runtime/src/assets_config.rs @@ -38,7 +38,9 @@ parameter_types! { impl pallet_nfts::Config for Runtime { type RuntimeEvent = RuntimeEvent; + // TODO: source from primitives type CollectionId = CollectionId; + // TODO: source from primitives type ItemId = ItemId; type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; @@ -50,8 +52,10 @@ impl pallet_nfts::Config for Runtime { type AttributeDepositBase = NftsAttributeDepositBase; type DepositPerByte = NftsDepositPerByte; type StringLimit = ConstU32<256>; + // TODO: source from primitives type KeyLimit = ConstU32<64>; type ValueLimit = ConstU32<256>; + // TODO: source from primitives type ApprovalsLimit = ConstU32<20>; type ItemAttributesApprovalsLimit = ConstU32<30>; type MaxTips = ConstU32<10>; diff --git a/runtime/src/extensions.rs b/runtime/src/extensions.rs index bc6257d3..b9ccbb59 100644 --- a/runtime/src/extensions.rs +++ b/runtime/src/extensions.rs @@ -8,7 +8,10 @@ use log; use pallet_contracts::chain_extension::{ BufInBufOutState, ChainExtension, Environment, Ext, InitState, RetVal, SysConfig, }; -use pop_api_primitives::storage_keys::{NftsKeys, ParachainSystemKeys, RuntimeStateKeys}; +use pop_api_primitives::{ + storage_keys::{NftsKeys, ParachainSystemKeys, RuntimeStateKeys}, + CollectionId, ItemId, +}; use sp_core::crypto::UncheckedFrom; use sp_runtime::{traits::Dispatchable, DispatchError}; @@ -21,7 +24,10 @@ pub struct PopApiExtension; impl ChainExtension for PopApiExtension where - T: pallet_contracts::Config + pallet_nfts::Config + cumulus_pallet_parachain_system::Config, + T: pallet_contracts::Config + + pallet_nfts::Config + + cumulus_pallet_parachain_system::Config + + frame_system::Config, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, ::RuntimeCall: Parameter + Dispatchable::RuntimeOrigin, PostInfo = PostDispatchInfo> @@ -138,94 +144,10 @@ where Ok(()) } -fn read_nfts_state( - key: NftsKeys, - env: &mut Environment, -) -> Result, DispatchError> -where - T: pallet_contracts::Config + pallet_nfts::Config + frame_system::Config, - E: Ext, -{ - match key { - NftsKeys::Owner => { - let (collection, item): ( - ::CollectionId, - ::ItemId, - ) = env.read_as()?; - - let maybe_owner = pallet_nfts::Pallet::::owner(collection, item); - Ok(maybe_owner.encode()) - }, - NftsKeys::CollectionOwner => { - let collection: ::CollectionId = env.read_as()?; - - let maybe_owner = pallet_nfts::Pallet::::collection_owner(collection); - - Ok(maybe_owner.encode()) - }, - NftsKeys::Attribute => { - // TODO: charge weight - let len = env.in_len(); - - let (collection, item, key): ( - ::CollectionId, - ::ItemId, - Vec, - ) = env.read_as_unbounded(len)?; - - let maybe_attribute = pallet_nfts::Pallet::::attribute(&collection, &item, &key); - - Ok(maybe_attribute.encode()) - }, - NftsKeys::CustomAttribute => { - // TODO: charge weight - let len = env.in_len(); - - let (account, collection, item, key): ( - ::AccountId, - ::CollectionId, - ::ItemId, - Vec, - ) = env.read_as_unbounded(len)?; - - let maybe_attribute = - pallet_nfts::Pallet::::custom_attribute(&account, &collection, &item, &key); - - Ok(maybe_attribute.encode()) - }, - NftsKeys::SystemAttribute => { - // TODO: charge weight - let len = env.in_len(); - - let (collection, maybe_item, key): ( - ::CollectionId, - Option<::ItemId>, - Vec, - ) = env.read_as_unbounded(len)?; - - let maybe_attribute = - pallet_nfts::Pallet::::system_attribute(&collection, maybe_item.as_ref(), &key); - - Ok(maybe_attribute.encode()) - }, - NftsKeys::CollectionAttribute => { - // TODO: charge weight - let len = env.in_len(); - - let (collection, key): (::CollectionId, Vec) = - env.read_as_unbounded(len)?; - - let maybe_attribute = pallet_nfts::Pallet::::collection_attribute(&collection, &key); - - Ok(maybe_attribute.encode()) - }, - } -} - fn read_state(env: Environment) -> Result<(), DispatchError> where T: pallet_contracts::Config - + pallet_nfts::Config + + pallet_nfts::Config + cumulus_pallet_parachain_system::Config + frame_system::Config, E: Ext, @@ -261,7 +183,7 @@ where Ok(relay_block_num.encode()) }, }, - } + }? .encode(); log::trace!( @@ -274,6 +196,52 @@ where }) } +fn read_nfts_state( + key: NftsKeys, + env: &mut Environment, +) -> Result, DispatchError> +where + T: pallet_contracts::Config + pallet_nfts::Config, + E: Ext, +{ + match key { + NftsKeys::Collection(collection) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Collection::::get(collection).encode()) + }, + NftsKeys::CollectionOwner(collection) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::collection_owner(collection).encode()) + }, + NftsKeys::Item(collection, item) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Item::::get(collection, item).encode()) + }, + NftsKeys::Owner(collection, item) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::owner(collection, item).encode()) + }, + NftsKeys::Attribute(collection, item, key) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::attribute(&collection, &item, &key).encode()) + }, + // NftsKeys::CustomAttribute(account, collection, item, key) => { + // env.charge_weight(T::DbWeight::get().reads(1_u64))?; + // Ok(pallet_nfts::Pallet::::custom_attribute(&account, &collection, &item, &key) + // .encode()) + // }, + NftsKeys::SystemAttribute(collection, item, key) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::system_attribute(&collection, item.as_ref(), &key) + .encode()) + }, + NftsKeys::CollectionAttribute(collection, key) => { + env.charge_weight(T::DbWeight::get().reads(1_u64))?; + Ok(pallet_nfts::Pallet::::collection_attribute(&collection, &key).encode()) + }, + } +} + #[cfg(test)] mod tests { pub use super::*;