From 4af011f418ede4689dcec68d61ebe3cd5fc98497 Mon Sep 17 00:00:00 2001
From: Daniel Shiposha <mrshiposha@gmail.com>
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>
---
 substrate/Cargo.lock                          | 11 +++
 substrate/Cargo.toml                          |  1 +
 substrate/bin/node/runtime/Cargo.toml         |  2 +
 substrate/bin/node/runtime/src/lib.rs         | 52 ++++++++++++-
 substrate/frame/nfts/runtime-api/Cargo.toml   | 28 +++++++
 substrate/frame/nfts/runtime-api/README.md    |  3 +
 substrate/frame/nfts/runtime-api/src/lib.rs   | 57 ++++++++++++++
 substrate/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 substrate/frame/nfts/runtime-api/Cargo.toml
 create mode 100644 substrate/frame/nfts/runtime-api/README.md
 create mode 100644 substrate/frame/nfts/runtime-api/src/lib.rs

diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock
index e485a651225..afe2b46ee5f 100644
--- a/substrate/Cargo.lock
+++ b/substrate/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/substrate/Cargo.toml b/substrate/Cargo.toml
index 2ae924bf6e1..b9f89365cd0 100644
--- a/substrate/Cargo.toml
+++ b/substrate/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/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml
index d7b42efd4d2..71fa9320a77 100644
--- a/substrate/bin/node/runtime/Cargo.toml
+++ b/substrate/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/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 1ed43c211f8..a332127fb8f 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/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<Block, AccountId, u32, u32> for Runtime {
+		fn owner(collection: u32, item: u32) -> Option<AccountId> {
+			<Nfts as Inspect<AccountId>>::owner(&collection, &item)
+		}
+
+		fn collection_owner(collection: u32) -> Option<AccountId> {
+			<Nfts as Inspect<AccountId>>::collection_owner(&collection)
+		}
+
+		fn attribute(
+			collection: u32,
+			item: u32,
+			key: Vec<u8>,
+		) -> Option<Vec<u8>> {
+			<Nfts as Inspect<AccountId>>::attribute(&collection, &item, &key)
+		}
+
+		fn custom_attribute(
+			account: AccountId,
+			collection: u32,
+			item: u32,
+			key: Vec<u8>,
+		) -> Option<Vec<u8>> {
+			<Nfts as Inspect<AccountId>>::custom_attribute(
+				&account,
+				&collection,
+				&item,
+				&key,
+			)
+		}
+
+		fn system_attribute(
+			collection: u32,
+			item: u32,
+			key: Vec<u8>,
+		) -> Option<Vec<u8>> {
+			<Nfts as Inspect<AccountId>>::system_attribute(&collection, &item, &key)
+		}
+
+		fn collection_attribute(collection: u32, key: Vec<u8>) -> Option<Vec<u8>> {
+			<Nfts as Inspect<AccountId>>::collection_attribute(&collection, &key)
+		}
+	}
+
 	impl pallet_mmr::primitives::MmrApi<
 		Block,
 		mmr::Hash,
