git.delta.rocks / unique-network / refs/commits / 7f98069c7b60

difftreelog

feat Add `collection_admins` method to eth collection.

Trubnikov Sergey2022-10-24parent: #ab2e6a7.patch.diff
in: master

19 files changed

modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -580,7 +580,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	fn collection_owner(&self) -> Result<(address, uint256)> {
 		Ok(convert_cross_account_to_tuple::<T>(
@@ -616,13 +616,16 @@
 	// 		.map_err(dispatch_to_evm::<T>)
 	// }
 
-	// TODO: need implement AbiWriter for &Vec<T>
-	// fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {
-	// 	let result = pallet_common::IsAdmin::<T>::iter_prefix((self.id,))
-	// 		.map(|(admin, _)| pallet_common::eth::convert_cross_account_to_tuple::<T>(&admin))
-	// 		.collect();
-	// 	Ok(result)
-	// }
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	fn collection_admins(&self) -> Result<Vec<(address, uint256)>> {
+		let result = crate::IsAdmin::<T>::iter_prefix((self.id,))
+			.map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::<T>(&admin))
+			.collect();
+		Ok(result)
+	}
 }
 
 /// ### Note
modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -18,7 +18,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -282,7 +282,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -303,6 +303,18 @@
 		newOwner;
 		dummy = 0;
 	}
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() public view returns (Tuple6[] memory) {
+		require(false, stub_error);
+		dummy;
+		return new Tuple6[](0);
+	}
 }
 
 /// @dev the ERC-165 identifier for this interface is 0x63034ac5
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -91,7 +91,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -355,7 +355,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -376,6 +376,18 @@
 		newOwner;
 		dummy = 0;
 	}
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() public view returns (Tuple17[] memory) {
+		require(false, stub_error);
+		dummy;
+		return new Tuple17[](0);
+	}
 }
 
 /// @dev anonymous struct
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -91,7 +91,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 contract Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -355,7 +355,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -376,6 +376,18 @@
 		newOwner;
 		dummy = 0;
 	}
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() public view returns (Tuple17[] memory) {
+		require(false, stub_error);
+		dummy;
+		return new Tuple17[](0);
+	}
 }
 
 /// @dev anonymous struct
modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -13,7 +13,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -184,7 +184,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -197,6 +197,14 @@
 	/// @dev EVM selector for this function is: 0x4f53e226,
 	///  or in textual repr: changeCollectionOwner(address)
 	function changeCollectionOwner(address newOwner) external;
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() external view returns (Tuple6[] memory);
 }
 
 /// @dev the ERC-165 identifier for this interface is 0x63034ac5
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -62,7 +62,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -233,7 +233,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -246,6 +246,14 @@
 	/// @dev EVM selector for this function is: 0x4f53e226,
 	///  or in textual repr: changeCollectionOwner(address)
 	function changeCollectionOwner(address newOwner) external;
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() external view returns (Tuple17[] memory);
 }
 
 /// @dev anonymous struct
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -62,7 +62,7 @@
 }
 
 /// @title A contract that allows you to work with collections.
-/// @dev the ERC-165 identifier for this interface is 0x62e22290
+/// @dev the ERC-165 identifier for this interface is 0x3af103fb
 interface Collection is Dummy, ERC165 {
 	/// Set collection property.
 	///
@@ -233,7 +233,7 @@
 
 	/// Get collection owner.
 	///
-	/// @return Tuble with sponsor address and his substrate mirror.
+	/// @return Tuple with sponsor address and his substrate mirror.
 	/// If address is canonical then substrate mirror is zero and vice versa.
 	/// @dev EVM selector for this function is: 0xdf727d3b,
 	///  or in textual repr: collectionOwner()
@@ -246,6 +246,14 @@
 	/// @dev EVM selector for this function is: 0x4f53e226,
 	///  or in textual repr: changeCollectionOwner(address)
 	function changeCollectionOwner(address newOwner) external;
+
+	/// Get collection administrators
+	///
+	/// @return Vector of tuples with admins address and his substrate mirror.
+	/// If address is canonical then substrate mirror is zero and vice versa.
+	/// @dev EVM selector for this function is: 0x5813216b,
+	///  or in textual repr: collectionAdmins()
+	function collectionAdmins() external view returns (Tuple17[] memory);
 }
 
 /// @dev anonymous struct
