git.delta.rocks / unique-network / refs/commits / 448629b28a12

difftreelog

Merge pull request #986 from UniqueNetwork/feature/add_mint_bulk_cross

Yaroslav Bolyukin2023-09-25parents: #6486f2a #752e2b0.patch.diff
in: master
Add mintBulkCross to NFT and RFT collections

20 files changed

modifiednode/cli/src/command.rsdiffbeforeafterboth
425 .map(|cfg| &cfg.registry);425 .map(|cfg| &cfg.registry);
426 let task_manager =426 let task_manager =
427 sc_service::TaskManager::new(runner.config().tokio_handle.clone(), *registry)427 sc_service::TaskManager::new(runner.config().tokio_handle.clone(), *registry)
428 .map_err(|e| format!("Error: {:?}", e))?;428 .map_err(|e| format!("Error: {e:?}"))?;
429 let info_provider = Some(timestamp_with_aura_info(12000));429 let info_provider = Some(timestamp_with_aura_info(12000));
430430
431 runner.async_run(|config| -> Result<(Pin<Box<dyn Future<Output = _>>>, _)> {431 runner.async_run(|config| -> Result<(Pin<Box<dyn Future<Output = _>>>, _)> {
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
119 } else if self.sub == Default::default() {119 } else if self.sub == Default::default() {
120 Ok(Some(T::CrossAccountId::from_eth(self.eth)))120 Ok(Some(T::CrossAccountId::from_eth(self.eth)))
121 } else {121 } else {
122 Err(format!("All fields of cross account is non zeroed {:?}", self).into())122 Err(format!("All fields of cross account is non zeroed {self:?}").into())
123 }123 }
124 }124 }
125125
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
26 char::{REPLACEMENT_CHARACTER, decode_utf16},26 char::{REPLACEMENT_CHARACTER, decode_utf16},
27 convert::TryInto,27 convert::TryInto,
28};28};
29use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};29use evm_coder::{abi::AbiType, AbiCoder, ToLog, generate_stubgen, solidity_interface, types::*};
30use frame_support::BoundedVec;30use frame_support::BoundedVec;
31use up_data_structs::{31use up_data_structs::{
32 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,32 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,
64 },64 },
65}65}
66
67/// Token minting parameters
68#[derive(AbiCoder, Default, Debug)]
69pub struct MintTokenData {
70 /// Minted token owner
71 pub owner: eth::CrossAddress,
72 /// Minted token properties
73 pub properties: Vec<eth::Property>,
74}
6675
67frontier_contract! {76frontier_contract! {
68 macro_rules! NonfungibleHandle_result {...}77 macro_rules! NonfungibleHandle_result {...}
981 Ok(true)990 Ok(true)
982 }991 }
992
993 /// @notice Function to mint a token.
994 /// @param data Array of pairs of token owner and token's properties for minted token
995 #[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]
996 fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {
997 let caller = T::CrossAccountId::from_eth(caller);
998 let budget = self
999 .recorder
1000 .weight_calls_budget(<StructureWeight<T>>::find_parent());
1001
1002 let mut create_nft_data = Vec::with_capacity(data.len());
1003 for MintTokenData { owner, properties } in data {
1004 let owner = owner.into_sub_cross_account::<T>()?;
1005 create_nft_data.push(CreateItemData::<T> {
1006 properties: properties
1007 .into_iter()
1008 .map(|property| property.try_into())
1009 .collect::<Result<Vec<_>>>()?
1010 .try_into()
1011 .map_err(|_| "too many properties")?,
1012 owner,
1013 });
1014 }
1015
1016 <Pallet<T>>::create_multiple_items(self, &caller, create_nft_data, &budget)
1017 .map_err(dispatch_to_evm::<T>)?;
1018 Ok(true)
1019 }
9831020
984 /// @notice Function to mint multiple tokens with the given tokenUris.1021 /// @notice Function to mint multiple tokens with the given tokenUris.
985 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1022 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
800}800}
801801
802/// @title Unique extensions for ERC721.802/// @title Unique extensions for ERC721.
803/// @dev the ERC-165 identifier for this interface is 0x307b061a803/// @dev the ERC-165 identifier for this interface is 0x9b397d16
804contract ERC721UniqueExtensions is Dummy, ERC165 {804contract ERC721UniqueExtensions is Dummy, ERC165 {
805 /// @notice A descriptive name for a collection of NFTs in this contract805 /// @notice A descriptive name for a collection of NFTs in this contract
806 /// @dev EVM selector for this function is: 0x06fdde03,806 /// @dev EVM selector for this function is: 0x06fdde03,
997 // return false;997 // return false;
998 // }998 // }
999
1000 /// @notice Function to mint a token.
1001 /// @param data Array of pairs of token owner and token's properties for minted token
1002 /// @dev EVM selector for this function is: 0xab427b0c,
1003 /// or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
1004 function mintBulkCross(MintTokenData[] memory data) public returns (bool) {
1005 require(false, stub_error);
1006 data;
1007 dummy = 0;
1008 return false;
1009 }
9991010
1000 // /// @notice Function to mint multiple tokens with the given tokenUris.1011 // /// @notice Function to mint multiple tokens with the given tokenUris.
1001 // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1012 // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
1044 string uri;1055 string uri;
1045}1056}
1057
1058/// Token minting parameters
1059struct MintTokenData {
1060 /// Minted token owner
1061 CrossAddress owner;
1062 /// Minted token properties
1063 Property[] properties;
1064}
10461065
1047/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension1066/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
1048/// @dev See https://eips.ethereum.org/EIPS/eip-7211067/// @dev See https://eips.ethereum.org/EIPS/eip-721
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
26 char::{REPLACEMENT_CHARACTER, decode_utf16},26 char::{REPLACEMENT_CHARACTER, decode_utf16},
27 convert::TryInto,27 convert::TryInto,
28};28};
29use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};29use evm_coder::{abi::AbiType, AbiCoder, ToLog, generate_stubgen, solidity_interface, types::*};
30use frame_support::{BoundedBTreeMap, BoundedVec};30use frame_support::{BoundedBTreeMap, BoundedVec};
31use pallet_common::{31use pallet_common::{
32 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,32 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
71 },71 },
72}72}
73
74/// Token minting parameters
75#[derive(AbiCoder, Default, Debug)]
76pub struct OwnerPieces {
77 /// Minted token owner
78 pub owner: eth::CrossAddress,
79 /// Number of token pieces
80 pub pieces: u128,
81}
82
83/// Token minting parameters
84#[derive(AbiCoder, Default, Debug)]
85pub struct MintTokenData {
86 /// Minted token owner and number of pieces
87 pub owners: Vec<OwnerPieces>,
88 /// Minted token properties
89 pub properties: Vec<eth::Property>,
90}
7391
74/// @title A contract that allows to set and delete token properties and change token property permissions.92/// @title A contract that allows to set and delete token properties and change token property permissions.
75#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]93#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]
1021 Ok(true)1039 Ok(true)
1022 }1040 }
1041
1042 /// @notice Function to mint a token.
1043 /// @param tokenProperties Properties of minted token
1044 #[weight(if token_properties.len() == 1 {
1045 <SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(token_properties.iter().next().unwrap().owners.len() as u32)
1046 } else {
1047 <SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(token_properties.len() as u32)
1048 } + <SelfWeightOf<T>>::set_token_properties(token_properties.len() as u32))]
1049 fn mint_bulk_cross(
1050 &mut self,
1051 caller: Caller,
1052 token_properties: Vec<MintTokenData>,
1053 ) -> Result<bool> {
1054 let caller = T::CrossAccountId::from_eth(caller);
1055 let budget = self
1056 .recorder
1057 .weight_calls_budget(<StructureWeight<T>>::find_parent());
1058 let has_multiple_tokens = token_properties.len() > 1;
1059
1060 let mut create_rft_data = Vec::with_capacity(token_properties.len());
1061 for MintTokenData { owners, properties } in token_properties {
1062 let has_multiple_owners = owners.len() > 1;
1063 if has_multiple_tokens & has_multiple_owners {
1064 return Err(
1065 "creation of multiple tokens supported only if they have single owner each"
1066 .into(),
1067 );
1068 }
1069 let users: BoundedBTreeMap<_, _, _> = owners
1070 .into_iter()
1071 .map(|data| Ok((data.owner.into_sub_cross_account::<T>()?, data.pieces)))
1072 .collect::<Result<BTreeMap<_, _>>>()?
1073 .try_into()
1074 .map_err(|_| "too many users")?;
1075 create_rft_data.push(CreateItemData::<T> {
1076 properties: properties
1077 .into_iter()
1078 .map(|property| property.try_into())
1079 .collect::<Result<Vec<_>>>()?
1080 .try_into()
1081 .map_err(|_| "too many properties")?,
1082 users,
1083 });
1084 }
1085
1086 <Pallet<T>>::create_multiple_items(self, &caller, create_rft_data, &budget)
1087 .map_err(dispatch_to_evm::<T>)?;
1088 Ok(true)
1089 }
10231090
1024 /// @notice Function to mint multiple tokens with the given tokenUris.1091 /// @notice Function to mint multiple tokens with the given tokenUris.
1025 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1092 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
800}800}
801801
802/// @title Unique extensions for ERC721.802/// @title Unique extensions for ERC721.
803/// @dev the ERC-165 identifier for this interface is 0x95c0f66c803/// @dev the ERC-165 identifier for this interface is 0x4abaabdb
804contract ERC721UniqueExtensions is Dummy, ERC165 {804contract ERC721UniqueExtensions is Dummy, ERC165 {
805 /// @notice A descriptive name for a collection of NFTs in this contract805 /// @notice A descriptive name for a collection of NFTs in this contract
806 /// @dev EVM selector for this function is: 0x06fdde03,806 /// @dev EVM selector for this function is: 0x06fdde03,
986 // return false;986 // return false;
987 // }987 // }
988
989 /// @notice Function to mint a token.
990 /// @param tokenProperties Properties of minted token
991 /// @dev EVM selector for this function is: 0xdf7a5db7,
992 /// or in textual repr: mintBulkCross((((address,uint256),uint128)[],(string,bytes)[])[])
993 function mintBulkCross(MintTokenData[] memory tokenProperties) public returns (bool) {
994 require(false, stub_error);
995 tokenProperties;
996 dummy = 0;
997 return false;
998 }
988999
989 // /// @notice Function to mint multiple tokens with the given tokenUris.1000 // /// @notice Function to mint multiple tokens with the given tokenUris.
990 // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1001 // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
1045 string uri;1056 string uri;
1046}1057}
1058
1059/// Token minting parameters
1060struct MintTokenData {
1061 /// Minted token owner and number of pieces
1062 OwnerPieces[] owners;
1063 /// Minted token properties
1064 Property[] properties;
1065}
1066
1067/// Token minting parameters
1068struct OwnerPieces {
1069 /// Minted token owner
1070 CrossAddress owner;
1071 /// Number of token pieces
1072 uint128 pieces;
1073}
10471074
1048/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension1075/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
1049/// @dev See https://eips.ethereum.org/EIPS/eip-7211076/// @dev See https://eips.ethereum.org/EIPS/eip-721
modifiedpallets/scheduler-v2/src/benchmarking.rsdiffbeforeafterboth
136 let bound = EncodedCall::bound() as u32;136 let bound = EncodedCall::bound() as u32;
137 let mut len = match maybe_lookup_len {137 let mut len = match maybe_lookup_len {
138 Some(len) => {138 Some(len) => {
139 len.min(<T::Preimages as PreimageRecipient<T::Hash>>::MaxSize::get() - 2)139 len.clamp(
140 bound,
141 <T::Preimages as PreimageRecipient<T::Hash>>::MaxSize::get() - 2,
140 .max(bound) - 3142 ) - 3
141 }143 }
142 None => bound.saturating_sub(4),144 None => bound.saturating_sub(4),
143 };145 };
modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth
25}25}
2626
27/// @title Contract, which allows users to operate with collections27/// @title Contract, which allows users to operate with collections
28/// @dev the ERC-165 identifier for this interface is 0x4135fff128/// @dev the ERC-165 identifier for this interface is 0x94e5af0d
29contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {29contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
30 /// Create a collection30 /// Create a collection
31 /// @return address Address of the newly created collection31 /// @return address Address of the newly created collection
32 /// @dev EVM selector for this function is: 0xa765ee5b,32 /// @dev EVM selector for this function is: 0x72b5bea7,
33 /// or in textual repr: createCollection(((address,uint256),string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],uint8))33 /// or in textual repr: createCollection((string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],(address,uint256),uint8))
34 function createCollection(CreateCollectionData memory data) public payable returns (address) {34 function createCollection(CreateCollectionData memory data) public payable returns (address) {
35 require(false, stub_error);35 require(false, stub_error);
36 data;36 data;
170170
171/// Collection properties171/// Collection properties
172struct CreateCollectionData {172struct CreateCollectionData {
173 /// Collection sponsor
174 CrossAddress pending_sponsor;
175 /// Collection name173 /// Collection name
176 string name;174 string name;
177 /// Collection description175 /// Collection description
192 CollectionNestingAndPermission nesting_settings;190 CollectionNestingAndPermission nesting_settings;
193 /// Collection limits191 /// Collection limits
194 CollectionLimitValue[] limits;192 CollectionLimitValue[] limits;
193 /// Collection sponsor
194 CrossAddress pending_sponsor;
195 /// Extra collection flags195 /// Extra collection flags
196 CollectionFlags flags;196 CollectionFlags flags;
197}197}
198198
199/// Cross account struct
200type CollectionFlags is uint8;199type CollectionFlags is uint8;
201200
202library CollectionFlagsLib {201library CollectionFlagsLib {
207 /// External collections can't be managed using `unique` api206 /// External collections can't be managed using `unique` api
208 CollectionFlags constant externalField = CollectionFlags.wrap(1);207 CollectionFlags constant externalField = CollectionFlags.wrap(1);
209208
210 /// Reserved bits209 /// Reserved flags
211 function reservedField(uint8 value) public pure returns (CollectionFlags) {210 function reservedField(uint8 value) public pure returns (CollectionFlags) {
212 require(value < 1 << 5, "out of bound value");211 require(value < 1 << 5, "out of bound value");
213 return CollectionFlags.wrap(value << 1);212 return CollectionFlags.wrap(value << 1);
214 }213 }
215}214}
215
216/// Cross account struct
217struct CrossAddress {
218 address eth;
219 uint256 sub;
220}
216221
217/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.222/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
218struct CollectionLimitValue {223struct CollectionLimitValue {
252 address[] restricted;257 address[] restricted;
253}258}
254
255/// Cross account struct
256struct CrossAddress {
257 address eth;
258 uint256 sub;
259}
260259
261/// Ethereum representation of Token Property Permissions.260/// Ethereum representation of Token Property Permissions.
262struct TokenPropertyPermission {261struct TokenPropertyPermission {
292291
293/// Type of tokens in collection292/// Type of tokens in collection
294enum CollectionMode {293enum CollectionMode {
294 /// Nonfungible
295 Nonfungible,
295 /// Fungible296 /// Fungible
296 Fungible,297 Fungible,
297 /// Nonfungible
298 Nonfungible,
299 /// Refungible298 /// Refungible
300 Refungible299 Refungible
301}300}
modifiedruntime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth
242 BurnFrom { .. }242 BurnFrom { .. }
243 | BurnFromCross { .. }243 | BurnFromCross { .. }
244 | MintBulk { .. }244 | MintBulk { .. }
245 | MintBulkCross { .. }
245 | MintBulkWithTokenUri { .. } => None,246 | MintBulkWithTokenUri { .. } => None,
246247
247 MintCross { .. } => withdraw_create_item::<T>(248 MintCross { .. } => withdraw_create_item::<T>(
modifiedruntime/common/tests/mod.rsdiffbeforeafterboth
33const PARA_ID: u32 = 2037;33const PARA_ID: u32 = 2037;
3434
35fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {35fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
36 TPublic::Pair::from_string(&format!("//{}", seed), None)36 TPublic::Pair::from_string(&format!("//{seed}"), None)
37 .expect("static values are valid; qed")37 .expect("static values are valid; qed")
38 .public()38 .public()
39}39}
modifiedtests/src/eth/abi/nonFungible.jsondiffbeforeafterboth
465 "stateMutability": "nonpayable",465 "stateMutability": "nonpayable",
466 "type": "function"466 "type": "function"
467 },467 },
468 {
469 "inputs": [
470 {
471 "components": [
472 {
473 "components": [
474 { "internalType": "address", "name": "eth", "type": "address" },
475 { "internalType": "uint256", "name": "sub", "type": "uint256" }
476 ],
477 "internalType": "struct CrossAddress",
478 "name": "owner",
479 "type": "tuple"
480 },
481 {
482 "components": [
483 { "internalType": "string", "name": "key", "type": "string" },
484 { "internalType": "bytes", "name": "value", "type": "bytes" }
485 ],
486 "internalType": "struct Property[]",
487 "name": "properties",
488 "type": "tuple[]"
489 }
490 ],
491 "internalType": "struct MintTokenData[]",
492 "name": "data",
493 "type": "tuple[]"
494 }
495 ],
496 "name": "mintBulkCross",
497 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
498 "stateMutability": "nonpayable",
499 "type": "function"
500 },
468 {501 {
469 "inputs": [502 "inputs": [
470 {503 {
modifiedtests/src/eth/abi/reFungible.jsondiffbeforeafterboth
447 "stateMutability": "nonpayable",447 "stateMutability": "nonpayable",
448 "type": "function"448 "type": "function"
449 },449 },
450 {
451 "inputs": [
452 {
453 "components": [
454 {
455 "components": [
456 {
457 "components": [
458 {
459 "internalType": "address",
460 "name": "eth",
461 "type": "address"
462 },
463 {
464 "internalType": "uint256",
465 "name": "sub",
466 "type": "uint256"
467 }
468 ],
469 "internalType": "struct CrossAddress",
470 "name": "owner",
471 "type": "tuple"
472 },
473 { "internalType": "uint128", "name": "pieces", "type": "uint128" }
474 ],
475 "internalType": "struct OwnerPieces[]",
476 "name": "owners",
477 "type": "tuple[]"
478 },
479 {
480 "components": [
481 { "internalType": "string", "name": "key", "type": "string" },
482 { "internalType": "bytes", "name": "value", "type": "bytes" }
483 ],
484 "internalType": "struct Property[]",
485 "name": "properties",
486 "type": "tuple[]"
487 }
488 ],
489 "internalType": "struct MintTokenData[]",
490 "name": "tokenProperties",
491 "type": "tuple[]"
492 }
493 ],
494 "name": "mintBulkCross",
495 "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
496 "stateMutability": "nonpayable",
497 "type": "function"
498 },
450 {499 {
451 "inputs": [500 "inputs": [
452 {501 {
modifiedtests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth
20}20}
2121
22/// @title Contract, which allows users to operate with collections22/// @title Contract, which allows users to operate with collections
23/// @dev the ERC-165 identifier for this interface is 0x4135fff123/// @dev the ERC-165 identifier for this interface is 0x94e5af0d
24interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {24interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
25 /// Create a collection25 /// Create a collection
26 /// @return address Address of the newly created collection26 /// @return address Address of the newly created collection
27 /// @dev EVM selector for this function is: 0xa765ee5b,27 /// @dev EVM selector for this function is: 0x72b5bea7,
28 /// or in textual repr: createCollection(((address,uint256),string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],uint8))28 /// or in textual repr: createCollection((string,string,string,uint8,uint8,(string,bytes)[],(string,(uint8,bool)[])[],(address,uint256)[],(bool,bool,address[]),(uint8,uint256)[],(address,uint256),uint8))
29 function createCollection(CreateCollectionData memory data) external payable returns (address);29 function createCollection(CreateCollectionData memory data) external payable returns (address);
3030
31 /// Create an NFT collection31 /// Create an NFT collection
103103
104/// Collection properties104/// Collection properties
105struct CreateCollectionData {105struct CreateCollectionData {
106 /// Collection sponsor
107 CrossAddress pending_sponsor;
108 /// Collection name106 /// Collection name
109 string name;107 string name;
110 /// Collection description108 /// Collection description
125 CollectionNestingAndPermission nesting_settings;123 CollectionNestingAndPermission nesting_settings;
126 /// Collection limits124 /// Collection limits
127 CollectionLimitValue[] limits;125 CollectionLimitValue[] limits;
126 /// Collection sponsor
127 CrossAddress pending_sponsor;
128 /// Extra collection flags128 /// Extra collection flags
129 CollectionFlags flags;129 CollectionFlags flags;
130}130}
131131
132/// Cross account struct
133type CollectionFlags is uint8;132type CollectionFlags is uint8;
134133
135library CollectionFlagsLib {134library CollectionFlagsLib {
140 /// External collections can't be managed using `unique` api139 /// External collections can't be managed using `unique` api
141 CollectionFlags constant externalField = CollectionFlags.wrap(1);140 CollectionFlags constant externalField = CollectionFlags.wrap(1);
142141
143 /// Reserved bits142 /// Reserved flags
144 function reservedField(uint8 value) public pure returns (CollectionFlags) {143 function reservedField(uint8 value) public pure returns (CollectionFlags) {
145 require(value < 1 << 5, "out of bound value");144 require(value < 1 << 5, "out of bound value");
146 return CollectionFlags.wrap(value << 1);145 return CollectionFlags.wrap(value << 1);
147 }146 }
148}147}
148
149/// Cross account struct
150struct CrossAddress {
151 address eth;
152 uint256 sub;
153}
149154
150/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.155/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
151struct CollectionLimitValue {156struct CollectionLimitValue {
185 address[] restricted;190 address[] restricted;
186}191}
187
188/// Cross account struct
189struct CrossAddress {
190 address eth;
191 uint256 sub;
192}
193192
194/// Ethereum representation of Token Property Permissions.193/// Ethereum representation of Token Property Permissions.
195struct TokenPropertyPermission {194struct TokenPropertyPermission {
225224
226/// Type of tokens in collection225/// Type of tokens in collection
227enum CollectionMode {226enum CollectionMode {
227 /// Nonfungible
228 Nonfungible,
228 /// Fungible229 /// Fungible
229 Fungible,230 Fungible,
230 /// Nonfungible
231 Nonfungible,
232 /// Refungible231 /// Refungible
233 Refungible232 Refungible
234}233}
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
551}551}
552552
553/// @title Unique extensions for ERC721.553/// @title Unique extensions for ERC721.
554/// @dev the ERC-165 identifier for this interface is 0x307b061a554/// @dev the ERC-165 identifier for this interface is 0x9b397d16
555interface ERC721UniqueExtensions is Dummy, ERC165 {555interface ERC721UniqueExtensions is Dummy, ERC165 {
556 /// @notice A descriptive name for a collection of NFTs in this contract556 /// @notice A descriptive name for a collection of NFTs in this contract
557 /// @dev EVM selector for this function is: 0x06fdde03,557 /// @dev EVM selector for this function is: 0x06fdde03,
674 // /// or in textual repr: mintBulk(address,uint256[])674 // /// or in textual repr: mintBulk(address,uint256[])
675 // function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);675 // function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);
676
677 /// @notice Function to mint a token.
678 /// @param data Array of pairs of token owner and token's properties for minted token
679 /// @dev EVM selector for this function is: 0xab427b0c,
680 /// or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
681 function mintBulkCross(MintTokenData[] memory data) external returns (bool);
676682
677 // /// @notice Function to mint multiple tokens with the given tokenUris.683 // /// @notice Function to mint multiple tokens with the given tokenUris.
678 // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive684 // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
705 string uri;711 string uri;
706}712}
713
714/// Token minting parameters
715struct MintTokenData {
716 /// Minted token owner
717 CrossAddress owner;
718 /// Minted token properties
719 Property[] properties;
720}
707721
708/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension722/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
709/// @dev See https://eips.ethereum.org/EIPS/eip-721723/// @dev See https://eips.ethereum.org/EIPS/eip-721
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
551}551}
552552
553/// @title Unique extensions for ERC721.553/// @title Unique extensions for ERC721.
554/// @dev the ERC-165 identifier for this interface is 0x95c0f66c554/// @dev the ERC-165 identifier for this interface is 0x4abaabdb
555interface ERC721UniqueExtensions is Dummy, ERC165 {555interface ERC721UniqueExtensions is Dummy, ERC165 {
556 /// @notice A descriptive name for a collection of NFTs in this contract556 /// @notice A descriptive name for a collection of NFTs in this contract
557 /// @dev EVM selector for this function is: 0x06fdde03,557 /// @dev EVM selector for this function is: 0x06fdde03,
668 // /// or in textual repr: mintBulk(address,uint256[])668 // /// or in textual repr: mintBulk(address,uint256[])
669 // function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);669 // function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);
670
671 /// @notice Function to mint a token.
672 /// @param tokenProperties Properties of minted token
673 /// @dev EVM selector for this function is: 0xdf7a5db7,
674 /// or in textual repr: mintBulkCross((((address,uint256),uint128)[],(string,bytes)[])[])
675 function mintBulkCross(MintTokenData[] memory tokenProperties) external returns (bool);
670676
671 // /// @notice Function to mint multiple tokens with the given tokenUris.677 // /// @notice Function to mint multiple tokens with the given tokenUris.
672 // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive678 // /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
706 string uri;712 string uri;
707}713}
714
715/// Token minting parameters
716struct MintTokenData {
717 /// Minted token owner and number of pieces
718 OwnerPieces[] owners;
719 /// Minted token properties
720 Property[] properties;
721}
722
723/// Token minting parameters
724struct OwnerPieces {
725 /// Minted token owner
726 CrossAddress owner;
727 /// Number of token pieces
728 uint128 pieces;
729}
708730
709/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension731/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
710/// @dev See https://eips.ethereum.org/EIPS/eip-721732/// @dev See https://eips.ethereum.org/EIPS/eip-721
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
18import {IKeyringPair} from '@polkadot/types/types';18import {IKeyringPair} from '@polkadot/types/types';
19import {Contract} from 'web3-eth-contract';19import {Contract} from 'web3-eth-contract';
20import {ITokenPropertyPermission} from '../util/playgrounds/types';20import {ITokenPropertyPermission} from '../util/playgrounds/types';
21import {CREATE_COLLECTION_DATA_DEFAULTS, CollectionMode, CreateCollectionData, TokenPermissionField} from './util/playgrounds/types';
2122
22describe('Check ERC721 token URI for NFT', () => {23describe('Check ERC721 token URI for NFT', () => {
23 let donor: IKeyringPair;24 let donor: IKeyringPair;
197 }198 }
198 });199 });
200
201 itEth('Can perform mintBulkCross()', async ({helper}) => {
202 const caller = await helper.eth.createAccountWithBalance(donor);
203 const callerCross = helper.ethCrossAccount.fromAddress(caller);
204 const receiver = helper.eth.createAccount();
205 const receiverCross = helper.ethCrossAccount.fromAddress(receiver);
206
207 const permissions = [
208 {code: TokenPermissionField.Mutable, value: true},
209 {code: TokenPermissionField.TokenOwner, value: true},
210 {code: TokenPermissionField.CollectionAdmin, value: true},
211 ];
212 const {collectionAddress} = await helper.eth.createCollection(
213 caller,
214 {
215 ...CREATE_COLLECTION_DATA_DEFAULTS,
216 name: 'A',
217 description: 'B',
218 tokenPrefix: 'C',
219 collectionMode: 'nft',
220 adminList: [callerCross],
221 tokenPropertyPermissions: [
222 {key: 'key_0_0', permissions},
223 {key: 'key_1_0', permissions},
224 {key: 'key_1_1', permissions},
225 {key: 'key_2_0', permissions},
226 {key: 'key_2_1', permissions},
227 {key: 'key_2_2', permissions},
228 ],
229 },
230 ).send();
231
232 const contract = await helper.ethNativeContract.collection(collectionAddress, 'nft', caller);
233 {
234 const nextTokenId = await contract.methods.nextTokenId().call();
235 expect(nextTokenId).to.be.equal('1');
236 const result = await contract.methods.mintBulkCross([
237 {
238 owner: receiverCross,
239 properties: [
240 {key: 'key_0_0', value: Buffer.from('value_0_0')},
241 ],
242 },
243 {
244 owner: receiverCross,
245 properties: [
246 {key: 'key_1_0', value: Buffer.from('value_1_0')},
247 {key: 'key_1_1', value: Buffer.from('value_1_1')},
248 ],
249 },
250 {
251 owner: receiverCross,
252 properties: [
253 {key: 'key_2_0', value: Buffer.from('value_2_0')},
254 {key: 'key_2_1', value: Buffer.from('value_2_1')},
255 {key: 'key_2_2', value: Buffer.from('value_2_2')},
256 ],
257 },
258 ]).send({from: caller});
259 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);
260 const bulkSize = 3;
261 for(let i = 0; i < bulkSize; i++) {
262 const event = events[i];
263 expect(event.address).to.equal(collectionAddress);
264 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
265 expect(event.returnValues.to).to.equal(receiver);
266 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);
267 }
268
269 const properties = [
270 await contract.methods.properties(+nextTokenId, []).call(),
271 await contract.methods.properties(+nextTokenId + 1, []).call(),
272 await contract.methods.properties(+nextTokenId + 2, []).call(),
273 ];
274 expect(properties).to.be.deep.equal([
275 [
276 ['key_0_0', helper.getWeb3().utils.toHex('value_0_0')],
277 ],
278 [
279 ['key_1_0', helper.getWeb3().utils.toHex('value_1_0')],
280 ['key_1_1', helper.getWeb3().utils.toHex('value_1_1')],
281 ],
282 [
283 ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')],
284 ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')],
285 ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')],
286 ],
287 ]);
288 }
289 });
199290
200 itEth('Can perform burn()', async ({helper}) => {291 itEth('Can perform burn()', async ({helper}) => {
201 const caller = await helper.eth.createAccountWithBalance(donor);292 const caller = await helper.eth.createAccountWithBalance(donor);
modifiedtests/src/eth/reFungible.test.tsdiffbeforeafterboth
18import {expect, itEth, usingEthPlaygrounds} from './util';18import {expect, itEth, usingEthPlaygrounds} from './util';
19import {IKeyringPair} from '@polkadot/types/types';19import {IKeyringPair} from '@polkadot/types/types';
20import {ITokenPropertyPermission} from '../util/playgrounds/types';20import {ITokenPropertyPermission} from '../util/playgrounds/types';
21import {CREATE_COLLECTION_DATA_DEFAULTS, TokenPermissionField} from './util/playgrounds/types';
2122
22describe('Refungible: Plain calls', () => {23describe('Refungible: Plain calls', () => {
23 let donor: IKeyringPair;24 let donor: IKeyringPair;
125 }126 }
126 });127 });
127128
129 itEth('Can perform mintBulkCross() with multiple tokens', async ({helper}) => {
130 const caller = await helper.eth.createAccountWithBalance(donor);
131 const callerCross = helper.ethCrossAccount.fromAddress(caller);
132 const receiver = helper.eth.createAccount();
133 const receiverCross = helper.ethCrossAccount.fromAddress(receiver);
134
135 const permissions = [
136 {code: TokenPermissionField.Mutable, value: true},
137 {code: TokenPermissionField.TokenOwner, value: true},
138 {code: TokenPermissionField.CollectionAdmin, value: true},
139 ];
140 const {collectionAddress} = await helper.eth.createCollection(
141 caller,
142 {
143 ...CREATE_COLLECTION_DATA_DEFAULTS,
144 name: 'A',
145 description: 'B',
146 tokenPrefix: 'C',
147 collectionMode: 'rft',
148 adminList: [callerCross],
149 tokenPropertyPermissions: [
150 {key: 'key_0_0', permissions},
151 {key: 'key_1_0', permissions},
152 {key: 'key_1_1', permissions},
153 {key: 'key_2_0', permissions},
154 {key: 'key_2_1', permissions},
155 {key: 'key_2_2', permissions},
156 ],
157 },
158 ).send();
159
160 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
161 const nextTokenId = await contract.methods.nextTokenId().call();
162 expect(nextTokenId).to.be.equal('1');
163 const result = await contract.methods.mintBulkCross([
164 {
165 owners: [{
166 owner: receiverCross,
167 pieces: 1,
168 }],
169 properties: [
170 {key: 'key_0_0', value: Buffer.from('value_0_0')},
171 ],
172 },
173 {
174 owners: [{
175 owner: receiverCross,
176 pieces: 2,
177 }],
178 properties: [
179 {key: 'key_1_0', value: Buffer.from('value_1_0')},
180 {key: 'key_1_1', value: Buffer.from('value_1_1')},
181 ],
182 },
183 {
184 owners: [{
185 owner: receiverCross,
186 pieces: 1,
187 }],
188 properties: [
189 {key: 'key_2_0', value: Buffer.from('value_2_0')},
190 {key: 'key_2_1', value: Buffer.from('value_2_1')},
191 {key: 'key_2_2', value: Buffer.from('value_2_2')},
192 ],
193 },
194 ]).send({from: caller});
195 const events = result.events.Transfer.sort((a: any, b: any) => +a.returnValues.tokenId - b.returnValues.tokenId);
196 const bulkSize = 3;
197 for(let i = 0; i < bulkSize; i++) {
198 const event = events[i];
199 expect(event.address).to.equal(collectionAddress);
200 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
201 expect(event.returnValues.to).to.equal(receiver);
202 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId + i}`);
203 }
204
205 const properties = [
206 await contract.methods.properties(+nextTokenId, []).call(),
207 await contract.methods.properties(+nextTokenId + 1, []).call(),
208 await contract.methods.properties(+nextTokenId + 2, []).call(),
209 ];
210 expect(properties).to.be.deep.equal([
211 [
212 ['key_0_0', helper.getWeb3().utils.toHex('value_0_0')],
213 ],
214 [
215 ['key_1_0', helper.getWeb3().utils.toHex('value_1_0')],
216 ['key_1_1', helper.getWeb3().utils.toHex('value_1_1')],
217 ],
218 [
219 ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')],
220 ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')],
221 ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')],
222 ],
223 ]);
224 });
225
226 itEth('Can perform mintBulkCross() with multiple owners', async ({helper}) => {
227 const caller = await helper.eth.createAccountWithBalance(donor);
228 const callerCross = helper.ethCrossAccount.fromAddress(caller);
229 const receiver = helper.eth.createAccount();
230 const receiverCross = helper.ethCrossAccount.fromAddress(receiver);
231 const receiver2 = helper.eth.createAccount();
232 const receiver2Cross = helper.ethCrossAccount.fromAddress(receiver2);
233
234 const permissions = [
235 {code: TokenPermissionField.Mutable, value: true},
236 {code: TokenPermissionField.TokenOwner, value: true},
237 {code: TokenPermissionField.CollectionAdmin, value: true},
238 ];
239 const {collectionAddress} = await helper.eth.createCollection(
240 caller,
241 {
242 ...CREATE_COLLECTION_DATA_DEFAULTS,
243 name: 'A',
244 description: 'B',
245 tokenPrefix: 'C',
246 collectionMode: 'rft',
247 adminList: [callerCross],
248 tokenPropertyPermissions: [
249 {key: 'key_2_0', permissions},
250 {key: 'key_2_1', permissions},
251 {key: 'key_2_2', permissions},
252 ],
253 },
254 ).send();
255
256 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
257 const nextTokenId = await contract.methods.nextTokenId().call();
258 expect(nextTokenId).to.be.equal('1');
259 const result = await contract.methods.mintBulkCross([{
260 owners: [
261 {
262 owner: receiverCross,
263 pieces: 1,
264 },
265 {
266 owner: receiver2Cross,
267 pieces: 2,
268 },
269 ],
270 properties: [
271 {key: 'key_2_0', value: Buffer.from('value_2_0')},
272 {key: 'key_2_1', value: Buffer.from('value_2_1')},
273 {key: 'key_2_2', value: Buffer.from('value_2_2')},
274 ],
275 }]).send({from: caller});
276 const event = result.events.Transfer;
277 expect(event.address).to.equal(collectionAddress);
278 expect(event.returnValues.from).to.equal('0x0000000000000000000000000000000000000000');
279 expect(event.returnValues.to).to.equal('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF');
280 expect(event.returnValues.tokenId).to.equal(`${+nextTokenId}`);
281
282 const properties = [
283 await contract.methods.properties(+nextTokenId, []).call(),
284 ];
285 expect(properties).to.be.deep.equal([[
286 ['key_2_0', helper.getWeb3().utils.toHex('value_2_0')],
287 ['key_2_1', helper.getWeb3().utils.toHex('value_2_1')],
288 ['key_2_2', helper.getWeb3().utils.toHex('value_2_2')],
289 ]]);
290 });
291
128 itEth('Can perform setApprovalForAll()', async ({helper}) => {292 itEth('Can perform setApprovalForAll()', async ({helper}) => {
129 const owner = await helper.eth.createAccountWithBalance(donor);293 const owner = await helper.eth.createAccountWithBalance(donor);
785 await contract.methods.setApprovalForAll(spender, false).send({from: owner});949 await contract.methods.setApprovalForAll(spender, false).send({from: owner});
786950
787 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;951 await expect(contract.methods.transferFromCross(ownerCross, recieverCross, token.tokenId).send({from: spender})).to.be.rejected;
952 });
953
954 itEth('[negative] Can perform mintBulkCross() with multiple owners and multiple tokens', async ({helper}) => {
955 const caller = await helper.eth.createAccountWithBalance(donor);
956 const callerCross = helper.ethCrossAccount.fromAddress(caller);
957 const receiver = helper.eth.createAccount();
958 const receiverCross = helper.ethCrossAccount.fromAddress(receiver);
959 const receiver2 = helper.eth.createAccount();
960 const receiver2Cross = helper.ethCrossAccount.fromAddress(receiver2);
961
962 const permissions = [
963 {code: TokenPermissionField.Mutable, value: true},
964 {code: TokenPermissionField.TokenOwner, value: true},
965 {code: TokenPermissionField.CollectionAdmin, value: true},
966 ];
967 const {collectionAddress} = await helper.eth.createCollection(
968 caller,
969 {
970 ...CREATE_COLLECTION_DATA_DEFAULTS,
971 name: 'A',
972 description: 'B',
973 tokenPrefix: 'C',
974 collectionMode: 'rft',
975 adminList: [callerCross],
976 tokenPropertyPermissions: [
977 {key: 'key_0_0', permissions},
978 {key: 'key_2_0', permissions},
979 {key: 'key_2_1', permissions},
980 {key: 'key_2_2', permissions},
981 ],
982 },
983 ).send();
984
985 const contract = await helper.ethNativeContract.collection(collectionAddress, 'rft', caller);
986 const nextTokenId = await contract.methods.nextTokenId().call();
987 expect(nextTokenId).to.be.equal('1');
988 const createData = [
989 {
990 owners: [{
991 owner: receiverCross,
992 pieces: 1,
993 }],
994 properties: [
995 {key: 'key_0_0', value: Buffer.from('value_0_0')},
996 ],
997 },
998 {
999 owners: [
1000 {
1001 owner: receiverCross,
1002 pieces: 1,
1003 },
1004 {
1005 owner: receiver2Cross,
1006 pieces: 2,
1007 },
1008 ],
1009 properties: [
1010 {key: 'key_2_0', value: Buffer.from('value_2_0')},
1011 {key: 'key_2_1', value: Buffer.from('value_2_1')},
1012 {key: 'key_2_2', value: Buffer.from('value_2_2')},
1013 ],
1014 },
1015 ];
1016
1017 await expect(contract.methods.mintBulkCross(createData).call({from: caller})).to.be.rejectedWith('creation of multiple tokens supported only if they have single owner each');
788 });1018 });
789});1019});
7901020