--- a/Cargo.lock +++ b/Cargo.lock @@ -6358,7 +6358,7 @@ [[package]] name = "pallet-nonfungible" -version = "0.1.5" +version = "0.1.6" dependencies = [ "ethereum", "evm-coder", @@ -6480,7 +6480,7 @@ [[package]] name = "pallet-refungible" -version = "0.2.4" +version = "0.2.5" dependencies = [ "derivative", "ethereum", --- a/pallets/fungible/src/stubs/UniqueFungible.sol +++ b/pallets/fungible/src/stubs/UniqueFungible.sol @@ -385,7 +385,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() --- a/pallets/nonfungible/CHANGELOG.md +++ b/pallets/nonfungible/CHANGELOG.md @@ -2,12 +2,20 @@ All notable changes to this project will be documented in this file. + + +## [v0.1.6] - 2022-20-10 + +### Change + +- Added `set_properties` method for `TokenProperties` interface. + ## [v0.1.5] - 2022-08-24 ### Change - - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`. - +- Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`. + ## [v0.1.4] 2022-08-16 ### Other changes @@ -28,7 +36,9 @@ - build: Upgrade polkadot to v0.9.25 cdfb9bdc7b205ff1b5134f034ef9973d769e5e6b ## [0.1.2] - 2022-07-25 + ### Changed + - New `token_uri` retrieval logic: If the collection has a `url` property and it is not empty, it is returned. @@ -39,8 +49,9 @@ otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings). ## [0.1.1] - 2022-07-14 + ### Added - Implementation of RPC method `token_owners`. - For reasons of compatibility with this pallet, returns only one owner if token exists. - This was an internal request to improve the web interface and support fractionalization event. + For reasons of compatibility with this pallet, returns only one owner if token exists. + This was an internal request to improve the web interface and support fractionalization event. --- a/pallets/nonfungible/Cargo.toml +++ b/pallets/nonfungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-nonfungible" -version = "0.1.5" +version = "0.1.6" license = "GPLv3" edition = "2021" --- a/pallets/nonfungible/src/erc.rs +++ b/pallets/nonfungible/src/erc.rs @@ -114,6 +114,47 @@ .map_err(dispatch_to_evm::) } + /// @notice Set token properties value. + /// @dev Throws error if `msg.sender` has no permission to edit the property. + /// @param tokenId ID of the token. + /// @param properties settable properties + fn set_properties( + &mut self, + caller: caller, + token_id: uint256, + properties: Vec<(string, bytes)>, + ) -> Result<()> { + let caller = T::CrossAccountId::from_eth(caller); + let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?; + + let nesting_budget = self + .recorder + .weight_calls_budget(>::find_parent()); + + let properties = properties + .into_iter() + .map(|(key, value)| { + let key = >::from(key) + .try_into() + .map_err(|_| "key too large")?; + + let value = value.0.try_into().map_err(|_| "value too large")?; + + Ok(Property { key, value }) + }) + .collect::>>()?; + + >::set_token_properties( + self, + &caller, + TokenId(token_id), + properties.into_iter(), + >::token_exists(&self, TokenId(token_id)), + &nesting_budget, + ) + .map_err(dispatch_to_evm::) + } + /// @notice Delete 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/stubs/UniqueNFT.sol +++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol @@ -18,7 +18,7 @@ } /// @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 0x41369377 +/// @dev the ERC-165 identifier for this interface is 0x55dba919 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. @@ -61,6 +61,19 @@ dummy = 0; } + /// @notice Set token properties value. + /// @dev Throws error if `msg.sender` has no permission to edit the property. + /// @param tokenId ID of the token. + /// @param properties settable properties + /// @dev EVM selector for this function is: 0x14ed3a6e, + /// or in textual repr: setProperties(uint256,(string,bytes)[]) + function setProperties(uint256 tokenId, Tuple19[] memory properties) public { + require(false, stub_error); + tokenId; + properties; + dummy = 0; + } + /// @notice Delete token property value. /// @dev Throws error if `msg.sender` has no permission to edit the property. /// @param tokenId ID of the token. @@ -458,7 +471,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() --- a/pallets/refungible/CHANGELOG.md +++ b/pallets/refungible/CHANGELOG.md @@ -2,12 +2,20 @@ All notable changes to this project will be documented in this file. +## [v0.2.5] - 2022-20-10 + +### Change + +- Added `set_properties` method for `TokenProperties` interface. + ## [v0.2.4] - 2022-08-24 ### Change - - Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`. +- Add bound `AsRef<[u8; 32]>` to `T::CrossAccountId`. + + ## [v0.2.3] 2022-08-16 ### Other changes --- a/pallets/refungible/Cargo.toml +++ b/pallets/refungible/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-refungible" -version = "0.2.4" +version = "0.2.5" license = "GPLv3" edition = "2021" --- a/pallets/refungible/src/erc.rs +++ b/pallets/refungible/src/erc.rs @@ -117,6 +117,47 @@ .map_err(dispatch_to_evm::) } + /// @notice Set token properties value. + /// @dev Throws error if `msg.sender` has no permission to edit the property. + /// @param tokenId ID of the token. + /// @param properties settable properties + fn set_properties( + &mut self, + caller: caller, + token_id: uint256, + properties: Vec<(string, bytes)>, + ) -> Result<()> { + let caller = T::CrossAccountId::from_eth(caller); + let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?; + + let nesting_budget = self + .recorder + .weight_calls_budget(>::find_parent()); + + let properties = properties + .into_iter() + .map(|(key, value)| { + let key = >::from(key) + .try_into() + .map_err(|_| "key too large")?; + + let value = value.0.try_into().map_err(|_| "value too large")?; + + Ok(Property { key, value }) + }) + .collect::>>()?; + + >::set_token_properties( + self, + &caller, + TokenId(token_id), + properties.into_iter(), + >::token_exists(&self, TokenId(token_id)), + &nesting_budget, + ) + .map_err(dispatch_to_evm::) + } + /// @notice Delete 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/stubs/UniqueRefungible.sol +++ b/pallets/refungible/src/stubs/UniqueRefungible.sol @@ -18,7 +18,7 @@ } /// @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 0x41369377 +/// @dev the ERC-165 identifier for this interface is 0x55dba919 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. @@ -61,6 +61,19 @@ dummy = 0; } + /// @notice Set token properties value. + /// @dev Throws error if `msg.sender` has no permission to edit the property. + /// @param tokenId ID of the token. + /// @param properties settable properties + /// @dev EVM selector for this function is: 0x14ed3a6e, + /// or in textual repr: setProperties(uint256,(string,bytes)[]) + function setProperties(uint256 tokenId, Tuple19[] memory properties) public { + require(false, stub_error); + tokenId; + properties; + dummy = 0; + } + /// @notice Delete token property value. /// @dev Throws error if `msg.sender` has no permission to edit the property. /// @param tokenId ID of the token. @@ -458,7 +471,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() --- a/tests/src/eth/api/UniqueFungible.sol +++ b/tests/src/eth/api/UniqueFungible.sol @@ -249,7 +249,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() --- a/tests/src/eth/api/UniqueNFT.sol +++ b/tests/src/eth/api/UniqueNFT.sol @@ -13,7 +13,7 @@ } /// @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 0x41369377 +/// @dev the ERC-165 identifier for this interface is 0x55dba919 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. @@ -43,6 +43,14 @@ bytes memory value ) external; + /// @notice Set token properties value. + /// @dev Throws error if `msg.sender` has no permission to edit the property. + /// @param tokenId ID of the token. + /// @param properties settable properties + /// @dev EVM selector for this function is: 0x14ed3a6e, + /// or in textual repr: setProperties(uint256,(string,bytes)[]) + function setProperties(uint256 tokenId, Tuple19[] memory properties) external; + /// @notice Delete token property value. /// @dev Throws error if `msg.sender` has no permission to edit the property. /// @param tokenId ID of the token. @@ -298,7 +306,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() --- a/tests/src/eth/api/UniqueRefungible.sol +++ b/tests/src/eth/api/UniqueRefungible.sol @@ -13,7 +13,7 @@ } /// @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 0x41369377 +/// @dev the ERC-165 identifier for this interface is 0x55dba919 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. @@ -43,6 +43,14 @@ bytes memory value ) external; + /// @notice Set token properties value. + /// @dev Throws error if `msg.sender` has no permission to edit the property. + /// @param tokenId ID of the token. + /// @param properties settable properties + /// @dev EVM selector for this function is: 0x14ed3a6e, + /// or in textual repr: setProperties(uint256,(string,bytes)[]) + function setProperties(uint256 tokenId, Tuple19[] memory properties) external; + /// @notice Delete token property value. /// @dev Throws error if `msg.sender` has no permission to edit the property. /// @param tokenId ID of the token. @@ -298,7 +306,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() --- a/tests/src/eth/nonFungibleAbi.json +++ b/tests/src/eth/nonFungibleAbi.json @@ -677,6 +677,24 @@ { "inputs": [ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { + "components": [ + { "internalType": "string", "name": "field_0", "type": "string" }, + { "internalType": "bytes", "name": "field_1", "type": "bytes" } + ], + "internalType": "struct Tuple19[]", + "name": "properties", + "type": "tuple[]" + } + ], + "name": "setProperties", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "key", "type": "string" }, { "internalType": "bytes", "name": "value", "type": "bytes" } ], --- a/tests/src/eth/reFungibleAbi.json +++ b/tests/src/eth/reFungibleAbi.json @@ -659,6 +659,24 @@ { "inputs": [ { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, + { + "components": [ + { "internalType": "string", "name": "field_0", "type": "string" }, + { "internalType": "bytes", "name": "field_1", "type": "bytes" } + ], + "internalType": "struct Tuple19[]", + "name": "properties", + "type": "tuple[]" + } + ], + "name": "setProperties", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "tokenId", "type": "uint256" }, { "internalType": "string", "name": "key", "type": "string" }, { "internalType": "bytes", "name": "value", "type": "bytes" } ], --- a/tests/src/eth/tokenProperties.test.ts +++ b/tests/src/eth/tokenProperties.test.ts @@ -16,6 +16,7 @@ import {itEth, usingEthPlaygrounds, expect} from './util'; import {IKeyringPair} from '@polkadot/types/types'; +import {ITokenPropertyPermission} from '../util/playgrounds/types'; describe('EVM token properties', () => { let donor: IKeyringPair; @@ -68,6 +69,64 @@ const [{value}] = await token.getProperties(['testKey']); expect(value).to.equal('testValue'); }); + + itEth('Can be multiple set for NFT ', async({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + + const properties = Array(5).fill(0).map((_, i) => { return {field_0: `key_${i}`, field_1: Buffer.from(`value_${i}`)}; }); + const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.field_0, permission: {tokenOwner: true, + collectionAdmin: true, + mutable: true}}; }); + + const collection = await helper.nft.mintCollection(alice, { + tokenPrefix: 'ethp', + tokenPropertyPermissions: permissions, + }); + + const token = await collection.mintToken(alice); + + const valuesBefore = await token.getProperties(properties.map(p => p.field_0)); + 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); + + await contract.methods.setProperties(token.tokenId, properties).send({from: caller}); + + const values = await token.getProperties(properties.map(p => p.field_0)); + expect(values).to.be.deep.equal(properties.map(p => { return {key: p.field_0, value: p.field_1.toString()}; })); + }); + + itEth('Can be multiple set for RFT ', async({helper}) => { + const caller = await helper.eth.createAccountWithBalance(donor); + + const properties = Array(5).fill(0).map((_, i) => { return {field_0: `key_${i}`, field_1: Buffer.from(`value_${i}`)}; }); + const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.field_0, 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.field_0)); + 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, 'rft', caller); + + await contract.methods.setProperties(token.tokenId, properties).send({from: caller}); + + const values = await token.getProperties(properties.map(p => p.field_0)); + expect(values).to.be.deep.equal(properties.map(p => { return {key: p.field_0, value: p.field_1.toString()}; })); + }); itEth('Can be deleted', async({helper}) => { const caller = await helper.eth.createAccountWithBalance(donor);