git.delta.rocks / unique-network / refs/commits / 9505eb68d4e4

difftreelog

Merge pull request #841 from UniqueNetwork/tests/generalization

Yaroslav Bolyukin2023-01-18parents: #b48e245 #ba92b38.patch.diff
in: master
Nesting to non-existing tokens

11 files changed

modifiedCargo.lockdiffbeforeafterboth
6754 "frame-benchmarking",6754 "frame-benchmarking",
6755 "frame-support",6755 "frame-support",
6756 "frame-system",6756 "frame-system",
6757 "log",
6757 "pallet-common",6758 "pallet-common",
6758 "pallet-evm",6759 "pallet-evm",
6759 "parity-scale-codec 3.2.1",6760 "parity-scale-codec 3.2.1",
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
1332 let permissive = nesting.permissive;1332 let permissive = nesting.permissive;
13331333
1334 if permissive {1334 if permissive {
1335 // Pass1335 ensure!(
1336 <TokenData<T>>::contains_key((handle.id, under)),
1337 <CommonError<T>>::TokenNotFound
1338 );
1336 } else if nesting.token_owner1339 } else if nesting.token_owner
1337 && <PalletStructure<T>>::check_indirectly_owned(1340 && <PalletStructure<T>>::check_indirectly_owned(
1338 sender.clone(),1341 sender.clone(),
1341 Some(from),1344 Some(from),
1342 nesting_budget,1345 nesting_budget,
1343 )? {1346 )? {
1344 // Pass1347 // Pass, token existence is checked in `check_indirectly_owned`
1345 } else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1348 } else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {
1346 // Pass1349 ensure!(
1350 <TokenData<T>>::contains_key((handle.id, under)),
1351 <CommonError<T>>::TokenNotFound
1352 );
1347 } else {1353 } else {
1348 fail!(<CommonError<T>>::UserIsNotAllowedToNest);1354 fail!(<CommonError<T>>::UserIsNotAllowedToNest);
1349 }1355 }
modifiedpallets/structure/Cargo.tomldiffbeforeafterboth
17] }17] }
18up-data-structs = { path = "../../primitives/data-structs", default-features = false }18up-data-structs = { path = "../../primitives/data-structs", default-features = false }
19pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.36" }19pallet-evm = { default-features = false, git = "https://github.com/uniquenetwork/frontier", branch = "unique-polkadot-v0.9.36" }
20log = { version = "0.4.17", default-features = false }
2021
21[features]22[features]
22default = ["std"]23default = ["std"]
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
54#![cfg_attr(not(feature = "std"), no_std)]54#![cfg_attr(not(feature = "std"), no_std)]
5555
56use pallet_common::CommonCollectionOperations;56use pallet_common::CommonCollectionOperations;
57use pallet_common::{erc::CrossAccountId, eth::is_collection};
57use sp_std::collections::btree_set::BTreeSet;58use sp_std::collections::btree_set::BTreeSet;
5859
59use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};60use frame_support::dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo};
86 BreadthLimit,87 BreadthLimit,
87 /// Couldn't find the token owner that is itself a token.88 /// Couldn't find the token owner that is itself a token.
88 TokenNotFound,89 TokenNotFound,
90 /// Tried to nest token under collection contract address, instead of token address
91 CantNestTokenUnderCollection,
89 }92 }
9093
91 #[pallet::event]94 #[pallet::event]
302 token_id: TokenId,305 token_id: TokenId,
303 nesting_budget: &dyn Budget,306 nesting_budget: &dyn Budget,
304 ) -> DispatchResult {307 ) -> DispatchResult {
305 Self::try_exec_if_owner_is_valid_nft(under, |collection, parent_id| {308 Self::try_exec_if_token(under, |collection, parent_id| {
306 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)309 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)
307 })310 })
308 }311 }
319 token_id: TokenId,322 token_id: TokenId,
320 nesting_budget: &dyn Budget,323 nesting_budget: &dyn Budget,
321 ) -> DispatchResult {324 ) -> DispatchResult {
322 Self::try_exec_if_owner_is_valid_nft(under, |collection, parent_id| {325 Self::try_exec_if_token(under, |collection, parent_id| {
323 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)?;326 collection.check_nesting(from, (collection_id, token_id), parent_id, nesting_budget)?;
324327
325 collection.nest(parent_id, (collection_id, token_id));328 collection.nest(parent_id, (collection_id, token_id));
336 collection_id: CollectionId,339 collection_id: CollectionId,
337 token_id: TokenId,340 token_id: TokenId,
338 ) {341 ) {
339 Self::exec_if_owner_is_valid_nft(owner, |collection, parent_id| {342 Self::exec_if_token(owner, |collection, parent_id| {
340 collection.nest(parent_id, (collection_id, token_id))343 collection.nest(parent_id, (collection_id, token_id))
341 });344 });
342 }345 }
347 collection_id: CollectionId,350 collection_id: CollectionId,
348 token_id: TokenId,351 token_id: TokenId,
349 ) {352 ) {
350 Self::exec_if_owner_is_valid_nft(owner, |collection, parent_id| {353 if let Err(e) = Self::try_exec_if_token(owner, |collection, parent_id| {
351 collection.unnest(parent_id, (collection_id, token_id))354 collection.unnest(parent_id, (collection_id, token_id));
355 Ok(())
352 });356 }) {
357 log::warn!("unnest precondition failed: {e:?}")
358 }
353 }359 }
354360
361 /// # Panics
362 /// If [`Self::try_exec_if_token`] fails
355 fn exec_if_owner_is_valid_nft(363 fn exec_if_token(
356 account: &T::CrossAccountId,364 account: &T::CrossAccountId,
357 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId),365 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId),
358 ) {366 ) {
359 Self::try_exec_if_owner_is_valid_nft(account, |collection, id| {367 Self::try_exec_if_token(account, |collection, id| {
360 action(collection, id);368 action(collection, id);
361 Ok(())369 Ok(())
362 })370 })
363 .unwrap();371 .unwrap();
364 }372 }
365373
374 /// If `account` is a token address, execute `action` providing found collection as an argument
375 /// Token may not exist, it is expected it will be checked in the callback.
366 fn try_exec_if_owner_is_valid_nft(376 fn try_exec_if_token(
367 account: &T::CrossAccountId,377 account: &T::CrossAccountId,
368 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,378 action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,
369 ) -> DispatchResult {379 ) -> DispatchResult {
380 if is_collection(&account.as_eth()) {
381 fail!(<Error<T>>::CantNestTokenUnderCollection);
382 }
370 let account = T::CrossTokenAddressMapping::address_to_token(account);383 let Some((collection, token)) = T::CrossTokenAddressMapping::address_to_token(account) else {
371
372 if account.is_none() {
373 return Ok(());384 return Ok(())
374 }385 };
375
376 let account = account.unwrap();
377386
378 let handle = <CollectionHandle<T>>::try_get(account.0);387 let handle = <CollectionHandle<T>>::try_get(collection)?;
379
380 if handle.is_err() {
381 return Ok(());
382 }
383
384 let handle = handle.unwrap();
385388
386 let dispatch = T::CollectionDispatch::dispatch(handle);389 let dispatch = T::CollectionDispatch::dispatch(handle);
387 let dispatch = dispatch.as_dyn();390 let dispatch = dispatch.as_dyn();
388391
389 action(dispatch, account.1)392 action(dispatch, token)
390 }393 }
391}394}
392395
modifiedtests/package.jsondiffbeforeafterboth
100 "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.test.ts",100 "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.test.ts",
101 "testEthFT": "mocha --timeout 9999999 -r ts-node/register ./**/eth/fungible.test.ts",101 "testEthFT": "mocha --timeout 9999999 -r ts-node/register ./**/eth/fungible.test.ts",
102 "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts",102 "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts",
103 "testPromotion": "yarn setup && mocha --timeout 9999999 -r ts-node/register ./**/app-promotion.*test.ts",103 "testPromotion": "yarn setup && mocha --timeout 9999999 -r ts-node/register ./**/appPromotion/*test.ts",
104 "testXcmUnique": "RUN_XCM_TESTS=1 mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmUnique.test.ts",104 "testXcmUnique": "RUN_XCM_TESTS=1 mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmUnique.test.ts",
105 "testXcmQuartz": "RUN_XCM_TESTS=1 mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmQuartz.test.ts",105 "testXcmQuartz": "RUN_XCM_TESTS=1 mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmQuartz.test.ts",
106 "testXcmOpal": "RUN_XCM_TESTS=1 mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmOpal.test.ts",106 "testXcmOpal": "RUN_XCM_TESTS=1 mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmOpal.test.ts",
deletedtests/src/app-promotion.seqtest.tsdiffbeforeafterboth