diff --git a/substrate/frame/nfts/runtime-api/Cargo.toml b/substrate/frame/nfts/runtime-api/Cargo.toml
new file mode 100644
index 00000000000..29d79e77685
--- /dev/null
+++ b/substrate/frame/nfts/runtime-api/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "pallet-nfts-runtime-api"
+version = "4.0.0-dev"
+authors = ["Parity Technologies <admin@parity.io>"]
+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/substrate/frame/nfts/runtime-api/README.md b/substrate/frame/nfts/runtime-api/README.md
new file mode 100644
index 00000000000..289036d2c0d
--- /dev/null
+++ b/substrate/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/substrate/frame/nfts/runtime-api/src/lib.rs b/substrate/frame/nfts/runtime-api/src/lib.rs
new file mode 100644
index 00000000000..0c23d178107
--- /dev/null
+++ b/substrate/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<AccountId, CollectionId, ItemId>
+	where
+		AccountId: Encode + Decode,
+		CollectionId: Encode,
+		ItemId: Encode,
+	{
+		fn owner(collection: CollectionId, item: ItemId) -> Option<AccountId>;
+
+		fn collection_owner(collection: CollectionId) -> Option<AccountId>;
+
+		fn attribute(
+			collection: CollectionId,
+			item: ItemId,
+			key: Vec<u8>,
+		) -> Option<Vec<u8>>;
+
+		fn custom_attribute(
+			account: AccountId,
+			collection: CollectionId,
+			item: ItemId,
+			key: Vec<u8>,
+		) -> Option<Vec<u8>>;
+
+		fn system_attribute(
+			collection: CollectionId,
+			item: ItemId,
+			key: Vec<u8>,
+		) -> Option<Vec<u8>>;
+
+		fn collection_attribute(collection: CollectionId, key: Vec<u8>) -> Option<Vec<u8>>;
+	}
+}
diff --git a/substrate/frame/nfts/src/impl_nonfungibles.rs b/substrate/frame/nfts/src/impl_nonfungibles.rs
index ac02355ba29..ef6bbe7656e 100644
--- a/substrate/frame/nfts/src/impl_nonfungibles.rs
+++ b/substrate/frame/nfts/src/impl_nonfungibles.rs
@@ -50,18 +50,48 @@ impl<T: Config<I>, I: 'static> Inspect<<T as SystemConfig>::AccountId> for Palle
 	fn attribute(
 		collection: &Self::CollectionId,
 		item: &Self::ItemId,
-		namespace: &AttributeNamespace<<T as SystemConfig>::AccountId>,
 		key: &[u8],
 	) -> Option<Vec<u8>> {
 		if key.is_empty() {
 			// We make the empty key map to the item metadata value.
 			ItemMetadataOf::<T, I>::get(collection, item).map(|m| m.data.into())
 		} else {
+			let namespace = AttributeNamespace::CollectionOwner;
 			let key = BoundedSlice::<_, _>::try_from(key).ok()?;
 			Attribute::<T, I>::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<Vec<u8>> {
+		let namespace = Account::<T, I>::get((account, collection, item))
+			.map(|_| AttributeNamespace::ItemOwner)
+			.unwrap_or_else(|| AttributeNamespace::Account(account.clone()));
+
+		let key = BoundedSlice::<_, _>::try_from(key).ok()?;
+		Attribute::<T, I>::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<Vec<u8>> {
+		let namespace = AttributeNamespace::Pallet;
+		let key = BoundedSlice::<_, _>::try_from(key).ok()?;
+		Attribute::<T, I>::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/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs
index 36ef9ab0aa6..175d94324aa 100644
--- a/substrate/frame/support/src/traits/tokens/nonfungible_v2.rs
+++ b/substrate/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<AccountId> {
 	/// 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<Vec<u8>> {
+		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<AccountId>,
 		_key: &[u8],
 	) -> Option<Vec<u8>> {
 		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<Vec<u8>> {
+		None
+	}
+
 	/// Returns the strongly-typed attribute value of `item` corresponding to `key`.
 	///
 	/// By default this just attempts to use `attribute`.
-	fn typed_attribute<K: Encode, V: Decode>(
+	fn typed_attribute<K: Encode, V: Decode>(item: &Self::ItemId, key: &K) -> Option<V> {
+		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<K: Encode, V: Decode>(
+		account: &AccountId,
 		item: &Self::ItemId,
-		namespace: &AttributeNamespace<AccountId>,
 		key: &K,
 	) -> Option<V> {
-		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<K: Encode, V: Decode>(item: &Self::ItemId, key: &K) -> Option<V> {
+		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<AccountId> {
 		<F as nonfungibles::Inspect<AccountId>>::owner(&A::get(), item)
 	}
-	fn attribute(
-		item: &Self::ItemId,
-		namespace: &AttributeNamespace<AccountId>,
-		key: &[u8],
-	) -> Option<Vec<u8>> {
-		<F as nonfungibles::Inspect<AccountId>>::attribute(&A::get(), item, namespace, key)
+	fn attribute(item: &Self::ItemId, key: &[u8]) -> Option<Vec<u8>> {
+		<F as nonfungibles::Inspect<AccountId>>::attribute(&A::get(), item, key)
+	}
+	fn custom_attribute(account: &AccountId, item: &Self::ItemId, key: &[u8]) -> Option<Vec<u8>> {
+		<F as nonfungibles::Inspect<AccountId>>::custom_attribute(account, &A::get(), item, key)
 	}
-	fn typed_attribute<K: Encode, V: Decode>(
+	fn system_attribute(item: &Self::ItemId, key: &[u8]) -> Option<Vec<u8>> {
+		<F as nonfungibles::Inspect<AccountId>>::system_attribute(&A::get(), item, key)
+	}
+	fn typed_attribute<K: Encode, V: Decode>(item: &Self::ItemId, key: &K) -> Option<V> {
+		<F as nonfungibles::Inspect<AccountId>>::typed_attribute(&A::get(), item, key)
+	}
+	fn typed_custom_attribute<K: Encode, V: Decode>(
+		account: &AccountId,
 		item: &Self::ItemId,
-		namespace: &AttributeNamespace<AccountId>,
 		key: &K,
 	) -> Option<V> {
-		<F as nonfungibles::Inspect<AccountId>>::typed_attribute(&A::get(), item, namespace, key)
+		<F as nonfungibles::Inspect<AccountId>>::typed_custom_attribute(
+			account,
+			&A::get(),
+			item,
+			key,
+		)
+	}
+	fn typed_system_attribute<K: Encode, V: Decode>(item: &Self::ItemId, key: &K) -> Option<V> {
+		<F as nonfungibles::Inspect<AccountId>>::typed_system_attribute(&A::get(), item, key)
 	}
 	fn can_transfer(item: &Self::ItemId) -> bool {
 		<F as nonfungibles::Inspect<AccountId>>::can_transfer(&A::get(), item)
diff --git a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs b/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs
index 9331f966822..5deb0c568f4 100644
--- a/substrate/frame/support/src/traits/tokens/nonfungibles_v2.rs
+++ b/substrate/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<AccountId> {
 	fn attribute(
 		_collection: &Self::CollectionId,
 		_item: &Self::ItemId,
-		_namespace: &AttributeNamespace<AccountId>,
+		_key: &[u8],
+	) -> Option<Vec<u8>> {
+		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<Vec<u8>> {
+		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<Vec<u8>> {
 		None
@@ -74,10 +93,36 @@ pub trait Inspect<AccountId> {
 	fn typed_attribute<K: Encode, V: Decode>(
 		collection: &Self::CollectionId,
 		item: &Self::ItemId,
-		namespace: &AttributeNamespace<AccountId>,
 		key: &K,
 	) -> Option<V> {
-		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<K: Encode, V: Decode>(
+		account: &AccountId,
+		collection: &Self::CollectionId,
+		item: &Self::ItemId,
+		key: &K,
+	) -> Option<V> {
+		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<K: Encode, V: Decode>(
+		collection: &Self::CollectionId,
+		item: &Self::ItemId,
+		key: &K,
+	) -> Option<V> {
+		key.using_encoded(|d| Self::system_attribute(collection, item, d))
 			.and_then(|v| V::decode(&mut &v[..]).ok())
 	}
 
-- 
GitLab