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
40 MAX_TOKEN_PREFIX_LENGTH,40 MAX_TOKEN_PREFIX_LENGTH,
41 COLLECTION_ADMINS_LIMIT,41 COLLECTION_ADMINS_LIMIT,
42 TokenId,42 TokenId,
43 TokenChild,
43 CollectionStats,44 CollectionStats,
44 MAX_TOKEN_OWNERSHIP,45 MAX_TOKEN_OWNERSHIP,
45 CollectionMode,46 CollectionMode,
502 CollectionStats,503 CollectionStats,
503 CollectionId,504 CollectionId,
504 TokenId,505 TokenId,
506 TokenChild,
505 PhantomType<(507 PhantomType<(
506 TokenData<T::CrossAccountId>,508 TokenData<T::CrossAccountId>,
507 RpcCollection<T::AccountId>,509 RpcCollection<T::AccountId>,
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
22use up_data_structs::{22use up_data_structs::{
23 AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,23 AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
24 mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,24 mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,
25 PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty,25 PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,
26};26};
27use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};27use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
28use pallet_common::{28use pallet_common::{
988 .is_some()988 .is_some()
989 }989 }
990
991 pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {
992 <TokenChildren<T>>::iter_prefix((collection_id, token_id))
993 .map(|((child_collection_id, child_id), _)| TokenChild {
994 collection: child_collection_id,
995 token: child_id,
996 })
997 .collect()
998 }
990999
991 /// Delegated to `create_multiple_items`1000 /// Delegated to `create_multiple_items`
992 pub fn create_item(1001 pub fn create_item(
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
583 }583 }
584}584}
585
586#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
587#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
588// todo possibly rename to be used generally as an address pair
589pub struct TokenChild {
590 pub token: TokenId,
591 pub collection: CollectionId,
592}
585593
586#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]594#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
587#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]595#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
1818
19use up_data_structs::{19use up_data_structs::{
20 CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits, Property,20 CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits, Property,
21 PropertyKeyPermission, TokenData,21 PropertyKeyPermission, TokenData, TokenChild,
22};22};
23use sp_std::vec::Vec;23use sp_std::vec::Vec;
24use codec::Decode;24use codec::Decode;
4141
42 fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;42 fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
43 fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;43 fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
44 fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>>;
4445
45 fn collection_properties(collection: CollectionId, properties: Option<Vec<Vec<u8>>>) -> Result<Vec<Property>>;46 fn collection_properties(collection: CollectionId, properties: Option<Vec<Vec<u8>>>) -> Result<Vec<Property>>;
4647
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
2929
30 Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))30 Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
31 }31 }
3232 fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
33 Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
34 }
33 fn collection_properties(35 fn collection_properties(
34 collection: CollectionId,36 collection: CollectionId,
35 keys: Option<Vec<Vec<u8>>>37 keys: Option<Vec<Vec<u8>>>
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
50 allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'),50 allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'),
51 tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),51 tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
52 topmostTokenOwner: fun('Get token owner, in case of nested token - find parent recursive', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),52 topmostTokenOwner: fun('Get token owner, in case of nested token - find parent recursive', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
53 tokenChildren: fun('Get tokens nested directly into the token', [collectionParam, tokenParam], 'Vec<UpDataStructsTokenChild>'),
53 constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),54 constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),
54 variableMetadata: fun('Get token variable metadata', [collectionParam, tokenParam], 'Vec<u8>'),55 variableMetadata: fun('Get token variable metadata', [collectionParam, tokenParam], 'Vec<u8>'),
55 collectionProperties: fun(56 collectionProperties: fun(
modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
8 createItemExpectSuccess,8 createItemExpectSuccess,
9 enableAllowListExpectSuccess,9 enableAllowListExpectSuccess,
10 enablePublicMintingExpectSuccess,10 enablePublicMintingExpectSuccess,
11 getTokenChildren,
11 getTokenOwner,12 getTokenOwner,
12 getTopmostTokenOwner,13 getTopmostTokenOwner,
13 normalizeAccountId,14 normalizeAccountId,
89 });90 });
90 });91 });
92
93 it('Checks token children', async () => {
94 await usingApi(async api => {
95 const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
96 await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: 'Owner'});
97 const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
98
99 const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');
100 const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)};
101 let children = await getTokenChildren(api, collectionA, targetToken);
102 expect(children.length).to.be.equal(0, 'Children length check at creation');
103
104 // Create a nested NFT token
105 const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress);
106 children = await getTokenChildren(api, collectionA, targetToken);
107 expect(children.length).to.be.equal(1, 'Children length check at nesting #1');
108 expect(children).to.have.deep.members([
109 {token: tokenA, collection: collectionA},
110 ], 'Children contents check at nesting #1');
111
112 // Create then nest
113 const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT');
114 await transferExpectSuccess(collectionA, tokenB, alice, targetAddress);
115 children = await getTokenChildren(api, collectionA, targetToken);
116 expect(children.length).to.be.equal(2, 'Children length check at nesting #2');
117 expect(children).to.have.deep.members([
118 {token: tokenA, collection: collectionA},
119 {token: tokenB, collection: collectionA},
120 ], 'Children contents check at nesting #2');
121
122 // Move token B to a different user outside the nesting tree
123 await transferFromExpectSuccess(collectionA, tokenB, alice, targetAddress, bob);
124 children = await getTokenChildren(api, collectionA, targetToken);
125 expect(children.length).to.be.equal(1, 'Children length check at unnesting');
126 expect(children).to.be.have.deep.members([
127 {token: tokenA, collection: collectionA},
128 ], 'Children contents check at unnesting');
129
130 // Create a fungible token in another collection and then nest
131 const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible');
132 await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible');
133 children = await getTokenChildren(api, collectionA, targetToken);
134 expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)');
135 expect(children).to.be.have.deep.members([
136 {token: tokenA, collection: collectionA},
137 {token: tokenC, collection: collectionB},
138 ], 'Children contents check at nesting #3 (from another collection)');
139
140 // Move the fungible token inside token A deeper in the nesting tree
141 await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible');
142 children = await getTokenChildren(api, collectionA, targetToken);
143 expect(children.length).to.be.equal(1, 'Children length check at deeper nesting');
144 expect(children).to.be.have.deep.members([
145 {token: tokenA, collection: collectionA},
146 ], 'Children contents check at deeper nesting');
147 });
148 });
91149
92 // ---------- Non-Fungible ----------150 // ---------- Non-Fungible ----------
93151
232 });290 });
233 });291 });
292
293 // TODO delete if this is actually wrong
294 // TODO remake all other nesting tests if this is right
295 it('Affirms that transfer is disallowed to transfer nested tokens', async () => {
296 await usingApi(async () => {
297 const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
298 await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: 'Owner'});
299
300 const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');
301 const tokenB = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)});
302
303 await transferExpectFailure(collection, tokenB, alice, bob);
304 });
305 });
234306
235 it('Disallows excessive token nesting', async () => {307 it('Disallows excessive token nesting', async () => {
236 await usingApi(async api => {308 await usingApi(async api => {
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
28import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api';28import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api';
29import {hexToStr, strToUTF16, utf16ToStr} from './util';29import {hexToStr, strToUTF16, utf16ToStr} from './util';
30import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup';30import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup';
31import {UpDataStructsTokenChild} from '../interfaces';
3132
32chai.use(chaiAsPromised);33chai.use(chaiAsPromised);
33const expect = chai.expect;34const expect = chai.expect;
1072 if (owner == null) throw new Error('owner == null');1073 if (owner == null) throw new Error('owner == null');
1073 return normalizeAccountId(owner);1074 return normalizeAccountId(owner);
1074}1075}
1076export async function getTokenChildren(
1077 api: ApiPromise,
1078 collectionId: number,
1079 tokenId: number,
1080): Promise<UpDataStructsTokenChild[]> {
1081 return (await api.rpc.unique.tokenChildren(collectionId, tokenId)).toJSON() as any;
1082}
1075export async function isTokenExists(1083export async function isTokenExists(
1076 api: ApiPromise,1084 api: ApiPromise,
1077 collectionId: number,1085 collectionId: number,