From da952fd3c8fb1ade16a77475aa38ff105d942687 Mon Sep 17 00:00:00 2001 From: PraetorP Date: Mon, 11 Jul 2022 06:38:48 +0000 Subject: [PATCH] initial commit --- --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -70,6 +70,15 @@ token: TokenId, at: Option, ) -> Result>; + + #[method(name = "unique_tokenOwners")] + fn token_owners( + &self, + collection: CollectionId, + token: TokenId, + at: Option, + ) -> Result>; + #[method(name = "unique_topmostTokenOwner")] fn topmost_token_owner( &self, @@ -473,6 +482,7 @@ pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option, unique_api); pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option, unique_api); pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option, unique_api); + pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec, unique_api); } #[allow(deprecated)] --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -1748,6 +1748,9 @@ /// * `token` - The token for which you need to find out the owner. fn token_owner(&self, token: TokenId) -> Option; + /// Token owners + fn token_owners(&self, token: TokenId) -> Vec; + /// Get the value of the token property by key. /// /// * `token` - Token with the property to get. --- a/pallets/fungible/src/common.rs +++ b/pallets/fungible/src/common.rs @@ -362,6 +362,10 @@ None } + fn token_owners(&self, token: TokenId) -> Vec { + >::token_owners(self.id, token).unwrap_or_else(|| vec![]) + } + fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option { None } --- a/pallets/fungible/src/lib.rs +++ b/pallets/fungible/src/lib.rs @@ -95,7 +95,7 @@ use pallet_evm_coder_substrate::WithRecorder; use sp_core::H160; use sp_runtime::{ArithmeticError, DispatchError, DispatchResult}; -use sp_std::{collections::btree_map::BTreeMap}; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; pub use pallet::*; @@ -613,4 +613,20 @@ nesting_budget, ) } + + pub fn token_owners( + collection: CollectionId, + _token: TokenId, + ) -> Option> { + let res: Vec = >::iter_prefix((collection,)) + .map(|r| r.0) + .take(10) + .collect(); + + if res.len() == 0 { + None + } else { + Some(res) + } + } } --- a/pallets/nonfungible/src/common.rs +++ b/pallets/nonfungible/src/common.rs @@ -26,7 +26,7 @@ weights::WeightInfo as _, }; use sp_runtime::DispatchError; -use sp_std::vec::Vec; +use sp_std::{vec::Vec, vec}; use crate::{ AccountBalance, Allowance, Config, CreateItemData, Error, NonfungibleHandle, Owned, Pallet, @@ -422,6 +422,10 @@ >::get((self.id, token)).map(|t| t.owner) } + fn token_owners(&self, token: TokenId) -> Vec { + self.token_owner(token).map_or_else(|| vec![], |t| vec![t]) + } + fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option { >::token_properties((self.id, token_id)) .get(key) --- a/pallets/refungible/src/common.rs +++ b/pallets/refungible/src/common.rs @@ -438,6 +438,10 @@ >::token_owner(self.id, token) } + fn token_owners(&self, token: TokenId) -> Vec { + >::token_owners(self.id, token).unwrap_or_else(|| vec![]) + } + fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option { None } --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -1134,4 +1134,20 @@ ) -> DispatchResult { >::set_token_property_permissions(collection, sender, property_permissions) } + + pub fn token_owners( + collection_id: CollectionId, + token: TokenId, + ) -> Option> { + let res: Vec = >::iter_prefix((collection_id, token)) + .map(|r| r.0) + .take(10) + .collect(); + + if res.len() == 0 { + None + } else { + Some(res) + } + } } --- a/primitives/rpc/src/lib.rs +++ b/primitives/rpc/src/lib.rs @@ -81,5 +81,6 @@ fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result>; fn effective_collection_limits(collection_id: CollectionId) -> Result>; fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Result>; + fn token_owners(collection: CollectionId, token: TokenId) -> Result>; } } --- a/runtime/common/src/runtime_apis.rs +++ b/runtime/common/src/runtime_apis.rs @@ -40,6 +40,11 @@ fn token_owner(collection: CollectionId, token: TokenId) -> Result, DispatchError> { dispatch_unique_runtime!(collection.token_owner(token)) } + + fn token_owners(collection: CollectionId, token: TokenId) -> Result, DispatchError> { + dispatch_unique_runtime!(collection.token_owners(token)) + } + fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result, DispatchError> { let budget = up_data_structs::budget::Value::new(10); --- a/tests/package.json +++ b/tests/package.json @@ -80,6 +80,7 @@ "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts", "testEthCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createCollection.test.ts", "testRFT": "mocha --timeout 9999999 -r ts-node/register ./**/refungible.test.ts", + "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts", "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json", "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .", "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .", --- a/tests/src/interfaces/augment-api-rpc.ts +++ b/tests/src/interfaces/augment-api-rpc.ts @@ -709,6 +709,10 @@ **/ tokenOwner: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable>>; /** + * Get token owners + **/ + tokenOwners: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable>>; + /** * Get token properties **/ tokenProperties: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, propertyKeys?: Vec | (Text | string)[], at?: Hash | string | Uint8Array) => Observable>>; --- a/tests/src/interfaces/unique/definitions.ts +++ b/tests/src/interfaces/unique/definitions.ts @@ -49,6 +49,7 @@ balance: fun('Get amount of specific account token', [collectionParam, crossAccountParam(), tokenParam], 'u128'), allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'), tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`), + tokenOwners: fun('Get token owners', [collectionParam, tokenParam], `Vec<${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'), constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec'), --- a/tests/src/refungible.test.ts +++ b/tests/src/refungible.test.ts @@ -53,14 +53,14 @@ it('Create refungible collection and token', async () => { await usingApi(async api => { const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}}); - expect(createCollectionResult.success).to.be.true; + expect(createCollectionResult.success).to.be.true; const collectionId = createCollectionResult.collectionId; - + const itemCountBefore = await getLastTokenId(api, collectionId); const result = await createRefungibleToken(api, alice, collectionId, 100n); - + const itemCountAfter = await getLastTokenId(api, collectionId); - + // What to expect // tslint:disable-next-line:no-unused-expression expect(result.success).to.be.true; @@ -69,7 +69,39 @@ expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString()); }); }); - + + it('RPC method tokenOnewrs for refungible collection and token', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; + const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString()))); + + const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}}); + const collectionId = createCollectionResult.collectionId; + + const result = await createRefungibleToken(api, alice, collectionId, 10_000n); + const aliceTokenId = result.itemId; + + + await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n); + await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n); + + for (let i = 0; i < 7; i++) { + await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 50*(i+1)); + } + + const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId); + const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s)); + + const aliceID = normalizeAccountId(alice); + const bobId = normalizeAccountId(bob); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]); + expect(owners.length == 10).to.be.true; + }); + }); + it('Transfer token pieces', async () => { await usingApi(async api => { const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId; --- a/tests/src/rpc.test.ts +++ b/tests/src/rpc.test.ts @@ -1,12 +1,53 @@ +import {IKeyringPair} from '@polkadot/types/types'; import {expect} from 'chai'; import usingApi from './substrate/substrate-api'; -import {createCollectionExpectSuccess, getTokenOwner} from './util/helpers'; +import {createCollection, createCollectionExpectSuccess, createFungibleItemExpectSuccess, CrossAccountId, getTokenOwner, normalizeAccountId, transfer, U128_MAX} from './util/helpers'; + +let alice: IKeyringPair; +let bob: IKeyringPair; + -describe('getTokenOwner', () => { +describe('integration test: RPC methods', () => { + before(async () => { + await usingApi(async (api, privateKeyWrapper) => { + alice = privateKeyWrapper('//Alice'); + bob = privateKeyWrapper('//Bob'); + }); + }); + + it('returns None for fungible collection', async () => { await usingApi(async api => { const collection = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}}); await expect(getTokenOwner(api, collection, 0)).to.be.rejectedWith(/^owner == null$/); }); }); + + it('RPC method tokenOnewrs for fungible collection and token', async () => { + await usingApi(async (api, privateKeyWrapper) => { + const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'}; + const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString()))); + + const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}}); + const collectionId = createCollectionResult.collectionId; + const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address); + + await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n); + await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n); + + for (let i = 0; i < 7; i++) { + await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 1); + } + + const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId); + const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s)); + const aliceID = normalizeAccountId(alice); + const bobId = normalizeAccountId(bob); + + // What to expect + // tslint:disable-next-line:no-unused-expression + expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]); + expect(owners.length == 10).to.be.true; + }); + }); }); \ No newline at end of file -- gitstuff