git.delta.rocks / unique-network / refs/commits / 669456d6c646

difftreelog

feat(fungible) substrate setting collection properties + tests

Fahrrader2022-12-15parent: #0ad755c.patch.diff
in: master

5 files changed

modifiedpallets/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.
modifiedpallets/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(
modifiedpallets/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
 	}
modifiedtests/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",
modifiedtests/src/nesting/collectionProperties.test.tsdiffbeforeafterboth
15// 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/>.
1616
17import {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';
2019
21describe('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 35
37 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-await
44 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 47
48 itSub('Sets properties for a collection', async ({helper}) => {
49 const collection = await helper[testSuite.mode].mintCollection(alice);
50
51 // As owner
52 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 administrator
57 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);
68
62 // alpha symbols69 // alpha symbols
63 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);
96
96 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);
111
117 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 121
127 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 });
134
135 [
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';
143124
144 const collection = await helper[testCase.mode].mintCollection(alice);125 const collection = await helper[testSuite.mode].mintCollection(alice);
145126
146 const maxCollectionPropertiesSize = 40960;127 const maxCollectionPropertiesSize = 40960;
147128
166 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 });
170151
171 [
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';
179154
180 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();
182157
183 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 });
194169
195 [
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';
203172
204 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();
206175
207 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 215
246 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-await
253 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));230
231 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);
239
262 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);
262
291 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 276
305 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);
279
314 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