git.delta.rocks / unique-network / refs/commits / 42f53fc98c25

difftreelog

Merge pull request #773 from UniqueNetwork/fix/properties-size-overflow

ut-akuznetsov2022-12-14parents: #6e587d7 #4a0df43.patch.diff
in: master
Fix/properties size overflow

18 files changed

modifiedpallets/common/src/lib.rsdiffbeforeafterboth
1816 /// The price of setting approval for all1816 /// The price of setting approval for all
1817 fn set_allowance_for_all() -> Weight;1817 fn set_allowance_for_all() -> Weight;
1818
1819 /// The price of repairing an item.
1820 fn repair_item() -> Weight;
1818}1821}
18191822
1820/// Weight info extension trait for refungible pallet.1823/// Weight info extension trait for refungible pallet.
2137 /// Tells whether the given `owner` approves the `operator`.2140 /// Tells whether the given `owner` approves the `operator`.
2138 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;2141 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;
2142
2143 /// Repairs a possibly broken item.
2144 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;
2139}2145}
21402146
2141/// Extension for RFT collection.2147/// Extension for RFT collection.
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
112 Weight::zero()112 Weight::zero()
113 }113 }
114
115 fn repair_item() -> Weight {
116 Weight::zero()
117 }
114}118}
115119
116/// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete120/// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete
442 false446 false
443 }447 }
448
449 /// Repairs a possibly broken item.
450 fn repair_item(&self, _token: TokenId) -> DispatchResultWithPostInfo {
451 fail!(<Error<T>>::FungibleTokensAreAlwaysValid)
452 }
444}453}
445454
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
129 SettingPropertiesNotAllowed,129 SettingPropertiesNotAllowed,
130 /// Setting allowance for all is not allowed.130 /// Setting allowance for all is not allowed.
131 SettingAllowanceForAllNotAllowed,131 SettingAllowanceForAllNotAllowed,
132 /// Only a fungible collection could be possibly broken; any fungible token is valid.
133 FungibleTokensAreAlwaysValid,
132 }134 }
133135
134 #[pallet::config]136 #[pallet::config]
modifiedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
237 };237 };
238 }: {<Pallet<T>>::allowance_for_all(&collection, &owner, &operator)}238 }: {<Pallet<T>>::allowance_for_all(&collection, &owner, &operator)}
239
240 repair_item {
241 bench_init!{
242 owner: sub; collection: collection(owner);
243 owner: cross_from_sub;
244 };
245 let item = create_max_item(&collection, &owner, owner.clone())?;
246 }: {<Pallet<T>>::repair_item(&collection, item)?}
239}247}
240248
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
127 <SelfWeightOf<T>>::set_allowance_for_all()127 <SelfWeightOf<T>>::set_allowance_for_all()
128 }128 }
129
130 fn repair_item() -> Weight {
131 <SelfWeightOf<T>>::repair_item()
132 }
129}133}
130134
131fn map_create_data<T: Config>(135fn map_create_data<T: Config>(
533 <Pallet<T>>::allowance_for_all(self, &owner, &operator)537 <Pallet<T>>::allowance_for_all(self, &owner, &operator)
534 }538 }
539
540 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo {
541 with_weight(
542 <Pallet<T>>::repair_item(self, token),
543 <CommonWeights<T>>::repair_item(),
544 )
545 }
535}546}
536547
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
1399 <CollectionAllowance<T>>::get((collection.id, owner, operator))1399 <CollectionAllowance<T>>::get((collection.id, owner, operator))
1400 }1400 }
1401
1402 pub fn repair_item(collection: &NonfungibleHandle<T>, token: TokenId) -> DispatchResult {
1403 <TokenProperties<T>>::mutate((collection.id, token), |properties| {
1404 properties.recompute_consumed_space();
1405 });
1406
1407 Ok(())
1408 }
1401}1409}
14021410
modifiedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
50 fn token_owner() -> Weight;50 fn token_owner() -> Weight;
51 fn set_allowance_for_all() -> Weight;51 fn set_allowance_for_all() -> Weight;
52 fn allowance_for_all() -> Weight;52 fn allowance_for_all() -> Weight;
53 fn repair_item() -> Weight;
53}54}
5455
55/// Weights for pallet_nonfungible using the Substrate node and recommended hardware.56/// Weights for pallet_nonfungible using the Substrate node and recommended hardware.
208 Weight::from_ref_time(6_161_000 as u64)209 Weight::from_ref_time(6_161_000 as u64)
209 .saturating_add(T::DbWeight::get().reads(1 as u64))210 .saturating_add(T::DbWeight::get().reads(1 as u64))
210 }211 }
212 // Storage: Nonfungible TokenProperties (r:1 w:1)
213 fn repair_item() -> Weight {
214 Weight::from_ref_time(5_701_000 as u64)
215 .saturating_add(T::DbWeight::get().reads(1 as u64))
216 .saturating_add(T::DbWeight::get().writes(1 as u64))
217 }
211}218}
212219
213// For backwards compatibility and tests220// For backwards compatibility and tests
365 Weight::from_ref_time(6_161_000 as u64)372 Weight::from_ref_time(6_161_000 as u64)
366 .saturating_add(RocksDbWeight::get().reads(1 as u64))373 .saturating_add(RocksDbWeight::get().reads(1 as u64))
367 }374 }
375 // Storage: Nonfungible TokenProperties (r:1 w:1)
376 fn repair_item() -> Weight {
377 Weight::from_ref_time(5_701_000 as u64)
378 .saturating_add(RocksDbWeight::get().reads(1 as u64))
379 .saturating_add(RocksDbWeight::get().writes(1 as u64))
380 }
368}381}
369382
modifiedpallets/refungible/src/benchmarking.rsdiffbeforeafterboth
305 };305 };
306 }: {<Pallet<T>>::allowance_for_all(&collection, &owner, &operator)}306 }: {<Pallet<T>>::allowance_for_all(&collection, &owner, &operator)}
307
308 repair_item {
309 bench_init!{
310 owner: sub; collection: collection(owner);
311 owner: cross_from_sub;
312 };
313 let item = create_max_item(&collection, &owner, [(owner.clone(), 100)])?;
314 }: {<Pallet<T>>::repair_item(&collection, item)?}
307}315}
308316
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
157 <SelfWeightOf<T>>::set_allowance_for_all()157 <SelfWeightOf<T>>::set_allowance_for_all()
158 }158 }
159
160 fn repair_item() -> Weight {
161 <SelfWeightOf<T>>::repair_item()
162 }
159}163}
160164
161fn map_create_data<T: Config>(165fn map_create_data<T: Config>(
537 <Pallet<T>>::allowance_for_all(self, &owner, &operator)541 <Pallet<T>>::allowance_for_all(self, &owner, &operator)
538 }542 }
543
544 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo {
545 with_weight(
546 <Pallet<T>>::repair_item(self, token),
547 <CommonWeights<T>>::repair_item(),
548 )
549 }
539}550}
540551
541impl<T: Config> RefungibleExtensions<T> for RefungibleHandle<T> {552impl<T: Config> RefungibleExtensions<T> for RefungibleHandle<T> {
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
1462 <CollectionAllowance<T>>::get((collection.id, owner, operator))1462 <CollectionAllowance<T>>::get((collection.id, owner, operator))
1463 }1463 }
1464
1465 pub fn repair_item(collection: &RefungibleHandle<T>, token: TokenId) -> DispatchResult {
1466 <TokenProperties<T>>::mutate((collection.id, token), |properties| {
1467 properties.recompute_consumed_space();
1468 });
1469
1470 Ok(())
1471 }
1464}1472}
14651473
modifiedpallets/refungible/src/weights.rsdiffbeforeafterboth
57 fn token_owner() -> Weight;57 fn token_owner() -> Weight;
58 fn set_allowance_for_all() -> Weight;58 fn set_allowance_for_all() -> Weight;
59 fn allowance_for_all() -> Weight;59 fn allowance_for_all() -> Weight;
60 fn repair_item() -> Weight;
60}61}
6162
62/// Weights for pallet_refungible using the Substrate node and recommended hardware.63/// Weights for pallet_refungible using the Substrate node and recommended hardware.
272 Weight::from_ref_time(5_901_000 as u64)273 Weight::from_ref_time(5_901_000 as u64)
273 .saturating_add(T::DbWeight::get().reads(1 as u64))274 .saturating_add(T::DbWeight::get().reads(1 as u64))
274 }275 }
276 // Storage: Refungible TokenProperties (r:1 w:1)
277 fn repair_item() -> Weight {
278 Weight::from_ref_time(5_489_000 as u64)
279 .saturating_add(T::DbWeight::get().reads(1 as u64))
280 .saturating_add(T::DbWeight::get().writes(1 as u64))
281 }
275}282}
276283
277// For backwards compatibility and tests284// For backwards compatibility and tests
486 Weight::from_ref_time(5_901_000 as u64)493 Weight::from_ref_time(5_901_000 as u64)
487 .saturating_add(RocksDbWeight::get().reads(1 as u64))494 .saturating_add(RocksDbWeight::get().reads(1 as u64))
488 }495 }
496 // Storage: Refungible TokenProperties (r:1 w:1)
497 fn repair_item() -> Weight {
498 Weight::from_ref_time(5_489_000 as u64)
499 .saturating_add(RocksDbWeight::get().reads(1 as u64))
500 .saturating_add(RocksDbWeight::get().writes(1 as u64))
501 }
489}502}
490503
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
983 })983 })
984 }984 }
985
986 /// Repairs a broken item
987 ///
988 /// # Arguments
989 ///
990 /// * `collection_id`: ID of the collection the item belongs to.
991 /// * `item_id`: ID of the item.
992 #[weight = T::CommonWeightInfo::repair_item()]
993 pub fn repair_item(
994 _origin,
995 collection_id: CollectionId,
996 item_id: TokenId,
997 ) -> DispatchResultWithPostInfo {
998 dispatch_tx::<T, _>(collection_id, |d| {
999 d.repair_item(item_id)
1000 })
1001 }
985 }1002 }
986}1003}
9871004
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
1133 scope: PropertyScope,1133 scope: PropertyScope,
1134 key: PropertyKey,1134 key: PropertyKey,
1135 value: Self::Value,1135 value: Self::Value,
1136 ) -> Result<(), PropertiesError>;1136 ) -> Result<Option<Self::Value>, PropertiesError>;
11371137
1138 /// Try to set property with scope from iterator.1138 /// Try to set property with scope from iterator.
1139 fn try_scoped_set_from_iter<I, KV>(1139 fn try_scoped_set_from_iter<I, KV>(
1154 }1154 }
11551155
1156 /// Try to set property.1156 /// Try to set property.
1157 fn try_set(&mut self, key: PropertyKey, value: Self::Value) -> Result<(), PropertiesError> {1157 fn try_set(
1158 &mut self,
1159 key: PropertyKey,
1160 value: Self::Value,
1161 ) -> Result<Option<Self::Value>, PropertiesError> {
1158 self.try_scoped_set(PropertyScope::None, key, value)1162 self.try_scoped_set(PropertyScope::None, key, value)
1159 }1163 }
11601164
1215 Ok(())1219 Ok(())
1216 }1220 }
1221
1222 pub fn values(&self) -> impl Iterator<Item = &Value> {
1223 self.0.values()
1224 }
1217}1225}
12181226
1219impl<Value> IntoIterator for PropertiesMap<Value> {1227impl<Value> IntoIterator for PropertiesMap<Value> {
1239 scope: PropertyScope,1247 scope: PropertyScope,
1240 key: PropertyKey,1248 key: PropertyKey,
1241 value: Self::Value,1249 value: Self::Value,
1242 ) -> Result<(), PropertiesError> {1250 ) -> Result<Option<Self::Value>, PropertiesError> {
1243 Self::check_property_key(&key)?;1251 Self::check_property_key(&key)?;
12441252
1245 let key = scope.apply(key)?;1253 let key = scope.apply(key)?;
1246 self.01254 self.0
1247 .try_insert(key, value)1255 .try_insert(key, value)
1248 .map_err(|_| PropertiesError::PropertyLimitReached)?;1256 .map_err(|_| PropertiesError::PropertyLimitReached)
1249
1250 Ok(())
1251 }1257 }
1252}1258}
12531259
1289 self.map.get(key)1295 self.map.get(key)
1290 }1296 }
1297
1298 /// Recomputes the consumed space for the current properties state.
1299 /// Needed to repair a token due to a bug fixed in the [PR #733](https://github.com/UniqueNetwork/unique-chain/pull/773).
1300 pub fn recompute_consumed_space(&mut self) {
1301 self.consumed_space = self.map.values().map(|value| value.len() as u32).sum();
1302 }
1291}1303}
12921304
1293impl IntoIterator for Properties {1305impl IntoIterator for Properties {
1307 scope: PropertyScope,1319 scope: PropertyScope,
1308 key: PropertyKey,1320 key: PropertyKey,
1309 value: Self::Value,1321 value: Self::Value,
1310 ) -> Result<(), PropertiesError> {1322 ) -> Result<Option<Self::Value>, PropertiesError> {
1311 let value_len = value.len();1323 let value_len = value.len();
13121324
1313 if self.consumed_space as usize + value_len > self.space_limit as usize1325 if self.consumed_space as usize + value_len > self.space_limit as usize
1316 return Err(PropertiesError::NoSpaceForProperty);1328 return Err(PropertiesError::NoSpaceForProperty);
1317 }1329 }
13181330
1319 self.map.try_scoped_set(scope, key, value)?;1331 let old_value = self.map.try_scoped_set(scope, key, value)?;
13201332
1333 if old_value.is_none() {
1321 self.consumed_space += value_len as u32;1334 self.consumed_space += value_len as u32;
1335 }
13221336
1323 Ok(())1337 Ok(old_value)
1324 }1338 }
1325}1339}
13261340
modifiedruntime/common/weights.rsdiffbeforeafterboth
125 max_weight_of!(set_allowance_for_all())125 max_weight_of!(set_allowance_for_all())
126 }126 }
127
128 fn repair_item() -> Weight {
129 max_weight_of!(repair_item())
130 }
127}131}
128132
129#[cfg(feature = "refungible")]133#[cfg(feature = "refungible")]
modifiedtests/package.jsondiffbeforeafterboth
41 "testEvent": "yarn setup && mocha --timeout 9999999 -r ts-node/register ./src/check-event/*.*test.ts",41 "testEvent": "yarn setup && mocha --timeout 9999999 -r ts-node/register ./src/check-event/*.*test.ts",
42 "testRmrk": "yarn setup && mocha --timeout 9999999 -r ts-node/register ./**/rmrk/*.*test.ts",42 "testRmrk": "yarn setup && mocha --timeout 9999999 -r ts-node/register ./**/rmrk/*.*test.ts",
43 "testEthPayable": "mocha --timeout 9999999 -r ts-node/register './**/eth/payable.test.ts'",43 "testEthPayable": "mocha --timeout 9999999 -r ts-node/register './**/eth/payable.test.ts'",
44 "testEthTokenProperties": "mocha --timeout 9999999 -r ts-node/register ./**/eth/tokenProperties.test.ts",
45 "testEvmCoder": "mocha --timeout 9999999 -r ts-node/register './**/eth/evmCoder.test.ts'",44 "testEvmCoder": "mocha --timeout 9999999 -r ts-node/register './**/eth/evmCoder.test.ts'",
46 "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nest.test.ts",45 "testNesting": "mocha --timeout 9999999 -r ts-node/register ./**/nest.test.ts",
47 "testUnnesting": "mocha --timeout 9999999 -r ts-node/register ./**/unnest.test.ts",46 "testUnnesting": "mocha --timeout 9999999 -r ts-node/register ./**/unnest.test.ts",
48 "testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/properties.test.ts ./**/getPropertiesRpc.test.ts",47 "testProperties": "mocha --timeout 9999999 -r ts-node/register ./**/collectionProperties.test.ts ./**/tokenProperties.test.ts ./**/getPropertiesRpc.test.ts",
49 "testMigration": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts",48 "testMigration": "mocha --timeout 9999999 -r ts-node/register ./**/nesting/migration-check.test.ts",
50 "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",49 "testAddCollectionAdmin": "mocha --timeout 9999999 -r ts-node/register ./**/addCollectionAdmin.test.ts",
51 "testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts",50 "testSetCollectionLimits": "mocha --timeout 9999999 -r ts-node/register ./**/setCollectionLimits.test.ts",
modifiedtests/src/nesting/collectionProperties.test.tsdiffbeforeafterboth
25 before(async () => {25 before(async () => {
26 await usingPlaygrounds(async (helper, privateKey) => {26 await usingPlaygrounds(async (helper, privateKey) => {
27 const donor = await privateKey({filename: __filename});27 const donor = await privateKey({filename: __filename});
28 [alice, bob] = await helper.arrange.createAccounts([50n, 10n], donor);28 [alice, bob] = await helper.arrange.createAccounts([100n, 10n], donor);
29 });29 });
30 });30 });
31 31
132 await testDeleteProperties(await helper.rft.mintCollection(alice));132 await testDeleteProperties(await helper.rft.mintCollection(alice));
133 });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 (${testCase.mode})`, testCase.requiredPallets, async({helper}) => {
142 const propKey = 'tok-prop';
143
144 const collection = await helper[testCase.mode].mintCollection(alice);
145
146 const maxCollectionPropertiesSize = 40960;
147
148 const propDataSize = 4096;
149
150 let propDataChar = 'a';
151 const makeNewPropData = () => {
152 propDataChar = String.fromCharCode(propDataChar.charCodeAt(0) + 1);
153 return `${propDataChar}`.repeat(propDataSize);
154 };
155
156 await collection.setProperties(alice, [{key: propKey, value: makeNewPropData()}]);
157 const originalSpace = await collection.getPropertiesConsumedSpace();
158 expect(originalSpace).to.be.equal(propDataSize);
159
160 const sameSizePropertiesPossibleNum = maxCollectionPropertiesSize / propDataSize;
161
162 // It is possible to modify a property as many times as needed.
163 // It will not consume any additional space.
164 for (let i = 0; i < sameSizePropertiesPossibleNum + 1; i++) {
165 await collection.setProperties(alice, [{key: propKey, value: makeNewPropData()}]);
166 const consumedSpace = await collection.getPropertiesConsumedSpace();
167 expect(consumedSpace).to.be.equal(originalSpace);
168 }
169 }));
170
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}) => {
178 const propKey = 'tok-prop';
179
180 const collection = await helper[testCase.mode].mintCollection(alice);
181 const originalSpace = await collection.getPropertiesConsumedSpace();
182
183 const propDataSize = 4096;
184 const propData = 'a'.repeat(propDataSize);
185
186 await collection.setProperties(alice, [{key: propKey, value: propData}]);
187 let consumedSpace = await collection.getPropertiesConsumedSpace();
188 expect(consumedSpace).to.be.equal(propDataSize);
189
190 await collection.deleteProperties(alice, [propKey]);
191 consumedSpace = await collection.getPropertiesConsumedSpace();
192 expect(consumedSpace).to.be.equal(originalSpace);
193 }));
134});194});
135 195
136describe('Negative Integration Test: Collection Properties', () => {196describe('Negative Integration Test: Collection Properties', () => {
modifiedtests/src/nesting/tokenProperties.test.tsdiffbeforeafterboth
28 before(async () => {28 before(async () => {
29 await usingPlaygrounds(async (helper, privateKey) => {29 await usingPlaygrounds(async (helper, privateKey) => {
30 const donor = await privateKey({filename: __filename});30 const donor = await privateKey({filename: __filename});
31 [alice, bob, charlie] = await helper.arrange.createAccounts([100n, 100n, 100n], donor);31 [alice, bob, charlie] = await helper.arrange.createAccounts([200n, 100n, 100n], donor);
32 });32 });
3333
34 permissions = [34 permissions = [
321 expect(await targetToken.getProperties()).to.be.empty;321 expect(await targetToken.getProperties()).to.be.empty;
322 });322 });
323
324 [
325 {mode: 'nft' as const, storage: 'nonfungible' as const, pieces: undefined, requiredPallets: []},
326 {mode: 'rft' as const, storage: 'refungible' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]},
327 ].map(testCase =>
328 itSub.ifWithPallets(`Allows modifying a token property multiple times (${testCase.mode})`, testCase.requiredPallets, async({helper}) => {
329 const propKey = 'tok-prop';
330
331 const collection = await helper[testCase.mode].mintCollection(alice, {
332 tokenPropertyPermissions: [
333 {
334 key: propKey,
335 permission: {mutable: true, tokenOwner: true},
336 },
337 ],
338 });
339
340 const maxTokenPropertiesSize = 32768;
341
342 const propDataSize = 4096;
343
344 let propDataChar = 'a';
345 const makeNewPropData = () => {
346 propDataChar = String.fromCharCode(propDataChar.charCodeAt(0) + 1);
347 return `${propDataChar}`.repeat(propDataSize);
348 };
349
350 const token = await (
351 testCase.pieces
352 ? collection.mintToken(alice, testCase.pieces)
353 : collection.mintToken(alice)
354 );
355
356 await token.setProperties(alice, [{key: propKey, value: makeNewPropData()}]);
357 const originalSpace = await token.getTokenPropertiesConsumedSpace();
358 expect(originalSpace).to.be.equal(propDataSize);
359
360 const sameSizePropertiesPossibleNum = maxTokenPropertiesSize / propDataSize;
361
362 // It is possible to modify a property as many times as needed.
363 // It will not consume any additional space.
364 for (let i = 0; i < sameSizePropertiesPossibleNum + 1; i++) {
365 await token.setProperties(alice, [{key: propKey, value: makeNewPropData()}]);
366 const consumedSpace = await token.getTokenPropertiesConsumedSpace();
367 expect(consumedSpace).to.be.equal(originalSpace);
368 }
369 }));
370
371 [
372 {mode: 'nft' as const, pieces: undefined, requiredPallets: []},
373 {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]},
374 ].map(testCase =>
375 itSub.ifWithPallets(`Adding then removing a token property doesn't change the consumed space (${testCase.mode})`, testCase.requiredPallets, async({helper}) => {
376 const propKey = 'tok-prop';
377
378 const collection = await helper[testCase.mode].mintCollection(alice, {
379 tokenPropertyPermissions: [
380 {
381 key: propKey,
382 permission: {mutable: true, tokenOwner: true},
383 },
384 ],
385 });
386 const token = await (
387 testCase.pieces
388 ? collection.mintToken(alice, testCase.pieces)
389 : collection.mintToken(alice)
390 );
391 const originalSpace = await token.getTokenPropertiesConsumedSpace();
392
393 const propDataSize = 4096;
394 const propData = 'a'.repeat(propDataSize);
395
396 await token.setProperties(alice, [{key: propKey, value: propData}]);
397 let consumedSpace = await token.getTokenPropertiesConsumedSpace();
398 expect(consumedSpace).to.be.equal(propDataSize);
399
400 await token.deleteProperties(alice, [propKey]);
401 consumedSpace = await token.getTokenPropertiesConsumedSpace();
402 expect(consumedSpace).to.be.equal(originalSpace);
403 }));
404
405 [
406 {mode: 'nft' as const, pieces: undefined, requiredPallets: []},
407 {mode: 'rft' as const, pieces: 100n, requiredPallets: [Pallets.ReFungible]},
408 ].map(testCase =>
409 itSub.ifWithPallets(`repair_item preserves valid consumed space (${testCase.mode})`, testCase.requiredPallets, async({helper}) => {
410 const propKey = 'tok-prop';
411
412 const collection = await helper[testCase.mode].mintCollection(alice, {
413 tokenPropertyPermissions: [
414 {
415 key: propKey,
416 permission: {mutable: true, tokenOwner: true},
417 },
418 ],
419 });
420 const token = await (
421 testCase.pieces
422 ? collection.mintToken(alice, testCase.pieces)
423 : collection.mintToken(alice)
424 );
425
426 const propDataSize = 4096;
427 const propData = 'a'.repeat(propDataSize);
428
429 await token.setProperties(alice, [{key: propKey, value: propData}]);
430 const originalSpace = await token.getTokenPropertiesConsumedSpace();
431 expect(originalSpace).to.be.equal(propDataSize);
432
433 await helper.executeExtrinsic(alice, 'api.tx.unique.repairItem', [token.collectionId, token.tokenId], true);
434 const recomputedSpace = await token.getTokenPropertiesConsumedSpace();
435 expect(recomputedSpace).to.be.equal(originalSpace);
436 }));
323});437});
324438
325describe('Negative Integration Test: Token Properties', () => {439describe('Negative Integration Test: Token Properties', () => {
476 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, amount);590 await testForbidsAddingPropertiesIfPropertyNotDeclared(token, amount);
477 });591 });
478592
479 async function testForbidsAddingTooManyProperties(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {593 async function testForbidsAddingTooLargeProperties(token: UniqueNFToken | UniqueRFToken, pieces: bigint) {
480 const originalSpace = await prepare(token, pieces);594 const originalSpace = await prepare(token, pieces);
481595
482 await expect(596 await expect(
504 expect(consumedSpace).to.be.equal(originalSpace);618 expect(consumedSpace).to.be.equal(originalSpace);
505 }619 }
506620
507 itSub('Forbids adding too many properties to a token (NFT)', async ({helper}) => {621 itSub('Forbids adding too large properties to a token (NFT)', async ({helper}) => {
508 const [token, amount] = await mintCollectionWithAllPermissionsAndToken(helper, 'NFT');622 const [token, amount] = await mintCollectionWithAllPermissionsAndToken(helper, 'NFT');
509 await testForbidsAddingTooManyProperties(token, amount);623 await testForbidsAddingTooLargeProperties(token, amount);
510 });624 });
511625
512 itSub.ifWithPallets('Forbids adding too many properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {626 itSub.ifWithPallets('Forbids adding too large properties to a token (ReFungible)', [Pallets.ReFungible], async ({helper}) => {
513 const [token, amount] = await mintCollectionWithAllPermissionsAndToken(helper, 'RFT');627 const [token, amount] = await mintCollectionWithAllPermissionsAndToken(helper, 'RFT');
514 await testForbidsAddingTooManyProperties(token, amount);628 await testForbidsAddingTooLargeProperties(token, amount);
515 });629 });
630
631 [
632 {mode: 'nft' as const, requiredPallets: []},
633 {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]},
634 ].map(testCase =>
635 itSub.ifWithPallets(`Forbids adding too many propeties to a token (${testCase.mode})`, testCase.requiredPallets, async({helper}) => {
636 const collection = await helper[testCase.mode].mintCollection(alice);
637 const maxPropertiesPerItem = 64;
638
639 for (let i = 0; i < maxPropertiesPerItem; i++) {
640 await collection.setTokenPropertyPermissions(alice, [{
641 key: `${i+1}`,
642 permission: {mutable: true, tokenOwner: true, collectionAdmin: true},
643 }]);
644 }
645
646 await expect(collection.setTokenPropertyPermissions(alice, [{
647 key: `${maxPropertiesPerItem}-th`,
648 permission: {mutable: true, tokenOwner: true, collectionAdmin: true},
649 }])).to.be.rejectedWith(/common\.PropertyLimitReached/);
650 }));
516});651});
517652
518describe('ReFungible token properties permissions tests', () => {653describe('ReFungible token properties permissions tests', () => {
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
1100 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();1100 return (await this.helper.callRpc('api.rpc.unique.collectionProperties', [collectionId, propertyKeys])).toHuman();
1101 }1101 }
1102
1103 async getPropertiesConsumedSpace(collectionId: number): Promise<number> {
1104 const api = this.helper.getApi();
1105 const props = (await api.query.common.collectionProperties(collectionId)).toJSON();
1106
1107 return (props! as any).consumedSpace;
1108 }
11021109
1103 async getCollectionOptions(collectionId: number) {1110 async getCollectionOptions(collectionId: number) {
1104 return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();1111 return (await this.helper.callRpc('api.rpc.unique.collectionById', [collectionId])).toHuman();
3035 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);3042 return await this.helper.collection.getProperties(this.collectionId, propertyKeys);
3036 }3043 }
3044
3045 async getPropertiesConsumedSpace() {
3046 return await this.helper.collection.getPropertiesConsumedSpace(this.collectionId);
3047 }
30373048
3038 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {3049 async getTokenNextSponsored(tokenId: number, addressObj: ICrossAccountId) {
3039 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);3050 return await this.helper.collection.getTokenNextSponsored(this.collectionId, tokenId, addressObj);
3158 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3169 return await this.helper.nft.getTokenProperties(this.collectionId, tokenId, propertyKeys);
3159 }3170 }
3171
3172 async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {
3173 const api = this.helper.getApi();
3174 const props = (await api.query.nonfungible.tokenProperties(this.collectionId, tokenId)).toJSON();
3175
3176 return (props! as any).consumedSpace;
3177 }
31603178
3161 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {3179 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId) {
3162 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);3180 return await this.helper.nft.transferToken(signer, this.collectionId, tokenId, addressObj);
3269 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);3287 return await this.helper.rft.getTokenProperties(this.collectionId, tokenId, propertyKeys);
3270 }3288 }
3289
3290 async getTokenPropertiesConsumedSpace(tokenId: number): Promise<number> {
3291 const api = this.helper.getApi();
3292 const props = (await api.query.refungible.tokenProperties(this.collectionId, tokenId)).toJSON();
3293
3294 return (props! as any).consumedSpace;
3295 }
32713296
3272 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {3297 async transferToken(signer: TSigner, tokenId: number, addressObj: ICrossAccountId, amount=1n) {
3273 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);3298 return await this.helper.rft.transferToken(signer, this.collectionId, tokenId, addressObj, amount);
3421 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);3446 return await this.collection.getTokenProperties(this.tokenId, propertyKeys);
3422 }3447 }
3448
3449 async getTokenPropertiesConsumedSpace() {
3450 return await this.collection.getTokenPropertiesConsumedSpace(this.tokenId);
3451 }
34233452
3424 async setProperties(signer: TSigner, properties: IProperty[]) {3453 async setProperties(signer: TSigner, properties: IProperty[]) {
3425 return await this.collection.setTokenProperties(signer, this.tokenId, properties);3454 return await this.collection.setTokenProperties(signer, this.tokenId, properties);