--- a/Cargo.lock +++ b/Cargo.lock @@ -6342,7 +6342,7 @@ [[package]] name = "pallet-nonfungible" -version = "0.1.9" +version = "0.1.11" dependencies = [ "ethereum 0.14.0", "evm-coder", @@ -6501,7 +6501,7 @@ [[package]] name = "pallet-refungible" -version = "0.2.8" +version = "0.2.10" dependencies = [ "derivative", "ethereum 0.14.0", --- a/crates/evm-coder/tests/abi_derive_generation.rs +++ b/crates/evm-coder/tests/abi_derive_generation.rs @@ -740,7 +740,7 @@ /// Some docs /// At multi /// line - #[derive(AbiCoder, Debug, PartialEq, Default)] + #[derive(AbiCoder, Debug, PartialEq, Default, Clone, Copy)] #[repr(u8)] enum Color { /// Docs for Red --- a/pallets/common/src/eth.rs +++ b/pallets/common/src/eth.rs @@ -162,3 +162,18 @@ CollectionAdmin, TokenOwner, } + +/// Ethereum representation of TokenPermissions (see [`up_data_structs::PropertyPermission`]) fields as an enumeration. +#[derive(AbiCoder, Copy, Clone, Default, Debug)] +#[repr(u8)] +pub enum EthTokenPermissions { + /// Permission to change the property and property permission. See [`up_data_structs::PropertyPermission::mutable`] + #[default] + Mutable, + + /// Change permission for the collection administrator. See [`up_data_structs::PropertyPermission::token_owner`] + TokenOwner, + + /// Permission to change the property for the owner of the token. See [`up_data_structs::PropertyPermission::collection_admin`] + CollectionAdmin, +} --- a/pallets/nonfungible/CHANGELOG.md +++ b/pallets/nonfungible/CHANGELOG.md @@ -4,6 +4,16 @@ +## [0.1.11] - 2022-12-16 + +### Added + +- The function `tokenPropertyPermissions` and `setTokenPropertyPermissions` to `TokenProperties` interface. + +### Changed + +- Hide `setTokenPropertyPermission` function in `TokenProperties` interface. + ## [0.1.10] - 2022-11-18 ### Added --- a/pallets/nonfungible/Cargo.toml +++ b/pallets/nonfungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-nonfungible" -version = "0.1.9" +version = "0.1.11" license = "GPLv3" edition = "2021" --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -34,11 +34,11 @@ CollectionPropertiesVec, }; use pallet_evm_coder_substrate::dispatch_to_evm; -use sp_std::vec::Vec; +use sp_std::{vec::Vec, vec}; use pallet_common::{ CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key}, - eth::EthCrossAccount, + eth::{EthCrossAccount, EthTokenPermissions}, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::call; @@ -60,6 +60,7 @@ /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. /// @param tokenOwner Permission to mutate property by token owner if property is mutable. #[weight(>::set_token_property_permissions(1))] + #[solidity(hide)] fn set_token_property_permission( &mut self, caller: caller, @@ -69,10 +70,10 @@ token_owner: bool, ) -> Result<()> { let caller = T::CrossAccountId::from_eth(caller); - >::set_property_permission( + >::set_token_property_permissions( self, &caller, - PropertyKeyPermission { + vec![PropertyKeyPermission { key: >::from(key) .try_into() .map_err(|_| "too long key")?, @@ -81,11 +82,78 @@ collection_admin, token_owner, }, - }, + }], ) .map_err(dispatch_to_evm::) } + /// @notice Set permissions for token property. + /// @dev Throws error if `msg.sender` is not admin or owner of the collection. + /// @param permissions Permissions for keys. + #[weight(>::set_token_property_permissions(permissions.len() as u32))] + fn set_token_property_permissions( + &mut self, + caller: caller, + permissions: Vec<(string, Vec<(EthTokenPermissions, bool)>)>, + ) -> Result<()> { + let caller = T::CrossAccountId::from_eth(caller); + const PERMISSIONS_FIELDS_COUNT: usize = 3; + + let mut perms = Vec::new(); + + for (key, pp) in permissions { + if pp.len() > PERMISSIONS_FIELDS_COUNT { + return Err(alloc::format!( + "Actual number of fields {} for {}, which exceeds the maximum value of {}", + pp.len(), + stringify!(EthTokenPermissions), + PERMISSIONS_FIELDS_COUNT + ) + .as_str() + .into()); + } + + let mut token_permission = PropertyPermission::default(); + + for (perm, value) in pp { + match perm { + EthTokenPermissions::Mutable => token_permission.mutable = value, + EthTokenPermissions::TokenOwner => token_permission.token_owner = value, + EthTokenPermissions::CollectionAdmin => { + token_permission.collection_admin = value + } + } + } + + perms.push(PropertyKeyPermission { + key: key.into_bytes().try_into().map_err(|_| "too long key")?, + permission: token_permission, + }); + } + + >::set_token_property_permissions(self, &caller, perms) + .map_err(dispatch_to_evm::) + } + + /// @notice Get permissions for token properties. + fn token_property_permissions( + &self, + ) -> Result)>> { + let perms = >::token_property_permission(self.id); + Ok(perms + .into_iter() + .map(|(key, pp)| { + let key = string::from_utf8(key.into_inner()).expect("Stored key must be valid"); + let pp = vec![ + (EthTokenPermissions::Mutable, pp.mutable), + (EthTokenPermissions::TokenOwner, pp.token_owner), + (EthTokenPermissions::CollectionAdmin, pp.collection_admin), + ]; + (key, pp) + }) + .collect()) + } + /// @notice Set token property value. /// @dev Throws error if `msg.sender` has no permission to edit the property. /// @param tokenId ID of the token. --- a/pallets/nonfungible/src/lib.rs +++ b/pallets/nonfungible/src/lib.rs @@ -103,7 +103,7 @@ AccessMode, CollectionId, CollectionFlags, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData, mapping::TokenAddressMapping, budget::Budget, Property, PropertyPermission, PropertyKey, PropertyValue, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, - TokenChild, AuxPropertyValue, + TokenChild, AuxPropertyValue, PropertiesPermissionMap, }; use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm}; use pallet_common::{ @@ -824,15 +824,8 @@ ) } - /// Set property permissions for the collection. - /// - /// Sender should be the owner or admin of the collection. - pub fn set_property_permission( - collection: &CollectionHandle, - sender: &T::CrossAccountId, - permission: PropertyKeyPermission, - ) -> DispatchResult { - >::set_property_permission(collection, sender, permission) + pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap { + >::property_permissions(collection_id) } pub fn check_token_immediate_ownership( --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -18,30 +18,44 @@ } /// @title A contract that allows to set and delete token properties and change token property permissions. -/// @dev the ERC-165 identifier for this interface is 0x91a97a68 +/// @dev the ERC-165 identifier for this interface is 0xde0695c2 contract TokenProperties is Dummy, ERC165 { + // /// @notice Set permissions for token property. + // /// @dev Throws error if `msg.sender` is not admin or owner of the collection. + // /// @param key Property key. + // /// @param isMutable Permission to mutate property. + // /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. + // /// @param tokenOwner Permission to mutate property by token owner if property is mutable. + // /// @dev EVM selector for this function is: 0x222d97fa, + // /// or in textual repr: setTokenPropertyPermission(string,bool,bool,bool) + // function setTokenPropertyPermission(string memory key, bool isMutable, bool collectionAdmin, bool tokenOwner) public { + // require(false, stub_error); + // key; + // isMutable; + // collectionAdmin; + // tokenOwner; + // dummy = 0; + // } + /// @notice Set permissions for token property. /// @dev Throws error if `msg.sender` is not admin or owner of the collection. - /// @param key Property key. - /// @param isMutable Permission to mutate property. - /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. - /// @param tokenOwner Permission to mutate property by token owner if property is mutable. - /// @dev EVM selector for this function is: 0x222d97fa, - /// or in textual repr: setTokenPropertyPermission(string,bool,bool,bool) - function setTokenPropertyPermission( - string memory key, - bool isMutable, - bool collectionAdmin, - bool tokenOwner - ) public { + /// @param permissions Permissions for keys. + /// @dev EVM selector for this function is: 0xbd92983a, + /// or in textual repr: setTokenPropertyPermissions((string,(uint8,bool)[])[]) + function setTokenPropertyPermissions(Tuple48[] memory permissions) public { require(false, stub_error); - key; - isMutable; - collectionAdmin; - tokenOwner; + permissions; dummy = 0; } + /// @dev EVM selector for this function is: 0xf23d7790, + /// or in textual repr: tokenPropertyPermissions() + function tokenPropertyPermissions() public view returns (Tuple48[] memory) { + require(false, stub_error); + dummy; + return new Tuple48[](0); + } + // /// @notice Set token property value. // /// @dev Throws error if `msg.sender` has no permission to edit the property. // /// @param tokenId ID of the token. @@ -118,6 +132,24 @@ bytes value; } +enum EthTokenPermissions { + Mutable, + TokenOwner, + CollectionAdmin +} + +/// @dev anonymous struct +struct Tuple48 { + string field_0; + Tuple46[] field_1; +} + +/// @dev anonymous struct +struct Tuple46 { + EthTokenPermissions field_0; + bool field_1; +} + /// @title A contract that allows you to work with collections. /// @dev the ERC-165 identifier for this interface is 0xb5e1747f contract Collection is Dummy, ERC165 { --- a/pallets/refungible/CHANGELOG.md +++ b/pallets/refungible/CHANGELOG.md @@ -4,6 +4,16 @@ +## [0.2.10] - 2022-12-16 + +### Added + +- The function `tokenPropertyPermissions` and `setTokenPropertyPermissions` to `TokenProperties` interface. + +### Changed + +- Hide `setTokenPropertyPermission` function in `TokenProperties` interface. + ## [0.2.9] - 2022-11-18 ### Added --- a/pallets/refungible/Cargo.toml +++ b/pallets/refungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-refungible" -version = "0.2.8" +version = "0.2.10" license = "GPLv3" edition = "2021" --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -33,7 +33,7 @@ use pallet_common::{ CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, erc::{CommonEvmHandler, CollectionCall, static_property::key}, - eth::EthCrossAccount, + eth::{EthCrossAccount, EthTokenPermissions}, Error as CommonError, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; @@ -63,6 +63,7 @@ /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. /// @param tokenOwner Permission to mutate property by token owner if property is mutable. #[weight(>::set_token_property_permissions(1))] + #[solidity(hide)] fn set_token_property_permission( &mut self, caller: caller, @@ -89,6 +90,77 @@ .map_err(dispatch_to_evm::) } + /// @notice Set permissions for token property. + /// @dev Throws error if `msg.sender` is not admin or owner of the collection. + /// @param permissions Permissions for keys. + #[weight(>::set_token_property_permissions(permissions.len() as u32))] + fn set_token_property_permissions( + &mut self, + caller: caller, + permissions: Vec<(string, Vec<(EthTokenPermissions, bool)>)>, + ) -> Result<()> { + let caller = T::CrossAccountId::from_eth(caller); + const PERMISSIONS_FIELDS_COUNT: usize = 3; + + let mut perms = Vec::new(); + + for (key, pp) in permissions { + if pp.len() > PERMISSIONS_FIELDS_COUNT { + return Err(alloc::format!( + "Actual number of fields {} for {}, which exceeds the maximum value of {}", + pp.len(), + stringify!(EthTokenPermissions), + PERMISSIONS_FIELDS_COUNT + ) + .as_str() + .into()); + } + + let mut token_permission = PropertyPermission { + mutable: false, + collection_admin: false, + token_owner: false, + }; + + for (perm, value) in pp { + match perm { + EthTokenPermissions::Mutable => token_permission.mutable = value, + EthTokenPermissions::TokenOwner => token_permission.token_owner = value, + EthTokenPermissions::CollectionAdmin => { + token_permission.collection_admin = value + } + } + } + + perms.push(PropertyKeyPermission { + key: key.into_bytes().try_into().map_err(|_| "too long key")?, + permission: token_permission, + }); + } + + >::set_token_property_permissions(self, &caller, perms) + .map_err(dispatch_to_evm::) + } + + /// @notice Get permissions for token properties. + fn token_property_permissions( + &self, + ) -> Result)>> { + let perms = >::token_property_permission(self.id); + Ok(perms + .into_iter() + .map(|(key, pp)| { + let key = string::from_utf8(key.into_inner()).expect("Stored key must be valid"); + let pp = vec![ + (EthTokenPermissions::Mutable, pp.mutable), + (EthTokenPermissions::TokenOwner, pp.token_owner), + (EthTokenPermissions::CollectionAdmin, pp.collection_admin), + ]; + (key, pp) + }) + .collect()) + } + /// @notice Set token property value. /// @dev Throws error if `msg.sender` has no permission to edit the property. /// @param tokenId ID of the token. --- a/pallets/refungible/src/lib.rs +++ b/pallets/refungible/src/lib.rs @@ -113,7 +113,7 @@ AccessMode, budget::Budget, CollectionId, CollectionFlags, CollectionPropertiesVec, CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH, MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission, - PropertyScope, PropertyValue, TokenId, TrySetProperty, + PropertyScope, PropertyValue, TokenId, TrySetProperty, PropertiesPermissionMap, }; pub use pallet::*; @@ -1378,6 +1378,10 @@ >::set_token_property_permissions(collection, sender, property_permissions) } + pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap { + >::property_permissions(collection_id) + } + pub fn set_scoped_token_property_permissions( collection: &RefungibleHandle, sender: &T::CrossAccountId, --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -18,30 +18,45 @@ } /// @title A contract that allows to set and delete token properties and change token property permissions. -/// @dev the ERC-165 identifier for this interface is 0x91a97a68 +/// @dev the ERC-165 identifier for this interface is 0xde0695c2 contract TokenProperties is Dummy, ERC165 { + // /// @notice Set permissions for token property. + // /// @dev Throws error if `msg.sender` is not admin or owner of the collection. + // /// @param key Property key. + // /// @param isMutable Permission to mutate property. + // /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. + // /// @param tokenOwner Permission to mutate property by token owner if property is mutable. + // /// @dev EVM selector for this function is: 0x222d97fa, + // /// or in textual repr: setTokenPropertyPermission(string,bool,bool,bool) + // function setTokenPropertyPermission(string memory key, bool isMutable, bool collectionAdmin, bool tokenOwner) public { + // require(false, stub_error); + // key; + // isMutable; + // collectionAdmin; + // tokenOwner; + // dummy = 0; + // } + /// @notice Set permissions for token property. /// @dev Throws error if `msg.sender` is not admin or owner of the collection. - /// @param key Property key. - /// @param isMutable Permission to mutate property. - /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. - /// @param tokenOwner Permission to mutate property by token owner if property is mutable. - /// @dev EVM selector for this function is: 0x222d97fa, - /// or in textual repr: setTokenPropertyPermission(string,bool,bool,bool) - function setTokenPropertyPermission( - string memory key, - bool isMutable, - bool collectionAdmin, - bool tokenOwner - ) public { + /// @param permissions Permissions for keys. + /// @dev EVM selector for this function is: 0xbd92983a, + /// or in textual repr: setTokenPropertyPermissions((string,(uint8,bool)[])[]) + function setTokenPropertyPermissions(Tuple53[] memory permissions) public { require(false, stub_error); - key; - isMutable; - collectionAdmin; - tokenOwner; + permissions; dummy = 0; } + /// @notice Get permissions for token properties. + /// @dev EVM selector for this function is: 0xf23d7790, + /// or in textual repr: tokenPropertyPermissions() + function tokenPropertyPermissions() public view returns (Tuple53[] memory) { + require(false, stub_error); + dummy; + return new Tuple53[](0); + } + // /// @notice Set token property value. // /// @dev Throws error if `msg.sender` has no permission to edit the property. // /// @param tokenId ID of the token. @@ -118,6 +133,24 @@ bytes value; } +enum EthTokenPermissions { + Mutable, + TokenOwner, + CollectionAdmin +} + +/// @dev anonymous struct +struct Tuple53 { + string field_0; + Tuple51[] field_1; +} + +/// @dev anonymous struct +struct Tuple51 { + EthTokenPermissions field_0; + bool field_1; +} + /// @title A contract that allows you to work with collections. /// @dev the ERC-165 identifier for this interface is 0xb5e1747f contract Collection is Dummy, ERC165 { --- a/primitives/data-structs/src/lib.rs +++ b/primitives/data-structs/src/lib.rs @@ -1010,7 +1010,7 @@ pub type PropertyValue = BoundedBytes>; /// Property permission. -#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone)] +#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen, PartialEq, Clone, Default)] #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))] pub struct PropertyPermission { /// Permission to change the property and property permission. --- a/tests/src/eth/abi/nonFungible.json +++ b/tests/src/eth/abi/nonFungible.json @@ -697,12 +697,29 @@ }, { "inputs": [ - { "internalType": "string", "name": "key", "type": "string" }, - { "internalType": "bool", "name": "isMutable", "type": "bool" }, - { "internalType": "bool", "name": "collectionAdmin", "type": "bool" }, - { "internalType": "bool", "name": "tokenOwner", "type": "bool" } + { + "components": [ + { "internalType": "string", "name": "field_0", "type": "string" }, + { + "components": [ + { + "internalType": "enum EthTokenPermissions", + "name": "field_0", + "type": "uint8" + }, + { "internalType": "bool", "name": "field_1", "type": "bool" } + ], + "internalType": "struct Tuple46[]", + "name": "field_1", + "type": "tuple[]" + } + ], + "internalType": "struct Tuple48[]", + "name": "permissions", + "type": "tuple[]" + } ], - "name": "setTokenPropertyPermission", + "name": "setTokenPropertyPermissions", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -743,6 +760,35 @@ "type": "function" }, { + "inputs": [], + "name": "tokenPropertyPermissions", + "outputs": [ + { + "components": [ + { "internalType": "string", "name": "field_0", "type": "string" }, + { + "components": [ + { + "internalType": "enum EthTokenPermissions", + "name": "field_0", + "type": "uint8" + }, + { "internalType": "bool", "name": "field_1", "type": "bool" } + ], + "internalType": "struct Tuple46[]", + "name": "field_1", + "type": "tuple[]" + } + ], + "internalType": "struct Tuple48[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [ { "internalType": "uint256", "name": "tokenId", "type": "uint256" } ], --- a/tests/src/eth/abi/reFungible.json +++ b/tests/src/eth/abi/reFungible.json @@ -679,12 +679,29 @@ }, { "inputs": [ - { "internalType": "string", "name": "key", "type": "string" }, - { "internalType": "bool", "name": "isMutable", "type": "bool" }, - { "internalType": "bool", "name": "collectionAdmin", "type": "bool" }, - { "internalType": "bool", "name": "tokenOwner", "type": "bool" } + { + "components": [ + { "internalType": "string", "name": "field_0", "type": "string" }, + { + "components": [ + { + "internalType": "enum EthTokenPermissions", + "name": "field_0", + "type": "uint8" + }, + { "internalType": "bool", "name": "field_1", "type": "bool" } + ], + "internalType": "struct Tuple51[]", + "name": "field_1", + "type": "tuple[]" + } + ], + "internalType": "struct Tuple53[]", + "name": "permissions", + "type": "tuple[]" + } ], - "name": "setTokenPropertyPermission", + "name": "setTokenPropertyPermissions", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -734,6 +751,35 @@ "type": "function" }, { + "inputs": [], + "name": "tokenPropertyPermissions", + "outputs": [ + { + "components": [ + { "internalType": "string", "name": "field_0", "type": "string" }, + { + "components": [ + { + "internalType": "enum EthTokenPermissions", + "name": "field_0", + "type": "uint8" + }, + { "internalType": "bool", "name": "field_1", "type": "bool" } + ], + "internalType": "struct Tuple51[]", + "name": "field_1", + "type": "tuple[]" + } + ], + "internalType": "struct Tuple53[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [ { "internalType": "uint256", "name": "tokenId", "type": "uint256" } ], --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -13,22 +13,28 @@ } /// @title A contract that allows to set and delete token properties and change token property permissions. -/// @dev the ERC-165 identifier for this interface is 0x91a97a68 +/// @dev the ERC-165 identifier for this interface is 0xde0695c2 interface TokenProperties is Dummy, ERC165 { + // /// @notice Set permissions for token property. + // /// @dev Throws error if `msg.sender` is not admin or owner of the collection. + // /// @param key Property key. + // /// @param isMutable Permission to mutate property. + // /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. + // /// @param tokenOwner Permission to mutate property by token owner if property is mutable. + // /// @dev EVM selector for this function is: 0x222d97fa, + // /// or in textual repr: setTokenPropertyPermission(string,bool,bool,bool) + // function setTokenPropertyPermission(string memory key, bool isMutable, bool collectionAdmin, bool tokenOwner) external; + /// @notice Set permissions for token property. /// @dev Throws error if `msg.sender` is not admin or owner of the collection. - /// @param key Property key. - /// @param isMutable Permission to mutate property. - /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. - /// @param tokenOwner Permission to mutate property by token owner if property is mutable. - /// @dev EVM selector for this function is: 0x222d97fa, - /// or in textual repr: setTokenPropertyPermission(string,bool,bool,bool) - function setTokenPropertyPermission( - string memory key, - bool isMutable, - bool collectionAdmin, - bool tokenOwner - ) external; + /// @param permissions Permissions for keys. + /// @dev EVM selector for this function is: 0xbd92983a, + /// or in textual repr: setTokenPropertyPermissions((string,(uint8,bool)[])[]) + function setTokenPropertyPermissions(Tuple43[] memory permissions) external; + + /// @dev EVM selector for this function is: 0xf23d7790, + /// or in textual repr: tokenPropertyPermissions() + function tokenPropertyPermissions() external view returns (Tuple43[] memory); // /// @notice Set token property value. // /// @dev Throws error if `msg.sender` has no permission to edit the property. @@ -79,6 +85,24 @@ bytes value; } +enum EthTokenPermissions { + Mutable, + TokenOwner, + CollectionAdmin +} + +/// @dev anonymous struct +struct Tuple43 { + string field_0; + Tuple41[] field_1; +} + +/// @dev anonymous struct +struct Tuple41 { + EthTokenPermissions field_0; + bool field_1; +} + /// @title A contract that allows you to work with collections. /// @dev the ERC-165 identifier for this interface is 0xb5e1747f interface Collection is Dummy, ERC165 { --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -13,22 +13,29 @@ } /// @title A contract that allows to set and delete token properties and change token property permissions. -/// @dev the ERC-165 identifier for this interface is 0x91a97a68 +/// @dev the ERC-165 identifier for this interface is 0xde0695c2 interface TokenProperties is Dummy, ERC165 { + // /// @notice Set permissions for token property. + // /// @dev Throws error if `msg.sender` is not admin or owner of the collection. + // /// @param key Property key. + // /// @param isMutable Permission to mutate property. + // /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. + // /// @param tokenOwner Permission to mutate property by token owner if property is mutable. + // /// @dev EVM selector for this function is: 0x222d97fa, + // /// or in textual repr: setTokenPropertyPermission(string,bool,bool,bool) + // function setTokenPropertyPermission(string memory key, bool isMutable, bool collectionAdmin, bool tokenOwner) external; + /// @notice Set permissions for token property. /// @dev Throws error if `msg.sender` is not admin or owner of the collection. - /// @param key Property key. - /// @param isMutable Permission to mutate property. - /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable. - /// @param tokenOwner Permission to mutate property by token owner if property is mutable. - /// @dev EVM selector for this function is: 0x222d97fa, - /// or in textual repr: setTokenPropertyPermission(string,bool,bool,bool) - function setTokenPropertyPermission( - string memory key, - bool isMutable, - bool collectionAdmin, - bool tokenOwner - ) external; + /// @param permissions Permissions for keys. + /// @dev EVM selector for this function is: 0xbd92983a, + /// or in textual repr: setTokenPropertyPermissions((string,(uint8,bool)[])[]) + function setTokenPropertyPermissions(Tuple47[] memory permissions) external; + + /// @notice Get permissions for token properties. + /// @dev EVM selector for this function is: 0xf23d7790, + /// or in textual repr: tokenPropertyPermissions() + function tokenPropertyPermissions() external view returns (Tuple47[] memory); // /// @notice Set token property value. // /// @dev Throws error if `msg.sender` has no permission to edit the property. @@ -79,6 +86,24 @@ bytes value; } +enum EthTokenPermissions { + Mutable, + TokenOwner, + CollectionAdmin +} + +/// @dev anonymous struct +struct Tuple47 { + string field_0; + Tuple45[] field_1; +} + +/// @dev anonymous struct +struct Tuple45 { + EthTokenPermissions field_0; + bool field_1; +} + /// @title A contract that allows you to work with collections. /// @dev the ERC-165 identifier for this interface is 0xb5e1747f interface Collection is Dummy, ERC165 { --- a/tests/src/eth/events.test.ts +++ b/tests/src/eth/events.test.ts @@ -19,7 +19,7 @@ import {EthUniqueHelper, itEth, usingEthPlaygrounds} from './util'; import {IEvent, TCollectionMode} from '../util/playgrounds/types'; import {Pallets, requirePalletsOrSkip} from '../util'; -import {NormalizedEvent} from './util/playgrounds/types'; +import {EthTokenPermissions, NormalizedEvent} from './util/playgrounds/types'; let donor: IKeyringPair; @@ -119,7 +119,13 @@ ethEvents.push(event); }); const {unsubscribe, collectedEvents: subEvents} = await helper.subscribeEvents([{section: 'common', names: ['PropertyPermissionSet']}]); - await collection.methods.setTokenPropertyPermission('testKey', true, true, true).send({from: owner}); + await collection.methods.setTokenPropertyPermissions([ + ['A', [ + [EthTokenPermissions.Mutable, true], + [EthTokenPermissions.TokenOwner, true], + [EthTokenPermissions.CollectionAdmin, true]], + ], + ]).send({from: owner}); await helper.wait.newBlocks(1); expect(ethEvents).to.be.like([ { @@ -374,7 +380,13 @@ const collectionHelper = helper.ethNativeContract.collectionHelpers(owner); const result = await collection.methods.mint(owner).send({from: owner}); const tokenId = result.events.Transfer.returnValues.tokenId; - await collection.methods.setTokenPropertyPermission('A', true, true, true).send({from: owner}); + await collection.methods.setTokenPropertyPermissions([ + ['A', [ + [EthTokenPermissions.Mutable, true], + [EthTokenPermissions.TokenOwner, true], + [EthTokenPermissions.CollectionAdmin, true]], + ], + ]).send({from: owner}); const ethEvents: any = []; --- a/tests/src/eth/tokenProperties.test.ts +++ b/tests/src/eth/tokenProperties.test.ts @@ -20,6 +20,7 @@ import {ITokenPropertyPermission} from '../util/playgrounds/types'; import {Pallets} from '../util'; import {UniqueNFTCollection, UniqueNFToken, UniqueRFTCollection} from '../util/playgrounds/unique'; +import {EthTokenPermissions} from './util/playgrounds/types'; describe('EVM token properties', () => { let donor: IKeyringPair; @@ -32,25 +33,170 @@ }); }); - itEth('Can be reconfigured', async({helper}) => { - const caller = await helper.eth.createAccountWithBalance(donor); - for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) { - const collection = await helper.nft.mintCollection(alice); - await collection.addAdmin(alice, {Ethereum: caller}); + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + ].map(testCase => + itEth.ifWithPallets(`[${testCase.mode}] Set and get token property permissions`, testCase.requiredPallets, async({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.ethCrossAccount.createAccountWithBalance(donor); + for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) { + const {collectionId, collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); + await collection.methods.addCollectionAdminCross(caller).send({from: owner}); + + await collection.methods.setTokenPropertyPermissions([ + ['testKey', [ + [EthTokenPermissions.Mutable, mutable], + [EthTokenPermissions.TokenOwner, tokenOwner], + [EthTokenPermissions.CollectionAdmin, collectionAdmin]], + ], + ]).send({from: caller.eth}); - const address = helper.ethAddress.fromCollectionId(collection.collectionId); - const contract = helper.ethNativeContract.collection(address, 'nft', caller); - - await contract.methods.setTokenPropertyPermission('testKey', mutable, collectionAdmin, tokenOwner).send({from: caller}); - - expect(await collection.getPropertyPermissions()).to.be.deep.equal([{ - key: 'testKey', - permission: {mutable, collectionAdmin, tokenOwner}, - }]); - } - }); + expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([{ + key: 'testKey', + permission: {mutable, collectionAdmin, tokenOwner}, + }]); + expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([ + ['testKey', [ + [EthTokenPermissions.Mutable.toString(), mutable], + [EthTokenPermissions.TokenOwner.toString(), tokenOwner], + [EthTokenPermissions.CollectionAdmin.toString(), collectionAdmin]], + ], + ]); + } + })); + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + ].map(testCase => + itEth.ifWithPallets(`[${testCase.mode}] Set and get multiple token property permissions as owner`, testCase.requiredPallets, async({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionId, collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); + + await collection.methods.setTokenPropertyPermissions([ + ['testKey_0', [ + [EthTokenPermissions.Mutable, true], + [EthTokenPermissions.TokenOwner, true], + [EthTokenPermissions.CollectionAdmin, true]], + ], + ['testKey_1', [ + [EthTokenPermissions.Mutable, true], + [EthTokenPermissions.TokenOwner, false], + [EthTokenPermissions.CollectionAdmin, true]], + ], + ['testKey_2', [ + [EthTokenPermissions.Mutable, false], + [EthTokenPermissions.TokenOwner, true], + [EthTokenPermissions.CollectionAdmin, false]], + ], + ]).send({from: owner}); + + expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([ + { + key: 'testKey_0', + permission: {mutable: true, tokenOwner: true, collectionAdmin: true}, + }, + { + key: 'testKey_1', + permission: {mutable: true, tokenOwner: false, collectionAdmin: true}, + }, + { + key: 'testKey_2', + permission: {mutable: false, tokenOwner: true, collectionAdmin: false}, + }, + ]); + + expect(await collection.methods.tokenPropertyPermissions().call({from: owner})).to.be.like([ + ['testKey_0', [ + [EthTokenPermissions.Mutable.toString(), true], + [EthTokenPermissions.TokenOwner.toString(), true], + [EthTokenPermissions.CollectionAdmin.toString(), true]], + ], + ['testKey_1', [ + [EthTokenPermissions.Mutable.toString(), true], + [EthTokenPermissions.TokenOwner.toString(), false], + [EthTokenPermissions.CollectionAdmin.toString(), true]], + ], + ['testKey_2', [ + [EthTokenPermissions.Mutable.toString(), false], + [EthTokenPermissions.TokenOwner.toString(), true], + [EthTokenPermissions.CollectionAdmin.toString(), false]], + ], + ]); + + })); + + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + ].map(testCase => + itEth.ifWithPallets(`[${testCase.mode}] Set and get multiple token property permissions as admin`, testCase.requiredPallets, async({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.ethCrossAccount.createAccountWithBalance(donor); + + const {collectionId, collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); + await collection.methods.addCollectionAdminCross(caller).send({from: owner}); + + await collection.methods.setTokenPropertyPermissions([ + ['testKey_0', [ + [EthTokenPermissions.Mutable, true], + [EthTokenPermissions.TokenOwner, true], + [EthTokenPermissions.CollectionAdmin, true]], + ], + ['testKey_1', [ + [EthTokenPermissions.Mutable, true], + [EthTokenPermissions.TokenOwner, false], + [EthTokenPermissions.CollectionAdmin, true]], + ], + ['testKey_2', [ + [EthTokenPermissions.Mutable, false], + [EthTokenPermissions.TokenOwner, true], + [EthTokenPermissions.CollectionAdmin, false]], + ], + ]).send({from: caller.eth}); + + expect(await helper[testCase.mode].getPropertyPermissions(collectionId)).to.be.deep.equal([ + { + key: 'testKey_0', + permission: {mutable: true, tokenOwner: true, collectionAdmin: true}, + }, + { + key: 'testKey_1', + permission: {mutable: true, tokenOwner: false, collectionAdmin: true}, + }, + { + key: 'testKey_2', + permission: {mutable: false, tokenOwner: true, collectionAdmin: false}, + }, + ]); + + expect(await collection.methods.tokenPropertyPermissions().call({from: caller.eth})).to.be.like([ + ['testKey_0', [ + [EthTokenPermissions.Mutable.toString(), true], + [EthTokenPermissions.TokenOwner.toString(), true], + [EthTokenPermissions.CollectionAdmin.toString(), true]], + ], + ['testKey_1', [ + [EthTokenPermissions.Mutable.toString(), true], + [EthTokenPermissions.TokenOwner.toString(), false], + [EthTokenPermissions.CollectionAdmin.toString(), true]], + ], + ['testKey_2', [ + [EthTokenPermissions.Mutable.toString(), false], + [EthTokenPermissions.TokenOwner.toString(), true], + [EthTokenPermissions.CollectionAdmin.toString(), false]], + ], + ]); + + })); + + [ { method: 'setProperties', methodParams: [[{key: 'testKey1', value: Buffer.from('testValue1')}, {key: 'testKey2', value: Buffer.from('testValue2')}]], @@ -301,6 +447,47 @@ const actualProps = await collectionEvm.methods.properties(token.tokenId, []).call(); expect(actualProps).to.deep.eq(expectedProps); })); + + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + ].map(testCase => + itEth.ifWithPallets(`[${testCase.mode}] Cant set token property permissions as non owner or admin`, testCase.requiredPallets, async({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + const caller = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); + + await expect(collection.methods.setTokenPropertyPermissions([ + ['testKey_0', [ + [EthTokenPermissions.Mutable, true], + [EthTokenPermissions.TokenOwner, true], + [EthTokenPermissions.CollectionAdmin, true]], + ], + ]).call({from: caller})).to.be.rejectedWith('NoPermission'); + })); + + [ + {mode: 'nft' as const, requiredPallets: []}, + {mode: 'rft' as const, requiredPallets: [Pallets.ReFungible]}, + ].map(testCase => + itEth.ifWithPallets(`[${testCase.mode}] Cant set token property permissions with invalid character`, testCase.requiredPallets, async({helper}) => { + const owner = await helper.eth.createAccountWithBalance(donor); + + const {collectionAddress} = await helper.eth.createCollection(testCase.mode, owner, 'A', 'B', 'C'); + const collection = await helper.ethNativeContract.collection(collectionAddress, testCase.mode, owner); + + await expect(collection.methods.setTokenPropertyPermissions([ + // "Space" is invalid character + ['testKey 0', [ + [EthTokenPermissions.Mutable, true], + [EthTokenPermissions.TokenOwner, true], + [EthTokenPermissions.CollectionAdmin, true]], + ], + ]).call({from: owner})).to.be.rejectedWith('InvalidCharacterInPropertyKey'); + })); + }); --- a/tests/src/eth/util/playgrounds/types.ts +++ b/tests/src/eth/util/playgrounds/types.ts @@ -20,3 +20,8 @@ export type EthProperty = string[]; +export enum EthTokenPermissions { + Mutable, + TokenOwner, + CollectionAdmin +} \ No newline at end of file