difftreelog
CORE-390 Add read only flag
in: master
8 files changed
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -87,18 +87,16 @@
check_is_owner(caller, self)?;
let sponsor = T::CrossAccountId::from_eth(sponsor);
- self.set_sponsor(sponsor.as_sub().clone());
- save(self);
- Ok(())
+ self.set_sponsor(sponsor.as_sub().clone()).map_err(dispatch_to_evm::<T>)?;
+ save(self)
}
fn confirm_collection_sponsorship(&mut self, caller: caller) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
- if !self.confirm_sponsorship(caller.as_sub()) {
+ if !self.confirm_sponsorship(caller.as_sub()).map_err(dispatch_to_evm::<T>)? {
return Err(Error::Revert("Caller is not set as sponsor".into()));
}
- save(self);
- Ok(())
+ save(self)
}
#[solidity(rename_selector = "setCollectionLimit")]
@@ -134,8 +132,7 @@
}
self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)
.map_err(dispatch_to_evm::<T>)?;
- save(self);
- Ok(())
+ save(self)
}
#[solidity(rename_selector = "setCollectionLimit")]
@@ -162,8 +159,7 @@
}
self.limits = <Pallet<T>>::clamp_limits(self.mode.clone(), &self.limits, limits)
.map_err(dispatch_to_evm::<T>)?;
- save(self);
- Ok(())
+ save(self)
}
fn contract_address(&self, _caller: caller) -> Result<address> {
@@ -296,7 +292,7 @@
}
}
-fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<()> {
+fn check_is_owner<T: Config>(caller: caller, collection: &CollectionHandle<T>) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
collection
.check_is_owner(&caller)
@@ -315,8 +311,10 @@
Ok(caller)
}
-fn save<T: Config>(collection: &CollectionHandle<T>) {
+fn save<T: Config>(collection: &CollectionHandle<T>) -> Result<void> {
+ collection.check_is_read_only().map_err(dispatch_to_evm::<T>)?;
<crate::CollectionById<T>>::insert(collection.id, collection.collection.clone());
+ Ok(())
}
pub fn token_uri_key() -> up_data_structs::PropertyKey {
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -148,23 +148,35 @@
.saturating_mul(writes),
))
}
-
- pub fn save(self) -> DispatchResult {
+ pub fn save(self) -> Result<(), DispatchError> {
+ self.check_is_read_only()?;
<CollectionById<T>>::insert(self.id, self.collection);
Ok(())
}
- pub fn set_sponsor(&mut self, sponsor: T::AccountId) {
+ pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {
+ self.check_is_read_only()?;
self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);
+ Ok(())
}
- pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {
+ pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {
+ self.check_is_read_only()?;
+
if self.collection.sponsorship.pending_sponsor() != Some(sender) {
- return false;
- };
+ return Ok(false);
+ }
self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());
- true
+ Ok(true)
+ }
+
+ pub fn check_is_read_only(&self) -> DispatchResult {
+ if self.read_only {
+ return Err(<Error<T>>::CollectionNotFound)?;
+ }
+
+ Ok(())
}
}
@@ -434,6 +446,9 @@
/// Empty property keys are forbidden
EmptyPropertyKey,
+
+ /// Collection is read only
+ CollectionIsReadOnly,
}
#[pallet::storage]
@@ -669,6 +684,7 @@
sponsorship,
limits,
permissions,
+ read_only,
} = <CollectionById<T>>::get(collection)?;
let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)
@@ -698,6 +714,7 @@
permissions,
token_property_permissions,
properties,
+ read_only,
})
}
}
@@ -778,6 +795,7 @@
Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)
})
.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,
+ read_only: false,
};
let mut collection_properties = up_data_structs::CollectionProperties::get();
@@ -834,6 +852,7 @@
collection: CollectionHandle<T>,
sender: &T::CrossAccountId,
) -> DispatchResult {
+ collection.check_is_read_only()?;
ensure!(
collection.limits.owner_can_destroy(),
<Error<T>>::NoPermission,
@@ -863,6 +882,7 @@
sender: &T::CrossAccountId,
property: Property,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
CollectionProperties::<T>::try_mutate(collection.id, |properties| {
@@ -908,6 +928,8 @@
sender: &T::CrossAccountId,
properties: Vec<Property>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
for property in properties {
Self::set_collection_property(collection, sender, property)?;
}
@@ -920,6 +942,7 @@
sender: &T::CrossAccountId,
property_key: PropertyKey,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
CollectionProperties::<T>::try_mutate(collection.id, |properties| {
@@ -941,6 +964,8 @@
sender: &T::CrossAccountId,
property_keys: Vec<PropertyKey>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
for key in property_keys {
Self::delete_collection_property(collection, sender, key)?;
}
@@ -965,6 +990,7 @@
sender: &T::CrossAccountId,
property_permission: PropertyKeyPermission,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);
@@ -996,6 +1022,8 @@
sender: &T::CrossAccountId,
property_permissions: Vec<PropertyKeyPermission>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
for prop_pemission in property_permissions {
Self::set_property_permission(collection, sender, prop_pemission)?;
}
@@ -1083,6 +1111,7 @@
user: &T::CrossAccountId,
allowed: bool,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
// =========
@@ -1102,6 +1131,7 @@
user: &T::CrossAccountId,
admin: bool,
) -> DispatchResult {
+ collection.check_is_read_only()?;
collection.check_is_owner_or_admin(sender)?;
let was_admin = <IsAdmin<T>>::get((collection.id, user));
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -168,6 +168,8 @@
owner: &T::CrossAccountId,
amount: u128,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
let total_supply = <TotalSupply<T>>::get(collection.id)
.checked_sub(amount)
.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -214,6 +216,8 @@
amount: u128,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed,
@@ -283,6 +287,8 @@
data: BTreeMap<T::CrossAccountId, u128>,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
if !collection.is_owner_or_admin(sender) {
ensure!(
collection.permissions.mint_mode(),
@@ -384,6 +390,7 @@
spender: &T::CrossAccountId,
amount: u128,
) -> DispatchResult {
+ collection.check_is_read_only()?;
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(owner)?;
collection.check_allowlist(spender)?;
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -336,6 +336,8 @@
sender: &T::CrossAccountId,
token: TokenId,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
let token_data =
<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
ensure!(
@@ -456,6 +458,7 @@
&property.key,
is_token_create,
)?;
+ collection.check_is_read_only()?;
<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
let property = property.clone();
@@ -494,6 +497,7 @@
property_key: PropertyKey,
) -> DispatchResult {
Self::check_token_change_permission(collection, sender, token_id, &property_key, false)?;
+ collection.check_is_read_only()?;
<TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {
properties.remove(&property_key)
@@ -570,6 +574,8 @@
token_id: TokenId,
property_keys: Vec<PropertyKey>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
for key in property_keys {
Self::delete_token_property(collection, sender, token_id, key)?;
}
@@ -616,6 +622,8 @@
token: TokenId,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed
@@ -894,6 +902,8 @@
token: TokenId,
spender: Option<&T::CrossAccountId>,
) -> DispatchResult {
+ collection.check_is_read_only()?;
+
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
if let Some(spender) = spender {
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -234,6 +234,7 @@
}
pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {
+ collection.check_is_read_only()?;
let burnt = <TokensBurnt<T>>::get(collection.id)
.checked_add(1)
.ok_or(ArithmeticError::Overflow)?;
@@ -253,6 +254,7 @@
token: TokenId,
amount: u128,
) -> DispatchResult {
+ collection.check_is_read_only()?;
let total_supply = <TotalSupply<T>>::get((collection.id, token))
.checked_sub(amount)
.ok_or(<CommonError<T>>::TokenValueTooLow)?;
@@ -325,6 +327,7 @@
amount: u128,
nesting_budget: &dyn Budget,
) -> DispatchResult {
+ collection.check_is_read_only()?;
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed
@@ -573,6 +576,7 @@
token: TokenId,
amount: u128,
) -> DispatchResult {
+ collection.check_is_read_only()?;
if collection.permissions.access() == AccessMode::AllowList {
collection.check_allowlist(sender)?;
collection.check_allowlist(spender)?;
pallets/unique/src/lib.rsdiffbeforeafterboth--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -304,6 +304,7 @@
pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let collection = <CollectionHandle<T>>::try_get(collection_id)?;
+ collection.check_is_read_only()?;
// =========
@@ -406,6 +407,7 @@
let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
+ target_collection.check_is_read_only()?;
target_collection.check_is_owner(&sender)?;
target_collection.owner = new_owner.clone();
@@ -487,7 +489,7 @@
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
target_collection.check_is_owner(&sender)?;
- target_collection.set_sponsor(new_sponsor.clone());
+ target_collection.set_sponsor(new_sponsor.clone())?;
<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(
collection_id,
@@ -511,7 +513,7 @@
let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
ensure!(
- target_collection.confirm_sponsorship(&sender),
+ target_collection.confirm_sponsorship(&sender)?,
Error::<T>::ConfirmUnsetSponsorFail
);
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -316,6 +316,9 @@
#[version(2.., upper(Default::default()))]
pub permissions: CollectionPermissions,
+ #[version(2.., upper(false))]
+ pub read_only: bool,
+
#[version(..2)]
pub variable_on_chain_schema: BoundedVec<u8, ConstU32<VARIABLE_ON_CHAIN_SCHEMA_LIMIT>>,
@@ -340,6 +343,7 @@
pub permissions: CollectionPermissions,
pub token_property_permissions: Vec<PropertyKeyPermission>,
pub properties: Vec<Property>,
+ pub read_only: bool,
}
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, Derivative, MaxEncodedLen)]
tests/src/createCollection.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect} from 'chai';18import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api';19import {createCollectionWithPropsExpectFailure, createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo, createCollectionWithPropsExpectSuccess} from './util/helpers';2021describe('integration test: ext. createCollection():', () => {22 it('Create new NFT collection', async () => {23 await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});24 });25 it('Create new NFT collection whith collection_name of maximum length (64 bytes)', async () => {26 await createCollectionExpectSuccess({name: 'A'.repeat(64)});27 });28 it('Create new NFT collection whith collection_description of maximum length (256 bytes)', async () => {29 await createCollectionExpectSuccess({description: 'A'.repeat(256)});30 });31 it('Create new NFT collection whith token_prefix of maximum length (16 bytes)', async () => {32 await createCollectionExpectSuccess({tokenPrefix: 'A'.repeat(16)});33 });34 it('Create new Fungible collection', async () => {35 await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});36 });37 it('Create new ReFungible collection', async () => {38 await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});39 });4041 it('create new collection with properties #1', async () => {42 await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'},43 properties: [{key: 'key1', value: 'val1'}],44 propPerm: [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]});45 });4647 it('create new collection with properties #2', async () => {48 await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'},49 properties: [{key: 'key1', value: 'val1'}],50 propPerm: [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]});51 });5253 it('create new collection with properties #3', async () => {54 await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'},55 properties: [{key: 'key1', value: 'val1'}],56 propPerm: [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]});57 });5859 it('Create new collection with extra fields', async () => {60 await usingApi(async (api, privateKeyWrapper) => {61 const alice = privateKeyWrapper('//Alice');62 const bob = privateKeyWrapper('//Bob');63 const tx = api.tx.unique.createCollectionEx({64 mode: {Fungible: 8},65 permissions: {66 access: 'AllowList',67 },68 name: [1],69 description: [2],70 tokenPrefix: '0x000000',71 pendingSponsor: bob.address,72 limits: {73 accountTokenOwnershipLimit: 3,74 },75 });76 const events = await submitTransactionAsync(alice, tx);77 const result = getCreateCollectionResult(events);7879 const collection = (await getDetailedCollectionInfo(api, result.collectionId))!;80 expect(collection.owner.toString()).to.equal(alice.address);81 expect(collection.mode.asFungible.toNumber()).to.equal(8);82 expect(collection.permissions.access.toHuman()).to.equal('AllowList');83 expect(collection.name.map(v => v.toNumber())).to.deep.equal([1]);84 expect(collection.description.map(v => v.toNumber())).to.deep.equal([2]);85 expect(collection.tokenPrefix.toString()).to.equal('0x000000');86 expect(collection.sponsorship.asUnconfirmed.toString()).to.equal(bob.address);87 expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.equal(3);88 });89 });90});9192describe('(!negative test!) integration test: ext. createCollection():', () => {93 it('(!negative test!) create new NFT collection whith incorrect data (collection_name)', async () => {94 await createCollectionExpectFailure({name: 'A'.repeat(65), mode: {type: 'NFT'}});95 });96 it('(!negative test!) create new NFT collection whith incorrect data (collection_description)', async () => {97 await createCollectionExpectFailure({description: 'A'.repeat(257), mode: {type: 'NFT'}});98 });99 it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => {100 await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17), mode: {type: 'NFT'}});101 });102 it('fails when bad limits are set', async () => {103 await usingApi(async (api, privateKeyWrapper) => {104 const alice = privateKeyWrapper('//Alice');105 const tx = api.tx.unique.createCollectionEx({mode: 'NFT', limits: {tokenLimit: 0}});106 await expect(executeTransaction(api, alice, tx)).to.be.rejectedWith(/^common.CollectionTokenLimitExceeded$/);107 });108 });109110 it('(!negative test!) create collection with incorrect property limit (64 elements)', async () => {111 const props = [];112113 for (let i = 0; i < 65; i++) {114 props.push({key: `key${i}`, value: `value${i}`});115 }116117 await createCollectionWithPropsExpectFailure({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, properties: props});118 });119120 it('(!negative test!) create collection with incorrect property limit (40 kb)', async () => {121 const props = [];122123 for (let i = 0; i < 32; i++) {124 props.push({key: `key${i}`.repeat(80), value: `value${i}`.repeat(80)});125 }126127 await createCollectionWithPropsExpectFailure({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, properties: props});128 });129});1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {expect} from 'chai';18import usingApi, {executeTransaction, submitTransactionAsync} from './substrate/substrate-api';19import {createCollectionWithPropsExpectFailure, createCollectionExpectFailure, createCollectionExpectSuccess, getCreateCollectionResult, getDetailedCollectionInfo, createCollectionWithPropsExpectSuccess} from './util/helpers';2021describe('integration test: ext. createCollection():', () => {22 it('Create new NFT collection', async () => {23 await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}});24 });25 it('Create new NFT collection whith collection_name of maximum length (64 bytes)', async () => {26 await createCollectionExpectSuccess({name: 'A'.repeat(64)});27 });28 it('Create new NFT collection whith collection_description of maximum length (256 bytes)', async () => {29 await createCollectionExpectSuccess({description: 'A'.repeat(256)});30 });31 it('Create new NFT collection whith token_prefix of maximum length (16 bytes)', async () => {32 await createCollectionExpectSuccess({tokenPrefix: 'A'.repeat(16)});33 });34 it('Create new Fungible collection', async () => {35 await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});36 });37 it('Create new ReFungible collection', async () => {38 await createCollectionExpectSuccess({mode: {type: 'ReFungible'}});39 });4041 it('create new collection with properties #1', async () => {42 await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'},43 properties: [{key: 'key1', value: 'val1'}],44 propPerm: [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]});45 });4647 it('create new collection with properties #2', async () => {48 await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'},49 properties: [{key: 'key1', value: 'val1'}],50 propPerm: [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]});51 });5253 it('create new collection with properties #3', async () => {54 await createCollectionWithPropsExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'},55 properties: [{key: 'key1', value: 'val1'}],56 propPerm: [{key: 'key1', permission: {tokenOwner: true, mutable: false, collectionAdmin: true}}]});57 });5859 it('Create new collection with extra fields', async () => {60 await usingApi(async (api, privateKeyWrapper) => {61 const alice = privateKeyWrapper('//Alice');62 const bob = privateKeyWrapper('//Bob');63 const tx = api.tx.unique.createCollectionEx({64 mode: {Fungible: 8},65 permissions: {66 access: 'AllowList',67 },68 name: [1],69 description: [2],70 tokenPrefix: '0x000000',71 pendingSponsor: bob.address,72 limits: {73 accountTokenOwnershipLimit: 3,74 },75 });76 const events = await submitTransactionAsync(alice, tx);77 const result = getCreateCollectionResult(events);7879 const collection = (await getDetailedCollectionInfo(api, result.collectionId))!;80 expect(collection.owner.toString()).to.equal(alice.address);81 expect(collection.mode.asFungible.toNumber()).to.equal(8);82 expect(collection.permissions.access.toHuman()).to.equal('AllowList');83 expect(collection.name.map(v => v.toNumber())).to.deep.equal([1]);84 expect(collection.description.map(v => v.toNumber())).to.deep.equal([2]);85 expect(collection.tokenPrefix.toString()).to.equal('0x000000');86 expect(collection.sponsorship.asUnconfirmed.toString()).to.equal(bob.address);87 expect(collection.limits.accountTokenOwnershipLimit.unwrap().toNumber()).to.equal(3);88 });89 });9091 it('Create new collection is not read only', async () => {92 await usingApi(async api => {93 const alice = privateKey('//Alice');94 const tx = api.tx.unique.createCollectionEx({95 readOnly: true96 });97 const events = await submitTransactionAsync(alice, tx);98 const result = getCreateCollectionResult(events);99100 const collection = (await getDetailedCollectionInfo(api, result.collectionId))!;101 expect(collection.readOnly.toHuman()).to.be.false;102 });103 });104});105106describe('(!negative test!) integration test: ext. createCollection():', () => {107 it('(!negative test!) create new NFT collection whith incorrect data (collection_name)', async () => {108 await createCollectionExpectFailure({name: 'A'.repeat(65), mode: {type: 'NFT'}});109 });110 it('(!negative test!) create new NFT collection whith incorrect data (collection_description)', async () => {111 await createCollectionExpectFailure({description: 'A'.repeat(257), mode: {type: 'NFT'}});112 });113 it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => {114 await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17), mode: {type: 'NFT'}});115 });116 it('fails when bad limits are set', async () => {117 await usingApi(async (api, privateKeyWrapper) => {118 const alice = privateKeyWrapper('//Alice');119 const tx = api.tx.unique.createCollectionEx({mode: 'NFT', limits: {tokenLimit: 0}});120 await expect(executeTransaction(api, alice, tx)).to.be.rejectedWith(/^common.CollectionTokenLimitExceeded$/);121 });122 });123124 it('(!negative test!) create collection with incorrect property limit (64 elements)', async () => {125 const props = [];126127 for (let i = 0; i < 65; i++) {128 props.push({key: `key${i}`, value: `value${i}`});129 }130131 await createCollectionWithPropsExpectFailure({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, properties: props});132 });133134 it('(!negative test!) create collection with incorrect property limit (40 kb)', async () => {135 const props = [];136137 for (let i = 0; i < 32; i++) {138 props.push({key: `key${i}`.repeat(80), value: `value${i}`.repeat(80)});139 }140141 await createCollectionWithPropsExpectFailure({name: 'A', description: 'B', tokenPrefix: 'C', mode: {type: 'NFT'}, properties: props});142 });143});