no changes

deletedtests/src/app-promotion.test.tsdiffbeforeafterboth

no changes

addedtests/src/sub/appPromotion/appPromotion.seqtest.tsdiffbeforeafterboth

no changes

addedtests/src/sub/appPromotion/appPromotion.test.tsdiffbeforeafterboth

no changes

addedtests/src/sub/nesting/common.test.tsdiffbeforeafterboth

no changes

modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
1391 return tokenData;1391 return tokenData;
1392 }1392 }
1393
1394 /**
1395 * Get token's owner
1396 * @param collectionId ID of collection
1397 * @param tokenId ID of token
1398 * @param blockHashAt optionally query the data at the block with this hash
1399 * @example getTokenOwner(10, 5);
1400 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}
1401 */
1402 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {
1403 let owner;
1404 if (typeof blockHashAt === 'undefined') {
1405 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);
1406 } else {
1407 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);
1408 }
1409 return CrossAccountId.fromLowerCaseKeys(owner.toJSON());
1410 }
1411
1412 /**
1413 * Recursively find the address that owns the token
1414 * @param collectionId ID of collection
1415 * @param tokenId ID of token
1416 * @param blockHashAt
1417 * @example getTokenTopmostOwner(10, 5);
1418 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}
1419 */
1420 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {
1421 let owner;
1422 if (typeof blockHashAt === 'undefined') {
1423 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);
1424 } else {
1425 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);
1426 }
1427
1428 if (owner === null) return null;
1429
1430 return owner.toHuman();
1431 }
1432
1433 /**
1434 * Nest one token into another
1435 * @param signer keyring of signer
1436 * @param tokenObj token to be nested
1437 * @param rootTokenObj token to be parent
1438 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});
1439 * @returns ```true``` if extrinsic success, otherwise ```false```
1440 */
1441 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {
1442 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);
1443 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);
1444 if(!result) {
1445 throw Error('Unable to nest token!');
1446 }
1447 return result;
1448 }
1449
1450 /**
1451 * Remove token from nested state
1452 * @param signer keyring of signer
1453 * @param tokenObj token to unnest
1454 * @param rootTokenObj parent of a token
1455 * @param toAddressObj address of a new token owner
1456 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});
1457 * @returns ```true``` if extrinsic success, otherwise ```false```
1458 */
1459 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {
1460 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);
1461 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);
1462 if(!result) {
1463 throw Error('Unable to unnest token!');
1464 }
1465 return result;
1466 }
13931467
1394 /**1468 /**
1395 * Set permissions to change token properties1469 * Set permissions to change token properties
1557 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));1631 return new UniqueNFToken(tokenId, this.getCollectionObject(collectionId));
1558 }1632 }
1559
1560 /**
1561 * Get token's owner
1562 * @param collectionId ID of collection
1563 * @param tokenId ID of token
1564 * @param blockHashAt optionally query the data at the block with this hash
1565 * @example getTokenOwner(10, 5);
1566 * @returns Address in CrossAccountId format, e.g. {Substrate: "5DnSF6RRjwteE3BrCj..."}
1567 */
1568 async getTokenOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId> {
1569 let owner;
1570 if (typeof blockHashAt === 'undefined') {
1571 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId]);
1572 } else {
1573 owner = await this.helper.callRpc('api.rpc.unique.tokenOwner', [collectionId, tokenId, blockHashAt]);
1574 }
1575 return CrossAccountId.fromLowerCaseKeys(owner.toJSON());
1576 }
15771633
1578 /**1634 /**
1579 * Is token approved to transfer1635 * Is token approved to transfer
1616 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);1672 return await super.transferTokenFrom(signer, collectionId, tokenId, fromAddressObj, toAddressObj, 1n);
1617 }1673 }
1618
1619 /**
1620 * Recursively find the address that owns the token
1621 * @param collectionId ID of collection
1622 * @param tokenId ID of token
1623 * @param blockHashAt
1624 * @example getTokenTopmostOwner(10, 5);
1625 * @returns address in CrossAccountId format, e.g. {Substrate: "5DyN4Y92vZCjv38fg..."}
1626 */
1627 async getTokenTopmostOwner(collectionId: number, tokenId: number, blockHashAt?: string): Promise<CrossAccountId | null> {
1628 let owner;
1629 if (typeof blockHashAt === 'undefined') {
1630 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId]);
1631 } else {
1632 owner = await this.helper.callRpc('api.rpc.unique.topmostTokenOwner', [collectionId, tokenId, blockHashAt]);
1633 }
1634
1635 if (owner === null) return null;
1636
1637 return owner.toHuman();
1638 }
16391674
1640 /**1675 /**
1641 * Get tokens nested in the provided token1676 * Get tokens nested in the provided token
1658 });1693 });
1659 }1694 }
1660
1661 /**
1662 * Nest one token into another
1663 * @param signer keyring of signer
1664 * @param tokenObj token to be nested
1665 * @param rootTokenObj token to be parent
1666 * @example nestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4});
1667 * @returns ```true``` if extrinsic success, otherwise ```false```
1668 */
1669 async nestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken): Promise<boolean> {
1670 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);
1671 const result = await this.transferToken(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress);
1672 if(!result) {
1673 throw Error('Unable to nest token!');
1674 }
1675 return result;
1676 }
1677
1678 /**
1679 * Remove token from nested state
1680 * @param signer keyring of signer
1681 * @param tokenObj token to unnest
1682 * @param rootTokenObj parent of a token
1683 * @param toAddressObj address of a new token owner
1684 * @example unnestToken(aliceKeyring, {collectionId: 10, tokenId: 5}, {collectionId: 10, tokenId: 4}, {Substrate: "5DyN4Y92vZCjv38fg..."});
1685 * @returns ```true``` if extrinsic success, otherwise ```false```
1686 */
1687 async unnestToken(signer: TSigner, tokenObj: IToken, rootTokenObj: IToken, toAddressObj: ICrossAccountId): Promise<boolean> {
1688 const rootTokenAddress = this.helper.util.getTokenAccount(rootTokenObj);
1689 const result = await this.transferTokenFrom(signer, tokenObj.collectionId, tokenObj.tokenId, rootTokenAddress, toAddressObj);
1690 if(!result) {
1691 throw Error('Unable to unnest token!');
1692 }
1693 return result;
1694 }
16951695
1696 /**1696 /**
1697 * Mint new collection1697 * Mint new collection
3531 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);3531 return await this.helper.rft.getToken(this.collectionId, tokenId, [], blockHashAt);
3532 }3532 }
3533
3534 async getTokenOwner(tokenId: number, blockHashAt?: string) {
3535 return await this.helper.rft.getTokenOwner(this.collectionId, tokenId, blockHashAt);
3536 }
35333537
3534 async getTokensByAddress(addressObj: ICrossAccountId) {3538 async getTokensByAddress(addressObj: ICrossAccountId) {
3535 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);3539 return await this.helper.rft.getTokensByAddress(this.collectionId, addressObj);
3539 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);3543 return await this.helper.rft.getTokenTop10Owners(this.collectionId, tokenId);
3540 }3544 }
3545
3546 async getTokenTopmostOwner(tokenId: number, blockHashAt?: string) {
3547 return await this.helper.rft.getTokenTopmostOwner(this.collectionId, tokenId, blockHashAt);
3548 }
35413549
3542 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {3550 async getTokenBalance(tokenId: number, addressObj: ICrossAccountId) {
3543 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);3551 return await this.helper.rft.getTokenBalance(this.collectionId, tokenId, addressObj);
3610 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);3618 return await this.helper.rft.setTokenPropertyPermissions(signer, this.collectionId, permissions);
3611 }3619 }
3620
3621 async nestToken(signer: TSigner, tokenId: number, toTokenObj: IToken) {
3622 return await this.helper.rft.nestToken(signer, {collectionId: this.collectionId, tokenId}, toTokenObj);
3623 }
3624
3625 async unnestToken(signer: TSigner, tokenId: number, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
3626 return await this.helper.rft.unnestToken(signer, {collectionId: this.collectionId, tokenId}, fromTokenObj, toAddressObj);
3627 }
36123628
3613 scheduleAt<T extends UniqueHelper>(3629 scheduleAt<T extends UniqueHelper>(
3614 executionBlockNumber: number,3630 executionBlockNumber: number,
3849 return await this.collection.getToken(this.tokenId, blockHashAt);3865 return await this.collection.getToken(this.tokenId, blockHashAt);
3850 }3866 }
3867
3868 async getOwner(blockHashAt?: string) {
3869 return await this.collection.getTokenOwner(this.tokenId, blockHashAt);
3870 }
38513871
3852 async getTop10Owners() {3872 async getTop10Owners() {
3853 return await this.collection.getTop10TokenOwners(this.tokenId);3873 return await this.collection.getTop10TokenOwners(this.tokenId);
3854 }3874 }
3875
3876 async getTopmostOwner(blockHashAt?: string) {
3877 return await this.collection.getTokenTopmostOwner(this.tokenId, blockHashAt);
3878 }
3879
3880 async nest(signer: TSigner, toTokenObj: IToken) {
3881 return await this.collection.nestToken(signer, this.tokenId, toTokenObj);
3882 }
3883
3884 async unnest(signer: TSigner, fromTokenObj: IToken, toAddressObj: ICrossAccountId) {
3885 return await this.collection.unnestToken(signer, this.tokenId, fromTokenObj, toAddressObj);
3886 }
38553887
3856 async getBalance(addressObj: ICrossAccountId) {3888 async getBalance(addressObj: ICrossAccountId) {
3857 return await this.collection.getTokenBalance(this.tokenId, addressObj);3889 return await this.collection.getTokenBalance(this.tokenId, addressObj);