difftreelog
Merge pull request #426 from UniqueNetwork/feature/token_owners
in: master
31 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5953,7 +5953,7 @@
[[package]]
name = "pallet-fungible"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"ethereum",
"evm-coder",
@@ -6198,7 +6198,7 @@
[[package]]
name = "pallet-nonfungible"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"ethereum",
"evm-coder",
@@ -12377,7 +12377,7 @@
[[package]]
name = "uc-rpc"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"anyhow",
"jsonrpsee",
@@ -12682,7 +12682,7 @@
[[package]]
name = "unique-runtime-common"
-version = "0.9.24"
+version = "0.9.25"
dependencies = [
"evm-coder",
"fp-rpc",
@@ -12754,7 +12754,7 @@
[[package]]
name = "up-rpc"
-version = "0.1.0"
+version = "0.1.1"
dependencies = [
"pallet-common",
"pallet-evm",
client/rpc/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/client/rpc/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
+
\ No newline at end of file
client/rpc/Cargo.tomldiffbeforeafterboth--- a/client/rpc/Cargo.toml
+++ b/client/rpc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "uc-rpc"
-version = "0.1.0"
+version = "0.1.1"
license = "GPLv3"
edition = "2021"
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -70,6 +70,16 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
+
+ /// Returns 10 tokens owners in no particular order.
+ #[method(name = "unique_tokenOwners")]
+ fn token_owners(
+ &self,
+ collection: CollectionId,
+ token: TokenId,
+ at: Option<BlockHash>,
+ ) -> Result<Vec<CrossAccountId>>;
+
#[method(name = "unique_topmostTokenOwner")]
fn topmost_token_owner(
&self,
@@ -473,6 +483,7 @@
pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>, unique_api);
pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);
pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128>, unique_api);
+ pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);
}
#[allow(deprecated)]
pallets/common/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/common/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
+
+
\ No newline at end of file
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1748,6 +1748,11 @@
/// * `token` - The token for which you need to find out the owner.
fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;
+ /// Returns 10 tokens owners in no particular order.
+ ///
+ /// * `token` - The token for which you need to find out the owners.
+ fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;
+
/// Get the value of the token property by key.
///
/// * `token` - Token with the property to get.
pallets/fungible/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/fungible/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
+
+
+
\ No newline at end of file
pallets/fungible/Cargo.tomldiffbeforeafterboth--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-fungible"
-version = "0.1.0"
+version = "0.1.1"
license = "GPLv3"
edition = "2021"
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -362,6 +362,11 @@
None
}
+ /// Returns 10 tokens owners in no particular order.
+ fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+ <Pallet<T>>::token_owners(self.id, token).unwrap_or_default()
+ }
+
fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
None
}
pallets/fungible/src/lib.rsdiffbeforeafterboth--- 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,26 @@
nesting_budget,
)
}
+
+ /// Returns 10 tokens owners in no particular order
+ ///
+ /// There is no direct way to get token holders in ascending order,
+ /// since `iter_prefix` returns values in no particular order.
+ /// Therefore, getting the 10 largest holders with a large value of holders
+ /// can lead to impact memory allocation + sorting with `n * log (n)`.
+ pub fn token_owners(
+ collection: CollectionId,
+ _token: TokenId,
+ ) -> Option<Vec<T::CrossAccountId>> {
+ let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))
+ .map(|(owner, _amount)| owner)
+ .take(10)
+ .collect();
+
+ if res.is_empty() {
+ None
+ } else {
+ Some(res)
+ }
+ }
}
pallets/nonfungible/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+- Implementation of RPC method `token_owners`.
+ For reasons of compatibility with this pallet, returns only one owner if token exists.
+ This was an internal request to improve the web interface and support fractionalization event.
\ No newline at end of file
pallets/nonfungible/Cargo.tomldiffbeforeafterboth--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-nonfungible"
-version = "0.1.0"
+version = "0.1.1"
license = "GPLv3"
edition = "2021"
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- 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,11 @@
<TokenData<T>>::get((self.id, token)).map(|t| t.owner)
}
+ /// Returns token owners.
+ fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+ self.token_owner(token).map_or_else(|| vec![], |t| vec![t])
+ }
+
fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue> {
<Pallet<T>>::token_properties((self.id, token_id))
.get(key)
pallets/refungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -1,6 +1,18 @@
-## v0.1.2 - 2022-07
+# Change Log
-### Refungible Pallet
+All notable changes to this project will be documented in this file.
+## [v0.1.2] - 2022-07-14
+
+### Other changes
+
feat(refungible-pallet): add ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))
-test(refungible-pallet): add tests for ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))
\ No newline at end of file
+test(refungible-pallet): add tests for ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))
+
+## [v0.1.1] - 2022-07-14
+
+### Other changes
+
+- feat: RPC method `token_owners` returning 10 owners in no particular order.
+
+This was an internal request to improve the web interface and support fractionalization event.
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -438,6 +438,11 @@
<Pallet<T>>::token_owner(self.id, token)
}
+ /// Returns 10 token in no particular order.
+ fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+ <Pallet<T>>::token_owners(self.id, token).unwrap_or_default()
+ }
+
fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
None
}
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -1235,4 +1235,26 @@
) -> DispatchResult {
<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)
}
+
+ /// Returns 10 token in no particular order.
+ ///
+ /// There is no direct way to get token holders in ascending order,
+ /// since `iter_prefix` returns values in no particular order.
+ /// Therefore, getting the 10 largest holders with a large value of holders
+ /// can lead to impact memory allocation + sorting with `n * log (n)`.
+ pub fn token_owners(
+ collection_id: CollectionId,
+ token: TokenId,
+ ) -> Option<Vec<T::CrossAccountId>> {
+ let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))
+ .map(|(owner, _amount)| owner)
+ .take(10)
+ .collect();
+
+ if res.is_empty() {
+ None
+ } else {
+ Some(res)
+ }
+ }
}
primitives/rpc/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/primitives/rpc/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
\ No newline at end of file
primitives/rpc/Cargo.tomldiffbeforeafterboth--- a/primitives/rpc/Cargo.toml
+++ b/primitives/rpc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "up-rpc"
-version = "0.1.0"
+version = "0.1.1"
license = "GPLv3"
edition = "2021"
primitives/rpc/src/lib.rsdiffbeforeafterboth--- 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<Option<u64>>;
fn effective_collection_limits(collection_id: CollectionId) -> Result<Option<CollectionLimits>>;
fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Result<Option<u128>>;
+ fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec<CrossAccountId>>;
}
}
runtime/common/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/runtime/common/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.9.25] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+ This was an internal request to improve the web interface and support fractionalization event.
+
+
+
\ No newline at end of file
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- 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<Option<CrossAccountId>, DispatchError> {
dispatch_unique_runtime!(collection.token_owner(token))
}
+
+ fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError> {
+ dispatch_unique_runtime!(collection.token_owners(token))
+ }
+
fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
let budget = up_data_structs::budget::Value::new(10);
tests/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/tests/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## 2022-07-14
+
+### Added
+ - Integrintegration tests of RPC method `token_owners`.
+ - Integrintegration tests of Fungible Pallet.
+
+
\ No newline at end of file
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -80,6 +80,8 @@
"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",
+ "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.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 .",
tests/src/fungible.test.tsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/fungible.test.ts
@@ -0,0 +1,184 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {default as usingApi} from './substrate/substrate-api';
+import {IKeyringPair} from '@polkadot/types/types';
+import {
+ getBalance,
+ createMultipleItemsExpectSuccess,
+ isTokenExists,
+ getLastTokenId,
+ getAllowance,
+ approve,
+ transferFrom,
+ createCollection,
+ transfer,
+ burnItem,
+ normalizeAccountId,
+ CrossAccountId,
+ createFungibleItemExpectSuccess,
+ U128_MAX,
+ burnFromExpectSuccess,
+} from './util/helpers';
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+
+describe('integration test: Fungible functionality:', () => {
+ before(async () => {
+ await usingApi(async (api, privateKeyWrapper) => {
+ alice = privateKeyWrapper('//Alice');
+ bob = privateKeyWrapper('//Bob');
+ });
+ });
+
+ it('Create fungible collection and token', async () => {
+ await usingApi(async api => {
+ const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
+ expect(createCollectionResult.success).to.be.true;
+ const collectionId = createCollectionResult.collectionId;
+ const defaultTokenId = await getLastTokenId(api, collectionId);
+ const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
+ const aliceBalance = await getBalance(api, collectionId, alice, aliceTokenId);
+ const itemCountAfter = await getLastTokenId(api, collectionId);
+
+ // What to expect
+ // tslint:disable-next-line:no-unused-expression
+ expect(itemCountAfter).to.be.equal(defaultTokenId);
+ expect(aliceBalance).to.be.equal(U128_MAX);
+ });
+ });
+
+ 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;
+
+ const eleven = privateKeyWrapper('11');
+ expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+ expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+ });
+ });
+
+ it('Transfer token', async () => {
+ await usingApi(async api => {
+ const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ const tokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address);
+
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
+ expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
+ expect(await transfer(api, collectionId, tokenId, alice, ethAcc, 140n)).to.be.true;
+
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(300n);
+ expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
+ expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(140n);
+ await expect(transfer(api, collectionId, tokenId, alice, bob, 350n)).to.eventually.be.rejected;
+ });
+ });
+
+ it('Tokens multiple creation', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+
+ const args = [
+ {Fungible: {Value: 500n}},
+ {Fungible: {Value: 400n}},
+ {Fungible: {Value: 300n}},
+ ];
+
+ await createMultipleItemsExpectSuccess(alice, collectionId, args);
+ expect(await getBalance(api, collectionId, alice, 0)).to.be.equal(1200n);
+ });
+ });
+
+ it('Burn some tokens ', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
+ expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
+ expect(await burnItem(api, alice, collectionId, tokenId, 499n)).to.be.true;
+ expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n);
+ });
+ });
+
+ it('Burn all tokens ', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
+ expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+ expect(await burnItem(api, alice, collectionId, tokenId, 500n)).to.be.true;
+ expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
+ expect((await api.rpc.unique.totalPieces(collectionId, tokenId)).value.toBigInt()).to.be.equal(0n);
+ });
+ });
+
+ it('Set allowance for token', async () => {
+ await usingApi(async api => {
+ const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+ const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+
+ const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 100n}, alice.address));
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+
+ expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
+ expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n);
+ expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(0n);
+
+ expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob, 20n)).to.be.true;
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n);
+ expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n);
+ expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n);
+
+ await burnFromExpectSuccess(bob, alice, collectionId, tokenId, 10n);
+
+ expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(70n);
+ expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(30n);
+ expect(await transferFrom(api, collectionId, tokenId, bob, alice, ethAcc, 10n)).to.be.true;
+ expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(10n);
+ });
+ });
+});
tests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -29,7 +29,13 @@
[key: string]: Codec;
};
common: {
+ /**
+ * Maximum admins per collection.
+ **/
collectionAdminsLimit: u32 & AugmentedConst<ApiType>;
+ /**
+ * Set price to create a collection.
+ **/
collectionCreationPrice: u128 & AugmentedConst<ApiType>;
/**
* Generic const
tests/src/interfaces/augment-api-events.tsdiffbeforeafterboth1// Auto-generated via `yarn polkadot-types-from-chain`, do not edit2/* eslint-disable */34import type { ApiTypes } from '@polkadot/api-base/types';5import type { Bytes, Null, Option, Result, U256, U8aFixed, bool, u128, u32, u64, u8 } from '@polkadot/types-codec';6import type { ITuple } from '@polkadot/types-codec/types';7import type { AccountId32, H160, H256 } from '@polkadot/types/interfaces/runtime';8import type { EthereumLog, EvmCoreErrorExitReason, FrameSupportScheduleLookupError, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchInfo, OrmlVestingVestingSchedule, PalletEvmAccountBasicCrossAccountIdRepr, RmrkTraitsNftAccountIdOrCollectionNftTuple, SpRuntimeDispatchError, XcmV1MultiLocation, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation } from '@polkadot/types/lookup';910declare module '@polkadot/api-base/types/events' {11 export interface AugmentedEvents<ApiType extends ApiTypes> {12 balances: {13 /**14 * A balance was set by root.15 **/16 BalanceSet: AugmentedEvent<ApiType, [who: AccountId32, free: u128, reserved: u128], { who: AccountId32, free: u128, reserved: u128 }>;17 /**18 * Some amount was deposited (e.g. for transaction fees).19 **/20 Deposit: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;21 /**22 * An account was removed whose balance was non-zero but below ExistentialDeposit,23 * resulting in an outright loss.24 **/25 DustLost: AugmentedEvent<ApiType, [account: AccountId32, amount: u128], { account: AccountId32, amount: u128 }>;26 /**27 * An account was created with some free balance.28 **/29 Endowed: AugmentedEvent<ApiType, [account: AccountId32, freeBalance: u128], { account: AccountId32, freeBalance: u128 }>;30 /**31 * Some balance was reserved (moved from free to reserved).32 **/33 Reserved: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;34 /**35 * Some balance was moved from the reserve of the first account to the second account.36 * Final argument indicates the destination balance type.37 **/38 ReserveRepatriated: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, amount: u128, destinationStatus: FrameSupportTokensMiscBalanceStatus], { from: AccountId32, to: AccountId32, amount: u128, destinationStatus: FrameSupportTokensMiscBalanceStatus }>;39 /**40 * Some amount was removed from the account (e.g. for misbehavior).41 **/42 Slashed: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;43 /**44 * Transfer succeeded.45 **/46 Transfer: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, amount: u128], { from: AccountId32, to: AccountId32, amount: u128 }>;47 /**48 * Some balance was unreserved (moved from reserved to free).49 **/50 Unreserved: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;51 /**52 * Some amount was withdrawn from the account (e.g. for transaction fees).53 **/54 Withdraw: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;55 /**56 * Generic event57 **/58 [key: string]: AugmentedEvent<ApiType>;59 };60 common: {61 /**62 * * collection_id63 * 64 * * item_id65 * 66 * * sender67 * 68 * * spender69 * 70 * * amount71 **/72 Approved: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;73 /**74 * New collection was created75 * 76 * # Arguments77 * 78 * * collection_id: Globally unique identifier of newly created collection.79 * 80 * * mode: [CollectionMode] converted into u8.81 * 82 * * account_id: Collection owner.83 **/84 CollectionCreated: AugmentedEvent<ApiType, [u32, u8, AccountId32]>;85 /**86 * New collection was destroyed87 * 88 * # Arguments89 * 90 * * collection_id: Globally unique identifier of collection.91 **/92 CollectionDestroyed: AugmentedEvent<ApiType, [u32]>;93 CollectionPropertyDeleted: AugmentedEvent<ApiType, [u32, Bytes]>;94 CollectionPropertySet: AugmentedEvent<ApiType, [u32, Bytes]>;95 /**96 * New item was created.97 * 98 * # Arguments99 * 100 * * collection_id: Id of the collection where item was created.101 * 102 * * item_id: Id of an item. Unique within the collection.103 * 104 * * recipient: Owner of newly created item105 * 106 * * amount: Always 1 for NFT107 **/108 ItemCreated: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;109 /**110 * Collection item was burned.111 * 112 * # Arguments113 * 114 * * collection_id.115 * 116 * * item_id: Identifier of burned NFT.117 * 118 * * owner: which user has destroyed its tokens119 * 120 * * amount: Always 1 for NFT121 **/122 ItemDestroyed: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;123 PropertyPermissionSet: AugmentedEvent<ApiType, [u32, Bytes]>;124 TokenPropertyDeleted: AugmentedEvent<ApiType, [u32, u32, Bytes]>;125 TokenPropertySet: AugmentedEvent<ApiType, [u32, u32, Bytes]>;126 /**127 * Item was transferred128 * 129 * * collection_id: Id of collection to which item is belong130 * 131 * * item_id: Id of an item132 * 133 * * sender: Original owner of item134 * 135 * * recipient: New owner of item136 * 137 * * amount: Always 1 for NFT138 **/139 Transfer: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;140 /**141 * Generic event142 **/143 [key: string]: AugmentedEvent<ApiType>;144 };145 cumulusXcm: {146 /**147 * Downward message executed with the given outcome.148 * \[ id, outcome \]149 **/150 ExecutedDownward: AugmentedEvent<ApiType, [U8aFixed, XcmV2TraitsOutcome]>;151 /**152 * Downward message is invalid XCM.153 * \[ id \]154 **/155 InvalidFormat: AugmentedEvent<ApiType, [U8aFixed]>;156 /**157 * Downward message is unsupported version of XCM.158 * \[ id \]159 **/160 UnsupportedVersion: AugmentedEvent<ApiType, [U8aFixed]>;161 /**162 * Generic event163 **/164 [key: string]: AugmentedEvent<ApiType>;165 };166 dmpQueue: {167 /**168 * Downward message executed with the given outcome.169 **/170 ExecutedDownward: AugmentedEvent<ApiType, [messageId: U8aFixed, outcome: XcmV2TraitsOutcome], { messageId: U8aFixed, outcome: XcmV2TraitsOutcome }>;171 /**172 * Downward message is invalid XCM.173 **/174 InvalidFormat: AugmentedEvent<ApiType, [messageId: U8aFixed], { messageId: U8aFixed }>;175 /**176 * Downward message is overweight and was placed in the overweight queue.177 **/178 OverweightEnqueued: AugmentedEvent<ApiType, [messageId: U8aFixed, overweightIndex: u64, requiredWeight: u64], { messageId: U8aFixed, overweightIndex: u64, requiredWeight: u64 }>;179 /**180 * Downward message from the overweight queue was executed.181 **/182 OverweightServiced: AugmentedEvent<ApiType, [overweightIndex: u64, weightUsed: u64], { overweightIndex: u64, weightUsed: u64 }>;183 /**184 * Downward message is unsupported version of XCM.185 **/186 UnsupportedVersion: AugmentedEvent<ApiType, [messageId: U8aFixed], { messageId: U8aFixed }>;187 /**188 * The weight limit for handling downward messages was reached.189 **/190 WeightExhausted: AugmentedEvent<ApiType, [messageId: U8aFixed, remainingWeight: u64, requiredWeight: u64], { messageId: U8aFixed, remainingWeight: u64, requiredWeight: u64 }>;191 /**192 * Generic event193 **/194 [key: string]: AugmentedEvent<ApiType>;195 };196 ethereum: {197 /**198 * An ethereum transaction was successfully executed. [from, to/contract_address, transaction_hash, exit_reason]199 **/200 Executed: AugmentedEvent<ApiType, [H160, H160, H256, EvmCoreErrorExitReason]>;201 /**202 * Generic event203 **/204 [key: string]: AugmentedEvent<ApiType>;205 };206 evm: {207 /**208 * A deposit has been made at a given address. \[sender, address, value\]209 **/210 BalanceDeposit: AugmentedEvent<ApiType, [AccountId32, H160, U256]>;211 /**212 * A withdrawal has been made from a given address. \[sender, address, value\]213 **/214 BalanceWithdraw: AugmentedEvent<ApiType, [AccountId32, H160, U256]>;215 /**216 * A contract has been created at given \[address\].217 **/218 Created: AugmentedEvent<ApiType, [H160]>;219 /**220 * A \[contract\] was attempted to be created, but the execution failed.221 **/222 CreatedFailed: AugmentedEvent<ApiType, [H160]>;223 /**224 * A \[contract\] has been executed successfully with states applied.225 **/226 Executed: AugmentedEvent<ApiType, [H160]>;227 /**228 * A \[contract\] has been executed with errors. States are reverted with only gas fees applied.229 **/230 ExecutedFailed: AugmentedEvent<ApiType, [H160]>;231 /**232 * Ethereum events from contracts.233 **/234 Log: AugmentedEvent<ApiType, [EthereumLog]>;235 /**236 * Generic event237 **/238 [key: string]: AugmentedEvent<ApiType>;239 };240 parachainSystem: {241 /**242 * Downward messages were processed using the given weight.243 **/244 DownwardMessagesProcessed: AugmentedEvent<ApiType, [weightUsed: u64, dmqHead: H256], { weightUsed: u64, dmqHead: H256 }>;245 /**246 * Some downward messages have been received and will be processed.247 **/248 DownwardMessagesReceived: AugmentedEvent<ApiType, [count: u32], { count: u32 }>;249 /**250 * An upgrade has been authorized.251 **/252 UpgradeAuthorized: AugmentedEvent<ApiType, [codeHash: H256], { codeHash: H256 }>;253 /**254 * The validation function was applied as of the contained relay chain block number.255 **/256 ValidationFunctionApplied: AugmentedEvent<ApiType, [relayChainBlockNum: u32], { relayChainBlockNum: u32 }>;257 /**258 * The relay-chain aborted the upgrade process.259 **/260 ValidationFunctionDiscarded: AugmentedEvent<ApiType, []>;261 /**262 * The validation function has been scheduled to apply.263 **/264 ValidationFunctionStored: AugmentedEvent<ApiType, []>;265 /**266 * Generic event267 **/268 [key: string]: AugmentedEvent<ApiType>;269 };270 polkadotXcm: {271 /**272 * Some assets have been placed in an asset trap.273 * 274 * \[ hash, origin, assets \]275 **/276 AssetsTrapped: AugmentedEvent<ApiType, [H256, XcmV1MultiLocation, XcmVersionedMultiAssets]>;277 /**278 * Execution of an XCM message was attempted.279 * 280 * \[ outcome \]281 **/282 Attempted: AugmentedEvent<ApiType, [XcmV2TraitsOutcome]>;283 /**284 * Expected query response has been received but the origin location of the response does285 * not match that expected. The query remains registered for a later, valid, response to286 * be received and acted upon.287 * 288 * \[ origin location, id, expected location \]289 **/290 InvalidResponder: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64, Option<XcmV1MultiLocation>]>;291 /**292 * Expected query response has been received but the expected origin location placed in293 * storage by this runtime previously cannot be decoded. The query remains registered.294 * 295 * This is unexpected (since a location placed in storage in a previously executing296 * runtime should be readable prior to query timeout) and dangerous since the possibly297 * valid response will be dropped. Manual governance intervention is probably going to be298 * needed.299 * 300 * \[ origin location, id \]301 **/302 InvalidResponderVersion: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64]>;303 /**304 * Query response has been received and query is removed. The registered notification has305 * been dispatched and executed successfully.306 * 307 * \[ id, pallet index, call index \]308 **/309 Notified: AugmentedEvent<ApiType, [u64, u8, u8]>;310 /**311 * Query response has been received and query is removed. The dispatch was unable to be312 * decoded into a `Call`; this might be due to dispatch function having a signature which313 * is not `(origin, QueryId, Response)`.314 * 315 * \[ id, pallet index, call index \]316 **/317 NotifyDecodeFailed: AugmentedEvent<ApiType, [u64, u8, u8]>;318 /**319 * Query response has been received and query is removed. There was a general error with320 * dispatching the notification call.321 * 322 * \[ id, pallet index, call index \]323 **/324 NotifyDispatchError: AugmentedEvent<ApiType, [u64, u8, u8]>;325 /**326 * Query response has been received and query is removed. The registered notification could327 * not be dispatched because the dispatch weight is greater than the maximum weight328 * originally budgeted by this runtime for the query result.329 * 330 * \[ id, pallet index, call index, actual weight, max budgeted weight \]331 **/332 NotifyOverweight: AugmentedEvent<ApiType, [u64, u8, u8, u64, u64]>;333 /**334 * A given location which had a version change subscription was dropped owing to an error335 * migrating the location to our new XCM format.336 * 337 * \[ location, query ID \]338 **/339 NotifyTargetMigrationFail: AugmentedEvent<ApiType, [XcmVersionedMultiLocation, u64]>;340 /**341 * A given location which had a version change subscription was dropped owing to an error342 * sending the notification to it.343 * 344 * \[ location, query ID, error \]345 **/346 NotifyTargetSendFail: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64, XcmV2TraitsError]>;347 /**348 * Query response has been received and is ready for taking with `take_response`. There is349 * no registered notification call.350 * 351 * \[ id, response \]352 **/353 ResponseReady: AugmentedEvent<ApiType, [u64, XcmV2Response]>;354 /**355 * Received query response has been read and removed.356 * 357 * \[ id \]358 **/359 ResponseTaken: AugmentedEvent<ApiType, [u64]>;360 /**361 * A XCM message was sent.362 * 363 * \[ origin, destination, message \]364 **/365 Sent: AugmentedEvent<ApiType, [XcmV1MultiLocation, XcmV1MultiLocation, XcmV2Xcm]>;366 /**367 * The supported version of a location has been changed. This might be through an368 * automatic notification or a manual intervention.369 * 370 * \[ location, XCM version \]371 **/372 SupportedVersionChanged: AugmentedEvent<ApiType, [XcmV1MultiLocation, u32]>;373 /**374 * Query response received which does not match a registered query. This may be because a375 * matching query was never registered, it may be because it is a duplicate response, or376 * because the query timed out.377 * 378 * \[ origin location, id \]379 **/380 UnexpectedResponse: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64]>;381 /**382 * An XCM version change notification message has been attempted to be sent.383 * 384 * \[ destination, result \]385 **/386 VersionChangeNotified: AugmentedEvent<ApiType, [XcmV1MultiLocation, u32]>;387 /**388 * Generic event389 **/390 [key: string]: AugmentedEvent<ApiType>;391 };392 rmrkCore: {393 CollectionCreated: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;394 CollectionDestroyed: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;395 CollectionLocked: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;396 IssuerChanged: AugmentedEvent<ApiType, [oldIssuer: AccountId32, newIssuer: AccountId32, collectionId: u32], { oldIssuer: AccountId32, newIssuer: AccountId32, collectionId: u32 }>;397 NFTAccepted: AugmentedEvent<ApiType, [sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32], { sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32 }>;398 NFTBurned: AugmentedEvent<ApiType, [owner: AccountId32, nftId: u32], { owner: AccountId32, nftId: u32 }>;399 NftMinted: AugmentedEvent<ApiType, [owner: AccountId32, collectionId: u32, nftId: u32], { owner: AccountId32, collectionId: u32, nftId: u32 }>;400 NFTRejected: AugmentedEvent<ApiType, [sender: AccountId32, collectionId: u32, nftId: u32], { sender: AccountId32, collectionId: u32, nftId: u32 }>;401 NFTSent: AugmentedEvent<ApiType, [sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32, approvalRequired: bool], { sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32, approvalRequired: bool }>;402 PrioritySet: AugmentedEvent<ApiType, [collectionId: u32, nftId: u32], { collectionId: u32, nftId: u32 }>;403 PropertySet: AugmentedEvent<ApiType, [collectionId: u32, maybeNftId: Option<u32>, key: Bytes, value: Bytes], { collectionId: u32, maybeNftId: Option<u32>, key: Bytes, value: Bytes }>;404 ResourceAccepted: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;405 ResourceAdded: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;406 ResourceRemoval: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;407 ResourceRemovalAccepted: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;408 /**409 * Generic event410 **/411 [key: string]: AugmentedEvent<ApiType>;412 };413 rmrkEquip: {414 BaseCreated: AugmentedEvent<ApiType, [issuer: AccountId32, baseId: u32], { issuer: AccountId32, baseId: u32 }>;415 EquippablesUpdated: AugmentedEvent<ApiType, [baseId: u32, slotId: u32], { baseId: u32, slotId: u32 }>;416 /**417 * Generic event418 **/419 [key: string]: AugmentedEvent<ApiType>;420 };421 scheduler: {422 /**423 * The call for the provided hash was not found so the task has been aborted.424 **/425 CallLookupFailed: AugmentedEvent<ApiType, [task: ITuple<[u32, u32]>, id: Option<U8aFixed>, error: FrameSupportScheduleLookupError], { task: ITuple<[u32, u32]>, id: Option<U8aFixed>, error: FrameSupportScheduleLookupError }>;426 /**427 * Canceled some task.428 **/429 Canceled: AugmentedEvent<ApiType, [when: u32, index: u32], { when: u32, index: u32 }>;430 /**431 * Dispatched some task.432 **/433 Dispatched: AugmentedEvent<ApiType, [task: ITuple<[u32, u32]>, id: Option<U8aFixed>, result: Result<Null, SpRuntimeDispatchError>], { task: ITuple<[u32, u32]>, id: Option<U8aFixed>, result: Result<Null, SpRuntimeDispatchError> }>;434 /**435 * Scheduled some task.436 **/437 Scheduled: AugmentedEvent<ApiType, [when: u32, index: u32], { when: u32, index: u32 }>;438 /**439 * Generic event440 **/441 [key: string]: AugmentedEvent<ApiType>;442 };443 structure: {444 /**445 * Executed call on behalf of token446 **/447 Executed: AugmentedEvent<ApiType, [Result<Null, SpRuntimeDispatchError>]>;448 /**449 * Generic event450 **/451 [key: string]: AugmentedEvent<ApiType>;452 };453 sudo: {454 /**455 * The \[sudoer\] just switched identity; the old key is supplied if one existed.456 **/457 KeyChanged: AugmentedEvent<ApiType, [oldSudoer: Option<AccountId32>], { oldSudoer: Option<AccountId32> }>;458 /**459 * A sudo just took place. \[result\]460 **/461 Sudid: AugmentedEvent<ApiType, [sudoResult: Result<Null, SpRuntimeDispatchError>], { sudoResult: Result<Null, SpRuntimeDispatchError> }>;462 /**463 * A sudo just took place. \[result\]464 **/465 SudoAsDone: AugmentedEvent<ApiType, [sudoResult: Result<Null, SpRuntimeDispatchError>], { sudoResult: Result<Null, SpRuntimeDispatchError> }>;466 /**467 * Generic event468 **/469 [key: string]: AugmentedEvent<ApiType>;470 };471 system: {472 /**473 * `:code` was updated.474 **/475 CodeUpdated: AugmentedEvent<ApiType, []>;476 /**477 * An extrinsic failed.478 **/479 ExtrinsicFailed: AugmentedEvent<ApiType, [dispatchError: SpRuntimeDispatchError, dispatchInfo: FrameSupportWeightsDispatchInfo], { dispatchError: SpRuntimeDispatchError, dispatchInfo: FrameSupportWeightsDispatchInfo }>;480 /**481 * An extrinsic completed successfully.482 **/483 ExtrinsicSuccess: AugmentedEvent<ApiType, [dispatchInfo: FrameSupportWeightsDispatchInfo], { dispatchInfo: FrameSupportWeightsDispatchInfo }>;484 /**485 * An account was reaped.486 **/487 KilledAccount: AugmentedEvent<ApiType, [account: AccountId32], { account: AccountId32 }>;488 /**489 * A new account was created.490 **/491 NewAccount: AugmentedEvent<ApiType, [account: AccountId32], { account: AccountId32 }>;492 /**493 * On on-chain remark happened.494 **/495 Remarked: AugmentedEvent<ApiType, [sender: AccountId32, hash_: H256], { sender: AccountId32, hash_: H256 }>;496 /**497 * Generic event498 **/499 [key: string]: AugmentedEvent<ApiType>;500 };501 treasury: {502 /**503 * Some funds have been allocated.504 **/505 Awarded: AugmentedEvent<ApiType, [proposalIndex: u32, award: u128, account: AccountId32], { proposalIndex: u32, award: u128, account: AccountId32 }>;506 /**507 * Some of our funds have been burnt.508 **/509 Burnt: AugmentedEvent<ApiType, [burntFunds: u128], { burntFunds: u128 }>;510 /**511 * Some funds have been deposited.512 **/513 Deposit: AugmentedEvent<ApiType, [value: u128], { value: u128 }>;514 /**515 * New proposal.516 **/517 Proposed: AugmentedEvent<ApiType, [proposalIndex: u32], { proposalIndex: u32 }>;518 /**519 * A proposal was rejected; funds were slashed.520 **/521 Rejected: AugmentedEvent<ApiType, [proposalIndex: u32, slashed: u128], { proposalIndex: u32, slashed: u128 }>;522 /**523 * Spending has finished; this is the amount that rolls over until next spend.524 **/525 Rollover: AugmentedEvent<ApiType, [rolloverBalance: u128], { rolloverBalance: u128 }>;526 /**527 * We have ended a spend period and will now allocate funds.528 **/529 Spending: AugmentedEvent<ApiType, [budgetRemaining: u128], { budgetRemaining: u128 }>;530 /**531 * Generic event532 **/533 [key: string]: AugmentedEvent<ApiType>;534 };535 unique: {536 /**537 * Address was add to allow list538 * 539 * # Arguments540 * 541 * * collection_id: Globally unique collection identifier.542 * 543 * * user: Address.544 **/545 AllowListAddressAdded: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;546 /**547 * Address was remove from allow list548 * 549 * # Arguments550 * 551 * * collection_id: Globally unique collection identifier.552 * 553 * * user: Address.554 **/555 AllowListAddressRemoved: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;556 /**557 * Collection admin was added558 * 559 * # Arguments560 * 561 * * collection_id: Globally unique collection identifier.562 * 563 * * admin: Admin address.564 **/565 CollectionAdminAdded: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;566 /**567 * Collection admin was removed568 * 569 * # Arguments570 * 571 * * collection_id: Globally unique collection identifier.572 * 573 * * admin: Admin address.574 **/575 CollectionAdminRemoved: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;576 /**577 * Collection limits was set578 * 579 * # Arguments580 * 581 * * collection_id: Globally unique collection identifier.582 **/583 CollectionLimitSet: AugmentedEvent<ApiType, [u32]>;584 /**585 * Collection owned was change586 * 587 * # Arguments588 * 589 * * collection_id: Globally unique collection identifier.590 * 591 * * owner: New owner address.592 **/593 CollectionOwnedChanged: AugmentedEvent<ApiType, [u32, AccountId32]>;594 CollectionPermissionSet: AugmentedEvent<ApiType, [u32]>;595 /**596 * Collection sponsor was removed597 * 598 * # Arguments599 * 600 * * collection_id: Globally unique collection identifier.601 **/602 CollectionSponsorRemoved: AugmentedEvent<ApiType, [u32]>;603 /**604 * Collection sponsor was set605 * 606 * # Arguments607 * 608 * * collection_id: Globally unique collection identifier.609 * 610 * * owner: New sponsor address.611 **/612 CollectionSponsorSet: AugmentedEvent<ApiType, [u32, AccountId32]>;613 /**614 * New sponsor was confirm615 * 616 * # Arguments617 * 618 * * collection_id: Globally unique collection identifier.619 * 620 * * sponsor: New sponsor address.621 **/622 SponsorshipConfirmed: AugmentedEvent<ApiType, [u32, AccountId32]>;623 /**624 * Generic event625 **/626 [key: string]: AugmentedEvent<ApiType>;627 };628 vesting: {629 /**630 * Claimed vesting.631 **/632 Claimed: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;633 /**634 * Added new vesting schedule.635 **/636 VestingScheduleAdded: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, vestingSchedule: OrmlVestingVestingSchedule], { from: AccountId32, to: AccountId32, vestingSchedule: OrmlVestingVestingSchedule }>;637 /**638 * Updated vesting schedules.639 **/640 VestingSchedulesUpdated: AugmentedEvent<ApiType, [who: AccountId32], { who: AccountId32 }>;641 /**642 * Generic event643 **/644 [key: string]: AugmentedEvent<ApiType>;645 };646 xcmpQueue: {647 /**648 * Bad XCM format used.649 **/650 BadFormat: AugmentedEvent<ApiType, [Option<H256>]>;651 /**652 * Bad XCM version used.653 **/654 BadVersion: AugmentedEvent<ApiType, [Option<H256>]>;655 /**656 * Some XCM failed.657 **/658 Fail: AugmentedEvent<ApiType, [Option<H256>, XcmV2TraitsError]>;659 /**660 * An XCM exceeded the individual message weight budget.661 **/662 OverweightEnqueued: AugmentedEvent<ApiType, [u32, u32, u64, u64]>;663 /**664 * An XCM from the overweight queue was executed with the given actual weight used.665 **/666 OverweightServiced: AugmentedEvent<ApiType, [u64, u64]>;667 /**668 * Some XCM was executed ok.669 **/670 Success: AugmentedEvent<ApiType, [Option<H256>]>;671 /**672 * An upward message was sent to the relay chain.673 **/674 UpwardMessageSent: AugmentedEvent<ApiType, [Option<H256>]>;675 /**676 * An HRMP message was sent to a sibling parachain.677 **/678 XcmpMessageSent: AugmentedEvent<ApiType, [Option<H256>]>;679 /**680 * Generic event681 **/682 [key: string]: AugmentedEvent<ApiType>;683 };684 } // AugmentedEvents685} // declare moduletests/src/interfaces/augment-api-query.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -69,24 +69,36 @@
[key: string]: QueryableStorageEntry<ApiType>;
};
common: {
+ /**
+ * Storage of collection admins count.
+ **/
adminAmount: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
* Allowlisted collection users
**/
allowlist: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<bool>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
/**
- * Collection info
+ * Storage of collection info.
**/
collectionById: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Option<UpDataStructsCollection>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
- * Collection properties
+ * Storage of collection properties.
**/
collectionProperties: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
+ /**
+ * Storage of collection properties permissions.
+ **/
collectionPropertyPermissions: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<BTreeMap<Bytes, UpDataStructsPropertyPermission>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
+ /**
+ * Storage of the count of created collections.
+ **/
createdCollectionCount: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
+ /**
+ * Storage of the count of deleted collections.
+ **/
destroyedCollectionCount: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
/**
- * Not used by code, exists only to provide some types to metadata
+ * Not used by code, exists only to provide some types to metadata.
**/
dummyStorageValue: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[UpDataStructsCollectionStats, u32, u32, UpDataStructsTokenChild, PhantomTypeUpDataStructs]>>>, []> & QueryableStorageEntry<ApiType, []>;
/**
@@ -231,20 +243,42 @@
[key: string]: QueryableStorageEntry<ApiType>;
};
nonfungible: {
+ /**
+ * Amount of tokens owned by account.
+ **/
accountBalance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<u32>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
+ /**
+ * Allowance set by an owner for a spender for a token.
+ **/
allowance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<Option<PalletEvmAccountBasicCrossAccountIdRepr>>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
/**
- * Used to enumerate tokens owned by account
+ * Used to enumerate tokens owned by account.
**/
owned: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: u32 | AnyNumber | Uint8Array) => Observable<bool>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32]>;
+ /**
+ * Custom data that is serialized to bytes and attached to a token property.
+ * Currently used to store RMRK data.
+ **/
tokenAuxProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
/**
- * Used to enumerate token's children
+ * Used to enumerate token's children.
**/
tokenChildren: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: ITuple<[u32, u32]> | [u32 | AnyNumber | Uint8Array, u32 | AnyNumber | Uint8Array]) => Observable<bool>, [u32, u32, ITuple<[u32, u32]>]> & QueryableStorageEntry<ApiType, [u32, u32, ITuple<[u32, u32]>]>;
+ /**
+ * Custom data serialized to bytes for token.
+ **/
tokenData: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<Option<PalletNonfungibleItemData>>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
+ /**
+ * Key-Value map stored for token.
+ **/
tokenProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
+ /**
+ * Amount of burnt tokens for collection.
+ **/
tokensBurnt: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
+ /**
+ * Amount of tokens minted for collection.
+ **/
tokensMinted: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
* Generic query
@@ -430,6 +464,9 @@
**/
tokenData: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<PalletRefungibleItemData>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
tokenProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
+ /**
+ * Amount of burnt tokens for collection
+ **/
tokensBurnt: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
/**
* Amount of tokens minted for collection
tests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth--- 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<Option<PalletEvmAccountBasicCrossAccountIdRepr>>>;
/**
+ * Returns 10 tokens owners in no particular order
+ **/
+ tokenOwners: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Vec<PalletEvmAccountBasicCrossAccountIdRepr>>>;
+ /**
* Get token properties
**/
tokenProperties: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, propertyKeys?: Vec<Text> | (Text | string)[], at?: Hash | string | Uint8Array) => Observable<Vec<UpDataStructsProperty>>>;
tests/src/interfaces/unique/definitions.tsdiffbeforeafterboth--- 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('Returns 10 tokens owners in no particular order', [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<UpDataStructsTokenChild>'),
constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),
tests/src/refungible.test.tsdiffbeforeafterboth--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -32,6 +32,8 @@
repartitionRFT,
createCollectionWithPropsExpectSuccess,
getDetailedCollectionInfo,
+ normalizeAccountId,
+ CrossAccountId,
getCreateItemsResult,
getDestroyItemsResult,
} from './util/helpers';
@@ -55,14 +57,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;
@@ -71,7 +73,43 @@
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).to.be.equal(10);
+
+ const eleven = privateKeyWrapper('11');
+ expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+ expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+ });
+ });
+
it('Transfer token pieces', async () => {
await usingApi(async api => {
const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
tests/src/rpc.test.tsdiffbeforeafterboth--- a/tests/src/rpc.test.ts
+++ b/tests/src/rpc.test.ts
@@ -1,12 +1,57 @@
+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 tokenOwners 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;
+
+ const eleven = privateKeyWrapper('11');
+ expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+ expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+ });
+ });
});
\ No newline at end of file