From 6bf8d7b241a90f46ecc74db726d164a000bdd6c4 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 24 Nov 2022 09:56:22 +0000 Subject: [PATCH] Merge pull request #728 from UniqueNetwork/feature/newCallMethods Added new call functions --- --- a/crates/evm-coder/src/abi/impls.rs +++ b/crates/evm-coder/src/abi/impls.rs @@ -184,8 +184,7 @@ impl AbiWrite for Property { fn abi_write(&self, writer: &mut AbiWriter) { - self.key.abi_write(writer); - self.value.abi_write(writer); + (&self.key, &self.value).abi_write(writer); } } --- a/crates/evm-coder/src/abi/traits.rs +++ b/crates/evm-coder/src/abi/traits.rs @@ -49,3 +49,9 @@ Ok(writer.into()) } } + +impl AbiWrite for &T { + fn abi_write(&self, writer: &mut AbiWriter) { + T::abi_write(self, writer); + } +} --- a/pallets/common/src/erc.rs +++ b/pallets/common/src/erc.rs @@ -178,7 +178,7 @@ /// /// @param keys Properties keys. Empty keys for all propertyes. /// @return Vector of properties key/value pairs. - fn collection_properties(&self, keys: Vec) -> Result> { + fn collection_properties(&self, keys: Vec) -> Result> { let keys = keys .into_iter() .map(|key| { @@ -200,7 +200,7 @@ let key = string::from_utf8(p.key.into()).map_err(|e| Error::Revert(format!("{}", e)))?; let value = bytes(p.value.to_vec()); - Ok((key, value)) + Ok(PropertyStruct { key, value }) }) .collect::>>()?; Ok(properties) --- a/pallets/fungible/CHANGELOG.md +++ b/pallets/fungible/CHANGELOG.md @@ -4,12 +4,22 @@ +## [0.1.8] - 2022-11-18 + +### Added + +- The function `description` to `ERC20UniqueExtensions` interface. + ## [0.1.7] - 2022-11-14 ### Changed - Added `transfer_cross` in eth functions. +### Changed + +- Use named structure `EthCrossAccount` in eth functions. + ## [0.1.6] - 2022-11-02 ### Changed --- a/pallets/fungible/src/erc.rs +++ b/pallets/fungible/src/erc.rs @@ -158,6 +158,13 @@ where T::AccountId: From<[u8; 32]>, { + /// @notice A description for the collection. + fn description(&self) -> Result { + Ok(decode_utf16(self.description.iter().copied()) + .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) + .collect::()) + } + #[weight(>::approve())] fn approve_cross( &mut self, --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -87,11 +87,11 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) public view returns (Tuple16[] memory) { + function collectionProperties(string[] memory keys) public view returns (Property[] memory) { require(false, stub_error); keys; dummy; - return new Tuple16[](0); + return new Property[](0); } // /// Set the sponsor of the collection. @@ -425,20 +425,23 @@ uint256 sub; } -/// @dev anonymous struct -struct Tuple16 { - string field_0; - bytes field_1; -} - /// @dev Property struct struct Property { string key; bytes value; } -/// @dev the ERC-165 identifier for this interface is 0x29f4dcd9 +/// @dev the ERC-165 identifier for this interface is 0x5b7038cf contract ERC20UniqueExtensions is Dummy, ERC165 { + /// @notice A description for the collection. + /// @dev EVM selector for this function is: 0x7284e416, + /// or in textual repr: description() + function description() public view returns (string memory) { + require(false, stub_error); + dummy; + return ""; + } + /// @dev EVM selector for this function is: 0x0ecd0ab0, /// or in textual repr: approveCross((address,uint256),uint256) function approveCross(EthCrossAccount memory spender, uint256 amount) public returns (bool) { --- a/pallets/nonfungible/CHANGELOG.md +++ b/pallets/nonfungible/CHANGELOG.md @@ -4,6 +4,12 @@ +## [0.1.10] - 2022-11-18 + +### Added + +- The functions `description`, `crossOwnerOf`, `tokenProperties` to `ERC721UniqueExtensions` interface. + ## [0.1.9] - 2022-11-14 ### Changed --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -37,7 +37,7 @@ use sp_std::vec::Vec; use pallet_common::{ erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key}, - CollectionHandle, CollectionPropertyPermissions, + CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::call; @@ -278,7 +278,7 @@ #[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)] impl NonfungibleHandle where - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev real implementation of this function lies in `ERC721UniqueExtensions` @@ -686,7 +686,7 @@ #[solidity_interface(name = ERC721UniqueExtensions)] impl NonfungibleHandle where - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { /// @notice A descriptive name for a collection of NFTs in this contract fn name(&self) -> Result { @@ -700,6 +700,56 @@ Ok(string::from_utf8_lossy(&self.token_prefix).into()) } + /// @notice A description for the collection. + fn description(&self) -> Result { + Ok(decode_utf16(self.description.iter().copied()) + .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) + .collect::()) + } + + /// Returns the owner (in cross format) of the token. + /// + /// @param tokenId Id for the token. + fn cross_owner_of(&self, token_id: uint256) -> Result { + Self::token_owner(&self, token_id.try_into()?) + .map(|o| EthCrossAccount::from_sub_cross_account::(&o)) + .ok_or(Error::Revert("key too large".into())) + } + + /// Returns the token properties. + /// + /// @param tokenId Id for the token. + /// @param keys Properties keys. Empty keys for all propertyes. + /// @return Vector of properties key/value pairs. + fn token_properties( + &self, + token_id: uint256, + keys: Vec, + ) -> Result> { + let keys = keys + .into_iter() + .map(|key| { + >::from(key) + .try_into() + .map_err(|_| Error::Revert("key too large".into())) + }) + .collect::>>()?; + + >::token_properties( + &self, + token_id.try_into()?, + if keys.is_empty() { None } else { Some(keys) }, + ) + .into_iter() + .map(|p| { + let key = string::from_utf8(p.key.to_vec()) + .map_err(|e| Error::Revert(alloc::format!("{}", e)))?; + let value = bytes(p.value.to_vec()); + Ok(PropertyStruct { key, value }) + }) + .collect::>>() + } + /// @notice Set or reaffirm the approved address for an NFT /// @dev The zero address indicates there is no approved address. /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized --- a/pallets/nonfungible/src/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -188,11 +188,11 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) public view returns (Tuple23[] memory) { + function collectionProperties(string[] memory keys) public view returns (Property[] memory) { require(false, stub_error); keys; dummy; - return new Tuple23[](0); + return new Property[](0); } // /// Set the sponsor of the collection. @@ -253,10 +253,10 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() public view returns (Tuple26 memory) { + function collectionSponsor() public view returns (Tuple30 memory) { require(false, stub_error); dummy; - return Tuple26(0x0000000000000000000000000000000000000000, 0); + return Tuple30(0x0000000000000000000000000000000000000000, 0); } /// Set limits for the collection. @@ -527,17 +527,11 @@ } /// @dev anonymous struct -struct Tuple26 { +struct Tuple30 { address field_0; uint256 field_1; } -/// @dev anonymous struct -struct Tuple23 { - string field_0; - bytes field_1; -} - /// @title ERC-721 Non-Fungible Token Standard, optional metadata extension /// @dev See https://eips.ethereum.org/EIPS/eip-721 /// @dev the ERC-165 identifier for this interface is 0x5b5e139f @@ -682,7 +676,7 @@ } /// @title Unique extensions for ERC721. -/// @dev the ERC-165 identifier for this interface is 0x0e9fc611 +/// @dev the ERC-165 identifier for this interface is 0xb8f094a0 contract ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -702,6 +696,42 @@ return ""; } + /// @notice A description for the collection. + /// @dev EVM selector for this function is: 0x7284e416, + /// or in textual repr: description() + function description() public view returns (string memory) { + require(false, stub_error); + dummy; + return ""; + } + + /// Returns the owner (in cross format) of the token. + /// + /// @param tokenId Id for the token. + /// @dev EVM selector for this function is: 0x2b29dace, + /// or in textual repr: crossOwnerOf(uint256) + function crossOwnerOf(uint256 tokenId) public view returns (EthCrossAccount memory) { + require(false, stub_error); + tokenId; + dummy; + return EthCrossAccount(0x0000000000000000000000000000000000000000, 0); + } + + /// Returns the token properties. + /// + /// @param tokenId Id for the token. + /// @param keys Properties keys. Empty keys for all propertyes. + /// @return Vector of properties key/value pairs. + /// @dev EVM selector for this function is: 0xefc26c69, + /// or in textual repr: tokenProperties(uint256,string[]) + function tokenProperties(uint256 tokenId, string[] memory keys) public view returns (Property[] memory) { + require(false, stub_error); + tokenId; + keys; + dummy; + return new Property[](0); + } + /// @notice Set or reaffirm the approved address for an NFT /// @dev The zero address indicates there is no approved address. /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized @@ -825,7 +855,7 @@ // /// @param tokens array of pairs of token ID and token URI for minted tokens // /// @dev EVM selector for this function is: 0x36543006, // /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) - // function mintBulkWithTokenURI(address to, Tuple11[] memory tokens) public returns (bool) { + // function mintBulkWithTokenURI(address to, Tuple15[] memory tokens) public returns (bool) { // require(false, stub_error); // to; // tokens; @@ -836,7 +866,7 @@ } /// @dev anonymous struct -struct Tuple11 { +struct Tuple15 { uint256 field_0; string field_1; } --- a/pallets/refungible/CHANGELOG.md +++ b/pallets/refungible/CHANGELOG.md @@ -4,6 +4,12 @@ +## [0.2.9] - 2022-11-18 + +### Added + +- The functions `description`, `crossOwnerOf`, `tokenProperties` to `ERC721UniqueExtensions` interface. + ## [0.2.8] - 2022-11-14 ### Changed --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -33,6 +33,7 @@ use pallet_common::{ CollectionHandle, CollectionPropertyPermissions, erc::{CommonEvmHandler, CollectionCall, static_property::key}, + CommonCollectionOperations, }; use pallet_evm::{account::CrossAccountId, PrecompileHandle}; use pallet_evm_coder_substrate::{call, dispatch_to_evm}; @@ -273,7 +274,7 @@ #[solidity_interface(name = ERC721Metadata)] impl RefungibleHandle where - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev real implementation of this function lies in `ERC721UniqueExtensions` @@ -713,7 +714,7 @@ #[solidity_interface(name = ERC721UniqueExtensions)] impl RefungibleHandle where - T::AccountId: From<[u8; 32]>, + T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>, { /// @notice A descriptive name for a collection of NFTs in this contract fn name(&self) -> Result { @@ -727,6 +728,55 @@ Ok(string::from_utf8_lossy(&self.token_prefix).into()) } + /// @notice A description for the collection. + fn description(&self) -> Result { + Ok(decode_utf16(self.description.iter().copied()) + .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) + .collect::()) + } + + /// Returns the owner (in cross format) of the token. + /// + /// @param tokenId Id for the token. + fn cross_owner_of(&self, token_id: uint256) -> Result { + Self::token_owner(&self, token_id.try_into()?) + .map(|o| EthCrossAccount::from_sub_cross_account::(&o)) + .ok_or(Error::Revert("key too large".into())) + } + + /// Returns the token properties. + /// + /// @param tokenId Id for the token. + /// @param keys Properties keys. Empty keys for all propertyes. + /// @return Vector of properties key/value pairs. + fn token_properties( + &self, + token_id: uint256, + keys: Vec, + ) -> Result> { + let keys = keys + .into_iter() + .map(|key| { + >::from(key) + .try_into() + .map_err(|_| Error::Revert("key too large".into())) + }) + .collect::>>()?; + + >::token_properties( + &self, + token_id.try_into()?, + if keys.is_empty() { None } else { Some(keys) }, + ) + .into_iter() + .map(|p| { + let key = string::from_utf8(p.key.to_vec()) + .map_err(|e| Error::Revert(alloc::format!("{}", e)))?; + let value = bytes(p.value.to_vec()); + Ok(PropertyStruct { key, value }) + }) + .collect::>>() + } /// @notice Transfer ownership of an RFT /// @dev Throws unless `msg.sender` is the current owner. Throws if `to` /// is the zero address. Throws if `tokenId` is not a valid RFT. --- a/pallets/refungible/src/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -188,11 +188,11 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) public view returns (Tuple22[] memory) { + function collectionProperties(string[] memory keys) public view returns (Property[] memory) { require(false, stub_error); keys; dummy; - return new Tuple22[](0); + return new Property[](0); } // /// Set the sponsor of the collection. @@ -253,10 +253,10 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() public view returns (Tuple25 memory) { + function collectionSponsor() public view returns (Tuple29 memory) { require(false, stub_error); dummy; - return Tuple25(0x0000000000000000000000000000000000000000, 0); + return Tuple29(0x0000000000000000000000000000000000000000, 0); } /// Set limits for the collection. @@ -527,17 +527,11 @@ } /// @dev anonymous struct -struct Tuple25 { +struct Tuple29 { address field_0; uint256 field_1; } -/// @dev anonymous struct -struct Tuple22 { - string field_0; - bytes field_1; -} - /// @dev the ERC-165 identifier for this interface is 0x5b5e139f contract ERC721Metadata is Dummy, ERC165 { // /// @notice A descriptive name for a collection of NFTs in this contract @@ -680,7 +674,7 @@ } /// @title Unique extensions for ERC721. -/// @dev the ERC-165 identifier for this interface is 0xab243667 +/// @dev the ERC-165 identifier for this interface is 0x1d4b64d6 contract ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -700,6 +694,42 @@ return ""; } + /// @notice A description for the collection. + /// @dev EVM selector for this function is: 0x7284e416, + /// or in textual repr: description() + function description() public view returns (string memory) { + require(false, stub_error); + dummy; + return ""; + } + + /// Returns the owner (in cross format) of the token. + /// + /// @param tokenId Id for the token. + /// @dev EVM selector for this function is: 0x2b29dace, + /// or in textual repr: crossOwnerOf(uint256) + function crossOwnerOf(uint256 tokenId) public view returns (EthCrossAccount memory) { + require(false, stub_error); + tokenId; + dummy; + return EthCrossAccount(0x0000000000000000000000000000000000000000, 0); + } + + /// Returns the token properties. + /// + /// @param tokenId Id for the token. + /// @param keys Properties keys. Empty keys for all propertyes. + /// @return Vector of properties key/value pairs. + /// @dev EVM selector for this function is: 0xefc26c69, + /// or in textual repr: tokenProperties(uint256,string[]) + function tokenProperties(uint256 tokenId, string[] memory keys) public view returns (Property[] memory) { + require(false, stub_error); + tokenId; + keys; + dummy; + return new Property[](0); + } + /// @notice Transfer ownership of an RFT /// @dev Throws unless `msg.sender` is the current owner. Throws if `to` /// is the zero address. Throws if `tokenId` is not a valid RFT. @@ -813,7 +843,7 @@ // /// @param tokens array of pairs of token ID and token URI for minted tokens // /// @dev EVM selector for this function is: 0x36543006, // /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) - // function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) public returns (bool) { + // function mintBulkWithTokenURI(address to, Tuple14[] memory tokens) public returns (bool) { // require(false, stub_error); // to; // tokens; @@ -835,7 +865,7 @@ } /// @dev anonymous struct -struct Tuple10 { +struct Tuple14 { uint256 field_0; string field_1; } --- a/tests/src/eth/abi/fungible.json +++ b/tests/src/eth/abi/fungible.json @@ -216,10 +216,10 @@ "outputs": [ { "components": [ - { "internalType": "string", "name": "field_0", "type": "string" }, - { "internalType": "bytes", "name": "field_1", "type": "bytes" } + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } ], - "internalType": "struct Tuple16[]", + "internalType": "struct Property[]", "name": "", "type": "tuple[]" } @@ -283,6 +283,13 @@ }, { "inputs": [], + "name": "description", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], "name": "hasCollectionPendingSponsor", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "view", --- a/tests/src/eth/abi/nonFungible.json +++ b/tests/src/eth/abi/nonFungible.json @@ -246,10 +246,10 @@ "outputs": [ { "components": [ - { "internalType": "string", "name": "field_0", "type": "string" }, - { "internalType": "bytes", "name": "field_1", "type": "bytes" } + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } ], - "internalType": "struct Tuple23[]", + "internalType": "struct Property[]", "name": "", "type": "tuple[]" } @@ -273,7 +273,7 @@ { "internalType": "address", "name": "field_0", "type": "address" }, { "internalType": "uint256", "name": "field_1", "type": "uint256" } ], - "internalType": "struct Tuple26", + "internalType": "struct Tuple30", "name": "", "type": "tuple" } @@ -297,6 +297,25 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "crossOwnerOf", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } + ], + "internalType": "struct EthCrossAccount", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "string[]", "name": "keys", "type": "string[]" } ], "name": "deleteCollectionProperties", @@ -316,6 +335,13 @@ }, { "inputs": [], + "name": "description", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], "name": "finishMinting", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", @@ -641,6 +667,26 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "string[]", "name": "keys", "type": "string[]" } + ], + "name": "tokenProperties", + "outputs": [ + { + "components": [ + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } + ], + "internalType": "struct Property[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "uint256", "name": "tokenId", "type": "uint256" } ], "name": "tokenURI", --- a/tests/src/eth/abi/reFungible.json +++ b/tests/src/eth/abi/reFungible.json @@ -228,10 +228,10 @@ "outputs": [ { "components": [ - { "internalType": "string", "name": "field_0", "type": "string" }, - { "internalType": "bytes", "name": "field_1", "type": "bytes" } + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } ], - "internalType": "struct Tuple22[]", + "internalType": "struct Property[]", "name": "", "type": "tuple[]" } @@ -255,7 +255,7 @@ { "internalType": "address", "name": "field_0", "type": "address" }, { "internalType": "uint256", "name": "field_1", "type": "uint256" } ], - "internalType": "struct Tuple25", + "internalType": "struct Tuple29", "name": "", "type": "tuple" } @@ -279,6 +279,25 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" } + ], + "name": "crossOwnerOf", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "eth", "type": "address" }, + { "internalType": "uint256", "name": "sub", "type": "uint256" } + ], + "internalType": "struct EthCrossAccount", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "string[]", "name": "keys", "type": "string[]" } ], "name": "deleteCollectionProperties", @@ -298,6 +317,13 @@ }, { "inputs": [], + "name": "description", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], "name": "finishMinting", "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], "stateMutability": "nonpayable", @@ -632,6 +658,26 @@ }, { "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { "internalType": "string[]", "name": "keys", "type": "string[]" } + ], + "name": "tokenProperties", + "outputs": [ + { + "components": [ + { "internalType": "string", "name": "key", "type": "string" }, + { "internalType": "bytes", "name": "value", "type": "bytes" } + ], + "internalType": "struct Property[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "uint256", "name": "tokenId", "type": "uint256" } ], "name": "tokenURI", --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -60,7 +60,7 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) external view returns (Tuple16[] memory); + function collectionProperties(string[] memory keys) external view returns (Property[] memory); // /// Set the sponsor of the collection. // /// @@ -276,12 +276,6 @@ struct EthCrossAccount { address eth; uint256 sub; -} - -/// @dev anonymous struct -struct Tuple16 { - string field_0; - bytes field_1; } /// @dev Property struct @@ -290,8 +284,13 @@ bytes value; } -/// @dev the ERC-165 identifier for this interface is 0x29f4dcd9 +/// @dev the ERC-165 identifier for this interface is 0x5b7038cf interface ERC20UniqueExtensions is Dummy, ERC165 { + /// @notice A description for the collection. + /// @dev EVM selector for this function is: 0x7284e416, + /// or in textual repr: description() + function description() external view returns (string memory); + /// @dev EVM selector for this function is: 0x0ecd0ab0, /// or in textual repr: approveCross((address,uint256),uint256) function approveCross(EthCrossAccount memory spender, uint256 amount) external returns (bool); --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -127,7 +127,7 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) external view returns (Tuple23[] memory); + function collectionProperties(string[] memory keys) external view returns (Property[] memory); // /// Set the sponsor of the collection. // /// @@ -169,7 +169,7 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() external view returns (Tuple26 memory); + function collectionSponsor() external view returns (Tuple27 memory); /// Set limits for the collection. /// @dev Throws error if limit not found. @@ -346,15 +346,9 @@ } /// @dev anonymous struct -struct Tuple26 { +struct Tuple27 { address field_0; uint256 field_1; -} - -/// @dev anonymous struct -struct Tuple23 { - string field_0; - bytes field_1; } /// @title ERC-721 Non-Fungible Token Standard, optional metadata extension @@ -452,7 +446,7 @@ } /// @title Unique extensions for ERC721. -/// @dev the ERC-165 identifier for this interface is 0x0e9fc611 +/// @dev the ERC-165 identifier for this interface is 0xb8f094a0 interface ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -464,6 +458,27 @@ /// or in textual repr: symbol() function symbol() external view returns (string memory); + /// @notice A description for the collection. + /// @dev EVM selector for this function is: 0x7284e416, + /// or in textual repr: description() + function description() external view returns (string memory); + + /// Returns the owner (in cross format) of the token. + /// + /// @param tokenId Id for the token. + /// @dev EVM selector for this function is: 0x2b29dace, + /// or in textual repr: crossOwnerOf(uint256) + function crossOwnerOf(uint256 tokenId) external view returns (EthCrossAccount memory); + + /// Returns the token properties. + /// + /// @param tokenId Id for the token. + /// @param keys Properties keys. Empty keys for all propertyes. + /// @return Vector of properties key/value pairs. + /// @dev EVM selector for this function is: 0xefc26c69, + /// or in textual repr: tokenProperties(uint256,string[]) + function tokenProperties(uint256 tokenId, string[] memory keys) external view returns (Property[] memory); + /// @notice Set or reaffirm the approved address for an NFT /// @dev The zero address indicates there is no approved address. /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized @@ -546,12 +561,12 @@ // /// @param tokens array of pairs of token ID and token URI for minted tokens // /// @dev EVM selector for this function is: 0x36543006, // /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) - // function mintBulkWithTokenURI(address to, Tuple11[] memory tokens) external returns (bool); + // function mintBulkWithTokenURI(address to, Tuple13[] memory tokens) external returns (bool); } /// @dev anonymous struct -struct Tuple11 { +struct Tuple13 { uint256 field_0; string field_1; } --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -127,7 +127,7 @@ /// @return Vector of properties key/value pairs. /// @dev EVM selector for this function is: 0x285fb8e6, /// or in textual repr: collectionProperties(string[]) - function collectionProperties(string[] memory keys) external view returns (Tuple22[] memory); + function collectionProperties(string[] memory keys) external view returns (Property[] memory); // /// Set the sponsor of the collection. // /// @@ -169,7 +169,7 @@ /// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw. /// @dev EVM selector for this function is: 0x6ec0a9f1, /// or in textual repr: collectionSponsor() - function collectionSponsor() external view returns (Tuple25 memory); + function collectionSponsor() external view returns (Tuple26 memory); /// Set limits for the collection. /// @dev Throws error if limit not found. @@ -346,15 +346,9 @@ } /// @dev anonymous struct -struct Tuple25 { +struct Tuple26 { address field_0; uint256 field_1; -} - -/// @dev anonymous struct -struct Tuple22 { - string field_0; - bytes field_1; } /// @dev the ERC-165 identifier for this interface is 0x5b5e139f @@ -450,7 +444,7 @@ } /// @title Unique extensions for ERC721. -/// @dev the ERC-165 identifier for this interface is 0xab243667 +/// @dev the ERC-165 identifier for this interface is 0x1d4b64d6 interface ERC721UniqueExtensions is Dummy, ERC165 { /// @notice A descriptive name for a collection of NFTs in this contract /// @dev EVM selector for this function is: 0x06fdde03, @@ -462,6 +456,27 @@ /// or in textual repr: symbol() function symbol() external view returns (string memory); + /// @notice A description for the collection. + /// @dev EVM selector for this function is: 0x7284e416, + /// or in textual repr: description() + function description() external view returns (string memory); + + /// Returns the owner (in cross format) of the token. + /// + /// @param tokenId Id for the token. + /// @dev EVM selector for this function is: 0x2b29dace, + /// or in textual repr: crossOwnerOf(uint256) + function crossOwnerOf(uint256 tokenId) external view returns (EthCrossAccount memory); + + /// Returns the token properties. + /// + /// @param tokenId Id for the token. + /// @param keys Properties keys. Empty keys for all propertyes. + /// @return Vector of properties key/value pairs. + /// @dev EVM selector for this function is: 0xefc26c69, + /// or in textual repr: tokenProperties(uint256,string[]) + function tokenProperties(uint256 tokenId, string[] memory keys) external view returns (Property[] memory); + /// @notice Transfer ownership of an RFT /// @dev Throws unless `msg.sender` is the current owner. Throws if `to` /// is the zero address. Throws if `tokenId` is not a valid RFT. @@ -539,7 +554,7 @@ // /// @param tokens array of pairs of token ID and token URI for minted tokens // /// @dev EVM selector for this function is: 0x36543006, // /// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[]) - // function mintBulkWithTokenURI(address to, Tuple10[] memory tokens) external returns (bool); + // function mintBulkWithTokenURI(address to, Tuple12[] memory tokens) external returns (bool); /// Returns EVM address for refungible token /// @@ -550,7 +565,7 @@ } /// @dev anonymous struct -struct Tuple10 { +struct Tuple12 { uint256 field_0; string field_1; } --- a/tests/src/eth/base.test.ts +++ b/tests/src/eth/base.test.ts @@ -117,7 +117,7 @@ }); itEth('ERC721UniqueExtensions support', async ({helper}) => { - await checkInterface(helper, '0x0e9fc611', true, true); + await checkInterface(helper, '0xb8f094a0', true, true); }); itEth('ERC721Burnable - 0x42966c68 - support', async ({helper}) => { --- a/tests/src/eth/createFTCollection.test.ts +++ b/tests/src/eth/createFTCollection.test.ts @@ -36,9 +36,11 @@ const owner = await helper.eth.createAccountWithBalance(donor); const sponsor = await helper.eth.createAccountWithBalance(donor); const ss58Format = helper.chain.getChainProperties().ss58Format; - const {collectionId, collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Sponsor', DECIMALS, 'absolutely anything', 'ENVY'); + const description = 'absolutely anything'; + + const {collectionId, collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Sponsor', DECIMALS, description, 'ENVY'); - const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner, true); + const collection = helper.ethNativeContract.collection(collectionAddress, 'ft', owner, true); await collection.methods.setCollectionSponsor(sponsor).send(); let data = (await helper.rft.getData(collectionId))!; @@ -57,8 +59,9 @@ const owner = await helper.eth.createAccountWithBalance(donor); const sponsor = await helper.eth.createAccountWithBalance(donor); const ss58Format = helper.chain.getChainProperties().ss58Format; - const {collectionId, collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Sponsor', DECIMALS, 'absolutely anything', 'ENVY'); - + const description = 'absolutely anything'; + const {collectionId, collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Sponsor', DECIMALS, description, 'ENVY'); + const collection = helper.ethNativeContract.collection(collectionAddress, 'rft', owner); const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor); await collection.methods.setCollectionSponsorCross(sponsorCross).send(); @@ -73,6 +76,7 @@ data = (await helper.rft.getData(collectionId))!; expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); + expect(await collection.methods.description().call()).to.deep.equal(description); }); itEth('Set limits', async ({helper}) => { --- a/tests/src/eth/createNFTCollection.test.ts +++ b/tests/src/eth/createNFTCollection.test.ts @@ -28,7 +28,7 @@ }); }); - itEth('Create collection with properties', async ({helper}) => { + itEth('Create collection with properties & get desctription', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const name = 'CollectionEVM'; @@ -37,7 +37,8 @@ const baseUri = 'BaseURI'; const {collectionId, collectionAddress, events} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, name, description, prefix, baseUri); - + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft'); + expect(events).to.be.deep.equal([ { address: '0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F', @@ -56,7 +57,9 @@ expect(data.description).to.be.eq(description); expect(data.raw.tokenPrefix).to.be.eq(prefix); expect(data.raw.mode).to.be.eq('NFT'); - + + expect(await contract.methods.description().call()).to.deep.equal(description); + const options = await collection.getOptions(); expect(options.tokenPropertyPermissions).to.be.deep.equal([ { @@ -92,11 +95,12 @@ expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); }); - itEth('[cross] Set sponsorship', async ({helper}) => { + itEth('[cross] Set sponsorship & get description', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const sponsor = await helper.eth.createAccountWithBalance(donor); const ss58Format = helper.chain.getChainProperties().ss58Format; - const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC'); + const description = 'absolutely anything'; + const {collectionId, collectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', description, 'ROC'); const collection = helper.ethNativeContract.collection(collectionAddress, 'nft', owner); const sponsorCross = helper.ethCrossAccount.fromAddress(sponsor); @@ -112,6 +116,8 @@ data = (await helper.nft.getData(collectionId))!; expect(data.raw.sponsorship.Confirmed).to.be.equal(evmToAddress(sponsor, Number(ss58Format))); + + expect(await sponsorCollection.methods.description().call()).to.deep.equal(description); }); itEth('Set limits', async ({helper}) => { --- a/tests/src/eth/createRFTCollection.test.ts +++ b/tests/src/eth/createRFTCollection.test.ts @@ -53,7 +53,7 @@ - itEth('Create collection with properties', async ({helper}) => { + itEth('Create collection with properties & get description', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const name = 'CollectionEVM'; @@ -61,7 +61,8 @@ const prefix = 'token prefix'; const baseUri = 'BaseURI'; - const {collectionId} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri); + const {collectionId, collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, name, description, prefix, baseUri); + const contract = helper.ethNativeContract.collection(collectionAddress, 'nft'); const collection = helper.rft.getCollectionObject(collectionId); const data = (await collection.getData())!; @@ -71,6 +72,8 @@ expect(data.raw.tokenPrefix).to.be.eq(prefix); expect(data.raw.mode).to.be.eq('ReFungible'); + expect(await contract.methods.description().call()).to.deep.equal(description); + const options = await collection.getOptions(); expect(options.tokenPropertyPermissions).to.be.deep.equal([ { --- a/tests/src/eth/nonFungible.test.ts +++ b/tests/src/eth/nonFungible.test.ts @@ -17,6 +17,7 @@ import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util'; import {IKeyringPair} from '@polkadot/types/types'; import {Contract} from 'web3-eth-contract'; +import exp from 'constants'; describe('NFT: Information getting', () => { @@ -149,7 +150,7 @@ }); }); - itEth('Can perform mint()', async ({helper}) => { + itEth('Can perform mint() & get crossOwner()', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const receiver = helper.eth.createAccount(); @@ -166,7 +167,8 @@ expect(event.returnValues.to).to.be.equal(receiver); expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI'); - + console.log(await contract.methods.crossOwnerOf(tokenId).call()); + expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']); // TODO: this wont work right now, need release 919000 first // await helper.methods.setOffchainSchema(collectionIdAddress, 'https://offchain-service.local/token-info/{id}').send(); // const tokenUri = await contract.methods.tokenURI(nextTokenId).call(); --- a/tests/src/eth/reFungible.test.ts +++ b/tests/src/eth/reFungible.test.ts @@ -117,7 +117,7 @@ }); }); - itEth('Can perform mint()', async ({helper}) => { + itEth('Can perform mint() & crossOwnerOf()', async ({helper}) => { const owner = await helper.eth.createAccountWithBalance(donor); const receiver = helper.eth.createAccount(); const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Minty', '6', '6', ''); @@ -132,6 +132,7 @@ const tokenId = event.returnValues.tokenId; expect(tokenId).to.be.equal('1'); + expect(await contract.methods.crossOwnerOf(tokenId).call()).to.be.like([receiver, '0']); expect(await contract.methods.tokenURI(tokenId).call()).to.be.equal('Test URI'); }); --- a/tests/src/eth/tokenProperties.test.ts +++ b/tests/src/eth/tokenProperties.test.ts @@ -14,10 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Unique Network. If not, see . -import {itEth, usingEthPlaygrounds, expect} from './util'; +import {itEth, usingEthPlaygrounds, expect, EthUniqueHelper} from './util'; import {IKeyringPair} from '@polkadot/types/types'; -import {ITokenPropertyPermission} from '../util/playgrounds/types'; +import {ITokenPropertyPermission, TCollectionMode} from '../util/playgrounds/types'; import {Pallets} from '../util'; +import {UniqueNFTCollection, UniqueRFTCollection} from '../util/playgrounds/unique'; describe('EVM token properties', () => { let donor: IKeyringPair; @@ -95,7 +96,7 @@ expect(value).to.equal('testValue'); }); - itEth('Can be multiple set for NFT ', async({helper}) => { + async function checkProps(helper: EthUniqueHelper, mode: TCollectionMode) { const caller = await helper.eth.createAccountWithBalance(donor); const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; }); @@ -103,56 +104,44 @@ collectionAdmin: true, mutable: true}}; }); - const collection = await helper.nft.mintCollection(alice, { + const collection = await helper[mode].mintCollection(alice, { tokenPrefix: 'ethp', tokenPropertyPermissions: permissions, - }); + }) as UniqueNFTCollection | UniqueRFTCollection; const token = await collection.mintToken(alice); const valuesBefore = await token.getProperties(properties.map(p => p.key)); expect(valuesBefore).to.be.deep.equal([]); + await collection.addAdmin(alice, {Ethereum: caller}); - + const address = helper.ethAddress.fromCollectionId(collection.collectionId); - const contract = helper.ethNativeContract.collection(address, 'nft', caller); + const contract = helper.ethNativeContract.collection(address, mode, caller); + + expect(await contract.methods.tokenProperties(token.tokenId, []).call()).to.be.deep.equal([]); await contract.methods.setProperties(token.tokenId, properties).send({from: caller}); const values = await token.getProperties(properties.map(p => p.key)); expect(values).to.be.deep.equal(properties.map(p => { return {key: p.key, value: p.value.toString()}; })); - }); - - itEth.ifWithPallets('Can be multiple set for RFT ', [Pallets.ReFungible], async({helper}) => { - const caller = await helper.eth.createAccountWithBalance(donor); - const properties = Array(5).fill(0).map((_, i) => { return {key: `key_${i}`, value: Buffer.from(`value_${i}`)}; }); - const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.key, permission: {tokenOwner: true, - collectionAdmin: true, - mutable: true}}; }); - - const collection = await helper.rft.mintCollection(alice, { - tokenPrefix: 'ethp', - tokenPropertyPermissions: permissions, - }); - - const token = await collection.mintToken(alice); - - const valuesBefore = await token.getProperties(properties.map(p => p.key)); - expect(valuesBefore).to.be.deep.equal([]); + expect(await contract.methods.tokenProperties(token.tokenId, []).call()).to.be.like(properties + .map(p => { return helper.ethProperty.property(p.key, p.value.toString()); })); - await collection.addAdmin(alice, {Ethereum: caller}); - - const address = helper.ethAddress.fromCollectionId(collection.collectionId); - const contract = helper.ethNativeContract.collection(address, 'rft', caller); - - await contract.methods.setProperties(token.tokenId, properties).send({from: caller}); - - const values = await token.getProperties(properties.map(p => p.key)); - expect(values).to.be.deep.equal(properties.map(p => { return {key: p.key, value: p.value.toString()}; })); + expect(await contract.methods.tokenProperties(token.tokenId, [properties[0].key]).call()) + .to.be.like([helper.ethProperty.property(properties[0].key, properties[0].value.toString())]); + } + + itEth('Can be multiple set/read for NFT ', async({helper}) => { + await checkProps(helper, 'nft'); + }); + + itEth.ifWithPallets('Can be multiple set/read for RFT ', [Pallets.ReFungible], async({helper}) => { + await checkProps(helper, 'rft'); }); - + itEth('Can be deleted', async({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor); const collection = await helper.nft.mintCollection(alice, { -- gitstuff