difftreelog
feat(rpc) token children
in: master
9 files changed
client/rpc/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/>.1617use std::sync::Arc;1819use codec::{Decode, Encode};20use jsonrpc_core::{Error as RpcError, ErrorCode, Result};21use jsonrpc_derive::rpc;22use up_data_structs::{23 RpcCollection, CollectionId, CollectionStats, CollectionLimits, TokenId, Property,24 PropertyKeyPermission, TokenData,25};26use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};27use sp_blockchain::HeaderBackend;28use up_rpc::UniqueApi as UniqueRuntimeApi;2930// RMRK31use rmrk_rpc::RmrkApi as RmrkRuntimeApi;32use up_data_structs::{33 RmrkCollectionId, RmrkNftId, RmrkBaseId, RmrkNftChild, RmrkThemeName, RmrkResourceId,34};3536pub use rmrk_unique_rpc::RmrkApi;3738#[rpc]39pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {40 #[rpc(name = "unique_accountTokens")]41 fn account_tokens(42 &self,43 collection: CollectionId,44 account: CrossAccountId,45 at: Option<BlockHash>,46 ) -> Result<Vec<TokenId>>;47 #[rpc(name = "unique_collectionTokens")]48 fn collection_tokens(49 &self,50 collection: CollectionId,51 at: Option<BlockHash>,52 ) -> Result<Vec<TokenId>>;53 #[rpc(name = "unique_tokenExists")]54 fn token_exists(55 &self,56 collection: CollectionId,57 token: TokenId,58 at: Option<BlockHash>,59 ) -> Result<bool>;6061 #[rpc(name = "unique_tokenOwner")]62 fn token_owner(63 &self,64 collection: CollectionId,65 token: TokenId,66 at: Option<BlockHash>,67 ) -> Result<Option<CrossAccountId>>;68 #[rpc(name = "unique_topmostTokenOwner")]69 fn topmost_token_owner(70 &self,71 collection: CollectionId,72 token: TokenId,73 at: Option<BlockHash>,74 ) -> Result<Option<CrossAccountId>>;7576 #[rpc(name = "unique_collectionProperties")]77 fn collection_properties(78 &self,79 collection: CollectionId,80 keys: Option<Vec<String>>,81 at: Option<BlockHash>,82 ) -> Result<Vec<Property>>;8384 #[rpc(name = "unique_tokenProperties")]85 fn token_properties(86 &self,87 collection: CollectionId,88 token_id: TokenId,89 keys: Option<Vec<String>>,90 at: Option<BlockHash>,91 ) -> Result<Vec<Property>>;9293 #[rpc(name = "unique_propertyPermissions")]94 fn property_permissions(95 &self,96 collection: CollectionId,97 keys: Option<Vec<String>>,98 at: Option<BlockHash>,99 ) -> Result<Vec<PropertyKeyPermission>>;100101 #[rpc(name = "unique_tokenData")]102 fn token_data(103 &self,104 collection: CollectionId,105 token_id: TokenId,106 keys: Option<Vec<String>>,107 at: Option<BlockHash>,108 ) -> Result<TokenData<CrossAccountId>>;109110 #[rpc(name = "unique_totalSupply")]111 fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;112 #[rpc(name = "unique_accountBalance")]113 fn account_balance(114 &self,115 collection: CollectionId,116 account: CrossAccountId,117 at: Option<BlockHash>,118 ) -> Result<u32>;119 #[rpc(name = "unique_balance")]120 fn balance(121 &self,122 collection: CollectionId,123 account: CrossAccountId,124 token: TokenId,125 at: Option<BlockHash>,126 ) -> Result<String>;127 #[rpc(name = "unique_allowance")]128 fn allowance(129 &self,130 collection: CollectionId,131 sender: CrossAccountId,132 spender: CrossAccountId,133 token: TokenId,134 at: Option<BlockHash>,135 ) -> Result<String>;136137 #[rpc(name = "unique_adminlist")]138 fn adminlist(139 &self,140 collection: CollectionId,141 at: Option<BlockHash>,142 ) -> Result<Vec<CrossAccountId>>;143 #[rpc(name = "unique_allowlist")]144 fn allowlist(145 &self,146 collection: CollectionId,147 at: Option<BlockHash>,148 ) -> Result<Vec<CrossAccountId>>;149 #[rpc(name = "unique_allowed")]150 fn allowed(151 &self,152 collection: CollectionId,153 user: CrossAccountId,154 at: Option<BlockHash>,155 ) -> Result<bool>;156 #[rpc(name = "unique_lastTokenId")]157 fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;158 #[rpc(name = "unique_collectionById")]159 fn collection_by_id(160 &self,161 collection: CollectionId,162 at: Option<BlockHash>,163 ) -> Result<Option<RpcCollection<AccountId>>>;164 #[rpc(name = "unique_collectionStats")]165 fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;166167 #[rpc(name = "unique_nextSponsored")]168 fn next_sponsored(169 &self,170 collection: CollectionId,171 account: CrossAccountId,172 token: TokenId,173 at: Option<BlockHash>,174 ) -> Result<Option<u64>>;175 #[rpc(name = "unique_effectiveCollectionLimits")]176 fn effective_collection_limits(177 &self,178 collection_id: CollectionId,179 at: Option<BlockHash>,180 ) -> Result<Option<CollectionLimits>>;181}182183mod rmrk_unique_rpc {184 use super::*;185186 #[rpc(server)]187 pub trait RmrkApi<188 BlockHash,189 AccountId,190 CollectionInfo,191 NftInfo,192 ResourceInfo,193 PropertyInfo,194 BaseInfo,195 PartType,196 Theme,197 >198 {199 #[rpc(name = "rmrk_lastCollectionIdx")]200 /// Get the latest created collection id201 fn last_collection_idx(&self, at: Option<BlockHash>) -> Result<RmrkCollectionId>;202203 #[rpc(name = "rmrk_collectionById")]204 /// Get collection by id205 fn collection_by_id(206 &self,207 id: RmrkCollectionId,208 at: Option<BlockHash>,209 ) -> Result<Option<CollectionInfo>>;210211 #[rpc(name = "rmrk_nftById")]212 /// Get NFT by collection id and NFT id213 fn nft_by_id(214 &self,215 collection_id: RmrkCollectionId,216 nft_id: RmrkNftId,217 at: Option<BlockHash>,218 ) -> Result<Option<NftInfo>>;219220 #[rpc(name = "rmrk_accountTokens")]221 /// Get tokens owned by an account in a collection222 fn account_tokens(223 &self,224 account_id: AccountId,225 collection_id: RmrkCollectionId,226 at: Option<BlockHash>,227 ) -> Result<Vec<RmrkNftId>>;228229 #[rpc(name = "rmrk_nftChildren")]230 /// Get NFT children231 fn nft_children(232 &self,233 collection_id: RmrkCollectionId,234 nft_id: RmrkNftId,235 at: Option<BlockHash>,236 ) -> Result<Vec<RmrkNftChild>>;237238 #[rpc(name = "rmrk_collectionProperties")]239 /// Get collection properties240 fn collection_properties(241 &self,242 collection_id: RmrkCollectionId,243 filter_keys: Option<Vec<String>>,244 at: Option<BlockHash>,245 ) -> Result<Vec<PropertyInfo>>;246247 #[rpc(name = "rmrk_nftProperties")]248 /// Get NFT properties249 fn nft_properties(250 &self,251 collection_id: RmrkCollectionId,252 nft_id: RmrkNftId,253 filter_keys: Option<Vec<String>>,254 at: Option<BlockHash>,255 ) -> Result<Vec<PropertyInfo>>;256257 #[rpc(name = "rmrk_nftResources")]258 /// Get NFT resources259 fn nft_resources(260 &self,261 collection_id: RmrkCollectionId,262 nft_id: RmrkNftId,263 at: Option<BlockHash>,264 ) -> Result<Vec<ResourceInfo>>;265266 #[rpc(name = "rmrk_nftResourcePriorities")]267 /// Get NFT resource priorities268 fn nft_resource_priorities(269 &self,270 collection_id: RmrkCollectionId,271 nft_id: RmrkNftId,272 at: Option<BlockHash>,273 ) -> Result<Vec<RmrkResourceId>>;274275 #[rpc(name = "rmrk_base")]276 /// Get base info277 fn base(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Option<BaseInfo>>;278279 #[rpc(name = "rmrk_baseParts")]280 /// Get all Base's parts281 fn base_parts(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Vec<PartType>>;282283 #[rpc(name = "rmrk_themeNames")]284 fn theme_names(285 &self,286 base_id: RmrkBaseId,287 at: Option<BlockHash>,288 ) -> Result<Vec<RmrkThemeName>>;289290 #[rpc(name = "rmrk_themes")]291 fn theme(292 &self,293 base_id: RmrkBaseId,294 theme_name: String,295 filter_keys: Option<Vec<String>>,296 at: Option<BlockHash>,297 ) -> Result<Option<Theme>>;298 }299}300301pub struct Unique<C, P> {302 client: Arc<C>,303 _marker: std::marker::PhantomData<P>,304}305306impl<C, P> Unique<C, P> {307 pub fn new(client: Arc<C>) -> Self {308 Self {309 client,310 _marker: Default::default(),311 }312 }313}314315pub enum Error {316 RuntimeError,317}318319impl From<Error> for i64 {320 fn from(e: Error) -> i64 {321 match e {322 Error::RuntimeError => 1,323 }324 }325}326327macro_rules! pass_method {328 (329 $method_name:ident(330 $($(#[map(|$map_arg:ident| $map:expr)])? $name:ident: $ty:ty),* $(,)?331 ) -> $result:ty $(=> $mapper:expr)?,332 //$runtime_name:ident $(<$($lt: tt),+>)*333 $runtime_api_macro:ident334 $(; changed_in $ver:expr, $changed_method_name:ident ($($changed_name:expr), * $(,)?) => $fixer:expr)*335 ) => {336 fn $method_name(337 &self,338 $(339 $name: $ty,340 )*341 at: Option<<Block as BlockT>::Hash>,342 ) -> Result<$result> {343 let api = self.client.runtime_api();344 let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));345 let _api_version = if let Ok(Some(api_version)) =346 api.api_version::<$runtime_api_macro!()>(&at)347 {348 api_version349 } else {350 // unreachable for our runtime351 return Err(RpcError {352 code: ErrorCode::InvalidParams,353 message: "Api is not available".into(),354 data: None,355 })356 };357358 let result = $(if _api_version < $ver {359 api.$changed_method_name(&at, $($changed_name),*).map(|r| r.map($fixer))360 } else)*361 { api.$method_name(&at, $($((|$map_arg: $ty| $map))? ($name)),*) };362363 let result = result.map_err(|e| RpcError {364 code: ErrorCode::ServerError(Error::RuntimeError.into()),365 message: "Unable to query".into(),366 data: Some(format!("{:?}", e).into()),367 })?;368 result.map_err(|e| RpcError {369 code: ErrorCode::InvalidParams,370 message: "Runtime returned error".into(),371 data: Some(format!("{:?}", e).into()),372 })$(.map($mapper))?373 }374 };375}376377macro_rules! unique_api {378 () => {379 dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>380 };381}382383macro_rules! rmrk_api {384 () => {385 dyn RmrkRuntimeApi<Block, AccountId, CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme>386 };387}388389#[allow(deprecated)]390impl<C, Block, CrossAccountId, AccountId>391 UniqueApi<<Block as BlockT>::Hash, CrossAccountId, AccountId> for Unique<C, Block>392where393 Block: BlockT,394 AccountId: Decode,395 C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,396 C::Api: UniqueRuntimeApi<Block, CrossAccountId, AccountId>,397 CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,398{399 pass_method!(400 account_tokens(collection: CollectionId, account: CrossAccountId) -> Vec<TokenId>, unique_api401 );402 pass_method!(403 collection_tokens(collection: CollectionId) -> Vec<TokenId>, unique_api404 );405 pass_method!(406 token_exists(collection: CollectionId, token: TokenId) -> bool, unique_api407 );408 pass_method!(409 token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api410 );411 pass_method!(412 topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api413 );414 pass_method!(total_supply(collection: CollectionId) -> u32, unique_api);415 pass_method!(account_balance(collection: CollectionId, account: CrossAccountId) -> u32, unique_api);416 pass_method!(balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> String => |v| v.to_string(), unique_api);417 pass_method!(418 allowance(collection: CollectionId, sender: CrossAccountId, spender: CrossAccountId, token: TokenId) -> String => |v| v.to_string(),419 unique_api420 );421422 pass_method!(collection_properties(423 collection: CollectionId,424425 #[map(|keys| string_keys_to_bytes_keys(keys))]426 keys: Option<Vec<String>>427 ) -> Vec<Property>, unique_api);428429 pass_method!(token_properties(430 collection: CollectionId,431 token_id: TokenId,432433 #[map(|keys| string_keys_to_bytes_keys(keys))]434 keys: Option<Vec<String>>435 ) -> Vec<Property>, unique_api);436437 pass_method!(property_permissions(438 collection: CollectionId,439440 #[map(|keys| string_keys_to_bytes_keys(keys))]441 keys: Option<Vec<String>>442 ) -> Vec<PropertyKeyPermission>, unique_api);443444 pass_method!(token_data(445 collection: CollectionId,446 token_id: TokenId,447448 #[map(|keys| string_keys_to_bytes_keys(keys))]449 keys: Option<Vec<String>>,450 ) -> TokenData<CrossAccountId>, unique_api);451452 pass_method!(adminlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);453 pass_method!(allowlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);454 pass_method!(allowed(collection: CollectionId, user: CrossAccountId) -> bool, unique_api);455 pass_method!(last_token_id(collection: CollectionId) -> TokenId, unique_api);456 pass_method!(collection_by_id(collection: CollectionId) -> Option<RpcCollection<AccountId>>, unique_api);457 pass_method!(collection_stats() -> CollectionStats, unique_api);458 pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>, unique_api);459 pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);460}461462#[allow(deprecated)]463impl<464 C,465 Block,466 AccountId,467 CollectionInfo,468 NftInfo,469 ResourceInfo,470 PropertyInfo,471 BaseInfo,472 PartType,473 Theme,474 >475 rmrk_unique_rpc::RmrkApi<476 <Block as BlockT>::Hash,477 AccountId,478 CollectionInfo,479 NftInfo,480 ResourceInfo,481 PropertyInfo,482 BaseInfo,483 PartType,484 Theme,485 > for Unique<C, Block>486where487 C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,488 C::Api: RmrkRuntimeApi<489 Block,490 AccountId,491 CollectionInfo,492 NftInfo,493 ResourceInfo,494 PropertyInfo,495 BaseInfo,496 PartType,497 Theme,498 >,499 AccountId: Decode + Encode,500 CollectionInfo: Decode,501 NftInfo: Decode,502 ResourceInfo: Decode,503 PropertyInfo: Decode,504 BaseInfo: Decode,505 PartType: Decode,506 Theme: Decode,507 Block: BlockT,508{509 pass_method!(last_collection_idx() -> RmrkCollectionId, rmrk_api);510 pass_method!(collection_by_id(id: RmrkCollectionId) -> Option<CollectionInfo>, rmrk_api);511 pass_method!(nft_by_id(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Option<NftInfo>, rmrk_api);512 pass_method!(account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Vec<RmrkNftId>, rmrk_api);513 pass_method!(nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<RmrkNftChild>, rmrk_api);514 pass_method!(515 collection_properties(516 collection_id: RmrkCollectionId,517518 #[map(|keys| string_keys_to_bytes_keys(keys))]519 filter_keys: Option<Vec<String>>520 ) -> Vec<PropertyInfo>,521 rmrk_api522 );523 pass_method!(524 nft_properties(525 collection_id: RmrkCollectionId,526 nft_id: RmrkNftId,527528 #[map(|keys| string_keys_to_bytes_keys(keys))]529 filter_keys: Option<Vec<String>>530 ) -> Vec<PropertyInfo>,531 rmrk_api532 );533 pass_method!(nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<ResourceInfo>, rmrk_api);534 pass_method!(nft_resource_priorities(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<RmrkResourceId>, rmrk_api);535 pass_method!(base(base_id: RmrkBaseId) -> Option<BaseInfo>, rmrk_api);536 pass_method!(base_parts(base_id: RmrkBaseId) -> Vec<PartType>, rmrk_api);537 pass_method!(theme_names(base_id: RmrkBaseId) -> Vec<RmrkThemeName>, rmrk_api);538 pass_method!(539 theme(540 base_id: RmrkBaseId,541542 #[map(|n| n.into_bytes())]543 theme_name: String,544545 #[map(|keys| string_keys_to_bytes_keys(keys))]546 filter_keys: Option<Vec<String>>547 ) -> Option<Theme>, rmrk_api);548}549550fn string_keys_to_bytes_keys(keys: Option<Vec<String>>) -> Option<Vec<Vec<u8>>> {551 keys.map(|keys| keys.into_iter().map(|key| key.into_bytes()).collect())552}pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -40,6 +40,7 @@
MAX_TOKEN_PREFIX_LENGTH,
COLLECTION_ADMINS_LIMIT,
TokenId,
+ TokenChild,
CollectionStats,
MAX_TOKEN_OWNERSHIP,
CollectionMode,
@@ -502,6 +503,7 @@
CollectionStats,
CollectionId,
TokenId,
+ TokenChild,
PhantomType<(
TokenData<T::CrossAccountId>,
RpcCollection<T::AccountId>,
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -22,7 +22,7 @@
use up_data_structs::{
AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,
- PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty,
+ PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,
};
use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
use pallet_common::{
@@ -988,6 +988,15 @@
.is_some()
}
+ pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {
+ <TokenChildren<T>>::iter_prefix((collection_id, token_id))
+ .map(|((child_collection_id, child_id), _)| TokenChild {
+ collection: child_collection_id,
+ token: child_id,
+ })
+ .collect()
+ }
+
/// Delegated to `create_multiple_items`
pub fn create_item(
collection: &NonfungibleHandle<T>,
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -585,6 +585,14 @@
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+// todo possibly rename to be used generally as an address pair
+pub struct TokenChild {
+ pub token: TokenId,
+ pub collection: CollectionId,
+}
+
+#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct CollectionStats {
pub created: u32,
pub destroyed: u32,
primitives/rpc/src/lib.rsdiffbeforeafterboth--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -18,7 +18,7 @@
use up_data_structs::{
CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits, Property,
- PropertyKeyPermission, TokenData,
+ PropertyKeyPermission, TokenData, TokenChild,
};
use sp_std::vec::Vec;
use codec::Decode;
@@ -41,6 +41,7 @@
fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
+ fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>>;
fn collection_properties(collection: CollectionId, properties: Option<Vec<Vec<u8>>>) -> Result<Vec<Property>>;
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -29,7 +29,9 @@
Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
}
-
+ fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
+ Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
+ }
fn collection_properties(
collection: CollectionId,
keys: Option<Vec<Vec<u8>>>
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -50,6 +50,7 @@
allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'),
tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
topmostTokenOwner: fun('Get token owner, in case of nested token - find parent recursive', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
+ tokenChildren: fun('Get tokens nested directly into the token', [collectionParam, tokenParam], 'Vec<UpDataStructsTokenChild>'),
constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),
variableMetadata: fun('Get token variable metadata', [collectionParam, tokenParam], 'Vec<u8>'),
collectionProperties: fun(
tests/src/nesting/nest.test.tsdiffbeforeafterboth--- a/tests/src/nesting/nest.test.ts
+++ b/tests/src/nesting/nest.test.ts
@@ -8,6 +8,7 @@
createItemExpectSuccess,
enableAllowListExpectSuccess,
enablePublicMintingExpectSuccess,
+ getTokenChildren,
getTokenOwner,
getTopmostTokenOwner,
normalizeAccountId,
@@ -89,6 +90,63 @@
});
});
+ it('Checks token children', async () => {
+ await usingApi(async api => {
+ const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: 'Owner'});
+ const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+
+ const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');
+ const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)};
+ let children = await getTokenChildren(api, collectionA, targetToken);
+ expect(children.length).to.be.equal(0, 'Children length check at creation');
+
+ // Create a nested NFT token
+ const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress);
+ children = await getTokenChildren(api, collectionA, targetToken);
+ expect(children.length).to.be.equal(1, 'Children length check at nesting #1');
+ expect(children).to.have.deep.members([
+ {token: tokenA, collection: collectionA},
+ ], 'Children contents check at nesting #1');
+
+ // Create then nest
+ const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT');
+ await transferExpectSuccess(collectionA, tokenB, alice, targetAddress);
+ children = await getTokenChildren(api, collectionA, targetToken);
+ expect(children.length).to.be.equal(2, 'Children length check at nesting #2');
+ expect(children).to.have.deep.members([
+ {token: tokenA, collection: collectionA},
+ {token: tokenB, collection: collectionA},
+ ], 'Children contents check at nesting #2');
+
+ // Move token B to a different user outside the nesting tree
+ await transferFromExpectSuccess(collectionA, tokenB, alice, targetAddress, bob);
+ children = await getTokenChildren(api, collectionA, targetToken);
+ expect(children.length).to.be.equal(1, 'Children length check at unnesting');
+ expect(children).to.be.have.deep.members([
+ {token: tokenA, collection: collectionA},
+ ], 'Children contents check at unnesting');
+
+ // Create a fungible token in another collection and then nest
+ const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible');
+ await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible');
+ children = await getTokenChildren(api, collectionA, targetToken);
+ expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)');
+ expect(children).to.be.have.deep.members([
+ {token: tokenA, collection: collectionA},
+ {token: tokenC, collection: collectionB},
+ ], 'Children contents check at nesting #3 (from another collection)');
+
+ // Move the fungible token inside token A deeper in the nesting tree
+ await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible');
+ children = await getTokenChildren(api, collectionA, targetToken);
+ expect(children.length).to.be.equal(1, 'Children length check at deeper nesting');
+ expect(children).to.be.have.deep.members([
+ {token: tokenA, collection: collectionA},
+ ], 'Children contents check at deeper nesting');
+ });
+ });
+
// ---------- Non-Fungible ----------
it('NFT: allows an Owner to nest/unnest their token', async () => {
@@ -232,6 +290,20 @@
});
});
+ // TODO delete if this is actually wrong
+ // TODO remake all other nesting tests if this is right
+ it('Affirms that transfer is disallowed to transfer nested tokens', async () => {
+ await usingApi(async () => {
+ const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+ await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: 'Owner'});
+
+ const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');
+ const tokenB = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)});
+
+ await transferExpectFailure(collection, tokenB, alice, bob);
+ });
+ });
+
it('Disallows excessive token nesting', async () => {
await usingApi(async api => {
const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -28,6 +28,7 @@
import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api';
import {hexToStr, strToUTF16, utf16ToStr} from './util';
import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup';
+import {UpDataStructsTokenChild} from '../interfaces';
chai.use(chaiAsPromised);
const expect = chai.expect;
@@ -1072,6 +1073,13 @@
if (owner == null) throw new Error('owner == null');
return normalizeAccountId(owner);
}
+export async function getTokenChildren(
+ api: ApiPromise,
+ collectionId: number,
+ tokenId: number,
+): Promise<UpDataStructsTokenChild[]> {
+ return (await api.rpc.unique.tokenChildren(collectionId, tokenId)).toJSON() as any;
+}
export async function isTokenExists(
api: ApiPromise,
collectionId: number,