From 4c2a12938b5483c3f3593ab8cef24186d28710c6 Mon Sep 17 00:00:00 2001 From: Daniel Shiposha Date: Thu, 23 Feb 2023 15:06:12 +0000 Subject: [PATCH] Nfts attribute read interface (#13349) * feat: add custom and system attributes to Inspect * feat: add nfts runtime api * fix: pass std feature to runtime api * fix: api copyright Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --------- Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- Cargo.lock | 11 +++ Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 2 + bin/node/runtime/src/lib.rs | 52 ++++++++++++- frame/nfts/runtime-api/Cargo.toml | 28 +++++++ frame/nfts/runtime-api/README.md | 3 + frame/nfts/runtime-api/src/lib.rs | 57 ++++++++++++++ frame/nfts/src/impl_nonfungibles.rs | 32 +++++++- .../src/traits/tokens/nonfungible_v2.rs | 76 ++++++++++++++----- .../src/traits/tokens/nonfungibles_v2.rs | 59 ++++++++++++-- 10 files changed, 291 insertions(+), 30 deletions(-) create mode 100644 frame/nfts/runtime-api/Cargo.toml create mode 100644 frame/nfts/runtime-api/README.md create mode 100644 frame/nfts/runtime-api/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e485a6512250e..afe2b46ee5fef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3558,6 +3558,7 @@ dependencies = [ "pallet-mmr", "pallet-multisig", "pallet-nfts", + "pallet-nfts-runtime-api", "pallet-nis", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", @@ -6038,6 +6039,16 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nfts-runtime-api" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "pallet-nfts", + "parity-scale-codec", + "sp-api", +] + [[package]] name = "pallet-nicks" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 2ae924bf6e1eb..b9f89365cd050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,6 +122,7 @@ members = [ "frame/proxy", "frame/message-queue", "frame/nfts", + "frame/nfts/runtime-api", "frame/nomination-pools", "frame/nomination-pools/fuzzer", "frame/nomination-pools/benchmarking", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index d7b42efd4d2d6..71fa9320a77d6 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -79,6 +79,7 @@ pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nfts" } +pallet-nfts-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nfts/runtime-api" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" } pallet-nomination-pools-runtime-api = { version = "1.0.0-dev", default-features = false, path = "../../../frame/nomination-pools/runtime-api" } @@ -203,6 +204,7 @@ std = [ "pallet-recovery/std", "pallet-uniques/std", "pallet-nfts/std", + "pallet-nfts-runtime-api/std", "pallet-vesting/std", "log/std", "frame-try-runtime?/std", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 1ed43c211f8a1..a332127fb8f00 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -33,10 +33,10 @@ use frame_support::{ pallet_prelude::Get, parameter_types, traits::{ - fungible::ItemOf, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, - Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, - KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, - WithdrawReasons, + fungible::ItemOf, tokens::nonfungibles_v2::Inspect, AsEnsureOriginWithArg, ConstBool, + ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, + Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, + U128CurrencyToVote, WithdrawReasons, }, weights::{ constants::{ @@ -2187,6 +2187,50 @@ impl_runtime_apis! { } } + impl pallet_nfts_runtime_api::NftsApi for Runtime { + fn owner(collection: u32, item: u32) -> Option { + >::owner(&collection, &item) + } + + fn collection_owner(collection: u32) -> Option { + >::collection_owner(&collection) + } + + fn attribute( + collection: u32, + item: u32, + key: Vec, + ) -> Option> { + >::attribute(&collection, &item, &key) + } + + fn custom_attribute( + account: AccountId, + collection: u32, + item: u32, + key: Vec, + ) -> Option> { + >::custom_attribute( + &account, + &collection, + &item, + &key, + ) + } + + fn system_attribute( + collection: u32, + item: u32, + key: Vec, + ) -> Option> { + >::system_attribute(&collection, &item, &key) + } + + fn collection_attribute(collection: u32, key: Vec) -> Option> { + >::collection_attribute(&collection, &key) + } + } + impl pallet_mmr::primitives::MmrApi< Block, mmr::Hash, diff --git a/frame/nfts/runtime-api/Cargo.toml b/frame/nfts/runtime-api/Cargo.toml new file mode 100644 index 0000000000000..29d79e7768515 --- /dev/null +++ b/frame/nfts/runtime-api/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pallet-nfts-runtime-api" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Runtime API for the FRAME NFTs pallet." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../../nfts" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "pallet-nfts/std", + "sp-api/std", +] diff --git a/frame/nfts/runtime-api/README.md b/frame/nfts/runtime-api/README.md new file mode 100644 index 0000000000000..289036d2c0d27 --- /dev/null +++ b/frame/nfts/runtime-api/README.md @@ -0,0 +1,3 @@ +RPC runtime API for the FRAME NFTs pallet. + +License: Apache-2.0 diff --git a/frame/nfts/runtime-api/src/lib.rs b/frame/nfts/runtime-api/src/lib.rs new file mode 100644 index 0000000000000..0c23d178107e7 --- /dev/null +++ b/frame/nfts/runtime-api/src/lib.rs @@ -0,0 +1,57 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime API definition for the FRAME NFTs pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use frame_support::dispatch::Vec; + +sp_api::decl_runtime_apis! { + pub trait NftsApi + where + AccountId: Encode + Decode, + CollectionId: Encode, + ItemId: Encode, + { + fn owner(collection: CollectionId, item: ItemId) -> Option; + + fn collection_owner(collection: CollectionId) -> Option; + + fn attribute( + collection: CollectionId, + item: ItemId, + key: Vec, + ) -> Option>; + + fn custom_attribute( + account: AccountId, + collection: CollectionId, + item: ItemId, + key: Vec, + ) -> Option>; + + fn system_attribute( + collection: CollectionId, + item: ItemId, + key: Vec, + ) -> Option>; + + fn collection_attribute(collection: CollectionId, key: Vec) -> Option>; + } +} diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index ac02355ba29ac..ef6bbe7656ef8 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -50,18 +50,48 @@ impl, I: 'static> Inspect<::AccountId> for Palle fn attribute( collection: &Self::CollectionId, item: &Self::ItemId, - namespace: &AttributeNamespace<::AccountId>, key: &[u8], ) -> Option> { if key.is_empty() { // We make the empty key map to the item metadata value. ItemMetadataOf::::get(collection, item).map(|m| m.data.into()) } else { + let namespace = AttributeNamespace::CollectionOwner; let key = BoundedSlice::<_, _>::try_from(key).ok()?; Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) } } + /// Returns the custom attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn custom_attribute( + account: &T::AccountId, + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + ) -> Option> { + let namespace = Account::::get((account, collection, item)) + .map(|_| AttributeNamespace::ItemOwner) + .unwrap_or_else(|| AttributeNamespace::Account(account.clone())); + + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) + } + + /// Returns the system attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn system_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + ) -> Option> { + let namespace = AttributeNamespace::Pallet; + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) + } + /// Returns the attribute value of `item` of `collection` corresponding to `key`. /// /// When `key` is empty, we return the item metadata value. diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index 36ef9ab0aa63f..175d94324aaa4 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -25,10 +25,7 @@ //! use. use super::nonfungibles_v2 as nonfungibles; -use crate::{ - dispatch::DispatchResult, - traits::{tokens::misc::AttributeNamespace, Get}, -}; +use crate::{dispatch::DispatchResult, traits::Get}; use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; @@ -45,23 +42,53 @@ pub trait Inspect { /// Returns the attribute value of `item` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn attribute( + fn attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + None + } + + /// Returns the custom attribute value of `item` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn custom_attribute( + _account: &AccountId, _item: &Self::ItemId, - _namespace: &AttributeNamespace, _key: &[u8], ) -> Option> { None } + /// Returns the system attribute value of `item` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn system_attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + None + } + /// Returns the strongly-typed attribute value of `item` corresponding to `key`. /// /// By default this just attempts to use `attribute`. - fn typed_attribute( + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + key.using_encoded(|d| Self::attribute(item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the strongly-typed custom attribute value of `item` corresponding to `key`. + /// + /// By default this just attempts to use `custom_attribute`. + fn typed_custom_attribute( + account: &AccountId, item: &Self::ItemId, - namespace: &AttributeNamespace, key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(item, namespace, d)) + key.using_encoded(|d| Self::custom_attribute(account, item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the strongly-typed system attribute value of `item` corresponding to `key`. + /// + /// By default this just attempts to use `system_attribute`. + fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { + key.using_encoded(|d| Self::system_attribute(item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } @@ -167,19 +194,32 @@ impl< fn owner(item: &Self::ItemId) -> Option { >::owner(&A::get(), item) } - fn attribute( - item: &Self::ItemId, - namespace: &AttributeNamespace, - key: &[u8], - ) -> Option> { - >::attribute(&A::get(), item, namespace, key) + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::attribute(&A::get(), item, key) + } + fn custom_attribute(account: &AccountId, item: &Self::ItemId, key: &[u8]) -> Option> { + >::custom_attribute(account, &A::get(), item, key) } - fn typed_attribute( + fn system_attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::system_attribute(&A::get(), item, key) + } + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_attribute(&A::get(), item, key) + } + fn typed_custom_attribute( + account: &AccountId, item: &Self::ItemId, - namespace: &AttributeNamespace, key: &K, ) -> Option { - >::typed_attribute(&A::get(), item, namespace, key) + >::typed_custom_attribute( + account, + &A::get(), + item, + key, + ) + } + fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_system_attribute(&A::get(), item, key) } fn can_transfer(item: &Self::ItemId) -> bool { >::can_transfer(&A::get(), item) diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index 9331f966822a6..5deb0c568f431 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -27,10 +27,7 @@ //! Implementations of these traits may be converted to implementations of corresponding //! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter. -use crate::{ - dispatch::{DispatchError, DispatchResult}, - traits::tokens::misc::AttributeNamespace, -}; +use crate::dispatch::{DispatchError, DispatchResult}; use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; @@ -61,7 +58,29 @@ pub trait Inspect { fn attribute( _collection: &Self::CollectionId, _item: &Self::ItemId, - _namespace: &AttributeNamespace, + _key: &[u8], + ) -> Option> { + None + } + + /// Returns the custom attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn custom_attribute( + _account: &AccountId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _key: &[u8], + ) -> Option> { + None + } + + /// Returns the system attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn system_attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, _key: &[u8], ) -> Option> { None @@ -74,10 +93,36 @@ pub trait Inspect { fn typed_attribute( collection: &Self::CollectionId, item: &Self::ItemId, - namespace: &AttributeNamespace, key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(collection, item, namespace, d)) + key.using_encoded(|d| Self::attribute(collection, item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the strongly-typed custom attribute value of `item` of `collection` corresponding to + /// `key`. + /// + /// By default this just attempts to use `custom_attribute`. + fn typed_custom_attribute( + account: &AccountId, + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::custom_attribute(account, collection, item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the strongly-typed system attribute value of `item` of `collection` corresponding to + /// `key`. + /// + /// By default this just attempts to use `system_attribute`. + fn typed_system_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::system_attribute(collection, item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) }