modifiedtests/src/eth/collectionAdmin.test.tsdiffbeforeafterboth
before · tests/src/eth/collectionAdmin.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.3// Unique Network is free software: you can redistribute it and/or modify4// it under the terms of the GNU General Public License as published by5// the Free Software Foundation, either version 3 of the License, or6// (at your option) any later version.7//8// Unique Network is distributed in the hope that it will be useful,9// but WITHOUT ANY WARRANTY; without even the implied warranty of10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the11// GNU General Public License for more details.1213// You should have received a copy of the GNU General Public License14// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1516import {IKeyringPair} from '@polkadot/types/types';17import {usingEthPlaygrounds, itEth, expect, EthUniqueHelper} from './util';1819async function recordEthFee(helper: EthUniqueHelper, userAddress: string, call: () => Promise<any>) {20  const before = await helper.balance.getSubstrate(helper.address.ethToSubstrate(userAddress));21  await call();22  await helper.wait.newBlocks(1);23  const after = await helper.balance.getSubstrate(helper.address.ethToSubstrate(userAddress));2425  expect(after < before).to.be.true;2627  return before - after;28}2930describe('Add collection admins', () => {31  let donor: IKeyringPair;3233  before(async function() {34    await usingEthPlaygrounds(async (_helper, privateKey) => {35      donor = await privateKey({filename: __filename});36    });37  });3839  itEth('Add admin by owner', async ({helper}) => {40    const owner = await helper.eth.createAccountWithBalance(donor);41    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');42    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);4344    const newAdmin = helper.eth.createAccount();4546    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();47    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);48    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())49      .to.be.eq(newAdmin.toLocaleLowerCase());50  });5152  itEth.skip('Add substrate admin by owner', async ({helper}) => {53    const owner = await helper.eth.createAccountWithBalance(donor);54    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');55    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);5657    const [newAdmin] = await helper.arrange.createAccounts([10n], donor);58    await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();5960    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);61    expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())62      .to.be.eq(newAdmin.address.toLocaleLowerCase());63  });6465  itEth('Verify owner or admin', async ({helper}) => {66    const owner = await helper.eth.createAccountWithBalance(donor);67    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');6869    const newAdmin = helper.eth.createAccount();70    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);71    expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.false;72    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();73    expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.true;74  });7576  itEth('(!negative tests!) Add admin by ADMIN is not allowed', async ({helper}) => {77    const owner = await helper.eth.createAccountWithBalance(donor);78    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');7980    const admin = await helper.eth.createAccountWithBalance(donor);81    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);82    await collectionEvm.methods.addCollectionAdmin(admin).send();8384    const user = helper.eth.createAccount();85    await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: admin}))86      .to.be.rejectedWith('NoPermission');8788    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);89    expect(adminList.length).to.be.eq(1);90    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())91      .to.be.eq(admin.toLocaleLowerCase());92  });9394  itEth('(!negative tests!) Add admin by USER is not allowed', async ({helper}) => {95    const owner = await helper.eth.createAccountWithBalance(donor);96    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');9798    const notAdmin = await helper.eth.createAccountWithBalance(donor);99    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);100101    const user = helper.eth.createAccount();102    await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: notAdmin}))103      .to.be.rejectedWith('NoPermission');104105    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);106    expect(adminList.length).to.be.eq(0);107  });108109  itEth.skip('(!negative tests!) Add substrate admin by ADMIN is not allowed', async ({helper}) => {110    const owner = await helper.eth.createAccountWithBalance(donor);111    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');112113    const admin = await helper.eth.createAccountWithBalance(donor);114    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);115    await collectionEvm.methods.addCollectionAdmin(admin).send();116117    const [notAdmin] = await helper.arrange.createAccounts([10n], donor);118    await expect(collectionEvm.methods.addCollectionAdminSubstrate(notAdmin.addressRaw).call({from: admin}))119      .to.be.rejectedWith('NoPermission');120121    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);122    expect(adminList.length).to.be.eq(1);123    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())124      .to.be.eq(admin.toLocaleLowerCase());125  });126127  itEth.skip('(!negative tests!) Add substrate admin by USER is not allowed', async ({helper}) => {128    const owner = await helper.eth.createAccountWithBalance(donor);129    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');130131    const notAdmin0 = await helper.eth.createAccountWithBalance(donor);132    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);133    const [notAdmin1] = await helper.arrange.createAccounts([10n], donor);134    await expect(collectionEvm.methods.addCollectionAdminSubstrate(notAdmin1.addressRaw).call({from: notAdmin0}))135      .to.be.rejectedWith('NoPermission');136137    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);138    expect(adminList.length).to.be.eq(0);139  });140});141142describe('Remove collection admins', () => {143  let donor: IKeyringPair;144145  before(async function() {146    await usingEthPlaygrounds(async (_helper, privateKey) => {147      donor = await privateKey({filename: __filename});148    });149  });150151  itEth('Remove admin by owner', async ({helper}) => {152    const owner = await helper.eth.createAccountWithBalance(donor);153    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');154155    const newAdmin = helper.eth.createAccount();156    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);157    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();158159    {160      const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);161      expect(adminList.length).to.be.eq(1);162      expect(adminList[0].asEthereum.toString().toLocaleLowerCase())163        .to.be.eq(newAdmin.toLocaleLowerCase());164    }165166    await collectionEvm.methods.removeCollectionAdmin(newAdmin).send();167    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);168    expect(adminList.length).to.be.eq(0);169  });170171  itEth.skip('Remove substrate admin by owner', async ({helper}) => {172    const owner = await helper.eth.createAccountWithBalance(donor);173    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');174175    const [newAdmin] = await helper.arrange.createAccounts([10n], donor);176    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);177    await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();178    {179      const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);180      expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())181        .to.be.eq(newAdmin.address.toLocaleLowerCase());182    }183184    await collectionEvm.methods.removeCollectionAdminSubstrate(newAdmin.addressRaw).send();185    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);186    expect(adminList.length).to.be.eq(0);187  });188189  itEth('(!negative tests!) Remove admin by ADMIN is not allowed', async ({helper}) => {190    const owner = await helper.eth.createAccountWithBalance(donor);191    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');192193    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);194195    const admin0 = await helper.eth.createAccountWithBalance(donor);196    await collectionEvm.methods.addCollectionAdmin(admin0).send();197    const admin1 = await helper.eth.createAccountWithBalance(donor);198    await collectionEvm.methods.addCollectionAdmin(admin1).send();199200    await expect(collectionEvm.methods.removeCollectionAdmin(admin1).call({from: admin0}))201      .to.be.rejectedWith('NoPermission');202    {203      const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);204      expect(adminList.length).to.be.eq(2);205      expect(adminList.toString().toLocaleLowerCase())206        .to.be.deep.contains(admin0.toLocaleLowerCase())207        .to.be.deep.contains(admin1.toLocaleLowerCase());208    }209  });210211  itEth('(!negative tests!) Remove admin by USER is not allowed', async ({helper}) => {212    const owner = await helper.eth.createAccountWithBalance(donor);213    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');214215    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);216217    const admin = await helper.eth.createAccountWithBalance(donor);218    await collectionEvm.methods.addCollectionAdmin(admin).send();219    const notAdmin = helper.eth.createAccount();220221    await expect(collectionEvm.methods.removeCollectionAdmin(admin).call({from: notAdmin}))222      .to.be.rejectedWith('NoPermission');223    {224      const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);225      expect(adminList[0].asEthereum.toString().toLocaleLowerCase())226        .to.be.eq(admin.toLocaleLowerCase());227      expect(adminList.length).to.be.eq(1);228    }229  });230231  itEth.skip('(!negative tests!) Remove substrate admin by ADMIN is not allowed', async ({helper}) => {232    const owner = await helper.eth.createAccountWithBalance(donor);233    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');234235    const [adminSub] = await helper.arrange.createAccounts([10n], donor);236    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);237    await collectionEvm.methods.addCollectionAdminSubstrate(adminSub.addressRaw).send();238    const adminEth = await helper.eth.createAccountWithBalance(donor);239    await collectionEvm.methods.addCollectionAdmin(adminEth).send();240241    await expect(collectionEvm.methods.removeCollectionAdminSubstrate(adminSub.addressRaw).call({from: adminEth}))242      .to.be.rejectedWith('NoPermission');243244    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);245    expect(adminList.length).to.be.eq(2);246    expect(adminList.toString().toLocaleLowerCase())247      .to.be.deep.contains(adminSub.address.toLocaleLowerCase())248      .to.be.deep.contains(adminEth.toLocaleLowerCase());249  });250251  itEth.skip('(!negative tests!) Remove substrate admin by USER is not allowed', async ({helper}) => {252    const owner = await helper.eth.createAccountWithBalance(donor);253    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');254255    const [adminSub] = await helper.arrange.createAccounts([10n], donor);256    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);257    await collectionEvm.methods.addCollectionAdminSubstrate(adminSub.addressRaw).send();258    const notAdminEth = await helper.eth.createAccountWithBalance(donor);259260    await expect(collectionEvm.methods.removeCollectionAdminSubstrate(adminSub.addressRaw).call({from: notAdminEth}))261      .to.be.rejectedWith('NoPermission');262263    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);264    expect(adminList.length).to.be.eq(1);265    expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())266      .to.be.eq(adminSub.address.toLocaleLowerCase());267  });268});269270describe('Change owner tests', () => {271  let donor: IKeyringPair;272273  before(async function() {274    await usingEthPlaygrounds(async (_helper, privateKey) => {275      donor = await privateKey({filename: __filename});276    });277  });278279  itEth('Change owner', async ({helper}) => {280    const owner = await helper.eth.createAccountWithBalance(donor);281    const newOwner = await helper.eth.createAccountWithBalance(donor);282    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');283    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);284285    await collectionEvm.methods.changeCollectionOwner(newOwner).send();286287    expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.false;288    expect(await collectionEvm.methods.isOwnerOrAdmin(newOwner).call()).to.be.true;289  });290291  itEth('change owner call fee', async ({helper}) => {292    const owner = await helper.eth.createAccountWithBalance(donor);293    const newOwner = await helper.eth.createAccountWithBalance(donor);294    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');295    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);296    const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.changeCollectionOwner(newOwner).send());297    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));298    expect(cost > 0);299  });300301  itEth('(!negative tests!) call setOwner by non owner', async ({helper}) => {302    const owner = await helper.eth.createAccountWithBalance(donor);303    const newOwner = await helper.eth.createAccountWithBalance(donor);304    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');305    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);306307    await expect(collectionEvm.methods.changeCollectionOwner(newOwner).send({from: newOwner})).to.be.rejected;308    expect(await collectionEvm.methods.isOwnerOrAdmin(newOwner).call()).to.be.false;309  });310});311312describe('Change substrate owner tests', () => {313  let donor: IKeyringPair;314315  before(async function() {316    await usingEthPlaygrounds(async (_helper, privateKey) => {317      donor = await privateKey({filename: __filename});318    });319  });320321  itEth.skip('Change owner', async ({helper}) => {322    const owner = await helper.eth.createAccountWithBalance(donor);323    const [newOwner] = await helper.arrange.createAccounts([10n], donor);324    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');325    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);326327    expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.true;328    expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.false;329330    await collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send();331332    expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.false;333    expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.true;334  });335336  itEth.skip('change owner call fee', async ({helper}) => {337    const owner = await helper.eth.createAccountWithBalance(donor);338    const [newOwner] = await helper.arrange.createAccounts([10n], donor);339    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');340    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);341342    const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send());343    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));344    expect(cost > 0);345  });346347  itEth.skip('(!negative tests!) call setOwner by non owner', async ({helper}) => {348    const owner = await helper.eth.createAccountWithBalance(donor);349    const otherReceiver = await helper.eth.createAccountWithBalance(donor);350    const [newOwner] = await helper.arrange.createAccounts([10n], donor);351    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');352    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);353354    await expect(collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send({from: otherReceiver})).to.be.rejected;355    expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.false;356  });357});
after · tests/src/eth/collectionAdmin.test.ts
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.3// Unique Network is free software: you can redistribute it and/or modify4// it under the terms of the GNU General Public License as published by5// the Free Software Foundation, either version 3 of the License, or6// (at your option) any later version.7//8// Unique Network is distributed in the hope that it will be useful,9// but WITHOUT ANY WARRANTY; without even the implied warranty of10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the11// GNU General Public License for more details.1213// You should have received a copy of the GNU General Public License14// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1516import {IKeyringPair} from '@polkadot/types/types';17import {IEthCrossAccountId} from '../util/playgrounds/types';18import {usingEthPlaygrounds, itEth, expect, EthUniqueHelper} from './util';1920async function recordEthFee(helper: EthUniqueHelper, userAddress: string, call: () => Promise<any>) {21  const before = await helper.balance.getSubstrate(helper.address.ethToSubstrate(userAddress));22  await call();23  await helper.wait.newBlocks(1);24  const after = await helper.balance.getSubstrate(helper.address.ethToSubstrate(userAddress));2526  expect(after < before).to.be.true;2728  return before - after;29}3031describe('Add collection admins', () => {32  let donor: IKeyringPair;3334  before(async function() {35    await usingEthPlaygrounds(async (_helper, privateKey) => {36      donor = await privateKey({filename: __filename});37    });38  });3940  itEth('Add admin by owner', async ({helper}) => {41    const owner = await helper.eth.createAccountWithBalance(donor);42    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');43    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);4445    const newAdmin = helper.eth.createAccount();4647    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();48    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);49    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())50      .to.be.eq(newAdmin.toLocaleLowerCase());51  });5253  itEth.skip('Add substrate admin by owner', async ({helper}) => {54    const owner = await helper.eth.createAccountWithBalance(donor);55    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');56    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);5758    const [newAdmin] = await helper.arrange.createAccounts([10n], donor);59    await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();6061    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);62    expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())63      .to.be.eq(newAdmin.address.toLocaleLowerCase());64  });6566  itEth('Verify owner or admin', async ({helper}) => {67    const owner = await helper.eth.createAccountWithBalance(donor);68    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');6970    const newAdmin = helper.eth.createAccount();71    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);72  73    expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.false;74    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();75    expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.true;76  });77  78  itEth.skip('Check adminlist', async ({helper}) => {79    const owner = await helper.eth.createAccountWithBalance(donor);80        81    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');82    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);83    84    const admin1 = helper.eth.createAccount();85    const [admin2] = await helper.arrange.createAccounts([10n], donor);86    await collectionEvm.methods.addCollectionAdmin(admin1).send();87    await collectionEvm.methods.addCollectionAdminSubstrate(admin2.addressRaw).send();8889    const adminListRpc = await helper.collection.getAdmins(collectionId);90    let adminListEth = await collectionEvm.methods.collectionAdmins().call();91    adminListEth = adminListEth.map((element: IEthCrossAccountId) => {92      return helper.address.convertCrossAccountFromEthCrossAcoount(element);93    });94    expect(adminListRpc).to.be.like(adminListEth);95  });  96    97  itEth('(!negative tests!) Add admin by ADMIN is not allowed', async ({helper}) => {98    const owner = await helper.eth.createAccountWithBalance(donor);99    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');100101    const admin = await helper.eth.createAccountWithBalance(donor);102    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);103    await collectionEvm.methods.addCollectionAdmin(admin).send();104105    const user = helper.eth.createAccount();106    await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: admin}))107      .to.be.rejectedWith('NoPermission');108109    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);110    expect(adminList.length).to.be.eq(1);111    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())112      .to.be.eq(admin.toLocaleLowerCase());113  });114115  itEth('(!negative tests!) Add admin by USER is not allowed', async ({helper}) => {116    const owner = await helper.eth.createAccountWithBalance(donor);117    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');118119    const notAdmin = await helper.eth.createAccountWithBalance(donor);120    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);121122    const user = helper.eth.createAccount();123    await expect(collectionEvm.methods.addCollectionAdmin(user).call({from: notAdmin}))124      .to.be.rejectedWith('NoPermission');125126    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);127    expect(adminList.length).to.be.eq(0);128  });129130  itEth.skip('(!negative tests!) Add substrate admin by ADMIN is not allowed', async ({helper}) => {131    const owner = await helper.eth.createAccountWithBalance(donor);132    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');133134    const admin = await helper.eth.createAccountWithBalance(donor);135    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);136    await collectionEvm.methods.addCollectionAdmin(admin).send();137138    const [notAdmin] = await helper.arrange.createAccounts([10n], donor);139    await expect(collectionEvm.methods.addCollectionAdminSubstrate(notAdmin.addressRaw).call({from: admin}))140      .to.be.rejectedWith('NoPermission');141142    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);143    expect(adminList.length).to.be.eq(1);144    expect(adminList[0].asEthereum.toString().toLocaleLowerCase())145      .to.be.eq(admin.toLocaleLowerCase());146  });147148  itEth.skip('(!negative tests!) Add substrate admin by USER is not allowed', async ({helper}) => {149    const owner = await helper.eth.createAccountWithBalance(donor);150    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');151152    const notAdmin0 = await helper.eth.createAccountWithBalance(donor);153    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);154    const [notAdmin1] = await helper.arrange.createAccounts([10n], donor);155    await expect(collectionEvm.methods.addCollectionAdminSubstrate(notAdmin1.addressRaw).call({from: notAdmin0}))156      .to.be.rejectedWith('NoPermission');157158    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);159    expect(adminList.length).to.be.eq(0);160  });161});162163describe('Remove collection admins', () => {164  let donor: IKeyringPair;165166  before(async function() {167    await usingEthPlaygrounds(async (_helper, privateKey) => {168      donor = await privateKey({filename: __filename});169    });170  });171172  itEth('Remove admin by owner', async ({helper}) => {173    const owner = await helper.eth.createAccountWithBalance(donor);174    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');175176    const newAdmin = helper.eth.createAccount();177    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);178    await collectionEvm.methods.addCollectionAdmin(newAdmin).send();179180    {181      const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);182      expect(adminList.length).to.be.eq(1);183      expect(adminList[0].asEthereum.toString().toLocaleLowerCase())184        .to.be.eq(newAdmin.toLocaleLowerCase());185    }186187    await collectionEvm.methods.removeCollectionAdmin(newAdmin).send();188    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);189    expect(adminList.length).to.be.eq(0);190  });191192  itEth.skip('Remove substrate admin by owner', async ({helper}) => {193    const owner = await helper.eth.createAccountWithBalance(donor);194    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');195196    const [newAdmin] = await helper.arrange.createAccounts([10n], donor);197    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);198    await collectionEvm.methods.addCollectionAdminSubstrate(newAdmin.addressRaw).send();199    {200      const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);201      expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())202        .to.be.eq(newAdmin.address.toLocaleLowerCase());203    }204205    await collectionEvm.methods.removeCollectionAdminSubstrate(newAdmin.addressRaw).send();206    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);207    expect(adminList.length).to.be.eq(0);208  });209210  itEth('(!negative tests!) Remove admin by ADMIN is not allowed', async ({helper}) => {211    const owner = await helper.eth.createAccountWithBalance(donor);212    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');213214    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);215216    const admin0 = await helper.eth.createAccountWithBalance(donor);217    await collectionEvm.methods.addCollectionAdmin(admin0).send();218    const admin1 = await helper.eth.createAccountWithBalance(donor);219    await collectionEvm.methods.addCollectionAdmin(admin1).send();220221    await expect(collectionEvm.methods.removeCollectionAdmin(admin1).call({from: admin0}))222      .to.be.rejectedWith('NoPermission');223    {224      const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);225      expect(adminList.length).to.be.eq(2);226      expect(adminList.toString().toLocaleLowerCase())227        .to.be.deep.contains(admin0.toLocaleLowerCase())228        .to.be.deep.contains(admin1.toLocaleLowerCase());229    }230  });231232  itEth('(!negative tests!) Remove admin by USER is not allowed', async ({helper}) => {233    const owner = await helper.eth.createAccountWithBalance(donor);234    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');235236    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);237238    const admin = await helper.eth.createAccountWithBalance(donor);239    await collectionEvm.methods.addCollectionAdmin(admin).send();240    const notAdmin = helper.eth.createAccount();241242    await expect(collectionEvm.methods.removeCollectionAdmin(admin).call({from: notAdmin}))243      .to.be.rejectedWith('NoPermission');244    {245      const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);246      expect(adminList[0].asEthereum.toString().toLocaleLowerCase())247        .to.be.eq(admin.toLocaleLowerCase());248      expect(adminList.length).to.be.eq(1);249    }250  });251252  itEth.skip('(!negative tests!) Remove substrate admin by ADMIN is not allowed', async ({helper}) => {253    const owner = await helper.eth.createAccountWithBalance(donor);254    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');255256    const [adminSub] = await helper.arrange.createAccounts([10n], donor);257    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);258    await collectionEvm.methods.addCollectionAdminSubstrate(adminSub.addressRaw).send();259    const adminEth = await helper.eth.createAccountWithBalance(donor);260    await collectionEvm.methods.addCollectionAdmin(adminEth).send();261262    await expect(collectionEvm.methods.removeCollectionAdminSubstrate(adminSub.addressRaw).call({from: adminEth}))263      .to.be.rejectedWith('NoPermission');264265    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);266    expect(adminList.length).to.be.eq(2);267    expect(adminList.toString().toLocaleLowerCase())268      .to.be.deep.contains(adminSub.address.toLocaleLowerCase())269      .to.be.deep.contains(adminEth.toLocaleLowerCase());270  });271272  itEth.skip('(!negative tests!) Remove substrate admin by USER is not allowed', async ({helper}) => {273    const owner = await helper.eth.createAccountWithBalance(donor);274    const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');275276    const [adminSub] = await helper.arrange.createAccounts([10n], donor);277    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);278    await collectionEvm.methods.addCollectionAdminSubstrate(adminSub.addressRaw).send();279    const notAdminEth = await helper.eth.createAccountWithBalance(donor);280281    await expect(collectionEvm.methods.removeCollectionAdminSubstrate(adminSub.addressRaw).call({from: notAdminEth}))282      .to.be.rejectedWith('NoPermission');283284    const adminList = await helper.callRpc('api.rpc.unique.adminlist', [collectionId]);285    expect(adminList.length).to.be.eq(1);286    expect(adminList[0].asSubstrate.toString().toLocaleLowerCase())287      .to.be.eq(adminSub.address.toLocaleLowerCase());288  });289});290291describe('Change owner tests', () => {292  let donor: IKeyringPair;293294  before(async function() {295    await usingEthPlaygrounds(async (_helper, privateKey) => {296      donor = await privateKey({filename: __filename});297    });298  });299300  itEth('Change owner', async ({helper}) => {301    const owner = await helper.eth.createAccountWithBalance(donor);302    const newOwner = await helper.eth.createAccountWithBalance(donor);303    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');304    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);305306    await collectionEvm.methods.changeCollectionOwner(newOwner).send();307308    expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.false;309    expect(await collectionEvm.methods.isOwnerOrAdmin(newOwner).call()).to.be.true;310  });311312  itEth('change owner call fee', async ({helper}) => {313    const owner = await helper.eth.createAccountWithBalance(donor);314    const newOwner = await helper.eth.createAccountWithBalance(donor);315    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');316    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);317    const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.changeCollectionOwner(newOwner).send());318    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));319    expect(cost > 0);320  });321322  itEth('(!negative tests!) call setOwner by non owner', async ({helper}) => {323    const owner = await helper.eth.createAccountWithBalance(donor);324    const newOwner = await helper.eth.createAccountWithBalance(donor);325    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');326    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);327328    await expect(collectionEvm.methods.changeCollectionOwner(newOwner).send({from: newOwner})).to.be.rejected;329    expect(await collectionEvm.methods.isOwnerOrAdmin(newOwner).call()).to.be.false;330  });331});332333describe('Change substrate owner tests', () => {334  let donor: IKeyringPair;335336  before(async function() {337    await usingEthPlaygrounds(async (_helper, privateKey) => {338      donor = await privateKey({filename: __filename});339    });340  });341342  itEth.skip('Change owner', async ({helper}) => {343    const owner = await helper.eth.createAccountWithBalance(donor);344    const [newOwner] = await helper.arrange.createAccounts([10n], donor);345    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');346    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);347348    expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.true;349    expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.false;350351    await collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send();352353    expect(await collectionEvm.methods.isOwnerOrAdmin(owner).call()).to.be.false;354    expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.true;355  });356357  itEth.skip('change owner call fee', async ({helper}) => {358    const owner = await helper.eth.createAccountWithBalance(donor);359    const [newOwner] = await helper.arrange.createAccounts([10n], donor);360    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');361    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);362363    const cost = await recordEthFee(helper, owner, () => collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send());364    expect(cost < BigInt(0.2 * Number(helper.balance.getOneTokenNominal())));365    expect(cost > 0);366  });367368  itEth.skip('(!negative tests!) call setOwner by non owner', async ({helper}) => {369    const owner = await helper.eth.createAccountWithBalance(donor);370    const otherReceiver = await helper.eth.createAccountWithBalance(donor);371    const [newOwner] = await helper.arrange.createAccounts([10n], donor);372    const {collectionAddress} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C');373    const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);374375    await expect(collectionEvm.methods.setOwnerSubstrate(newOwner.addressRaw).send({from: otherReceiver})).to.be.rejected;376    expect(await collectionEvm.methods.isOwnerOrAdminSubstrate(newOwner.addressRaw).call()).to.be.false;377  });378});
modifiedtests/src/eth/fungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/fungibleAbi.json
+++ b/tests/src/eth/fungibleAbi.json
@@ -126,6 +126,23 @@
   },
   {
     "inputs": [],
+    "name": "collectionAdmins",
+    "outputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple6[]",
+        "name": "",
+        "type": "tuple[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
     "name": "collectionOwner",
     "outputs": [
       {
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -156,6 +156,23 @@
   },
   {
     "inputs": [],
+    "name": "collectionAdmins",
+    "outputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple17[]",
+        "name": "",
+        "type": "tuple[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
     "name": "collectionOwner",
     "outputs": [
       {
modifiedtests/src/eth/reFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -156,6 +156,23 @@
   },
   {
     "inputs": [],
+    "name": "collectionAdmins",
+    "outputs": [
+      {
+        "components": [
+          { "internalType": "address", "name": "field_0", "type": "address" },
+          { "internalType": "uint256", "name": "field_1", "type": "uint256" }
+        ],
+        "internalType": "struct Tuple17[]",
+        "name": "",
+        "type": "tuple[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
     "name": "collectionOwner",
     "outputs": [
       {
modifiedtests/src/util/playgrounds/types.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/types.ts
+++ b/tests/src/util/playgrounds/types.ts
@@ -70,6 +70,13 @@
   ethereum?: TEthereumAccount;
 }
 
+export interface IEthCrossAccountId {
+  0: TEthereumAccount;
+  1: TSubstrateAccount;
+  field_0: TEthereumAccount;
+  field_1: TSubstrateAccount;
+}
+
 export interface ICollectionLimits {
   accountTokenOwnershipLimit?: number | null;
   sponsoredDataSize?: number | null;
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
--- a/tests/src/util/playgrounds/unique.ts
+++ b/tests/src/util/playgrounds/unique.ts
@@ -7,9 +7,11 @@
 
 import {ApiPromise, WsProvider, Keyring} from '@polkadot/api';
 import {ApiInterfaceEvents, SignerOptions} from '@polkadot/api/types';
-import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm} from '@polkadot/util-crypto';
+import {encodeAddress, decodeAddress, keccakAsHex, evmToAddress, addressToEvm, base58Encode, blake2AsU8a} from '@polkadot/util-crypto';
 import {IKeyringPair} from '@polkadot/types/types';
-import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata} from './types';
+import {IApiListeners, IBlock, IEvent, IChainProperties, ICollectionCreationOptions, ICollectionLimits, ICollectionPermissions, ICrossAccountId, ICrossAccountIdLower, ILogger, INestingPermissions, IProperty, IStakingInfo, ISchedulerOptions, ISubstrateBalance, IToken, ITokenPropertyPermission, ITransactionResult, IUniqueHelperLog, TApiAllowedListeners, TEthereumAccount, TSigner, TSubstrateAccount, IForeignAssetMetadata, TNetworks, MoonbeamAssetInfo, DemocracyStandardAccountVote, AcalaAssetMetadata, IEthCrossAccountId} from './types';
+import {hexToU8a} from '@polkadot/util/hex';
+import {u8aConcat} from '@polkadot/util/u8a';
 
 export class CrossAccountId implements ICrossAccountId {
   Substrate?: TSubstrateAccount;
@@ -2309,6 +2311,73 @@
 
     return siblingPrefix + encodedParaId + suffix;
   }
+
+  /**
+   * Encode key to substrate address
+   * @param key key for encoding address
+   * @param ss58Format prefix for encoding to the address of the corresponding network
+   * @returns encoded substrate address
+   */
+  encodeSubstrateAddress (key: Uint8Array | string | bigint, ss58Format = 42): string {
+    const u8a :Uint8Array = typeof key === 'string'
+      ? hexToU8a(key)
+      : typeof key === 'bigint'
+        ? hexToU8a(key.toString(16))
+        : key;
+  
+    if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) {
+      throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`);
+    }
+  
+    const allowedDecodedLengths = [1, 2, 4, 8, 32, 33];
+    if (!allowedDecodedLengths.includes(u8a.length)) {
+      throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(', ')}`);
+    }
+  
+    const u8aPrefix = ss58Format < 64
+      ? new Uint8Array([ss58Format])
+      : new Uint8Array([
+        ((ss58Format & 0xfc) >> 2) | 0x40,
+        (ss58Format >> 8) | ((ss58Format & 0x03) << 6),
+      ]);
+
+    const input = u8aConcat(u8aPrefix, u8a);
+  
+    return base58Encode(u8aConcat(
+      input,
+      blake2AsU8a(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1),
+    ));
+  }
+
+  /**
+   * Restore substrate address from bigint representation
+   * @param number decimal representation of substrate address
+   * @returns substrate address
+   */
+  restoreCrossAccountFromBigInt(number: bigint): TSubstrateAccount {
+    if (this.helper.api === null) {
+      throw 'Not connected';
+    }
+    const res = this.helper.api.registry.createType('AccountId', '0x' + number.toString(16).padStart(64, '0')).toJSON();
+    if (res === undefined || res === null) {
+      throw 'Restore address error';
+    }
+    return res.toString();
+  }
+
+  /**
+   * Convert etherium cross account id to substrate cross account id
+   * @param ethCrossAccount etherium cross account
+   * @returns substrate cross account id
+   */
+  convertCrossAccountFromEthCrossAcoount(ethCrossAccount: IEthCrossAccountId): ICrossAccountId {
+    if (ethCrossAccount.field_1 === '0') {
+      return {Ethereum: ethCrossAccount.field_0.toLocaleLowerCase()};
+    }
+    
+    const ss58 = this.restoreCrossAccountFromBigInt(BigInt(ethCrossAccount.field_1));
+    return {Substrate: ss58};
+  }
 }
 
 class StakingGroup extends HelperGroup<UniqueHelper> {