difftreelog
Added `set_properties` method for `TokenProperties` interface.
in: master
19 files changed
Cargo.lockdiffbeforeafterboth--- 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",
pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth--- 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()
pallets/nonfungible/CHANGELOG.mddiffbeforeafterboth--- 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.
+<!-- bureaucrate goes here -->
+
+## [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`.
-<!-- bureaucrate goes here -->
+- 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.
pallets/nonfungible/Cargo.tomldiffbeforeafterboth--- 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"
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -114,6 +114,47 @@
.map_err(dispatch_to_evm::<T>)
}
+ /// @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(<StructureWeight<T>>::find_parent());
+
+ let properties = properties
+ .into_iter()
+ .map(|(key, value)| {
+ let key = <Vec<u8>>::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::<Result<Vec<_>>>()?;
+
+ <Pallet<T>>::set_token_properties(
+ self,
+ &caller,
+ TokenId(token_id),
+ properties.into_iter(),
+ <Pallet<T>>::token_exists(&self, TokenId(token_id)),
+ &nesting_budget,
+ )
+ .map_err(dispatch_to_evm::<T>)
+ }
+
/// @notice Delete token property value.
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- 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()
pallets/refungible/CHANGELOG.mddiffbeforeafterboth--- 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`.
+
<!-- bureaucrate goes here -->
+
## [v0.2.3] 2022-08-16
### Other changes
pallets/refungible/Cargo.tomldiffbeforeafterboth--- 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"
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -117,6 +117,47 @@
.map_err(dispatch_to_evm::<T>)
}
+ /// @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(<StructureWeight<T>>::find_parent());
+
+ let properties = properties
+ .into_iter()
+ .map(|(key, value)| {
+ let key = <Vec<u8>>::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::<Result<Vec<_>>>()?;
+
+ <Pallet<T>>::set_token_properties(
+ self,
+ &caller,
+ TokenId(token_id),
+ properties.into_iter(),
+ <Pallet<T>>::token_exists(&self, TokenId(token_id)),
+ &nesting_budget,
+ )
+ .map_err(dispatch_to_evm::<T>)
+ }
+
/// @notice Delete token property value.
/// @dev Throws error if `msg.sender` has no permission to edit the property.
/// @param tokenId ID of the token.
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- 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()
tests/src/eth/api/UniqueFungible.soldiffbeforeafterboth--- 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()
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- 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()
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- 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()
tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth--- 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" }
],
tests/src/eth/reFungibleAbi.jsondiffbeforeafterboth--- 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" }
],
tests/src/eth/tokenProperties.test.tsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect} from './util';18import {IKeyringPair} from '@polkadot/types/types';1920describe('EVM token properties', () => {21 let donor: IKeyringPair;22 let alice: IKeyringPair;2324 before(async function() {25 await usingEthPlaygrounds(async (helper, privateKey) => {26 donor = await privateKey({filename: __filename});27 [alice] = await helper.arrange.createAccounts([100n], donor);28 });29 });3031 itEth('Can be reconfigured', async({helper}) => {32 const caller = await helper.eth.createAccountWithBalance(donor);33 for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {34 const collection = await helper.nft.mintCollection(alice);35 await collection.addAdmin(alice, {Ethereum: caller});36 37 const address = helper.ethAddress.fromCollectionId(collection.collectionId);38 const contract = helper.ethNativeContract.collection(address, 'nft', caller);39 40 await contract.methods.setTokenPropertyPermission('testKey', mutable, collectionAdmin, tokenOwner).send({from: caller});41 42 expect(await collection.getPropertyPermissions()).to.be.deep.equal([{43 key: 'testKey',44 permission: {mutable, collectionAdmin, tokenOwner},45 }]);46 }47 });4849 itEth('Can be set', async({helper}) => {50 const caller = await helper.eth.createAccountWithBalance(donor);51 const collection = await helper.nft.mintCollection(alice, {52 tokenPropertyPermissions: [{53 key: 'testKey',54 permission: {55 collectionAdmin: true,56 },57 }],58 });59 const token = await collection.mintToken(alice);6061 await collection.addAdmin(alice, {Ethereum: caller});6263 const address = helper.ethAddress.fromCollectionId(collection.collectionId);64 const contract = helper.ethNativeContract.collection(address, 'nft', caller);6566 await contract.methods.setProperty(token.tokenId, 'testKey', Buffer.from('testValue')).send({from: caller});6768 const [{value}] = await token.getProperties(['testKey']);69 expect(value).to.equal('testValue');70 });7172 itEth('Can be deleted', async({helper}) => {73 const caller = await helper.eth.createAccountWithBalance(donor);74 const collection = await helper.nft.mintCollection(alice, {75 tokenPropertyPermissions: [{76 key: 'testKey',77 permission: {78 mutable: true,79 collectionAdmin: true,80 },81 }],82 });83 84 const token = await collection.mintToken(alice);85 await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);8687 await collection.addAdmin(alice, {Ethereum: caller});8889 const address = helper.ethAddress.fromCollectionId(collection.collectionId);90 const contract = helper.ethNativeContract.collection(address, 'nft', caller);9192 await contract.methods.deleteProperty(token.tokenId, 'testKey').send({from: caller});9394 const result = await token.getProperties(['testKey']);95 expect(result.length).to.equal(0);96 });9798 itEth('Can be read', async({helper}) => {99 const caller = helper.eth.createAccount();100 const collection = await helper.nft.mintCollection(alice, {101 tokenPropertyPermissions: [{102 key: 'testKey',103 permission: {104 collectionAdmin: true,105 },106 }],107 });108 109 const token = await collection.mintToken(alice);110 await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);111112 const address = helper.ethAddress.fromCollectionId(collection.collectionId);113 const contract = helper.ethNativeContract.collection(address, 'nft', caller);114115 const value = await contract.methods.property(token.tokenId, 'testKey').call();116 expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));117 });118});119120121type ElementOf<A> = A extends readonly (infer T)[] ? T : never;122function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {123 if(args.length === 0) {124 yield internalRest as any;125 return;126 }127 for(const value of args[0]) {128 yield* cartesian([...internalRest, value], ...args.slice(1)) as any;129 }130}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617import {itEth, usingEthPlaygrounds, expect} from './util';18import {IKeyringPair} from '@polkadot/types/types';19import {ITokenPropertyPermission} from '../util/playgrounds/types';2021describe('EVM token properties', () => {22 let donor: IKeyringPair;23 let alice: IKeyringPair;2425 before(async function() {26 await usingEthPlaygrounds(async (helper, privateKey) => {27 donor = await privateKey({filename: __filename});28 [alice] = await helper.arrange.createAccounts([100n], donor);29 });30 });3132 itEth('Can be reconfigured', async({helper}) => {33 const caller = await helper.eth.createAccountWithBalance(donor);34 for(const [mutable,collectionAdmin, tokenOwner] of cartesian([], [false, true], [false, true], [false, true])) {35 const collection = await helper.nft.mintCollection(alice);36 await collection.addAdmin(alice, {Ethereum: caller});37 38 const address = helper.ethAddress.fromCollectionId(collection.collectionId);39 const contract = helper.ethNativeContract.collection(address, 'nft', caller);40 41 await contract.methods.setTokenPropertyPermission('testKey', mutable, collectionAdmin, tokenOwner).send({from: caller});42 43 expect(await collection.getPropertyPermissions()).to.be.deep.equal([{44 key: 'testKey',45 permission: {mutable, collectionAdmin, tokenOwner},46 }]);47 }48 });4950 itEth('Can be set', async({helper}) => {51 const caller = await helper.eth.createAccountWithBalance(donor);52 const collection = await helper.nft.mintCollection(alice, {53 tokenPropertyPermissions: [{54 key: 'testKey',55 permission: {56 collectionAdmin: true,57 },58 }],59 });60 const token = await collection.mintToken(alice);6162 await collection.addAdmin(alice, {Ethereum: caller});6364 const address = helper.ethAddress.fromCollectionId(collection.collectionId);65 const contract = helper.ethNativeContract.collection(address, 'nft', caller);6667 await contract.methods.setProperty(token.tokenId, 'testKey', Buffer.from('testValue')).send({from: caller});6869 const [{value}] = await token.getProperties(['testKey']);70 expect(value).to.equal('testValue');71 });72 73 itEth('Can be multiple set for NFT ', async({helper}) => {74 const caller = await helper.eth.createAccountWithBalance(donor);75 76 const properties = Array(5).fill(0).map((_, i) => { return {field_0: `key_${i}`, field_1: Buffer.from(`value_${i}`)}; });77 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.field_0, permission: {tokenOwner: true,78 collectionAdmin: true,79 mutable: true}}; });80 81 const collection = await helper.nft.mintCollection(alice, {82 tokenPrefix: 'ethp',83 tokenPropertyPermissions: permissions,84 });85 86 const token = await collection.mintToken(alice);87 88 const valuesBefore = await token.getProperties(properties.map(p => p.field_0));89 expect(valuesBefore).to.be.deep.equal([]);90 91 await collection.addAdmin(alice, {Ethereum: caller});9293 const address = helper.ethAddress.fromCollectionId(collection.collectionId);94 const contract = helper.ethNativeContract.collection(address, 'nft', caller);9596 await contract.methods.setProperties(token.tokenId, properties).send({from: caller});9798 const values = await token.getProperties(properties.map(p => p.field_0));99 expect(values).to.be.deep.equal(properties.map(p => { return {key: p.field_0, value: p.field_1.toString()}; }));100 });101 102 itEth('Can be multiple set for RFT ', async({helper}) => {103 const caller = await helper.eth.createAccountWithBalance(donor);104 105 const properties = Array(5).fill(0).map((_, i) => { return {field_0: `key_${i}`, field_1: Buffer.from(`value_${i}`)}; });106 const permissions: ITokenPropertyPermission[] = properties.map(p => { return {key: p.field_0, permission: {tokenOwner: true,107 collectionAdmin: true,108 mutable: true}}; });109 110 const collection = await helper.rft.mintCollection(alice, {111 tokenPrefix: 'ethp',112 tokenPropertyPermissions: permissions,113 });114 115 const token = await collection.mintToken(alice);116 117 const valuesBefore = await token.getProperties(properties.map(p => p.field_0));118 expect(valuesBefore).to.be.deep.equal([]);119 120 await collection.addAdmin(alice, {Ethereum: caller});121122 const address = helper.ethAddress.fromCollectionId(collection.collectionId);123 const contract = helper.ethNativeContract.collection(address, 'rft', caller);124125 await contract.methods.setProperties(token.tokenId, properties).send({from: caller});126127 const values = await token.getProperties(properties.map(p => p.field_0));128 expect(values).to.be.deep.equal(properties.map(p => { return {key: p.field_0, value: p.field_1.toString()}; }));129 });130131 itEth('Can be deleted', async({helper}) => {132 const caller = await helper.eth.createAccountWithBalance(donor);133 const collection = await helper.nft.mintCollection(alice, {134 tokenPropertyPermissions: [{135 key: 'testKey',136 permission: {137 mutable: true,138 collectionAdmin: true,139 },140 }],141 });142 143 const token = await collection.mintToken(alice);144 await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);145146 await collection.addAdmin(alice, {Ethereum: caller});147148 const address = helper.ethAddress.fromCollectionId(collection.collectionId);149 const contract = helper.ethNativeContract.collection(address, 'nft', caller);150151 await contract.methods.deleteProperty(token.tokenId, 'testKey').send({from: caller});152153 const result = await token.getProperties(['testKey']);154 expect(result.length).to.equal(0);155 });156157 itEth('Can be read', async({helper}) => {158 const caller = helper.eth.createAccount();159 const collection = await helper.nft.mintCollection(alice, {160 tokenPropertyPermissions: [{161 key: 'testKey',162 permission: {163 collectionAdmin: true,164 },165 }],166 });167 168 const token = await collection.mintToken(alice);169 await token.setProperties(alice, [{key: 'testKey', value: 'testValue'}]);170171 const address = helper.ethAddress.fromCollectionId(collection.collectionId);172 const contract = helper.ethNativeContract.collection(address, 'nft', caller);173174 const value = await contract.methods.property(token.tokenId, 'testKey').call();175 expect(value).to.equal(helper.getWeb3().utils.toHex('testValue'));176 });177});178179180type ElementOf<A> = A extends readonly (infer T)[] ? T : never;181function* cartesian<T extends Array<Array<any>>, R extends Array<any>>(internalRest: [...R], ...args: [...T]): Generator<[...R, ...{[K in keyof T]: ElementOf<T[K]>}]> {182 if(args.length === 0) {183 yield internalRest as any;184 return;185 }186 for(const value of args[0]) {187 yield* cartesian([...internalRest, value], ...args.slice(1)) as any;188 }189}