git.delta.rocks / unique-network / refs/commits / 762c5a79597b

difftreelog

feat(rpc) token children

Fahrrader2022-06-03parent: #fb4beb0.patch.diff
in: master

9 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
21use jsonrpc_derive::rpc;21use jsonrpc_derive::rpc;
22use up_data_structs::{22use up_data_structs::{
23 RpcCollection, CollectionId, CollectionStats, CollectionLimits, TokenId, Property,23 RpcCollection, CollectionId, CollectionStats, CollectionLimits, TokenId, Property,
24 PropertyKeyPermission, TokenData,24 PropertyKeyPermission, TokenData, TokenChild,
25};25};
26use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};26use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};
27use sp_blockchain::HeaderBackend;27use sp_blockchain::HeaderBackend;
72 token: TokenId,72 token: TokenId,
73 at: Option<BlockHash>,73 at: Option<BlockHash>,
74 ) -> Result<Option<CrossAccountId>>;74 ) -> Result<Option<CrossAccountId>>;
75 #[rpc(name = "unique_tokenChildren")]
76 fn token_children(
77 &self,
78 collection: CollectionId,
79 token: TokenId,
80 at: Option<BlockHash>,
81 ) -> Result<Vec<TokenChild>>;
7582
76 #[rpc(name = "unique_collectionProperties")]83 #[rpc(name = "unique_collectionProperties")]
77 fn collection_properties(84 fn collection_properties(
411 pass_method!(418 pass_method!(
412 topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api419 topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api
413 );420 );
421 pass_method!(token_children(collection: CollectionId, token: TokenId) -> Vec<TokenChild>, unique_api);
414 pass_method!(total_supply(collection: CollectionId) -> u32, unique_api);422 pass_method!(total_supply(collection: CollectionId) -> u32, unique_api);
415 pass_method!(account_balance(collection: CollectionId, account: CrossAccountId) -> u32, unique_api);423 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);424 pass_method!(balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> String => |v| v.to_string(), unique_api);
modifiedpallets/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>,
modifiedpallets/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>,
modifiedprimitives/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,
modifiedprimitives/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>>;
 
modifiedruntime/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>>>
modifiedtests/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(
modifiedtests/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'}});
modifiedtests/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,