From 75d8b9094b27443fc80a4510e60fd73a9323b7b9 Mon Sep 17 00:00:00 2001 From: kozyrevdev <73348153+kozyrevdev@users.noreply.github.com> Date: Thu, 17 Feb 2022 06:34:58 +0000 Subject: [PATCH] Merge pull request #277 from UniqueNetwork/feature/create-collection-ex Add createCollectionEx call --- --- a/pallets/common/src/lib.rs +++ b/pallets/common/src/lib.rs @@ -14,7 +14,10 @@ use up_data_structs::{ COLLECTION_NUMBER_LIMIT, Collection, CollectionId, CreateItemData, ExistenceRequirement, MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, Pays, PostDispatchInfo, - TokenId, Weight, WithdrawReasons, CollectionStats, CustomDataLimit, + TokenId, Weight, WithdrawReasons, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, + NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits, + CustomDataLimit, CreateCollectionData, SponsorshipState, }; pub use pallet::*; use sp_core::H160; @@ -285,6 +288,10 @@ TokenVariableDataLimitExceeded, /// Exceeded max admin count CollectionAdminCountExceeded, + /// Collection limit bounds per collection exceeded + CollectionLimitBoundsExceeded, + /// Tried to enable permissions which are only permitted to be disabled + OwnerPermissionsCantBeReverted, /// Collection settings not allowing items transferring TransferNotAllowed, @@ -395,7 +402,10 @@ } impl Pallet { - pub fn init_collection(data: Collection) -> Result { + pub fn init_collection( + owner: T::AccountId, + data: CreateCollectionData, + ) -> Result { { ensure!( data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize, @@ -418,6 +428,29 @@ // ========= + let collection = Collection { + owner: owner.clone(), + name: data.name, + mode: data.mode.clone(), + mint_mode: false, + access: data.access.unwrap_or_default(), + description: data.description, + token_prefix: data.token_prefix, + offchain_schema: data.offchain_schema, + schema_version: data.schema_version.unwrap_or_default(), + sponsorship: data + .pending_sponsor + .map(SponsorshipState::Unconfirmed) + .unwrap_or_default(), + variable_on_chain_schema: data.variable_on_chain_schema, + const_on_chain_schema: data.const_on_chain_schema, + limits: data + .limits + .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits)) + .unwrap_or_else(|| Ok(CollectionLimits::default()))?, + meta_update_permission: data.meta_update_permission.unwrap_or_default(), + }; + // Take a (non-refundable) deposit of collection creation { let mut imbalance = @@ -429,7 +462,7 @@ ), ); ::Currency::settle( - &data.owner, + &owner, imbalance, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive, @@ -438,12 +471,8 @@ } >::put(created_count); - >::deposit_event(Event::CollectionCreated( - id, - data.mode.id(), - data.owner.clone(), - )); - >::insert(id, data); + >::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone())); + >::insert(id, collection); Ok(id) } @@ -527,6 +556,61 @@ Ok(()) } + + pub fn clamp_limits( + mode: CollectionMode, + old_limit: &CollectionLimits, + mut new_limit: CollectionLimits, + ) -> Result { + macro_rules! limit_default { + ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{ + $( + if let Some($new) = $new.$field { + let $old = $old.$field($($arg)?); + let _ = $new; + let _ = $old; + $check + } else { + $new.$field = $old.$field + } + )* + }}; + } + + limit_default!(old_limit, new_limit, + account_token_ownership_limit => ensure!( + new_limit <= MAX_TOKEN_OWNERSHIP, + >::CollectionLimitBoundsExceeded, + ), + sponsor_transfer_timeout(match mode { + CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, + }) => ensure!( + new_limit <= MAX_SPONSOR_TIMEOUT, + >::CollectionLimitBoundsExceeded, + ), + sponsored_data_size => ensure!( + new_limit <= CUSTOM_DATA_LIMIT, + >::CollectionLimitBoundsExceeded, + ), + token_limit => ensure!( + old_limit >= new_limit && new_limit > 0, + >::CollectionTokenLimitExceeded + ), + owner_can_transfer => ensure!( + old_limit || !new_limit, + >::OwnerPermissionsCantBeReverted, + ), + owner_can_destroy => ensure!( + old_limit || !new_limit, + >::OwnerPermissionsCantBeReverted, + ), + sponsored_data_rate_limit => {}, + transfers_enabled => {}, + ); + Ok(new_limit) + } } #[macro_export] --- a/pallets/fungible/src/lib.rs +++ b/pallets/fungible/src/lib.rs @@ -2,7 +2,7 @@ use core::ops::Deref; use frame_support::{ensure}; -use up_data_structs::{AccessMode, Collection, CollectionId, TokenId}; +use up_data_structs::{AccessMode, Collection, CollectionId, TokenId, CreateCollectionData}; use pallet_common::{ Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId, }; @@ -100,8 +100,11 @@ } impl Pallet { - pub fn init_collection(data: Collection) -> Result { - >::init_collection(data) + pub fn init_collection( + owner: T::AccountId, + data: CreateCollectionData, + ) -> Result { + >::init_collection(owner, data) } pub fn destroy_collection( collection: FungibleHandle, --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -2,7 +2,9 @@ use erc::ERC721Events; use frame_support::{BoundedVec, ensure}; -use up_data_structs::{AccessMode, Collection, CollectionId, CustomDataLimit, TokenId}; +use up_data_structs::{ + AccessMode, Collection, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, +}; use pallet_common::{ Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId, }; @@ -140,8 +142,11 @@ // unchecked calls skips any permission checks impl Pallet { - pub fn init_collection(data: Collection) -> Result { - >::init_collection(data) + pub fn init_collection( + owner: T::AccountId, + data: CreateCollectionData, + ) -> Result { + >::init_collection(owner, data) } pub fn destroy_collection( collection: NonfungibleHandle, --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -3,6 +3,7 @@ use frame_support::{ensure, BoundedVec}; use up_data_structs::{ AccessMode, Collection, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId, + CreateCollectionData, }; use pallet_common::{ Error as CommonError, Event as CommonEvent, Pallet as PalletCommon, account::CrossAccountId, @@ -155,8 +156,11 @@ // unchecked calls skips any permission checks impl Pallet { - pub fn init_collection(data: Collection) -> Result { - >::init_collection(data) + pub fn init_collection( + owner: T::AccountId, + data: CreateCollectionData, + ) -> Result { + >::init_collection(owner, data) } pub fn destroy_collection( collection: RefungibleHandle, --- a/pallets/unique/src/lib.rs +++ b/pallets/unique/src/lib.rs @@ -36,13 +36,11 @@ use frame_system::{self as system, ensure_signed}; use sp_runtime::{sp_std::prelude::Vec}; use up_data_structs::{ - MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, CUSTOM_DATA_LIMIT, - VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, OFFCHAIN_SCHEMA_LIMIT, - FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - NFT_SPONSOR_TRANSFER_TIMEOUT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, + MAX_DECIMAL_POINTS, VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, + OFFCHAIN_SCHEMA_LIMIT, MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId, CollectionMode, TokenId, SchemaVersion, SponsorshipState, MetaUpdatePermission, - CustomDataLimit, + CreateCollectionData, CustomDataLimit, }; use pallet_common::{ account::CrossAccountId, CollectionHandle, Pallet as PalletCommon, Error as CommonError, @@ -84,10 +82,6 @@ ConfirmUnsetSponsorFail, /// Length of items properties must be greater than 0. EmptyArgument, - /// Collection limit bounds per collection exceeded - CollectionLimitBoundsExceeded, - /// Tried to enable permissions which are only permitted to be disabled - OwnerPermissionsCantBeReverted, } } @@ -321,42 +315,39 @@ // returns collection ID #[weight = >::create_collection()] #[transactional] + #[deprecated] pub fn create_collection(origin, collection_name: BoundedVec>, collection_description: BoundedVec>, token_prefix: BoundedVec>, - mode: CollectionMode) -> DispatchResult { - - // Anyone can create a collection - let who = ensure_signed(origin)?; - - // Create new collection - let new_collection = Collection { - owner: who, + mode: CollectionMode) -> DispatchResult { + let data: CreateCollectionData = CreateCollectionData { name: collection_name, - mode: mode.clone(), - mint_mode: false, - access: AccessMode::Normal, description: collection_description, token_prefix, - offchain_schema: BoundedVec::default(), - schema_version: SchemaVersion::ImageURL, - sponsorship: SponsorshipState::Disabled, - variable_on_chain_schema: BoundedVec::default(), - const_on_chain_schema: BoundedVec::default(), - limits: Default::default(), - meta_update_permission: Default::default(), + mode, + ..Default::default() }; + Self::create_collection_ex(origin, data) + } + + /// This method creates a collection + /// + /// Prefer it to deprecated [`created_collection`] method + #[weight = >::create_collection()] + #[transactional] + pub fn create_collection_ex(origin, data: CreateCollectionData) -> DispatchResult { + let owner = ensure_signed(origin)?; - let _id = match mode { - CollectionMode::NFT => {>::init_collection(new_collection)?}, + let _id = match data.mode { + CollectionMode::NFT => {>::init_collection(owner, data)?}, CollectionMode::Fungible(decimal_points) => { // check params ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::::CollectionDecimalPointLimitExceeded); - >::init_collection(new_collection)? + >::init_collection(owner, data)? } CollectionMode::ReFungible => { - >::init_collection(new_collection)? + >::init_collection(owner, data)? } }; @@ -1093,61 +1084,12 @@ collection_id: CollectionId, new_limit: CollectionLimits, ) -> DispatchResult { - let mut new_limit = new_limit; let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?); let mut target_collection = >::try_get(collection_id)?; target_collection.check_is_owner(&sender)?; let old_limit = &target_collection.limits; - macro_rules! limit_default { - ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{ - $( - if let Some($new) = $new.$field { - let $old = $old.$field($($arg)?); - let _ = $new; - let _ = $old; - $check - } else { - $new.$field = $old.$field - } - )* - }}; - } - - limit_default!(old_limit, new_limit, - account_token_ownership_limit => ensure!( - new_limit <= MAX_TOKEN_OWNERSHIP, - >::CollectionLimitBoundsExceeded, - ), - sponsor_transfer_timeout(match target_collection.mode { - CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, - }) => ensure!( - new_limit <= MAX_SPONSOR_TIMEOUT, - >::CollectionLimitBoundsExceeded, - ), - sponsored_data_size => ensure!( - new_limit <= CUSTOM_DATA_LIMIT, - >::CollectionLimitBoundsExceeded, - ), - token_limit => ensure!( - old_limit >= new_limit && new_limit > 0, - >::CollectionTokenLimitExceeded - ), - owner_can_transfer => ensure!( - old_limit || !new_limit, - >::OwnerPermissionsCantBeReverted, - ), - owner_can_destroy => ensure!( - old_limit || !new_limit, - >::OwnerPermissionsCantBeReverted, - ), - sponsored_data_rate_limit => {}, - transfers_enabled => {}, - ); - - target_collection.limits = new_limit; + target_collection.limits = >::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?; >::deposit_event(Event::::CollectionLimitSet( collection_id --- a/pallets/unique/src/tests.rs +++ b/pallets/unique/src/tests.rs @@ -5,7 +5,7 @@ use up_data_structs::{ COLLECTION_NUMBER_LIMIT, CollectionId, CreateItemData, CreateFungibleData, CreateNftData, CreateReFungibleData, MAX_DECIMAL_POINTS, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, - TokenId, + TokenId, MAX_TOKEN_OWNERSHIP, }; use frame_support::{assert_noop, assert_ok}; use sp_std::convert::TryInto; --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -263,6 +263,31 @@ pub meta_update_permission: MetaUpdatePermission, } +#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Debug, Derivative, MaxEncodedLen)] +#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] +#[derivative(Default(bound = ""))] +pub struct CreateCollectionData { + #[derivative(Default(value = "CollectionMode::NFT"))] + pub mode: CollectionMode, + pub access: Option, + #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))] + pub name: BoundedVec>, + #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))] + pub description: BoundedVec>, + #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))] + pub token_prefix: BoundedVec>, + #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))] + pub offchain_schema: BoundedVec>, + pub schema_version: Option, + pub pending_sponsor: Option, + pub limits: Option, + #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))] + pub variable_on_chain_schema: BoundedVec>, + #[cfg_attr(feature = "serde1", serde(with = "bounded_serde"))] + pub const_on_chain_schema: BoundedVec>, + pub meta_update_permission: Option, +} + #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct NftItemType { --- a/tests/package.json +++ b/tests/package.json @@ -68,9 +68,10 @@ "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts", "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts", "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.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 --input src/interfaces/ --package .", - "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint ws://localhost:9944 --output src/interfaces/ --package .", - "polkadot-types": "yarn polkadot-types-from-defs && yarn polkadot-types-from-chain" + "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .", + "polkadot-types": "yarn polkadot-types-fetch-metadata && yarn polkadot-types-from-defs && yarn polkadot-types-from-chain" }, "author": "", "license": "SEE LICENSE IN ../LICENSE", --- a/tests/src/check-event/createCollectionEvent.test.ts +++ b/tests/src/check-event/createCollectionEvent.test.ts @@ -27,7 +27,7 @@ }); it('Check event from createCollection(): ', async () => { await usingApi(async (api: ApiPromise) => { - const tx = api.tx.unique.createCollection([0x31], [0x32], '0x33', 'NFT'); + const tx = api.tx.unique.createCollectionEx({name: [0x31], description: [0x32], tokenPrefix: '0x33', mode: 'NFT'}); const events = await submitTransactionAsync(alice, tx); const msg = JSON.stringify(uniqueEventMessage(events)); expect(msg).to.be.contain(checkSection); --- a/tests/src/createCollection.test.ts +++ b/tests/src/createCollection.test.ts @@ -3,12 +3,11 @@ // file 'LICENSE', which is part of this source code package. // -import chai from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import {createCollectionExpectFailure, createCollectionExpectSuccess} from './util/helpers'; +import {expect} from 'chai'; +import privateKey from './substrate/privateKey'; +import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api'; +import {createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo} from './util/helpers'; -chai.use(chaiAsPromised); - describe('integration test: ext. createCollection():', () => { it('Create new NFT collection', async () => { await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}}); @@ -28,6 +27,45 @@ it('Create new ReFungible collection', async () => { await createCollectionExpectSuccess({mode: {type: 'ReFungible'}}); }); + it('Create new collection with extra fields', async () => { + await usingApi(async api => { + const alice = privateKey('//Alice'); + const bob = privateKey('//Bob'); + const tx = api.tx.unique.createCollectionEx({ + mode: {Fungible: 8}, + access: 'AllowList', + name: [1], + description: [2], + tokenPrefix: '0x000000', + offchainSchema: '0x111111', + schemaVersion: 'Unique', + pendingSponsor: bob.address, + limits: { + accountTokenOwnershipLimit: 3, + }, + variableOnChainSchema: '0x222222', + constOnChainSchema: '0x333333', + metaUpdatePermission: 'Admin', + }); + const events = await submitTransactionAsync(alice, tx); + const result = getCreateCollectionResult(events); + + const collection = (await getDetailedCollectionInfo(api, result.collectionId))!; + expect(collection.owner.toString()).to.equal(alice.address); + expect(collection.mode.asFungible.toNumber()).to.equal(8); + expect(collection.access.isAllowList).to.be.true; + expect(collection.name.map(v => v.toNumber())).to.deep.equal([1]); + expect(collection.description.map(v => v.toNumber())).to.deep.equal([2]); + expect(collection.tokenPrefix.toString()).to.equal('0x000000'); + expect(collection.offchainSchema.toString()).to.equal('0x111111'); + expect(collection.schemaVersion.isUnique).to.be.true; + expect(collection.sponsorship.asUnconfirmed.toString()).to.equal(bob.address); + expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.equal(3); + expect(collection.variableOnChainSchema.toString()).to.equal('0x222222'); + expect(collection.constOnChainSchema.toString()).to.equal('0x333333'); + expect(collection.metaUpdatePermission.isAdmin).to.be.true; + }); + }); }); describe('(!negative test!) integration test: ext. createCollection():', () => { @@ -40,4 +78,11 @@ it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => { await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17), mode: {type: 'NFT'}}); }); + it('fails when bad limits are set', async () => { + await usingApi(async api => { + const alice = privateKey('//Alice'); + const tx = api.tx.unique.createCollectionEx({mode: 'NFT', limits: {tokenLimit: 0}}); + await expect(executeTransaction(api, alice, tx)).to.be.rejectedWith(/^common.CollectionTokenLimitExceeded$/); + }); + }); }); --- /dev/null +++ b/tests/src/interfaces/.gitignore @@ -0,0 +1 @@ +metadata.json --- a/tests/src/interfaces/augment-api-errors.ts +++ b/tests/src/interfaces/augment-api-errors.ts @@ -69,6 +69,10 @@ **/ CollectionDescriptionLimitExceeded: AugmentedError; /** + * Collection limit bounds per collection exceeded + **/ + CollectionLimitBoundsExceeded: AugmentedError; + /** * Collection name can not be longer than 63 char. **/ CollectionNameLimitExceeded: AugmentedError; @@ -97,6 +101,10 @@ **/ NoPermission: AugmentedError; /** + * Tried to enable permissions which are only permitted to be disabled + **/ + OwnerPermissionsCantBeReverted: AugmentedError; + /** * Collection is not in mint mode. **/ PublicMintingNotAllowed: AugmentedError; @@ -437,10 +445,6 @@ **/ CollectionDecimalPointLimitExceeded: AugmentedError; /** - * Collection limit bounds per collection exceeded - **/ - CollectionLimitBoundsExceeded: AugmentedError; - /** * This address is not set as sponsor, use setCollectionSponsor first. **/ ConfirmUnsetSponsorFail: AugmentedError; @@ -448,10 +452,6 @@ * Length of items properties must be greater than 0. **/ EmptyArgument: AugmentedError; - /** - * Tried to enable permissions which are only permitted to be disabled - **/ - OwnerPermissionsCantBeReverted: AugmentedError; /** * Generic error **/ --- a/tests/src/interfaces/augment-api-tx.ts +++ b/tests/src/interfaces/augment-api-tx.ts @@ -2,7 +2,7 @@ /* eslint-disable */ import type { CumulusPrimitivesParachainInherentParachainInherentData } from './polkadot'; -import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique'; +import type { PalletCommonAccountBasicCrossAccountIdRepr, UpDataStructsAccessMode, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion } from './unique'; import type { ApiTypes, SubmittableExtrinsic } from '@polkadot/api/types'; import type { Bytes, Compact, Option, U256, Vec, bool, u128, u16, u32, u64 } from '@polkadot/types'; import type { Extrinsic } from '@polkadot/types/interfaces/extrinsics'; @@ -671,6 +671,12 @@ **/ createCollection: AugmentedSubmittable<(collectionName: Vec | (u16 | AnyNumber | Uint8Array)[], collectionDescription: Vec | (u16 | AnyNumber | Uint8Array)[], tokenPrefix: Bytes | string | Uint8Array, mode: UpDataStructsCollectionMode | { NFT: any } | { Fungible: any } | { ReFungible: any } | string | Uint8Array) => SubmittableExtrinsic, [Vec, Vec, Bytes, UpDataStructsCollectionMode]>; /** + * This method creates a collection + * + * Prefer it to deprecated [`created_collection`] method + **/ + createCollectionEx: AugmentedSubmittable<(data: UpDataStructsCreateCollectionData | { mode?: any; access?: any; name?: any; description?: any; tokenPrefix?: any; offchainSchema?: any; schemaVersion?: any; pendingSponsor?: any; limits?: any; variableOnChainSchema?: any; constOnChainSchema?: any; metaUpdatePermission?: any } | string | Uint8Array) => SubmittableExtrinsic, [UpDataStructsCreateCollectionData]>; + /** * This method creates a concrete instance of NFT Collection created with CreateCollection method. * * # Permissions --- a/tests/src/interfaces/augment-types.ts +++ b/tests/src/interfaces/augment-types.ts @@ -3,7 +3,7 @@ import type { EthereumBlock, EthereumLog, EthereumReceipt, EthereumTransactionLegacyTransaction, EvmCoreErrorExitReason, FpRpcTransactionStatus } from './ethereum'; import type { CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmpQueueInboundStatus, CumulusPalletXcmpQueueOutboundStatus, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV1AbridgedHostConfiguration, PolkadotPrimitivesV1PersistedValidationData } from './polkadot'; -import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique'; +import type { PalletCommonAccountBasicCrossAccountIdRepr, PalletNonfungibleItemData, PalletRefungibleItemData, PalletUnqSchedulerCallSpec, PalletUnqSchedulerReleases, PalletUnqSchedulerScheduledV2, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionId, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateItemData, UpDataStructsMetaUpdatePermission, UpDataStructsSchemaVersion, UpDataStructsSponsorshipState, UpDataStructsTokenId } from './unique'; import type { BitVec, Bool, Bytes, Data, I128, I16, I256, I32, I64, I8, Json, Null, Raw, StorageKey, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types'; import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets'; import type { BlockAttestations, IncludedBlocks, MoreAttestations } from '@polkadot/types/interfaces/attestations'; @@ -1021,6 +1021,7 @@ UpDataStructsCollectionLimits: UpDataStructsCollectionLimits; UpDataStructsCollectionMode: UpDataStructsCollectionMode; UpDataStructsCollectionStats: UpDataStructsCollectionStats; + UpDataStructsCreateCollectionData: UpDataStructsCreateCollectionData; UpDataStructsCreateItemData: UpDataStructsCreateItemData; UpDataStructsMetaUpdatePermission: UpDataStructsMetaUpdatePermission; UpDataStructsSchemaVersion: UpDataStructsSchemaVersion; --- a/tests/src/interfaces/unique/definitions.ts +++ b/tests/src/interfaces/unique/definitions.ts @@ -67,6 +67,20 @@ constOnChainSchema: 'Vec', metaUpdatePermission: 'UpDataStructsMetaUpdatePermission', }, + UpDataStructsCreateCollectionData: { + mode: 'UpDataStructsCollectionMode', + access: 'Option', + name: 'Vec', + description: 'Vec', + tokenPrefix: 'Vec', + offchainSchema: 'Vec', + schemaVersion: 'Option', + pendingSponsor: 'Option', + limits: 'Option', + variableOnChainSchema: 'Vec', + constOnChainSchema: 'Vec', + metaUpdatePermission: 'Option', + }, UpDataStructsCollectionStats: { created: 'u32', destroyed: 'u32', @@ -76,7 +90,13 @@ UpDataStructsTokenId: 'u32', PalletNonfungibleItemData: mkDummy('NftItemData'), PalletRefungibleItemData: mkDummy('RftItemData'), - UpDataStructsCollectionMode: mkDummy('CollectionMode'), + UpDataStructsCollectionMode: { + _enum: { + NFT: null, + Fungible: 'u32', + ReFungible: null, + }, + }, UpDataStructsCreateItemData: mkDummy('CreateItemData'), UpDataStructsCollectionLimits: { accountTokenOwnershipLimit: 'Option', @@ -101,7 +121,9 @@ UpDataStructsAccessMode: { _enum: ['Normal', 'AllowList'], }, - UpDataStructsSchemaVersion: mkDummy('SchemaVersion'), + UpDataStructsSchemaVersion: { + _enum: ['ImageURL', 'Unique'], + }, PalletUnqSchedulerScheduledV2: mkDummy('ScheduledV2'), PalletUnqSchedulerCallSpec: mkDummy('CallSpec'), --- a/tests/src/interfaces/unique/types.ts +++ b/tests/src/interfaces/unique/types.ts @@ -77,8 +77,11 @@ } /** @name UpDataStructsCollectionMode */ -export interface UpDataStructsCollectionMode extends Struct { - readonly dummyCollectionMode: u32; +export interface UpDataStructsCollectionMode extends Enum { + readonly isNft: boolean; + readonly isFungible: boolean; + readonly asFungible: u32; + readonly isReFungible: boolean; } /** @name UpDataStructsCollectionStats */ @@ -88,6 +91,22 @@ readonly alive: u32; } +/** @name UpDataStructsCreateCollectionData */ +export interface UpDataStructsCreateCollectionData extends Struct { + readonly mode: UpDataStructsCollectionMode; + readonly access: Option; + readonly name: Vec; + readonly description: Vec; + readonly tokenPrefix: Bytes; + readonly offchainSchema: Bytes; + readonly schemaVersion: Option; + readonly pendingSponsor: Option; + readonly limits: Option; + readonly variableOnChainSchema: Bytes; + readonly constOnChainSchema: Bytes; + readonly metaUpdatePermission: Option; +} + /** @name UpDataStructsCreateItemData */ export interface UpDataStructsCreateItemData extends Struct { readonly dummyCreateItemData: u32; @@ -101,8 +120,9 @@ } /** @name UpDataStructsSchemaVersion */ -export interface UpDataStructsSchemaVersion extends Struct { - readonly dummySchemaVersion: u32; +export interface UpDataStructsSchemaVersion extends Enum { + readonly isImageUrl: boolean; + readonly isUnique: boolean; } /** @name UpDataStructsSponsorshipState */ --- a/tests/src/substrate/substrate-api.ts +++ b/tests/src/substrate/substrate-api.ts @@ -95,6 +95,32 @@ return TransactionStatus.Fail; } +export function executeTransaction(api: ApiPromise, sender: IKeyringPair, transaction: SubmittableExtrinsic<'promise'>): Promise { + return new Promise(async (res, rej) => { + try { + await transaction.signAndSend(sender, ({events, status}) => { + if (!status.isInBlock && !status.isFinalized) return; + for (const {event} of events) { + if (api.events.system.ExtrinsicSuccess.is(event)) { + res(events); + } else if (api.events.system.ExtrinsicFailed.is(event)) { + const {data: [error]} = event; + if (error.isModule) { + const decoded = api.registry.findMetaError(error.asModule); + const {method, section} = decoded; + rej(new Error(`${section}.${method}`)); + } else { + rej(new Error(error.toString())); + } + } + } + }); + } catch (e) { + rej(e); + } + }); +} + export function submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic): Promise { /* eslint no-async-promise-executor: "off" */ --- a/tests/src/util/helpers.ts +++ b/tests/src/util/helpers.ts @@ -49,7 +49,7 @@ }; } else if ('Substrate' in input) { return input; - }else if ('substrate' in input) { + } else if ('substrate' in input) { return { Substrate: (input as any).substrate, }; @@ -116,15 +116,15 @@ export interface IChainLimits { collectionNumbersLimit: number; - accountTokenOwnershipLimit: number; - collectionsAdminsLimit: number; - customDataLimit: number; - nftSponsorTransferTimeout: number; - fungibleSponsorTransferTimeout: number; - refungibleSponsorTransferTimeout: number; - offchainSchemaLimit: number; - variableOnChainSchemaLimit: number; - constOnChainSchemaLimit: number; + accountTokenOwnershipLimit: number; + collectionsAdminsLimit: number; + customDataLimit: number; + nftSponsorTransferTimeout: number; + fungibleSponsorTransferTimeout: number; + refungibleSponsorTransferTimeout: number; + offchainSchemaLimit: number; + variableOnChainSchemaLimit: number; + constOnChainSchemaLimit: number; } export interface IReFungibleTokenDataType { @@ -283,7 +283,7 @@ modeprm = {refungible: null}; } - const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any); + const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any}); const events = await submitTransactionAsync(alicePrivateKey, tx); const result = getCreateCollectionResult(events); @@ -329,7 +329,7 @@ // Run the CreateCollection transaction const alicePrivateKey = privateKey('//Alice'); - const tx = api.tx.unique.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), modeprm as any); + const tx = api.tx.unique.createCollectionEx({name: strToUTF16(name), description: strToUTF16(description), tokenPrefix: strToUTF16(tokenPrefix), mode: modeprm as any}); const events = await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected; const result = getCreateCollectionResult(events); @@ -557,7 +557,7 @@ await usingApi(async (api) => { - const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled); + const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled); const events = await submitTransactionAsync(sender, tx); const result = getGenericResult(events); @@ -569,7 +569,7 @@ await usingApi(async (api) => { - const tx = api.tx.unique.setTransfersEnabledFlag (collectionId, enabled); + const tx = api.tx.unique.setTransfersEnabledFlag(collectionId, enabled); const events = await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected; const result = getGenericResult(events); @@ -811,8 +811,7 @@ } export async function -getFreeBalance(account: IKeyringPair) : Promise -{ +getFreeBalance(account: IKeyringPair): Promise { let balance = 0n; await usingApi(async (api) => { balance = BigInt((await api.query.system.account(account.address)).data.free.toString()); -- gitstuff