difftreelog
Merge pull request #780 from UniqueNetwork/feature/properties-for-ft-collections
in: master
5 files changed
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1222,7 +1222,8 @@
Ok(())
}
- /// Set scouped collection property.
+ /// Set a scoped collection property, where the scope is a special prefix
+ /// prohibiting a user to change the property.
///
/// * `collection_id` - ID of the collection for which the property is being set.
/// * `scope` - Property scope.
@@ -1240,7 +1241,8 @@
Ok(())
}
- /// Set scouped collection properties.
+ /// Set scoped collection properties, where the scope is a special prefix
+ /// prohibiting a user to change the properties.
///
/// * `collection_id` - ID of the collection for which the properties is being set.
/// * `scope` - Property scope.
pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -18,7 +18,10 @@
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
use up_data_structs::{TokenId, CollectionId, CreateItemExData, budget::Budget, CreateItemData};
-use pallet_common::{CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight};
+use pallet_common::{
+ CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
+ weights::WeightInfo as _,
+};
use pallet_structure::Error as StructureError;
use sp_runtime::ArithmeticError;
use sp_std::{vec::Vec, vec};
@@ -53,14 +56,12 @@
<SelfWeightOf<T>>::burn_item()
}
- fn set_collection_properties(_amount: u32) -> Weight {
- // Error
- Weight::zero()
+ fn set_collection_properties(amount: u32) -> Weight {
+ <pallet_common::SelfWeightOf<T>>::set_collection_properties(amount)
}
- fn delete_collection_properties(_amount: u32) -> Weight {
- // Error
- Weight::zero()
+ fn delete_collection_properties(amount: u32) -> Weight {
+ <pallet_common::SelfWeightOf<T>>::delete_collection_properties(amount)
}
fn set_token_properties(_amount: u32) -> Weight {
@@ -294,18 +295,28 @@
fn set_collection_properties(
&self,
- _sender: T::CrossAccountId,
- _property: Vec<Property>,
+ sender: T::CrossAccountId,
+ properties: Vec<Property>,
) -> DispatchResultWithPostInfo {
- fail!(<Error<T>>::SettingPropertiesNotAllowed)
+ let weight = <CommonWeights<T>>::set_collection_properties(properties.len() as u32);
+
+ with_weight(
+ <Pallet<T>>::set_collection_properties(self, &sender, properties),
+ weight,
+ )
}
fn delete_collection_properties(
&self,
- _sender: &T::CrossAccountId,
- _property_keys: Vec<PropertyKey>,
+ sender: &T::CrossAccountId,
+ property_keys: Vec<PropertyKey>,
) -> DispatchResultWithPostInfo {
- fail!(<Error<T>>::SettingPropertiesNotAllowed)
+ let weight = <CommonWeights<T>>::delete_collection_properties(property_keys.len() as u32);
+
+ with_weight(
+ <Pallet<T>>::delete_collection_properties(self, sender, property_keys),
+ weight,
+ )
}
fn set_token_properties(
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -80,11 +80,11 @@
use core::ops::Deref;
use evm_coder::ToLog;
-use frame_support::{ensure};
+use frame_support::ensure;
use pallet_evm::account::CrossAccountId;
use up_data_structs::{
AccessMode, CollectionId, CollectionFlags, TokenId, CreateCollectionData,
- mapping::TokenAddressMapping, budget::Budget,
+ mapping::TokenAddressMapping, budget::Budget, PropertyKey, Property,
};
use pallet_common::{
Error as CommonError, Event as CommonEvent, Pallet as PalletCommon,
@@ -260,7 +260,25 @@
Ok(())
}
- ///Checks if collection has tokens. Return `true` if it has.
+ /// Add properties to the collection.
+ pub fn set_collection_properties(
+ collection: &FungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ properties: Vec<Property>,
+ ) -> DispatchResult {
+ <PalletCommon<T>>::set_collection_properties(collection, sender, properties)
+ }
+
+ /// Delete properties of the collection, associated with the provided keys.
+ pub fn delete_collection_properties(
+ collection: &FungibleHandle<T>,
+ sender: &T::CrossAccountId,
+ property_keys: Vec<PropertyKey>,
+ ) -> DispatchResult {
+ <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)
+ }
+
+ /// Checks if collection has tokens. Return `true` if it has.
fn collection_has_tokens(collection_id: CollectionId) -> bool {
<TotalSupply<T>>::get(collection_id) != 0
}
tests/package.jsondiffbeforeafterboth--- a/tests/package.json
+++ b/tests/package.json
@@ -45,6 +45,7 @@
"testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nest.test.ts",
"testUnnesting": "mocha --timeout 9999999 -r ts-node/register ./**/unnest.test.ts",
"testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/collectionProperties.test.ts ./**/tokenProperties.test.ts ./**/getPropertiesRpc.test.ts",
+ "testCollectionProperties": "mocha --timeout 9999999 -r ts-node/register ./**/collectionProperties.test.ts",
"testMigration": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts",
"testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",
"testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts",
tests/src/nesting/collectionProperties.test.tsdiffbeforeafterboth15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.161617import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';18import {itSub, Pallets, usingPlaygrounds, expect} from '../util';18import {itSub, Pallets, usingPlaygrounds, expect, requirePalletsOrSkip} from '../util';19import {UniqueBaseCollection} from '../util/playgrounds/unique';201921describe('Integration Test: Collection Properties', () => {20describe('Integration Test: Collection Properties', () => {22 let alice: IKeyringPair;21 let alice: IKeyringPair;34 expect(await collection.getProperties()).to.be.empty;33 expect(await collection.getProperties()).to.be.empty;35 });34 });36 3537 async function testSetsPropertiesForCollection(collection: UniqueBaseCollection) {36 [38 // As owner37 {mode: 'nft' as const, requiredPallets: []},39 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;38 {mode: 'ft' as const, requiredPallets: []},40 39 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, 41 await collection.addAdmin(alice, {Substrate: bob.address});40 ].map(testSuite => describe(`${testSuite.mode.toUpperCase()}`, () => {42 41 before(async function() {43 // As administrator42 // eslint-disable-next-line require-await44 await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;43 await usingPlaygrounds(async helper => {45 44 requirePalletsOrSkip(this, helper, testSuite.requiredPallets);46 const properties = await collection.getProperties();45 });47 expect(properties).to.include.deep.members([48 {key: 'electron', value: 'come bond'},49 {key: 'black_hole', value: ''},50 ]);51 }52 53 itSub('Sets properties for a NFT collection', async ({helper}) => {54 await testSetsPropertiesForCollection(await helper.nft.mintCollection(alice));55 });46 });56 4748 itSub('Sets properties for a collection', async ({helper}) => {49 const collection = await helper[testSuite.mode].mintCollection(alice);5051 // As owner52 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}])).to.be.fulfilled;53 57 itSub.ifWithPallets('Sets properties for a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {54 await collection.addAdmin(alice, {Substrate: bob.address});58 await testSetsPropertiesForCollection(await helper.rft.mintCollection(alice));55 56 // As administrator57 await expect(collection.setProperties(bob, [{key: 'black_hole'}])).to.be.fulfilled;58 59 const properties = await collection.getProperties();60 expect(properties).to.include.deep.members([61 {key: 'electron', value: 'come bond'},62 {key: 'black_hole', value: ''},63 ]);59 });64 });60 65 61 async function testCheckValidNames(collection: UniqueBaseCollection) {66 itSub('Check valid names for collection properties keys', async ({helper}) => {67 const collection = await helper[testSuite.mode].mintCollection(alice);6862 // alpha symbols69 // alpha symbols63 await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;70 await expect(collection.setProperties(alice, [{key: 'answer'}])).to.be.fulfilled;82 {key: '-', value: ''},89 {key: '-', value: ''},83 {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},90 {key: 'once.in.a.long.long.while...', value: 'you get a little lost'},84 ]);91 ]);85 }92 });86 93 87 itSub('Check valid names for NFT collection properties keys', async ({helper}) => {88 await testCheckValidNames(await helper.nft.mintCollection(alice));89 });90 91 itSub.ifWithPallets('Check valid names for ReFungible collection properties keys', [Pallets.ReFungible], async ({helper}) => {92 await testCheckValidNames(await helper.rft.mintCollection(alice));93 });94 95 async function testChangesProperties(collection: UniqueBaseCollection) {94 itSub('Changes properties of a collection', async ({helper}) => {95 const collection = await helper[testSuite.mode].mintCollection(alice);9696 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;97 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: ''}])).to.be.fulfilled;97 98 103 {key: 'electron', value: 'come bond'},104 {key: 'electron', value: 'come bond'},104 {key: 'black_hole', value: 'LIGO'},105 {key: 'black_hole', value: 'LIGO'},105 ]);106 ]);106 }107 });107 108 108 itSub('Changes properties of a NFT collection', async ({helper}) => {109 await testChangesProperties(await helper.nft.mintCollection(alice));110 });111 112 itSub.ifWithPallets('Changes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {113 await testChangesProperties(await helper.rft.mintCollection(alice));114 });115 116 async function testDeleteProperties(collection: UniqueBaseCollection) {109 itSub('Deletes properties of a collection', async ({helper}) => {110 const collection = await helper[testSuite.mode].mintCollection(alice);111117 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;112 await expect(collection.setProperties(alice, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}])).to.be.fulfilled;118 113 122 expect(properties).to.be.deep.equal([117 expect(properties).to.be.deep.equal([123 {key: 'black_hole', value: 'LIGO'},118 {key: 'black_hole', value: 'LIGO'},124 ]);119 ]);125 }120 });126 121127 itSub('Deletes properties of a NFT collection', async ({helper}) => {128 await testDeleteProperties(await helper.nft.mintCollection(alice));129 });130 131 itSub.ifWithPallets('Deletes properties of a ReFungible collection', [Pallets.ReFungible], async ({helper}) => {132 await testDeleteProperties(await helper.rft.mintCollection(alice));133 });134135 [136 // TODO enable properties for FT collection in Substrate (release 040)137 // {mode: 'ft' as const, requiredPallets: []},138 {mode: 'nft' as const, requiredPallets: []},139 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, 140 ].map(testCase =>141 itSub.ifWithPallets(`Allows modifying a collection property multiple times with the same size (${testCase.mode})`, testCase.requiredPallets, async({helper}) => {122 itSub('Allows modifying a collection property multiple times with the same size', async({helper}) => {142 const propKey = 'tok-prop';123 const propKey = 'tok-prop';143124144 const collection = await helper[testCase.mode].mintCollection(alice);125 const collection = await helper[testSuite.mode].mintCollection(alice);145126146 const maxCollectionPropertiesSize = 40960;127 const maxCollectionPropertiesSize = 40960;147128166 const consumedSpace = await collection.getPropertiesConsumedSpace();147 const consumedSpace = await collection.getPropertiesConsumedSpace();167 expect(consumedSpace).to.be.equal(originalSpace);148 expect(consumedSpace).to.be.equal(originalSpace);168 }149 }169 }));150 });170151171 [172 // TODO enable properties for FT collection in Substrate (release 040)173 // {mode: 'ft' as const, requiredPallets: []},174 {mode: 'nft' as const, requiredPallets: []},175 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, 176 ].map(testCase =>177 itSub.ifWithPallets(`Adding then removing a collection property doesn't change the consumed space (${testCase.mode})`, testCase.requiredPallets, async({helper}) => {152 itSub('Adding then removing a collection property doesn\'t change the consumed space', async({helper}) => {178 const propKey = 'tok-prop';153 const propKey = 'tok-prop';179154180 const collection = await helper[testCase.mode].mintCollection(alice);155 const collection = await helper[testSuite.mode].mintCollection(alice);181 const originalSpace = await collection.getPropertiesConsumedSpace();156 const originalSpace = await collection.getPropertiesConsumedSpace();182157183 const propDataSize = 4096;158 const propDataSize = 4096;190 await collection.deleteProperties(alice, [propKey]);165 await collection.deleteProperties(alice, [propKey]);191 consumedSpace = await collection.getPropertiesConsumedSpace();166 consumedSpace = await collection.getPropertiesConsumedSpace();192 expect(consumedSpace).to.be.equal(originalSpace);167 expect(consumedSpace).to.be.equal(originalSpace);193 }));168 });194169195 [196 // TODO enable properties for FT collection in Substrate (release 040)197 // {mode: 'ft' as const, requiredPallets: []},198 {mode: 'nft' as const, requiredPallets: []},199 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, 200 ].map(testCase =>201 itSub.ifWithPallets(`Modifying a collection property with different sizes correctly changes the consumed space (${testCase.mode})`, testCase.requiredPallets, async({helper}) => {170 itSub('Modifying a collection property with different sizes correctly changes the consumed space', async({helper}) => {202 const propKey = 'tok-prop';171 const propKey = 'tok-prop';203172204 const collection = await helper[testCase.mode].mintCollection(alice);173 const collection = await helper[testSuite.mode].mintCollection(alice);205 const originalSpace = await collection.getPropertiesConsumedSpace();174 const originalSpace = await collection.getPropertiesConsumedSpace();206175207 const initPropDataSize = 4096;176 const initPropDataSize = 4096;229 consumedSpace = await collection.getPropertiesConsumedSpace();198 consumedSpace = await collection.getPropertiesConsumedSpace();230 expectedConsumedSpaceDiff = biggerPropDataSize - smallerPropDataSize;199 expectedConsumedSpaceDiff = biggerPropDataSize - smallerPropDataSize;231 expect(consumedSpace).to.be.equal(biggerPropDataSize - expectedConsumedSpaceDiff);200 expect(consumedSpace).to.be.equal(biggerPropDataSize - expectedConsumedSpaceDiff);232 }));201 });202 }));233});203});234 204 235describe('Negative Integration Test: Collection Properties', () => {205describe('Negative Integration Test: Collection Properties', () => {243 });213 });244 });214 });245 215246 async function testFailsSetPropertiesIfNotOwnerOrAdmin(collection: UniqueBaseCollection) { 216 [247 await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))217 {mode: 'nft' as const, requiredPallets: []},248 .to.be.rejectedWith(/common\.NoPermission/);218 {mode: 'ft' as const, requiredPallets: []},249 219 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, 250 expect(await collection.getProperties()).to.be.empty;220 ].map(testSuite => describe(`${testSuite.mode.toUpperCase()}`, () => {251 }221 before(async function() {252 222 // eslint-disable-next-line require-await253 itSub('Fails to set properties in a NFT collection if not its onwer/administrator', async ({helper}) => {223 await usingPlaygrounds(async helper => {254 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.nft.mintCollection(alice));224 requirePalletsOrSkip(this, helper, testSuite.requiredPallets);225 });255 });226 });256 227 228 itSub('Fails to set properties in a collection if not its onwer/administrator', async ({helper}) => {257 itSub.ifWithPallets('Fails to set properties in a ReFungible collection if not its onwer/administrator', [Pallets.ReFungible], async ({helper}) => {229 const collection = await helper[testSuite.mode].mintCollection(alice);258 await testFailsSetPropertiesIfNotOwnerOrAdmin(await helper.rft.mintCollection(alice));230231 await expect(collection.setProperties(bob, [{key: 'electron', value: 'come bond'}, {key: 'black_hole', value: 'LIGO'}]))232 .to.be.rejectedWith(/common\.NoPermission/);233 259 });234 expect(await collection.getProperties()).to.be.empty;260 235 });236 261 async function testFailsSetPropertiesThatExeedLimits(collection: UniqueBaseCollection) {237 itSub('Fails to set properties that exceed the limits', async ({helper}) => {238 const collection = await helper[testSuite.mode].mintCollection(alice);239262 const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber();240 const spaceLimit = (await (collection.helper!.api! as any).query.common.collectionProperties(collection.collectionId)).spaceLimit.toNumber();263 241 277 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);255 ])).to.be.rejectedWith(/common\.NoSpaceForProperty/);278 256 279 expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;257 expect(await collection.getProperties(['electron', 'black_hole'])).to.be.empty;280 }258 });281 259 282 itSub('Fails to set properties that exceed the limits (NFT)', async ({helper}) => {283 await testFailsSetPropertiesThatExeedLimits(await helper.nft.mintCollection(alice));284 });285 286 itSub.ifWithPallets('Fails to set properties that exceed the limits (ReFungible)', [Pallets.ReFungible], async ({helper}) => {287 await testFailsSetPropertiesThatExeedLimits(await helper.rft.mintCollection(alice));288 });289 290 async function testFailsSetMorePropertiesThanAllowed(collection: UniqueBaseCollection) {260 itSub('Fails to set more properties than it is allowed', async ({helper}) => {261 const collection = await helper[testSuite.mode].mintCollection(alice);262291 const propertiesToBeSet = [];263 const propertiesToBeSet = [];292 for (let i = 0; i < 65; i++) {264 for (let i = 0; i < 65; i++) {300 to.be.rejectedWith(/common\.PropertyLimitReached/);272 to.be.rejectedWith(/common\.PropertyLimitReached/);301 273 302 expect(await collection.getProperties()).to.be.empty;274 expect(await collection.getProperties()).to.be.empty;303 }275 });304 276305 itSub('Fails to set more properties than it is allowed (NFT)', async ({helper}) => {306 await testFailsSetMorePropertiesThanAllowed(await helper.nft.mintCollection(alice));307 });308 309 itSub.ifWithPallets('Fails to set more properties than it is allowed (ReFungible)', [Pallets.ReFungible], async ({helper}) => {310 await testFailsSetMorePropertiesThanAllowed(await helper.rft.mintCollection(alice));311 });312 313 async function testFailsSetPropertiesWithInvalidNames(collection: UniqueBaseCollection) {277 itSub('Fails to set properties with invalid names', async ({helper}) => {278 const collection = await helper[testSuite.mode].mintCollection(alice);279314 const invalidProperties = [280 const invalidProperties = [315 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],281 [{key: 'electron', value: 'negative'}, {key: 'string theory', value: 'understandable'}],347 `on trying to delete the non-existent badly-named property #${i}`,313 `on trying to delete the non-existent badly-named property #${i}`,348 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);314 ).to.be.rejectedWith(/common\.InvalidCharacterInPropertyKey/);349 }315 }350 }316 });351 352 itSub('Fails to set properties with invalid names (NFT)', async ({helper}) => {353 await testFailsSetPropertiesWithInvalidNames(await helper.nft.mintCollection(alice));354 });317 }));355 356 itSub.ifWithPallets('Fails to set properties with invalid names (ReFungible)', [Pallets.ReFungible], async ({helper}) => {357 await testFailsSetPropertiesWithInvalidNames(await helper.rft.mintCollection(alice));358 });359});318});360 319