difftreelog
feat add mint_bulk_cross
in: master
15 files changed
pallets/common/src/eth.rsdiffbeforeafterboth1// 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/>.1617//! The module contains a number of functions for converting and checking ethereum identifiers.1819use alloc::format;20use sp_std::{vec, vec::Vec};21use evm_coder::{22 AbiCoder,23 types::{Address, String},24};25pub use pallet_evm::{Config, account::CrossAccountId};26use sp_core::{H160, U256};27use up_data_structs::{CollectionId, CollectionFlags};28use pallet_evm_coder_substrate::execution::Error;2930// 0x17c4e6453Cc49AAAaEACA894e6D9683e00000001 - collection 131// TODO: Unhardcode prefix32const ETH_COLLECTION_PREFIX: [u8; 16] = [33 0x17, 0xc4, 0xe6, 0x45, 0x3c, 0xc4, 0x9a, 0xaa, 0xae, 0xac, 0xa8, 0x94, 0xe6, 0xd9, 0x68, 0x3e,34];3536/// Maps the ethereum address of the collection in substrate.37pub fn map_eth_to_id(eth: &Address) -> Option<CollectionId> {38 if eth[0..16] != ETH_COLLECTION_PREFIX {39 return None;40 }41 let mut id_bytes = [0; 4];42 id_bytes.copy_from_slice(ð[16..20]);43 Some(CollectionId(u32::from_be_bytes(id_bytes)))44}4546/// Maps the substrate collection id in ethereum.47pub fn collection_id_to_address(id: CollectionId) -> Address {48 let mut out = [0; 20];49 out[0..16].copy_from_slice(Ð_COLLECTION_PREFIX);50 out[16..20].copy_from_slice(&u32::to_be_bytes(id.0));51 H160(out)52}5354/// Check if the ethereum address is a collection.55pub fn is_collection(address: &Address) -> bool {56 address[0..16] == ETH_COLLECTION_PREFIX57}5859/// Convert `U256` to `CrossAccountId`.60pub fn convert_uint256_to_cross_account<T: Config>(from: U256) -> T::CrossAccountId61where62 T::AccountId: From<[u8; 32]>,63{64 let mut new_admin_arr = [0_u8; 32];65 from.to_big_endian(&mut new_admin_arr);66 let account_id = T::AccountId::from(new_admin_arr);67 T::CrossAccountId::from_sub(account_id)68}6970/// Cross account struct71#[derive(Debug, Default, AbiCoder)]72pub struct CrossAddress {73 pub(crate) eth: Address,74 pub(crate) sub: U256,75}7677impl CrossAddress {78 /// Converts `CrossAccountId` to [`CrossAddress`] to be correctly usable with Ethereum.79 pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self80 where81 T: pallet_evm::Config,82 T::AccountId: AsRef<[u8; 32]>,83 {84 if cross_account_id.is_canonical_substrate() {85 Self::from_sub::<T>(cross_account_id.as_sub())86 } else {87 Self::from_eth(*cross_account_id.as_eth())88 }89 }90 /// Creates [`CrossAddress`] from Substrate account.91 pub fn from_sub<T>(account_id: &T::AccountId) -> Self92 where93 T: pallet_evm::Config,94 T::AccountId: AsRef<[u8; 32]>,95 {96 Self {97 eth: Default::default(),98 sub: U256::from_big_endian(account_id.as_ref()),99 }100 }101 /// Creates [`CrossAddress`] from Ethereum account.102 pub fn from_eth(address: Address) -> Self {103 Self {104 eth: address,105 sub: Default::default(),106 }107 }108109 /// Converts [`CrossAddress`] to `Option<CrossAccountId>`.110 pub fn into_option_sub_cross_account<T>(&self) -> Result<Option<T::CrossAccountId>, Error>111 where112 T: pallet_evm::Config,113 T::AccountId: From<[u8; 32]>,114 {115 if self.eth == Default::default() && self.sub == Default::default() {116 Ok(None)117 } else if self.eth == Default::default() {118 Ok(Some(convert_uint256_to_cross_account::<T>(self.sub)))119 } else if self.sub == Default::default() {120 Ok(Some(T::CrossAccountId::from_eth(self.eth)))121 } else {122 Err(format!("All fields of cross account is non zeroed {:?}", self).into())123 }124 }125126 /// Converts [`CrossAddress`] to `CrossAccountId`.127 pub fn into_sub_cross_account<T>(&self) -> Result<T::CrossAccountId, Error>128 where129 T: pallet_evm::Config,130 T::AccountId: From<[u8; 32]>,131 {132 if self.eth == Default::default() && self.sub == Default::default() {133 Err("All fields of cross account is zeroed".into())134 } else if self.eth == Default::default() {135 Ok(convert_uint256_to_cross_account::<T>(self.sub))136 } else if self.sub == Default::default() {137 Ok(T::CrossAccountId::from_eth(self.eth))138 } else {139 Err("All fields of cross account is non zeroed".into())140 }141 }142}143144/// Type of tokens in collection145#[derive(AbiCoder, Copy, Clone, Default, Debug, PartialEq)]146#[repr(u8)]147pub enum CollectionMode {148 /// Nonfungible149 #[default]150 Nonfungible,151 /// Fungible152 Fungible,153 /// Refungible154 Refungible,155}156157/// Ethereum representation of collection [`PropertyKey`](up_data_structs::PropertyKey) and [`PropertyValue`](up_data_structs::PropertyValue).158#[derive(Debug, Default, AbiCoder)]159pub struct Property {160 key: evm_coder::types::String,161 value: evm_coder::types::Bytes,162}163164impl Property {165 /// Property key.166 pub fn key(&self) -> &str {167 self.key.as_str()168 }169170 /// Property value.171 pub fn value(&self) -> &[u8] {172 self.value.0.as_slice()173 }174}175176impl TryFrom<up_data_structs::Property> for Property {177 type Error = Error;178179 fn try_from(from: up_data_structs::Property) -> Result<Self, Self::Error> {180 let key = evm_coder::types::String::from_utf8(from.key.into())181 .map_err(|e| Self::Error::Revert(format!("utf8 conversion error: {e}")))?;182 let value = evm_coder::types::Bytes(from.value.to_vec());183 Ok(Property { key, value })184 }185}186187impl TryInto<up_data_structs::Property> for Property {188 type Error = Error;189190 fn try_into(self) -> Result<up_data_structs::Property, Self::Error> {191 let key = <Vec<u8>>::from(self.key)192 .try_into()193 .map_err(|_| "key too large")?;194195 let value = self.value.0.try_into().map_err(|_| "value too large")?;196197 Ok(up_data_structs::Property { key, value })198 }199}200201/// [`CollectionLimits`](up_data_structs::CollectionLimits) fields representation for EVM.202#[derive(Debug, Default, Clone, Copy, AbiCoder)]203#[repr(u8)]204pub enum CollectionLimitField {205 /// How many tokens can a user have on one account.206 #[default]207 AccountTokenOwnership,208209 /// How many bytes of data are available for sponsorship.210 SponsoredDataSize,211212 /// In any case, chain default: [`SponsoringRateLimit::SponsoringDisabled`]213 SponsoredDataRateLimit,214215 /// How many tokens can be mined into this collection.216 TokenLimit,217218 /// Timeouts for transfer sponsoring.219 SponsorTransferTimeout,220221 /// Timeout for sponsoring an approval in passed blocks.222 SponsorApproveTimeout,223224 /// Whether the collection owner of the collection can send tokens (which belong to other users).225 OwnerCanTransfer,226227 /// Can the collection owner burn other people's tokens.228 OwnerCanDestroy,229230 /// Is it possible to send tokens from this collection between users.231 TransferEnabled,232}233234/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.235#[derive(Debug, Default, AbiCoder)]236pub struct CollectionLimit {237 field: CollectionLimitField,238 value: Option<U256>,239}240241impl CollectionLimit {242 /// Create [`CollectionLimit`] from field and value.243 pub fn new(field: CollectionLimitField, value: Option<u32>) -> Self {244 Self {245 field,246 value: value.map(|value| value.into()),247 }248 }249 /// Whether the field contains a value.250 pub fn has_value(&self) -> bool {251 self.value.is_some()252 }253254 /// Set corresponding property in CollectionLimits struct255 pub fn apply_limit(&self, limits: &mut up_data_structs::CollectionLimits) -> Result<(), Error> {256 let value = self257 .value258 .ok_or::<Error>("can't convert `None` value to boolean".into())?;259 let value = Some(value.try_into().map_err(|error| {260 Error::Revert(format!(261 "can't convert value to u32 \"{value}\" because: \"{error}\""262 ))263 })?);264265 let convert_value_to_bool = || match value {266 Some(value) => match value {267 0 => Ok(Some(false)),268 1 => Ok(Some(true)),269 _ => Err(Error::Revert(format!(270 "can't convert value to boolean \"{value}\""271 ))),272 },273 None => Ok(None),274 };275276 match self.field {277 CollectionLimitField::AccountTokenOwnership => {278 limits.account_token_ownership_limit = value;279 }280 CollectionLimitField::SponsoredDataSize => {281 limits.sponsored_data_size = value;282 }283 CollectionLimitField::SponsoredDataRateLimit => {284 limits.sponsored_data_rate_limit =285 value.map(up_data_structs::SponsoringRateLimit::Blocks);286 }287 CollectionLimitField::TokenLimit => {288 limits.token_limit = value;289 }290 CollectionLimitField::SponsorTransferTimeout => {291 limits.sponsor_transfer_timeout = value;292 }293 CollectionLimitField::SponsorApproveTimeout => {294 limits.sponsor_approve_timeout = value;295 }296 CollectionLimitField::OwnerCanTransfer => {297 limits.owner_can_transfer = convert_value_to_bool()?;298 }299 CollectionLimitField::OwnerCanDestroy => {300 limits.owner_can_destroy = convert_value_to_bool()?;301 }302 CollectionLimitField::TransferEnabled => {303 limits.transfers_enabled = convert_value_to_bool()?;304 }305 };306 Ok(())307 }308}309310/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.311#[derive(Debug, Default, AbiCoder)]312pub struct CollectionLimitValue {313 field: CollectionLimitField,314 value: U256,315}316317impl CollectionLimitValue {318 /// Create [`CollectionLimitValue`] from field and value.319 pub fn new(field: CollectionLimitField, value: u32) -> Self {320 Self {321 field,322 value: value.into(),323 }324 }325326 /// Set corresponding property in CollectionLimits struct327 pub fn apply_limit(&self, limits: &mut up_data_structs::CollectionLimits) -> Result<(), Error> {328 let value = self.value;329 let value: u32 = value.try_into().map_err(|error| {330 Error::Revert(format!(331 "can't convert value to u32 \"{value}\" because: \"{error}\""332 ))333 })?;334335 let convert_value_to_bool = || match value {336 0 => Ok(Some(false)),337 1 => Ok(Some(true)),338 _ => Err(Error::Revert(format!(339 "can't convert value to boolean \"{value}\""340 ))),341 };342343 match self.field {344 CollectionLimitField::AccountTokenOwnership => {345 limits.account_token_ownership_limit = Some(value);346 }347 CollectionLimitField::SponsoredDataSize => {348 limits.sponsored_data_size = Some(value);349 }350 CollectionLimitField::SponsoredDataRateLimit => {351 limits.sponsored_data_rate_limit =352 Some(up_data_structs::SponsoringRateLimit::Blocks(value));353 }354 CollectionLimitField::TokenLimit => {355 limits.token_limit = Some(value);356 }357 CollectionLimitField::SponsorTransferTimeout => {358 limits.sponsor_transfer_timeout = Some(value);359 }360 CollectionLimitField::SponsorApproveTimeout => {361 limits.sponsor_approve_timeout = Some(value);362 }363 CollectionLimitField::OwnerCanTransfer => {364 limits.owner_can_transfer = convert_value_to_bool()?;365 }366 CollectionLimitField::OwnerCanDestroy => {367 limits.owner_can_destroy = convert_value_to_bool()?;368 }369 CollectionLimitField::TransferEnabled => {370 limits.transfers_enabled = convert_value_to_bool()?;371 }372 };373 Ok(())374 }375}376377impl TryInto<up_data_structs::CollectionLimits> for CollectionLimit {378 type Error = Error;379380 fn try_into(self) -> Result<up_data_structs::CollectionLimits, Self::Error> {381 let mut limits = up_data_structs::CollectionLimits::default();382 self.apply_limit(&mut limits)?;383 Ok(limits)384 }385}386387impl FromIterator<CollectionLimitValue> for Result<up_data_structs::CollectionLimits, Error> {388 fn from_iter<T: IntoIterator<Item = CollectionLimitValue>>(389 iter: T,390 ) -> Result<up_data_structs::CollectionLimits, Error> {391 let mut limits = up_data_structs::CollectionLimits::default();392 for value in iter.into_iter() {393 value.apply_limit(&mut limits)?;394 }395 Ok(limits)396 }397}398399/// Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) fields as an enumeration.400#[derive(Default, Debug, Clone, Copy, AbiCoder)]401#[repr(u8)]402pub enum CollectionPermissionField {403 /// Owner of token can nest tokens under it.404 #[default]405 TokenOwner,406407 /// Admin of token collection can nest tokens under token.408 CollectionAdmin,409}410411/// Ethereum representation of TokenPermissions (see [`up_data_structs::PropertyPermission`]) fields as an enumeration.412#[derive(AbiCoder, Copy, Clone, Default, Debug)]413#[repr(u8)]414pub enum TokenPermissionField {415 /// Permission to change the property and property permission. See [`up_data_structs::PropertyPermission::mutable`]416 #[default]417 Mutable,418419 /// Change permission for the collection administrator. See [`up_data_structs::PropertyPermission::token_owner`]420 TokenOwner,421422 /// Permission to change the property for the owner of the token. See [`up_data_structs::PropertyPermission::collection_admin`]423 CollectionAdmin,424}425426/// Ethereum representation of TokenPermissions (see [`up_data_structs::PropertyPermission`]) as an key and value.427#[derive(Debug, Default, AbiCoder)]428pub struct PropertyPermission {429 /// TokenPermission field.430 code: TokenPermissionField,431 /// TokenPermission value.432 value: bool,433}434435impl PropertyPermission {436 /// Make vector of [`PropertyPermission`] from [`up_data_structs::PropertyPermission`].437 pub fn into_vec(pp: up_data_structs::PropertyPermission) -> Vec<Self> {438 vec![439 PropertyPermission {440 code: TokenPermissionField::Mutable,441 value: pp.mutable,442 },443 PropertyPermission {444 code: TokenPermissionField::TokenOwner,445 value: pp.token_owner,446 },447 PropertyPermission {448 code: TokenPermissionField::CollectionAdmin,449 value: pp.collection_admin,450 },451 ]452 }453454 /// Make [`up_data_structs::PropertyPermission`] from vector of [`PropertyPermission`].455 pub fn from_vec(permission: Vec<Self>) -> up_data_structs::PropertyPermission {456 let mut token_permission = up_data_structs::PropertyPermission::default();457458 for PropertyPermission { code, value } in permission {459 match code {460 TokenPermissionField::Mutable => token_permission.mutable = value,461 TokenPermissionField::TokenOwner => token_permission.token_owner = value,462 TokenPermissionField::CollectionAdmin => token_permission.collection_admin = value,463 }464 }465 token_permission466 }467}468469/// Ethereum representation of Token Property Permissions.470#[derive(Debug, Default, AbiCoder)]471pub struct TokenPropertyPermission {472 /// Token property key.473 key: evm_coder::types::String,474 /// Token property permissions.475 permissions: Vec<PropertyPermission>,476}477478impl479 From<(480 up_data_structs::PropertyKey,481 up_data_structs::PropertyPermission,482 )> for TokenPropertyPermission483{484 fn from(485 value: (486 up_data_structs::PropertyKey,487 up_data_structs::PropertyPermission,488 ),489 ) -> Self {490 let (key, permission) = value;491 let key = evm_coder::types::String::from_utf8(key.into_inner())492 .expect("Stored key must be valid");493 let permissions = PropertyPermission::into_vec(permission);494 Self { key, permissions }495 }496}497498impl TokenPropertyPermission {499 /// Convert vector of [`TokenPropertyPermission`] into vector of [`up_data_structs::PropertyKeyPermission`].500 pub fn into_property_key_permissions(501 permissions: Vec<TokenPropertyPermission>,502 ) -> Result<Vec<up_data_structs::PropertyKeyPermission>, Error> {503 let mut perms = Vec::new();504505 for TokenPropertyPermission { key, permissions } in permissions {506 let token_permission = PropertyPermission::from_vec(permissions);507508 perms.push(up_data_structs::PropertyKeyPermission {509 key: key.into_bytes().try_into().map_err(|_| "too long key")?,510 permission: token_permission,511 });512 }513 Ok(perms)514 }515}516517/// Data for creation token with uri.518#[derive(Debug, AbiCoder)]519pub struct TokenUri {520 /// Id of new token.521 pub id: U256,522523 /// Uri of new token.524 pub uri: String,525}526527/// Nested collections and permissions528#[derive(Debug, Default, AbiCoder)]529pub struct CollectionNestingAndPermission {530 /// Owner of token can nest tokens under it.531 pub token_owner: bool,532 /// Admin of token collection can nest tokens under token.533 pub collection_admin: bool,534 /// If set - only tokens from specified collections can be nested.535 pub restricted: Vec<Address>,536}537538impl CollectionNestingAndPermission {539 /// Create [`CollectionNesting`].540 pub fn new(token_owner: bool, collection_admin: bool, restricted: Vec<Address>) -> Self {541 Self {542 token_owner,543 collection_admin,544 restricted,545 }546 }547}548549/// Collection properties550#[derive(Debug, Default, AbiCoder)]551pub struct CreateCollectionData {552 /// Collection name553 pub name: String,554 /// Collection description555 pub description: String,556 /// Token prefix557 pub token_prefix: String,558 /// Token type (NFT, FT or RFT)559 pub mode: CollectionMode,560 /// Fungible token precision561 pub decimals: u8,562 /// Custom Properties563 pub properties: Vec<Property>,564 /// Permissions for token properties565 pub token_property_permissions: Vec<TokenPropertyPermission>,566 /// Collection admins567 pub admin_list: Vec<CrossAddress>,568 /// Nesting settings569 pub nesting_settings: CollectionNestingAndPermission,570 /// Collection limits571 pub limits: Vec<CollectionLimitValue>,572 /// Collection sponsor573 pub pending_sponsor: CrossAddress,574 /// Extra collection flags575 pub flags: CollectionFlags,576}577578/// Nested collections.579#[derive(Debug, Default, AbiCoder)]580pub struct CollectionNesting {581 token_owner: bool,582 ids: Vec<U256>,583}584585impl CollectionNesting {586 /// Create [`CollectionNesting`].587 pub fn new(token_owner: bool, ids: Vec<U256>) -> Self {588 Self { token_owner, ids }589 }590}591592/// Ethereum representation of `NestingPermissions` (see [`up_data_structs::NestingPermissions`]) field.593#[derive(Debug, Default, AbiCoder)]594pub struct CollectionNestingPermission {595 field: CollectionPermissionField,596 value: bool,597}598599impl CollectionNestingPermission {600 /// Create [`CollectionNestingPermission`].601 pub fn new(field: CollectionPermissionField, value: bool) -> Self {602 Self { field, value }603 }604}605606/// Ethereum representation of `AccessMode` (see [`up_data_structs::AccessMode`]).607#[derive(AbiCoder, Copy, Clone, Default, Debug)]608#[repr(u8)]609pub enum AccessMode {610 /// Access grant for owner and admins. Used as default.611 #[default]612 Normal,613 /// Like a [`Normal`](AccessMode::Normal) but also users in allow list.614 AllowList,615}616617impl From<up_data_structs::AccessMode> for AccessMode {618 fn from(value: up_data_structs::AccessMode) -> Self {619 match value {620 up_data_structs::AccessMode::Normal => AccessMode::Normal,621 up_data_structs::AccessMode::AllowList => AccessMode::AllowList,622 }623 }624}625626impl From<AccessMode> for up_data_structs::AccessMode {627 fn from(value: AccessMode) -> Self {628 match value {629 AccessMode::Normal => up_data_structs::AccessMode::Normal,630 AccessMode::AllowList => up_data_structs::AccessMode::AllowList,631 }632 }633}634635/// Token minting parameters636#[derive(AbiCoder, Default, Debug)]637pub struct MintTokenData {638 /// Minted token owner639 pub owner: CrossAddress,640 /// Minted token properties641 pub properties: Vec<Property>,642}pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -981,13 +981,41 @@
Ok(true)
}
+ /// @notice Function to mint a token.
+ /// @param data Array of pairs of token owner and token's properties for minted token
+ #[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]
+ fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<eth::MintTokenData>) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ let mut create_nft_data = Vec::with_capacity(data.len());
+ for eth::MintTokenData { owner, properties } in data {
+ let owner = owner.into_sub_cross_account::<T>()?;
+ create_nft_data.push(CreateItemData::<T> {
+ properties: properties
+ .into_iter()
+ .map(|property| property.try_into())
+ .collect::<Result<Vec<_>>>()?
+ .try_into()
+ .map_err(|_| "too many properties")?,
+ owner,
+ });
+ }
+
+ <Pallet<T>>::create_multiple_items(self, &caller, create_nft_data, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+
/// @notice Function to mint multiple tokens with the given tokenUris.
/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
/// numbers and first number should be obtained with `nextTokenId` method
/// @param to The new owner
/// @param tokens array of pairs of token ID and token URI for minted tokens
#[solidity(hide, rename_selector = "mintBulkWithTokenURI")]
- #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]
+ #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]
fn mint_bulk_with_token_uri(
&mut self,
caller: Caller,
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
@@ -800,7 +800,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x307b061a
+/// @dev the ERC-165 identifier for this interface is 0x9b397d16
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,
@@ -997,6 +997,17 @@
// return false;
// }
+ /// @notice Function to mint a token.
+ /// @param data Array of pairs of token owner and token's properties for minted token
+ /// @dev EVM selector for this function is: 0xab427b0c,
+ /// or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
+ function mintBulkCross(MintTokenData[] memory data) public returns (bool) {
+ require(false, stub_error);
+ data;
+ dummy = 0;
+ return false;
+ }
+
// /// @notice Function to mint multiple tokens with the given tokenUris.
// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
// /// numbers and first number should be obtained with `nextTokenId` method
@@ -1044,6 +1055,11 @@
string uri;
}
+struct MintTokenData {
+ CrossAddress owner;
+ Property[] properties;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -1021,6 +1021,43 @@
Ok(true)
}
+ /// @notice Function to mint a token.
+ /// @param tokenProperties Properties of minted token
+ #[weight(<SelfWeightOf<T>>::create_multiple_items(token_properties.len() as u32) + <SelfWeightOf<T>>::set_token_properties(token_properties.len() as u32))]
+ fn mint_bulk_cross(
+ &mut self,
+ caller: Caller,
+ token_properties: Vec<eth::MintTokenData>,
+ ) -> Result<bool> {
+ let caller = T::CrossAccountId::from_eth(caller);
+ let budget = self
+ .recorder
+ .weight_calls_budget(<StructureWeight<T>>::find_parent());
+
+ let mut create_rft_data = Vec::with_capacity(token_properties.len());
+ for eth::MintTokenData { owner, properties } in token_properties {
+ let owner = owner.into_sub_cross_account::<T>()?;
+ let users: BoundedBTreeMap<_, _, _> = [(owner, 1)]
+ .into_iter()
+ .collect::<BTreeMap<_, _>>()
+ .try_into()
+ .map_err(|_| "too many users")?;
+ create_rft_data.push(CreateItemData::<T> {
+ properties: properties
+ .into_iter()
+ .map(|property| property.try_into())
+ .collect::<Result<Vec<_>>>()?
+ .try_into()
+ .map_err(|_| "too many properties")?,
+ users,
+ });
+ }
+
+ <Pallet<T>>::create_multiple_items(self, &caller, create_rft_data, &budget)
+ .map_err(dispatch_to_evm::<T>)?;
+ Ok(true)
+ }
+
/// @notice Function to mint multiple tokens with the given tokenUris.
/// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
/// numbers and first number should be obtained with `nextTokenId` method
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
@@ -800,7 +800,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x95c0f66c
+/// @dev the ERC-165 identifier for this interface is 0x3e828d60
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,
@@ -986,6 +986,17 @@
// return false;
// }
+ /// @notice Function to mint a token.
+ /// @param tokenProperties Properties of minted token
+ /// @dev EVM selector for this function is: 0xab427b0c,
+ /// or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
+ function mintBulkCross(MintTokenData[] memory tokenProperties) public returns (bool) {
+ require(false, stub_error);
+ tokenProperties;
+ dummy = 0;
+ return false;
+ }
+
// /// @notice Function to mint multiple tokens with the given tokenUris.
// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
// /// numbers and first number should be obtained with `nextTokenId` method
@@ -1045,6 +1056,11 @@
string uri;
}
+struct MintTokenData {
+ CrossAddress owner;
+ Property[] properties;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -25,12 +25,12 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x4135fff1
+/// @dev the ERC-165 identifier for this interface is 0x94e5af0d
contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create a collection
/// @return address Address of the newly created collection
- /// @dev EVM selector for this function is: 0xa765ee5b,
- /// 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))
+ /// @dev EVM selector for this function is: 0x72b5bea7,
+ /// 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))
function createCollection(CreateCollectionData memory data) public payable returns (address) {
require(false, stub_error);
data;
@@ -170,8 +170,6 @@
/// Collection properties
struct CreateCollectionData {
- /// Collection sponsor
- CrossAddress pending_sponsor;
/// Collection name
string name;
/// Collection description
@@ -192,11 +190,12 @@
CollectionNestingAndPermission nesting_settings;
/// Collection limits
CollectionLimitValue[] limits;
+ /// Collection sponsor
+ CrossAddress pending_sponsor;
/// Extra collection flags
CollectionFlags flags;
}
-/// Cross account struct
type CollectionFlags is uint8;
library CollectionFlagsLib {
@@ -207,13 +206,19 @@
/// External collections can't be managed using `unique` api
CollectionFlags constant externalField = CollectionFlags.wrap(1);
- /// Reserved bits
+ /// Reserved flags
function reservedField(uint8 value) public pure returns (CollectionFlags) {
require(value < 1 << 5, "out of bound value");
return CollectionFlags.wrap(value << 1);
}
}
+/// Cross account struct
+struct CrossAddress {
+ address eth;
+ uint256 sub;
+}
+
/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
struct CollectionLimitValue {
CollectionLimitField field;
@@ -250,12 +255,6 @@
bool collection_admin;
/// If set - only tokens from specified collections can be nested.
address[] restricted;
-}
-
-/// Cross account struct
-struct CrossAddress {
- address eth;
- uint256 sub;
}
/// Ethereum representation of Token Property Permissions.
@@ -292,10 +291,10 @@
/// Type of tokens in collection
enum CollectionMode {
- /// Fungible
- Fungible,
/// Nonfungible
Nonfungible,
+ /// Fungible
+ Fungible,
/// Refungible
Refungible
}
runtime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth--- a/runtime/common/ethereum/sponsoring/refungible.rs
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -242,6 +242,7 @@
BurnFrom { .. }
| BurnFromCross { .. }
| MintBulk { .. }
+ | MintBulkCross { .. }
| MintBulkWithTokenUri { .. } => None,
MintCross { .. } => withdraw_create_item::<T>(
tests/src/eth/abi/nonFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -469,6 +469,39 @@
"inputs": [
{
"components": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "owner",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "string", "name": "key", "type": "string" },
+ { "internalType": "bytes", "name": "value", "type": "bytes" }
+ ],
+ "internalType": "struct Property[]",
+ "name": "properties",
+ "type": "tuple[]"
+ }
+ ],
+ "internalType": "struct MintTokenData[]",
+ "name": "data",
+ "type": "tuple[]"
+ }
+ ],
+ "name": "mintBulkCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
{ "internalType": "address", "name": "eth", "type": "address" },
{ "internalType": "uint256", "name": "sub", "type": "uint256" }
],
tests/src/eth/abi/reFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -451,6 +451,39 @@
"inputs": [
{
"components": [
+ {
+ "components": [
+ { "internalType": "address", "name": "eth", "type": "address" },
+ { "internalType": "uint256", "name": "sub", "type": "uint256" }
+ ],
+ "internalType": "struct CrossAddress",
+ "name": "owner",
+ "type": "tuple"
+ },
+ {
+ "components": [
+ { "internalType": "string", "name": "key", "type": "string" },
+ { "internalType": "bytes", "name": "value", "type": "bytes" }
+ ],
+ "internalType": "struct Property[]",
+ "name": "properties",
+ "type": "tuple[]"
+ }
+ ],
+ "internalType": "struct MintTokenData[]",
+ "name": "tokenProperties",
+ "type": "tuple[]"
+ }
+ ],
+ "name": "mintBulkCross",
+ "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "components": [
{ "internalType": "address", "name": "eth", "type": "address" },
{ "internalType": "uint256", "name": "sub", "type": "uint256" }
],
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -20,12 +20,12 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x4135fff1
+/// @dev the ERC-165 identifier for this interface is 0x94e5af0d
interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create a collection
/// @return address Address of the newly created collection
- /// @dev EVM selector for this function is: 0xa765ee5b,
- /// 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))
+ /// @dev EVM selector for this function is: 0x72b5bea7,
+ /// 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))
function createCollection(CreateCollectionData memory data) external payable returns (address);
/// Create an NFT collection
@@ -103,8 +103,6 @@
/// Collection properties
struct CreateCollectionData {
- /// Collection sponsor
- CrossAddress pending_sponsor;
/// Collection name
string name;
/// Collection description
@@ -125,11 +123,12 @@
CollectionNestingAndPermission nesting_settings;
/// Collection limits
CollectionLimitValue[] limits;
+ /// Collection sponsor
+ CrossAddress pending_sponsor;
/// Extra collection flags
CollectionFlags flags;
}
-/// Cross account struct
type CollectionFlags is uint8;
library CollectionFlagsLib {
@@ -140,13 +139,19 @@
/// External collections can't be managed using `unique` api
CollectionFlags constant externalField = CollectionFlags.wrap(1);
- /// Reserved bits
+ /// Reserved flags
function reservedField(uint8 value) public pure returns (CollectionFlags) {
require(value < 1 << 5, "out of bound value");
return CollectionFlags.wrap(value << 1);
}
}
+/// Cross account struct
+struct CrossAddress {
+ address eth;
+ uint256 sub;
+}
+
/// [`CollectionLimits`](up_data_structs::CollectionLimits) field representation for EVM.
struct CollectionLimitValue {
CollectionLimitField field;
@@ -183,12 +188,6 @@
bool collection_admin;
/// If set - only tokens from specified collections can be nested.
address[] restricted;
-}
-
-/// Cross account struct
-struct CrossAddress {
- address eth;
- uint256 sub;
}
/// Ethereum representation of Token Property Permissions.
@@ -225,10 +224,10 @@
/// Type of tokens in collection
enum CollectionMode {
- /// Fungible
- Fungible,
/// Nonfungible
Nonfungible,
+ /// Fungible
+ Fungible,
/// Refungible
Refungible
}
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -551,7 +551,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x307b061a
+/// @dev the ERC-165 identifier for this interface is 0x9b397d16
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,
@@ -674,6 +674,12 @@
// /// or in textual repr: mintBulk(address,uint256[])
// function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);
+ /// @notice Function to mint a token.
+ /// @param data Array of pairs of token owner and token's properties for minted token
+ /// @dev EVM selector for this function is: 0xab427b0c,
+ /// or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
+ function mintBulkCross(MintTokenData[] memory data) external returns (bool);
+
// /// @notice Function to mint multiple tokens with the given tokenUris.
// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
// /// numbers and first number should be obtained with `nextTokenId` method
@@ -705,6 +711,11 @@
string uri;
}
+struct MintTokenData {
+ CrossAddress owner;
+ Property[] properties;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -551,7 +551,7 @@
}
/// @title Unique extensions for ERC721.
-/// @dev the ERC-165 identifier for this interface is 0x95c0f66c
+/// @dev the ERC-165 identifier for this interface is 0x3e828d60
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,
@@ -668,6 +668,12 @@
// /// or in textual repr: mintBulk(address,uint256[])
// function mintBulk(address to, uint256[] memory tokenIds) external returns (bool);
+ /// @notice Function to mint a token.
+ /// @param tokenProperties Properties of minted token
+ /// @dev EVM selector for this function is: 0xab427b0c,
+ /// or in textual repr: mintBulkCross(((address,uint256),(string,bytes)[])[])
+ function mintBulkCross(MintTokenData[] memory tokenProperties) external returns (bool);
+
// /// @notice Function to mint multiple tokens with the given tokenUris.
// /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive
// /// numbers and first number should be obtained with `nextTokenId` method
@@ -706,6 +712,11 @@
string uri;
}
+struct MintTokenData {
+ CrossAddress owner;
+ Property[] properties;
+}
+
/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
/// @dev See https://eips.ethereum.org/EIPS/eip-721
/// @dev the ERC-165 identifier for this interface is 0x780e9d63