From 7f98069c7b600e353248d5fb5d2b79b7acac33f9 Mon Sep 17 00:00:00 2001 From: Trubnikov Sergey Date: Mon, 24 Oct 2022 21:43:48 +0000 Subject: [PATCH] feat: Add `collection_admins` method to eth collection. --- --- 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::( @@ -616,13 +616,16 @@ // .map_err(dispatch_to_evm::) // } - // TODO: need implement AbiWriter for &Vec - // fn collection_admins(&self) -> Result> { - // let result = pallet_common::IsAdmin::::iter_prefix((self.id,)) - // .map(|(admin, _)| pallet_common::eth::convert_cross_account_to_tuple::(&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> { + let result = crate::IsAdmin::::iter_prefix((self.id,)) + .map(|(admin, _)| crate::eth::convert_cross_account_to_tuple::(&admin)) + .collect(); + Ok(result) + } } /// ### Note --- 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 --- 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 --- 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 --- 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 --- 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 --- 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 --- a/tests/src/eth/collectionAdmin.test.ts +++ b/tests/src/eth/collectionAdmin.test.ts @@ -14,6 +14,7 @@ // along with Unique Network. If not, see . import {IKeyringPair} from '@polkadot/types/types'; +import {IEthCrossAccountId} from '../util/playgrounds/types'; import {usingEthPlaygrounds, itEth, expect, EthUniqueHelper} from './util'; async function recordEthFee(helper: EthUniqueHelper, userAddress: string, call: () => Promise) { @@ -68,11 +69,31 @@ const newAdmin = helper.eth.createAccount(); const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.false; await collectionEvm.methods.addCollectionAdmin(newAdmin).send(); expect(await collectionEvm.methods.isOwnerOrAdmin(newAdmin).call()).to.be.true; }); + + itEth.skip('Check adminlist', async ({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); + const collectionEvm = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); + + const admin1 = helper.eth.createAccount(); + const [admin2] = await helper.arrange.createAccounts([10n], donor); + await collectionEvm.methods.addCollectionAdmin(admin1).send(); + await collectionEvm.methods.addCollectionAdminSubstrate(admin2.addressRaw).send(); + const adminListRpc = await helper.collection.getAdmins(collectionId); + let adminListEth = await collectionEvm.methods.collectionAdmins().call(); + adminListEth = adminListEth.map((element: IEthCrossAccountId) => { + return helper.address.convertCrossAccountFromEthCrossAcoount(element); + }); + expect(adminListRpc).to.be.like(adminListEth); + }); + itEth('(!negative tests!) Add admin by ADMIN is not allowed', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const {collectionAddress, collectionId} = await helper.eth.createNFTCollection(owner, 'A', 'B', 'C'); --- 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": [ { --- 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": [ { --- 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": [ { --- 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; --- 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 { -- gitstuff