difftreelog
fix EVM mint with properties, minor improvements
in: master
9 files changed
pallets/common/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -29,9 +29,8 @@
use sp_std::{vec, vec::Vec};
use up_data_structs::{
AccessMode, CollectionId, CollectionMode, CollectionPermissions, CreateCollectionData,
- NestingPermissions, PropertiesPermissionMap, Property, PropertyKey, PropertyValue,
- MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_COLLECTION_NAME_LENGTH, MAX_PROPERTIES_PER_ITEM,
- MAX_TOKEN_PREFIX_LENGTH,
+ NestingPermissions, Property, PropertyKey, PropertyValue, MAX_COLLECTION_DESCRIPTION_LENGTH,
+ MAX_COLLECTION_NAME_LENGTH, MAX_PROPERTIES_PER_ITEM, MAX_TOKEN_PREFIX_LENGTH,
};
use crate::{BenchmarkPropertyWriter, CollectionHandle, Config, Pallet};
@@ -190,31 +189,6 @@
#[block]
{
<Pallet<T>>::set_collection_properties(&collection, &owner, props.into_iter())?;
- }
-
- Ok(())
- }
-
- #[benchmark]
- fn delete_collection_properties(
- b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
- ) -> Result<(), BenchmarkError> {
- bench_init! {
- owner: sub; collection: collection(owner);
- owner: cross_from_sub;
- };
- let props = (0..b)
- .map(|p| Property {
- key: property_key(p as usize),
- value: property_value(),
- })
- .collect::<Vec<_>>();
- <Pallet<T>>::set_collection_properties(&collection, &owner, props.into_iter())?;
- let to_delete = (0..b).map(|p| property_key(p as usize)).collect::<Vec<_>>();
-
- #[block]
- {
- <Pallet<T>>::delete_collection_properties(&collection, &owner, to_delete.into_iter())?;
}
Ok(())
@@ -253,7 +227,7 @@
}
#[benchmark]
- fn init_token_properties_common() -> Result<(), BenchmarkError> {
+ fn property_writer_load_collection_info() -> Result<(), BenchmarkError> {
bench_init! {
owner: sub; collection: collection(owner);
sender: sub;
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -126,7 +126,7 @@
///
/// @param key Property key.
#[solidity(hide)]
- #[weight(<SelfWeightOf<T>>::delete_collection_properties(1))]
+ #[weight(<SelfWeightOf<T>>::set_collection_properties(1))]
fn delete_collection_property(&mut self, caller: Caller, key: String) -> Result<()> {
let caller = T::CrossAccountId::from_eth(caller);
let key = <Vec<u8>>::from(key)
@@ -139,7 +139,7 @@
/// Delete collection properties.
///
/// @param keys Properties keys.
- #[weight(<SelfWeightOf<T>>::delete_collection_properties(keys.len() as u32))]
+ #[weight(<SelfWeightOf<T>>::set_collection_properties(keys.len() as u32))]
fn delete_collection_properties(&mut self, caller: Caller, keys: Vec<String>) -> Result<()> {
let caller = T::CrossAccountId::from_eth(caller);
let keys = keys
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -2626,8 +2626,8 @@
impl<T: Config> BenchmarkPropertyWriter<T> {
/// Creates a [`PropertyWriter`] for benchmarking tokens properties writing.
pub fn new<'a, Handle>(
- collection: &Handle,
- collection_lazy_info: PropertyWriterLazyCollectionInfo,
+ collection: &'a Handle,
+ collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,
) -> PropertyWriter<'a, Self, T, Handle>
where
Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -18,7 +18,6 @@
use pallet_common::{
bench_init,
benchmarking::{create_collection_raw, property_key, property_value},
- CommonCollectionOperations,
};
use sp_std::prelude::*;
use up_data_structs::{
@@ -131,53 +130,12 @@
#[block]
{
<Pallet<T>>::burn(&collection, &burner, item)?;
- }
-
- Ok(())
- }
-
- #[benchmark]
- fn burn_recursively_self_raw() -> Result<(), BenchmarkError> {
- bench_init! {
- owner: sub; collection: collection(owner);
- sender: cross_from_sub(owner); burner: cross_sub;
- };
- let item = create_max_item(&collection, &sender, burner.clone())?;
-
- #[block]
- {
- <Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)?;
}
Ok(())
}
#[benchmark]
- fn burn_recursively_breadth_plus_self_plus_self_per_each_raw(
- b: Linear<0, 200>,
- ) -> Result<(), BenchmarkError> {
- bench_init! {
- owner: sub; collection: collection(owner);
- sender: cross_from_sub(owner); burner: cross_sub;
- };
- let item = create_max_item(&collection, &sender, burner.clone())?;
- for _ in 0..b {
- create_max_item(
- &collection,
- &sender,
- T::CrossTokenAddressMapping::token_to_address(collection.id, item),
- )?;
- }
-
- #[block]
- {
- <Pallet<T>>::burn_recursively(&collection, &burner, item, &Unlimited, &Unlimited)?;
- }
-
- Ok(())
- }
-
- #[benchmark]
fn transfer_raw() -> Result<(), BenchmarkError> {
bench_init! {
owner: sub; collection: collection(owner);
@@ -262,116 +220,34 @@
{
<Pallet<T>>::burn_from(&collection, &burner, &sender, item, &Unlimited)?;
}
- }
- // set_token_properties {
- // let b in 0..MAX_PROPERTIES_PER_ITEM;
- // bench_init!{
- // owner: sub; collection: collection(owner);
- // owner: cross_from_sub;
- // };
- // let perms = (0..b).map(|k| PropertyKeyPermission {
- // key: property_key(k as usize),
- // permission: PropertyPermission {
- // mutable: false,
- // collection_admin: true,
- // token_owner: true,
- // },
- // }).collect::<Vec<_>>();
- // <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
- // let props = (0..b).map(|k| Property {
- // key: property_key(k as usize),
- // value: property_value(),
- // }).collect::<Vec<_>>();
- // let item = create_max_item(&collection, &owner, owner.clone())?;
- // }: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), &Unlimited)?}
-
- // load_token_properties {
- // bench_init!{
- // owner: sub; collection: collection(owner);
- // owner: cross_from_sub;
- // };
-
- // let item = create_max_item(&collection, &owner, owner.clone())?;
- // }: {
- // pallet_common::BenchmarkPropertyWriter::<T>::load_token_properties(
- // &collection,
- // item,
- // )
- // }
-
- // write_token_properties {
- // let b in 0..MAX_PROPERTIES_PER_ITEM;
- // bench_init!{
- // owner: sub; collection: collection(owner);
- // owner: cross_from_sub;
- // };
+ Ok(())
+ }
- // let perms = (0..b).map(|k| PropertyKeyPermission {
- // key: property_key(k as usize),
- // permission: PropertyPermission {
- // mutable: false,
- // collection_admin: true,
- // token_owner: true,
- // },
- // }).collect::<Vec<_>>();
- // <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
- // let props = (0..b).map(|k| Property {
- // key: property_key(k as usize),
- // value: property_value(),
- // }).collect::<Vec<_>>();
- // let item = create_max_item(&collection, &owner, owner.clone())?;
-
- // let lazy_collection_info = pallet_common::BenchmarkPropertyWriter::<T>::load_collection_info(
- // &collection,
- // &owner,
- // );
- // }: {
- // let mut property_writer = pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
-
- // property_writer.write_token_properties(
- // item,
- // props.into_iter(),
- // crate::erc::ERC721TokenEvent::TokenChanged {
- // token_id: item.into(),
- // }
- // .to_log(T::ContractAddress::get()),
- // )?
- // }
-
#[benchmark]
- fn set_token_property_permissions(
- b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
- ) -> Result<(), BenchmarkError> {
+ fn load_token_properties() -> Result<(), BenchmarkError> {
bench_init! {
owner: sub; collection: collection(owner);
owner: cross_from_sub;
};
- let perms = (0..b)
- .map(|k| PropertyKeyPermission {
- key: property_key(k as usize),
- permission: PropertyPermission {
- mutable: false,
- collection_admin: false,
- token_owner: false,
- },
- })
- .collect::<Vec<_>>();
+ let item = create_max_item(&collection, &owner, owner.clone())?;
+
#[block]
{
- <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+ pallet_common::BenchmarkPropertyWriter::<T>::load_token_properties(&collection, item);
}
Ok(())
}
#[benchmark]
- fn set_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
+ fn write_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
bench_init! {
owner: sub; collection: collection(owner);
owner: cross_from_sub;
};
+
let perms = (0..b)
.map(|k| PropertyKeyPermission {
key: property_key(k as usize),
@@ -391,71 +267,29 @@
.collect::<Vec<_>>();
let item = create_max_item(&collection, &owner, owner.clone())?;
+ let lazy_collection_info =
+ pallet_common::BenchmarkPropertyWriter::<T>::load_collection_info(&collection, &owner);
+
#[block]
{
- <Pallet<T>>::set_token_properties(
- &collection,
- &owner,
+ let mut property_writer =
+ pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
+
+ property_writer.write_token_properties(
item,
props.into_iter(),
- &Unlimited,
+ crate::erc::ERC721TokenEvent::TokenChanged {
+ token_id: item.into(),
+ }
+ .to_log(T::ContractAddress::get()),
)?;
}
Ok(())
}
- // TODO:
#[benchmark]
- fn init_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
- // bench_init! {
- // owner: sub; collection: collection(owner);
- // owner: cross_from_sub;
- // };
-
- // let perms = (0..b)
- // .map(|k| PropertyKeyPermission {
- // key: property_key(k as usize),
- // permission: PropertyPermission {
- // mutable: false,
- // collection_admin: true,
- // token_owner: true,
- // },
- // })
- // .collect::<Vec<_>>();
- // <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
- #[block]
- {}
- // let props = (0..b)
- // .map(|k| Property {
- // key: property_key(k as usize),
- // value: property_value(),
- // })
- // .collect::<Vec<_>>();
- // let item = create_max_item(&collection, &owner, owner.clone())?;
-
- // let (is_collection_admin, property_permissions) =
- // load_is_admin_and_property_permissions(&collection, &owner);
- // #[block]
- // {
- // let mut property_writer =
- // pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
-
- // property_writer.write_token_properties(
- // item,
- // props.into_iter(),
- // crate::erc::ERC721TokenEvent::TokenChanged {
- // token_id: item.into(),
- // }
- // .to_log(T::ContractAddress::get()),
- // )?;
- // }
-
- Ok(())
- }
-
- #[benchmark]
- fn delete_token_properties(
+ fn set_token_property_permissions(
b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
) -> Result<(), BenchmarkError> {
bench_init! {
@@ -466,54 +300,16 @@
.map(|k| PropertyKeyPermission {
key: property_key(k as usize),
permission: PropertyPermission {
- mutable: true,
- collection_admin: true,
- token_owner: true,
+ mutable: false,
+ collection_admin: false,
+ token_owner: false,
},
- })
- .collect::<Vec<_>>();
- <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
- let props = (0..b)
- .map(|k| Property {
- key: property_key(k as usize),
- value: property_value(),
})
.collect::<Vec<_>>();
- let item = create_max_item(&collection, &owner, owner.clone())?;
- <Pallet<T>>::set_token_properties(
- &collection,
- &owner,
- item,
- props.into_iter(),
- &Unlimited,
- )?;
- let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
#[block]
{
- <Pallet<T>>::delete_token_properties(
- &collection,
- &owner,
- item,
- to_delete.into_iter(),
- &Unlimited,
- )?;
- }
-
- Ok(())
- }
-
- #[benchmark]
- fn token_owner() -> Result<(), BenchmarkError> {
- bench_init! {
- owner: sub; collection: collection(owner);
- owner: cross_from_sub;
- };
- let item = create_max_item(&collection, &owner, owner.clone())?;
-
- #[block]
- {
- collection.token_owner(item).unwrap();
+ <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
}
Ok(())
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -39,24 +39,21 @@
impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
fn create_multiple_items_ex(data: &CreateItemExData<T::CrossAccountId>) -> Weight {
match data {
- CreateItemExData::NFT(t) => <SelfWeightOf<T>>::create_multiple_items_ex(t.len() as u32)
- .saturating_add(write_token_properties_total_weight::<T, _>(
- t.iter().map(|t| t.properties.len() as u32),
- <SelfWeightOf<T>>::write_token_properties,
- )),
+ CreateItemExData::NFT(t) => mint_with_props_weight::<T>(
+ <SelfWeightOf<T>>::create_multiple_items_ex(t.len() as u32),
+ t.iter().map(|t| t.properties.len() as u32),
+ ),
_ => Weight::zero(),
}
}
fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {
- <SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
- write_token_properties_total_weight::<T, _>(
- data.iter().map(|t| match t {
- up_data_structs::CreateItemData::NFT(n) => n.properties.len() as u32,
- _ => 0,
- }),
- <SelfWeightOf<T>>::write_token_properties,
- ),
+ mint_with_props_weight::<T>(
+ <SelfWeightOf<T>>::create_multiple_items(data.len() as u32),
+ data.iter().map(|t| match t {
+ up_data_structs::CreateItemData::NFT(n) => n.properties.len() as u32,
+ _ => 0,
+ }),
)
}
@@ -113,6 +110,16 @@
}
}
+pub(crate) fn mint_with_props_weight<T: Config>(
+ create_no_data_weight: Weight,
+ tokens: impl Iterator<Item = u32> + Clone,
+) -> Weight {
+ create_no_data_weight.saturating_add(write_token_properties_total_weight::<T, _>(
+ tokens,
+ <SelfWeightOf<T>>::write_token_properties,
+ ))
+}
+
fn map_create_data<T: Config>(
data: up_data_structs::CreateItemData,
to: &T::CrossAccountId,
pallets/nonfungible/src/erc.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//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{decode_utf16, REPLACEMENT_CHARACTER},27 convert::TryInto,28};2930use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*, AbiCoder, ToLog};31use frame_support::BoundedVec;32use pallet_common::{33 erc::{static_property::key, CollectionCall, CommonEvmHandler, PrecompileResult},34 eth::{self, TokenUri},35 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, CommonWeightInfo,36};37use pallet_evm::{account::CrossAccountId, PrecompileHandle};38use pallet_evm_coder_substrate::{39 call, dispatch_to_evm,40 execution::{Error, PreDispatch, Result},41 frontier_contract, SubstrateRecorder,42};43use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};44use sp_core::{Get, U256};45use sp_std::{vec, vec::Vec};46use up_data_structs::{47 budget::Budget, CollectionId, CollectionPropertiesVec, Property, PropertyKey,48 PropertyKeyPermission, PropertyPermission, TokenId,49};5051use crate::{52 common::CommonWeights, weights::WeightInfo, AccountBalance, Config, CreateItemData,53 NonfungibleHandle, Pallet, SelfWeightOf, TokenData, TokenProperties, TokensMinted,54};5556/// Nft events.57#[derive(ToLog)]58pub enum ERC721TokenEvent {59 /// The token has been changed.60 TokenChanged {61 /// Token ID.62 #[indexed]63 token_id: U256,64 },65}6667/// Token minting parameters68#[derive(AbiCoder, Default, Debug)]69pub struct MintTokenData {70 /// Minted token owner71 pub owner: eth::CrossAddress,72 /// Minted token properties73 pub properties: Vec<eth::Property>,74}7576frontier_contract! {77 macro_rules! NonfungibleHandle_result {...}78 impl<T: Config> Contract for NonfungibleHandle<T> {...}79}8081fn nesting_budget<T: Config>(recorder: &SubstrateRecorder<T>) -> impl Budget + '_ {82 recorder.weight_calls_budget(<StructureWeight<T>>::find_parent())83}8485/// @title A contract that allows to set and delete token properties and change token property permissions.86#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]87impl<T: Config> NonfungibleHandle<T> {88 /// @notice Set permissions for token property.89 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.90 /// @param key Property key.91 /// @param isMutable Permission to mutate property.92 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.93 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.94 #[solidity(hide)]95 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]96 fn set_token_property_permission(97 &mut self,98 caller: Caller,99 key: String,100 is_mutable: bool,101 collection_admin: bool,102 token_owner: bool,103 ) -> Result<()> {104 let caller = T::CrossAccountId::from_eth(caller);105 <Pallet<T>>::set_token_property_permissions(106 self,107 &caller,108 vec![PropertyKeyPermission {109 key: <Vec<u8>>::from(key)110 .try_into()111 .map_err(|_| "too long key")?,112 permission: PropertyPermission {113 mutable: is_mutable,114 collection_admin,115 token_owner,116 },117 }],118 )119 .map_err(dispatch_to_evm::<T>)120 }121122 /// @notice Set permissions for token property.123 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.124 /// @param permissions Permissions for keys.125 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]126 fn set_token_property_permissions(127 &mut self,128 caller: Caller,129 permissions: Vec<eth::TokenPropertyPermission>,130 ) -> Result<()> {131 let caller = T::CrossAccountId::from_eth(caller);132 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;133134 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)135 .map_err(dispatch_to_evm::<T>)136 }137138 /// @notice Get permissions for token properties.139 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {140 let perms = <Pallet<T>>::token_property_permission(self.id);141 Ok(perms142 .into_iter()143 .map(eth::TokenPropertyPermission::from)144 .collect())145 }146147 /// @notice Set token property value.148 /// @dev Throws error if `msg.sender` has no permission to edit the property.149 /// @param tokenId ID of the token.150 /// @param key Property key.151 /// @param value Property value.152 #[solidity(hide)]153 #[weight(<CommonWeights<T>>::set_token_properties(1))]154 fn set_property(155 &mut self,156 caller: Caller,157 token_id: U256,158 key: String,159 value: Bytes,160 ) -> Result<()> {161 let caller = T::CrossAccountId::from_eth(caller);162 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;163 let key = <Vec<u8>>::from(key)164 .try_into()165 .map_err(|_| "key too long")?;166 let value = value.0.try_into().map_err(|_| "value too long")?;167168 <Pallet<T>>::set_token_property(169 self,170 &caller,171 TokenId(token_id),172 Property { key, value },173 &nesting_budget(&self.recorder),174 )175 .map_err(dispatch_to_evm::<T>)176 }177178 /// @notice Set token properties value.179 /// @dev Throws error if `msg.sender` has no permission to edit the property.180 /// @param tokenId ID of the token.181 /// @param properties settable properties182 #[weight(<CommonWeights<T>>::set_token_properties(properties.len() as u32))]183 fn set_properties(184 &mut self,185 caller: Caller,186 token_id: U256,187 properties: Vec<eth::Property>,188 ) -> Result<()> {189 let caller = T::CrossAccountId::from_eth(caller);190 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;191192 let properties = properties193 .into_iter()194 .map(eth::Property::try_into)195 .collect::<Result<Vec<_>>>()?;196197 <Pallet<T>>::set_token_properties(198 self,199 &caller,200 TokenId(token_id),201 properties.into_iter(),202 &nesting_budget(&self.recorder),203 )204 .map_err(dispatch_to_evm::<T>)205 }206207 /// @notice Delete token property value.208 /// @dev Throws error if `msg.sender` has no permission to edit the property.209 /// @param tokenId ID of the token.210 /// @param key Property key.211 #[solidity(hide)]212 #[weight(<CommonWeights<T>>::delete_token_properties(1))]213 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {214 let caller = T::CrossAccountId::from_eth(caller);215 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;216 let key = <Vec<u8>>::from(key)217 .try_into()218 .map_err(|_| "key too long")?;219220 <Pallet<T>>::delete_token_property(221 self,222 &caller,223 TokenId(token_id),224 key,225 &nesting_budget(&self.recorder),226 )227 .map_err(dispatch_to_evm::<T>)228 }229230 /// @notice Delete token properties value.231 /// @dev Throws error if `msg.sender` has no permission to edit the property.232 /// @param tokenId ID of the token.233 /// @param keys Properties key.234 #[weight(<CommonWeights<T>>::delete_token_properties(keys.len() as u32))]235 fn delete_properties(236 &mut self,237 token_id: U256,238 caller: Caller,239 keys: Vec<String>,240 ) -> Result<()> {241 let caller = T::CrossAccountId::from_eth(caller);242 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;243 let keys = keys244 .into_iter()245 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))246 .collect::<Result<Vec<_>>>()?;247248 <Pallet<T>>::delete_token_properties(249 self,250 &caller,251 TokenId(token_id),252 keys.into_iter(),253 &nesting_budget(&self.recorder),254 )255 .map_err(dispatch_to_evm::<T>)256 }257258 /// @notice Get token property value.259 /// @dev Throws error if key not found260 /// @param tokenId ID of the token.261 /// @param key Property key.262 /// @return Property value bytes263 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {264 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;265 let key = <Vec<u8>>::from(key)266 .try_into()267 .map_err(|_| "key too long")?;268269 let props =270 <TokenProperties<T>>::get((self.id, token_id)).ok_or("token properties not found")?;271 let prop = props.get(&key).ok_or("key not found")?;272273 Ok(prop.to_vec().into())274 }275}276277#[derive(ToLog)]278pub enum ERC721Events {279 /// @dev This emits when ownership of any NFT changes by any mechanism.280 /// This event emits when NFTs are created (`from` == 0) and destroyed281 /// (`to` == 0). Exception: during contract creation, any number of NFTs282 /// may be created and assigned without emitting Transfer. At the time of283 /// any transfer, the approved address for that NFT (if any) is reset to none.284 Transfer {285 #[indexed]286 from: Address,287 #[indexed]288 to: Address,289 #[indexed]290 token_id: U256,291 },292 /// @dev This emits when the approved address for an NFT is changed or293 /// reaffirmed. The zero address indicates there is no approved address.294 /// When a Transfer event emits, this also indicates that the approved295 /// address for that NFT (if any) is reset to none.296 Approval {297 #[indexed]298 owner: Address,299 #[indexed]300 approved: Address,301 #[indexed]302 token_id: U256,303 },304 /// @dev This emits when an operator is enabled or disabled for an owner.305 /// The operator can manage all NFTs of the owner.306 #[allow(dead_code)]307 ApprovalForAll {308 #[indexed]309 owner: Address,310 #[indexed]311 operator: Address,312 approved: bool,313 },314}315316/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension317/// @dev See https://eips.ethereum.org/EIPS/eip-721318#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]319impl<T: Config> NonfungibleHandle<T>320where321 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,322{323 /// @notice A descriptive name for a collection of NFTs in this contract324 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`325 #[solidity(hide, rename_selector = "name")]326 fn name_proxy(&self) -> String {327 self.name()328 }329330 /// @notice An abbreviated name for NFTs in this contract331 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`332 #[solidity(hide, rename_selector = "symbol")]333 fn symbol_proxy(&self) -> String {334 self.symbol()335 }336337 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.338 ///339 /// @dev If the token has a `url` property and it is not empty, it is returned.340 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.341 /// If the collection property `baseURI` is empty or absent, return "" (empty string)342 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix343 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).344 ///345 /// @return token's const_metadata346 #[solidity(rename_selector = "tokenURI")]347 fn token_uri(&self, token_id: U256) -> Result<String> {348 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;349350 match get_token_property(self, token_id_u32, &key::url()).as_deref() {351 Err(_) | Ok("") => (),352 Ok(url) => {353 return Ok(url.into());354 }355 };356357 let base_uri =358 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())359 .map(BoundedVec::into_inner)360 .map(String::from_utf8)361 .transpose()362 .map_err(|e| {363 Error::Revert(alloc::format!(364 "can not convert value \"baseURI\" to string with error \"{e}\""365 ))366 })?;367368 let base_uri = match base_uri.as_deref() {369 None | Some("") => {370 return Ok("".into());371 }372 Some(base_uri) => base_uri.into(),373 };374375 Ok(376 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {377 Err(_) | Ok("") => base_uri,378 Ok(suffix) => base_uri + suffix,379 },380 )381 }382}383384/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension385/// @dev See https://eips.ethereum.org/EIPS/eip-721386#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]387impl<T: Config> NonfungibleHandle<T> {388 /// @notice Enumerate valid NFTs389 /// @param index A counter less than `totalSupply()`390 /// @return The token identifier for the `index`th NFT,391 /// (sort order not specified)392 fn token_by_index(&self, index: U256) -> U256 {393 index394 }395396 /// @dev Not implemented397 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {398 // TODO: Not implemetable399 Err("not implemented".into())400 }401402 /// @notice Count NFTs tracked by this contract403 /// @return A count of valid NFTs tracked by this contract, where each one of404 /// them has an assigned and queryable owner not equal to the zero address405 fn total_supply(&self) -> Result<U256> {406 self.consume_store_reads(1)?;407 Ok(<Pallet<T>>::total_supply(self).into())408 }409}410411/// @title ERC-721 Non-Fungible Token Standard412/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md413#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]414impl<T: Config> NonfungibleHandle<T> {415 /// @notice Count all NFTs assigned to an owner416 /// @dev NFTs assigned to the zero address are considered invalid, and this417 /// function throws for queries about the zero address.418 /// @param owner An address for whom to query the balance419 /// @return The number of NFTs owned by `owner`, possibly zero420 fn balance_of(&self, owner: Address) -> Result<U256> {421 self.consume_store_reads(1)?;422 let owner = T::CrossAccountId::from_eth(owner);423 let balance = <AccountBalance<T>>::get((self.id, owner));424 Ok(balance.into())425 }426 /// @notice Find the owner of an NFT427 /// @dev NFTs assigned to zero address are considered invalid, and queries428 /// about them do throw.429 /// @param tokenId The identifier for an NFT430 /// @return The address of the owner of the NFT431 fn owner_of(&self, token_id: U256) -> Result<Address> {432 self.consume_store_reads(1)?;433 let token: TokenId = token_id.try_into()?;434 Ok(*<TokenData<T>>::get((self.id, token))435 .ok_or("token not found")?436 .owner437 .as_eth())438 }439 /// @dev Not implemented440 #[solidity(rename_selector = "safeTransferFrom")]441 fn safe_transfer_from_with_data(442 &mut self,443 _from: Address,444 _to: Address,445 _token_id: U256,446 _data: Bytes,447 ) -> Result<()> {448 // TODO: Not implemetable449 Err("not implemented".into())450 }451 /// @dev Not implemented452 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {453 // TODO: Not implemetable454 Err("not implemented".into())455 }456457 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE458 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE459 /// THEY MAY BE PERMANENTLY LOST460 /// @dev Throws unless `msg.sender` is the current owner or an authorized461 /// operator for this NFT. Throws if `from` is not the current owner. Throws462 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.463 /// @param from The current owner of the NFT464 /// @param to The new owner465 /// @param tokenId The NFT to transfer466 #[weight(<CommonWeights<T>>::transfer_from())]467 fn transfer_from(468 &mut self,469 caller: Caller,470 from: Address,471 to: Address,472 token_id: U256,473 ) -> Result<()> {474 let caller = T::CrossAccountId::from_eth(caller);475 let from = T::CrossAccountId::from_eth(from);476 let to = T::CrossAccountId::from_eth(to);477 let token = token_id.try_into()?;478479 <Pallet<T>>::transfer_from(480 self,481 &caller,482 &from,483 &to,484 token,485 &nesting_budget(&self.recorder),486 )487 .map_err(|e| dispatch_to_evm::<T>(e.error))?;488 Ok(())489 }490491 /// @notice Set or reaffirm the approved address for an NFT492 /// @dev The zero address indicates there is no approved address.493 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized494 /// operator of the current owner.495 /// @param approved The new approved NFT controller496 /// @param tokenId The NFT to approve497 #[weight(<SelfWeightOf<T>>::approve())]498 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {499 let caller = T::CrossAccountId::from_eth(caller);500 let approved = T::CrossAccountId::from_eth(approved);501 let token = token_id.try_into()?;502503 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))504 .map_err(dispatch_to_evm::<T>)?;505 Ok(())506 }507508 /// @notice Sets or unsets the approval of a given operator.509 /// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.510 /// @param operator Operator511 /// @param approved Should operator status be granted or revoked?512 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]513 fn set_approval_for_all(514 &mut self,515 caller: Caller,516 operator: Address,517 approved: bool,518 ) -> Result<()> {519 let caller = T::CrossAccountId::from_eth(caller);520 let operator = T::CrossAccountId::from_eth(operator);521522 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)523 .map_err(dispatch_to_evm::<T>)?;524 Ok(())525 }526527 /// @notice Get the approved address for a single NFT528 /// @dev Throws if `tokenId` is not a valid NFT529 /// @param tokenId The NFT to find the approved address for530 /// @return The approved address for this NFT, or the zero address if there is none531 fn get_approved(&self, token_id: U256) -> Result<Address> {532 let token_id = token_id.try_into()?;533 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;534 Ok(if let Some(operator) = operator {535 *operator.as_eth()536 } else {537 Address::zero()538 })539 }540541 /// @notice Tells whether the given `owner` approves the `operator`.542 #[weight(<SelfWeightOf<T>>::allowance_for_all())]543 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {544 let owner = T::CrossAccountId::from_eth(owner);545 let operator = T::CrossAccountId::from_eth(operator);546547 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))548 }549}550551/// @title ERC721 Token that can be irreversibly burned (destroyed).552#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]553impl<T: Config> NonfungibleHandle<T> {554 /// @notice Burns a specific ERC721 token.555 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized556 /// operator of the current owner.557 /// @param tokenId The NFT to approve558 #[weight(<SelfWeightOf<T>>::burn_item())]559 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {560 let caller = T::CrossAccountId::from_eth(caller);561 let token = token_id.try_into()?;562563 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;564 Ok(())565 }566}567568/// @title ERC721 minting logic.569#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]570impl<T: Config> NonfungibleHandle<T> {571 /// @notice Function to mint a token.572 /// @param to The new owner573 /// @return uint256 The id of the newly minted token574 #[weight(<SelfWeightOf<T>>::create_item())]575 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {576 let token_id: U256 = <TokensMinted<T>>::get(self.id)577 .checked_add(1)578 .ok_or("item id overflow")?579 .into();580 self.mint_check_id(caller, to, token_id)?;581 Ok(token_id)582 }583584 /// @notice Function to mint a token.585 /// @dev `tokenId` should be obtained with `nextTokenId` method,586 /// unlike standard, you can't specify it manually587 /// @param to The new owner588 /// @param tokenId ID of the minted NFT589 #[solidity(hide, rename_selector = "mint")]590 #[weight(<SelfWeightOf<T>>::create_item())]591 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {592 let caller = T::CrossAccountId::from_eth(caller);593 let to = T::CrossAccountId::from_eth(to);594 let token_id: u32 = token_id.try_into()?;595596 if <TokensMinted<T>>::get(self.id)597 .checked_add(1)598 .ok_or("item id overflow")?599 != token_id600 {601 return Err("item id should be next".into());602 }603604 <Pallet<T>>::create_item(605 self,606 &caller,607 CreateItemData::<T> {608 properties: BoundedVec::default(),609 owner: to,610 },611 &nesting_budget(&self.recorder),612 )613 .map_err(dispatch_to_evm::<T>)?;614615 Ok(true)616 }617618 /// @notice Function to mint token with the given tokenUri.619 /// @param to The new owner620 /// @param tokenUri Token URI that would be stored in the NFT properties621 /// @return uint256 The id of the newly minted token622 #[solidity(rename_selector = "mintWithTokenURI")]623 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]624 fn mint_with_token_uri(625 &mut self,626 caller: Caller,627 to: Address,628 token_uri: String,629 ) -> Result<U256> {630 let token_id: U256 = <TokensMinted<T>>::get(self.id)631 .checked_add(1)632 .ok_or("item id overflow")?633 .into();634 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;635 Ok(token_id)636 }637638 /// @notice Function to mint token with the given tokenUri.639 /// @dev `tokenId` should be obtained with `nextTokenId` method,640 /// unlike standard, you can't specify it manually641 /// @param to The new owner642 /// @param tokenId ID of the minted NFT643 /// @param tokenUri Token URI that would be stored in the NFT properties644 #[solidity(hide, rename_selector = "mintWithTokenURI")]645 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]646 fn mint_with_token_uri_check_id(647 &mut self,648 caller: Caller,649 to: Address,650 token_id: U256,651 token_uri: String,652 ) -> Result<bool> {653 let key = key::url();654 let permission = get_token_permission::<T>(self.id, &key)?;655 if !permission.collection_admin {656 return Err("operation is not allowed".into());657 }658659 let caller = T::CrossAccountId::from_eth(caller);660 let to = T::CrossAccountId::from_eth(to);661 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;662663 if <TokensMinted<T>>::get(self.id)664 .checked_add(1)665 .ok_or("item id overflow")?666 != token_id667 {668 return Err("item id should be next".into());669 }670671 let mut properties = CollectionPropertiesVec::default();672 properties673 .try_push(Property {674 key,675 value: token_uri676 .into_bytes()677 .try_into()678 .map_err(|_| "token uri is too long")?,679 })680 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;681682 <Pallet<T>>::create_item(683 self,684 &caller,685 CreateItemData::<T> {686 properties,687 owner: to,688 },689 &nesting_budget(&self.recorder),690 )691 .map_err(dispatch_to_evm::<T>)?;692 Ok(true)693 }694}695696fn get_token_property<T: Config>(697 collection: &CollectionHandle<T>,698 token_id: u32,699 key: &up_data_structs::PropertyKey,700) -> Result<String> {701 collection.consume_store_reads(1)?;702 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))703 .map_err(|_| Error::Revert("token properties not found".into()))?;704 if let Some(property) = properties.get(key) {705 return Ok(String::from_utf8_lossy(property).into());706 }707708 Err("property tokenURI not found".into())709}710711fn get_token_permission<T: Config>(712 collection_id: CollectionId,713 key: &PropertyKey,714) -> Result<PropertyPermission> {715 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)716 .map_err(|_| Error::Revert("no permissions for collection".into()))?;717 let a = token_property_permissions718 .get(key)719 .map(Clone::clone)720 .ok_or_else(|| {721 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();722 Error::Revert(alloc::format!("no permission for key {key}"))723 })?;724 Ok(a)725}726727/// @title Unique extensions for ERC721.728#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]729impl<T: Config> NonfungibleHandle<T>730where731 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,732{733 /// @notice A descriptive name for a collection of NFTs in this contract734 fn name(&self) -> String {735 decode_utf16(self.name.iter().copied())736 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))737 .collect::<String>()738 }739740 /// @notice An abbreviated name for NFTs in this contract741 fn symbol(&self) -> String {742 String::from_utf8_lossy(&self.token_prefix).into()743 }744745 /// @notice A description for the collection.746 fn description(&self) -> String {747 decode_utf16(self.description.iter().copied())748 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))749 .collect::<String>()750 }751752 /// Returns the owner (in cross format) of the token.753 ///754 /// @param tokenId Id for the token.755 #[solidity(hide)]756 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {757 Self::owner_of_cross(self, token_id)758 }759760 /// Returns the owner (in cross format) of the token.761 ///762 /// @param tokenId Id for the token.763 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {764 Self::token_owner(self, token_id.try_into()?)765 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))766 .map_err(|_| Error::Revert("token not found".into()))767 }768769 /// @notice Count all NFTs assigned to an owner770 /// @param owner An cross address for whom to query the balance771 /// @return The number of NFTs owned by `owner`, possibly zero772 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {773 self.consume_store_reads(1)?;774 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));775 Ok(balance.into())776 }777778 /// Returns the token properties.779 ///780 /// @param tokenId Id for the token.781 /// @param keys Properties keys. Empty keys for all propertyes.782 /// @return Vector of properties key/value pairs.783 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {784 let keys = keys785 .into_iter()786 .map(|key| {787 <Vec<u8>>::from(key)788 .try_into()789 .map_err(|_| Error::Revert("key too large".into()))790 })791 .collect::<Result<Vec<_>>>()?;792793 <Self as CommonCollectionOperations<T>>::token_properties(794 self,795 token_id.try_into()?,796 if keys.is_empty() { None } else { Some(keys) },797 )798 .into_iter()799 .map(eth::Property::try_from)800 .collect::<Result<Vec<_>>>()801 }802803 /// @notice Set or reaffirm the approved address for an NFT804 /// @dev The zero address indicates there is no approved address.805 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized806 /// operator of the current owner.807 /// @param approved The new substrate address approved NFT controller808 /// @param tokenId The NFT to approve809 #[weight(<SelfWeightOf<T>>::approve())]810 fn approve_cross(811 &mut self,812 caller: Caller,813 approved: eth::CrossAddress,814 token_id: U256,815 ) -> Result<()> {816 let caller = T::CrossAccountId::from_eth(caller);817 let approved = approved.into_sub_cross_account::<T>()?;818 let token = token_id.try_into()?;819820 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))821 .map_err(dispatch_to_evm::<T>)?;822 Ok(())823 }824825 /// @notice Transfer ownership of an NFT826 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`827 /// is the zero address. Throws if `tokenId` is not a valid NFT.828 /// @param to The new owner829 /// @param tokenId The NFT to transfer830 #[weight(<CommonWeights<T>>::transfer())]831 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {832 let caller = T::CrossAccountId::from_eth(caller);833 let to = T::CrossAccountId::from_eth(to);834 let token = token_id.try_into()?;835836 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))837 .map_err(|e| dispatch_to_evm::<T>(e.error))?;838 Ok(())839 }840841 /// @notice Transfer ownership of an NFT842 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`843 /// is the zero address. Throws if `tokenId` is not a valid NFT.844 /// @param to The new owner845 /// @param tokenId The NFT to transfer846 #[weight(<CommonWeights<T>>::transfer())]847 fn transfer_cross(848 &mut self,849 caller: Caller,850 to: eth::CrossAddress,851 token_id: U256,852 ) -> Result<()> {853 let caller = T::CrossAccountId::from_eth(caller);854 let to = to.into_sub_cross_account::<T>()?;855 let token = token_id.try_into()?;856857 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))858 .map_err(|e| dispatch_to_evm::<T>(e.error))?;859 Ok(())860 }861862 /// @notice Transfer ownership of an NFT from cross account address to cross account address863 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`864 /// is the zero address. Throws if `tokenId` is not a valid NFT.865 /// @param from Cross acccount address of current owner866 /// @param to Cross acccount address of new owner867 /// @param tokenId The NFT to transfer868 #[weight(<CommonWeights<T>>::transfer_from())]869 fn transfer_from_cross(870 &mut self,871 caller: Caller,872 from: eth::CrossAddress,873 to: eth::CrossAddress,874 token_id: U256,875 ) -> Result<()> {876 let caller = T::CrossAccountId::from_eth(caller);877 let from = from.into_sub_cross_account::<T>()?;878 let to = to.into_sub_cross_account::<T>()?;879 let token_id = token_id.try_into()?;880881 Pallet::<T>::transfer_from(882 self,883 &caller,884 &from,885 &to,886 token_id,887 &nesting_budget(&self.recorder),888 )889 .map_err(|e| dispatch_to_evm::<T>(e.error))?;890 Ok(())891 }892893 /// @notice Burns a specific ERC721 token.894 /// @dev Throws unless `msg.sender` is the current owner or an authorized895 /// operator for this NFT. Throws if `from` is not the current owner. Throws896 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.897 /// @param from The current owner of the NFT898 /// @param tokenId The NFT to transfer899 #[solidity(hide)]900 #[weight(<SelfWeightOf<T>>::burn_from())]901 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {902 let caller = T::CrossAccountId::from_eth(caller);903 let from = T::CrossAccountId::from_eth(from);904 let token = token_id.try_into()?;905906 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))907 .map_err(dispatch_to_evm::<T>)?;908 Ok(())909 }910911 /// @notice Burns a specific ERC721 token.912 /// @dev Throws unless `msg.sender` is the current owner or an authorized913 /// operator for this NFT. Throws if `from` is not the current owner. Throws914 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.915 /// @param from The current owner of the NFT916 /// @param tokenId The NFT to transfer917 #[weight(<SelfWeightOf<T>>::burn_from())]918 fn burn_from_cross(919 &mut self,920 caller: Caller,921 from: eth::CrossAddress,922 token_id: U256,923 ) -> Result<()> {924 let caller = T::CrossAccountId::from_eth(caller);925 let from = from.into_sub_cross_account::<T>()?;926 let token = token_id.try_into()?;927928 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))929 .map_err(dispatch_to_evm::<T>)?;930 Ok(())931 }932933 /// @notice Returns next free NFT ID.934 fn next_token_id(&self) -> Result<U256> {935 self.consume_store_reads(1)?;936 Ok(<Pallet<T>>::next_token_id(self)937 .map_err(dispatch_to_evm::<T>)?938 .into())939 }940941 /// @notice Function to mint multiple tokens.942 /// @dev `tokenIds` should be an array of consecutive numbers and first number943 /// should be obtained with `nextTokenId` method944 /// @param to The new owner945 /// @param tokenIds IDs of the minted NFTs946 #[solidity(hide)]947 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]948 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {949 let caller = T::CrossAccountId::from_eth(caller);950 let to = T::CrossAccountId::from_eth(to);951 let mut expected_index = <TokensMinted<T>>::get(self.id)952 .checked_add(1)953 .ok_or("item id overflow")?;954955 let total_tokens = token_ids.len();956 for id in token_ids.into_iter() {957 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;958 if id != expected_index {959 return Err("item id should be next".into());960 }961 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;962 }963 let data = (0..total_tokens)964 .map(|_| CreateItemData::<T> {965 properties: BoundedVec::default(),966 owner: to.clone(),967 })968 .collect();969970 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))971 .map_err(dispatch_to_evm::<T>)?;972 Ok(true)973 }974975 /// @notice Function to mint a token.976 /// @param data Array of pairs of token owner and token's properties for minted token977 #[weight(<SelfWeightOf<T>>::create_multiple_items(data.len() as u32) + <SelfWeightOf<T>>::set_token_properties(data.len() as u32))]978 fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {979 let caller = T::CrossAccountId::from_eth(caller);980981 let mut create_nft_data = Vec::with_capacity(data.len());982 for MintTokenData { owner, properties } in data {983 let owner = owner.into_sub_cross_account::<T>()?;984 create_nft_data.push(CreateItemData::<T> {985 properties: properties986 .into_iter()987 .map(|property| property.try_into())988 .collect::<Result<Vec<_>>>()?989 .try_into()990 .map_err(|_| "too many properties")?,991 owner,992 });993 }994995 <Pallet<T>>::create_multiple_items(996 self,997 &caller,998 create_nft_data,999 &nesting_budget(&self.recorder),1000 )1001 .map_err(dispatch_to_evm::<T>)?;1002 Ok(true)1003 }10041005 /// @notice Function to mint multiple tokens with the given tokenUris.1006 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1007 /// numbers and first number should be obtained with `nextTokenId` method1008 /// @param to The new owner1009 /// @param tokens array of pairs of token ID and token URI for minted tokens1010 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1011 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]1012 fn mint_bulk_with_token_uri(1013 &mut self,1014 caller: Caller,1015 to: Address,1016 tokens: Vec<TokenUri>,1017 ) -> Result<bool> {1018 let key = key::url();1019 let caller = T::CrossAccountId::from_eth(caller);1020 let to = T::CrossAccountId::from_eth(to);1021 let mut expected_index = <TokensMinted<T>>::get(self.id)1022 .checked_add(1)1023 .ok_or("item id overflow")?;10241025 let mut data = Vec::with_capacity(tokens.len());1026 for TokenUri { id, uri } in tokens {1027 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1028 if id != expected_index {1029 return Err("item id should be next".into());1030 }1031 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10321033 let mut properties = CollectionPropertiesVec::default();1034 properties1035 .try_push(Property {1036 key: key.clone(),1037 value: uri1038 .into_bytes()1039 .try_into()1040 .map_err(|_| "token uri is too long")?,1041 })1042 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;10431044 data.push(CreateItemData::<T> {1045 properties,1046 owner: to.clone(),1047 });1048 }10491050 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))1051 .map_err(dispatch_to_evm::<T>)?;1052 Ok(true)1053 }10541055 /// @notice Function to mint a token.1056 /// @param to The new owner crossAccountId1057 /// @param properties Properties of minted token1058 /// @return uint256 The id of the newly minted token1059 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1060 fn mint_cross(1061 &mut self,1062 caller: Caller,1063 to: eth::CrossAddress,1064 properties: Vec<eth::Property>,1065 ) -> Result<U256> {1066 let token_id = <TokensMinted<T>>::get(self.id)1067 .checked_add(1)1068 .ok_or("item id overflow")?;10691070 let to = to.into_sub_cross_account::<T>()?;10711072 let properties = properties1073 .into_iter()1074 .map(eth::Property::try_into)1075 .collect::<Result<Vec<_>>>()?1076 .try_into()1077 .map_err(|_| Error::Revert("too many properties".to_string()))?;10781079 let caller = T::CrossAccountId::from_eth(caller);10801081 <Pallet<T>>::create_item(1082 self,1083 &caller,1084 CreateItemData::<T> {1085 properties,1086 owner: to,1087 },1088 &nesting_budget(&self.recorder),1089 )1090 .map_err(dispatch_to_evm::<T>)?;10911092 Ok(token_id.into())1093 }10941095 /// @notice Returns collection helper contract address1096 fn collection_helper_address(&self) -> Address {1097 T::ContractAddress::get()1098 }1099}11001101#[solidity_interface(1102 name = UniqueNFT,1103 is(1104 ERC721,1105 ERC721Enumerable,1106 ERC721UniqueExtensions,1107 ERC721UniqueMintable,1108 ERC721Burnable,1109 ERC721Metadata(if(this.flags.erc721metadata)),1110 Collection(via(common_mut returns CollectionHandle<T>)),1111 TokenProperties,1112 ),1113 enum(derive(PreDispatch)),1114)]1115impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11161117// Not a tests, but code generators1118generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1119generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11201121impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1122where1123 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1124{1125 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11261127 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1128 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1129 }1130}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/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{decode_utf16, REPLACEMENT_CHARACTER},27 convert::TryInto,28};2930use evm_coder::{abi::AbiType, generate_stubgen, solidity_interface, types::*, AbiCoder, ToLog};31use frame_support::BoundedVec;32use pallet_common::{33 erc::{static_property::key, CollectionCall, CommonEvmHandler, PrecompileResult},34 eth::{self, TokenUri},35 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations, CommonWeightInfo,36};37use pallet_evm::{account::CrossAccountId, PrecompileHandle};38use pallet_evm_coder_substrate::{39 call, dispatch_to_evm,40 execution::{Error, PreDispatch, Result},41 frontier_contract, SubstrateRecorder,42};43use pallet_structure::{weights::WeightInfo as _, SelfWeightOf as StructureWeight};44use sp_core::{Get, U256};45use sp_std::{vec, vec::Vec};46use up_data_structs::{47 budget::Budget, CollectionId, CollectionPropertiesVec, Property, PropertyKey,48 PropertyKeyPermission, PropertyPermission, TokenId,49};5051use crate::{52 common::{mint_with_props_weight, CommonWeights},53 weights::WeightInfo,54 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, SelfWeightOf, TokenData,55 TokenProperties, TokensMinted,56};5758/// Nft events.59#[derive(ToLog)]60pub enum ERC721TokenEvent {61 /// The token has been changed.62 TokenChanged {63 /// Token ID.64 #[indexed]65 token_id: U256,66 },67}6869/// Token minting parameters70#[derive(AbiCoder, Default, Debug)]71pub struct MintTokenData {72 /// Minted token owner73 pub owner: eth::CrossAddress,74 /// Minted token properties75 pub properties: Vec<eth::Property>,76}7778frontier_contract! {79 macro_rules! NonfungibleHandle_result {...}80 impl<T: Config> Contract for NonfungibleHandle<T> {...}81}8283fn nesting_budget<T: Config>(recorder: &SubstrateRecorder<T>) -> impl Budget + '_ {84 recorder.weight_calls_budget(<StructureWeight<T>>::find_parent())85}8687/// @title A contract that allows to set and delete token properties and change token property permissions.88#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]89impl<T: Config> NonfungibleHandle<T> {90 /// @notice Set permissions for token property.91 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.92 /// @param key Property key.93 /// @param isMutable Permission to mutate property.94 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.95 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.96 #[solidity(hide)]97 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]98 fn set_token_property_permission(99 &mut self,100 caller: Caller,101 key: String,102 is_mutable: bool,103 collection_admin: bool,104 token_owner: bool,105 ) -> Result<()> {106 let caller = T::CrossAccountId::from_eth(caller);107 <Pallet<T>>::set_token_property_permissions(108 self,109 &caller,110 vec![PropertyKeyPermission {111 key: <Vec<u8>>::from(key)112 .try_into()113 .map_err(|_| "too long key")?,114 permission: PropertyPermission {115 mutable: is_mutable,116 collection_admin,117 token_owner,118 },119 }],120 )121 .map_err(dispatch_to_evm::<T>)122 }123124 /// @notice Set permissions for token property.125 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.126 /// @param permissions Permissions for keys.127 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]128 fn set_token_property_permissions(129 &mut self,130 caller: Caller,131 permissions: Vec<eth::TokenPropertyPermission>,132 ) -> Result<()> {133 let caller = T::CrossAccountId::from_eth(caller);134 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;135136 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)137 .map_err(dispatch_to_evm::<T>)138 }139140 /// @notice Get permissions for token properties.141 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {142 let perms = <Pallet<T>>::token_property_permission(self.id);143 Ok(perms144 .into_iter()145 .map(eth::TokenPropertyPermission::from)146 .collect())147 }148149 /// @notice Set token property value.150 /// @dev Throws error if `msg.sender` has no permission to edit the property.151 /// @param tokenId ID of the token.152 /// @param key Property key.153 /// @param value Property value.154 #[solidity(hide)]155 #[weight(<CommonWeights<T>>::set_token_properties(1))]156 fn set_property(157 &mut self,158 caller: Caller,159 token_id: U256,160 key: String,161 value: Bytes,162 ) -> Result<()> {163 let caller = T::CrossAccountId::from_eth(caller);164 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;165 let key = <Vec<u8>>::from(key)166 .try_into()167 .map_err(|_| "key too long")?;168 let value = value.0.try_into().map_err(|_| "value too long")?;169170 <Pallet<T>>::set_token_property(171 self,172 &caller,173 TokenId(token_id),174 Property { key, value },175 &nesting_budget(&self.recorder),176 )177 .map_err(dispatch_to_evm::<T>)178 }179180 /// @notice Set token properties value.181 /// @dev Throws error if `msg.sender` has no permission to edit the property.182 /// @param tokenId ID of the token.183 /// @param properties settable properties184 #[weight(<CommonWeights<T>>::set_token_properties(properties.len() as u32))]185 fn set_properties(186 &mut self,187 caller: Caller,188 token_id: U256,189 properties: Vec<eth::Property>,190 ) -> Result<()> {191 let caller = T::CrossAccountId::from_eth(caller);192 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;193194 let properties = properties195 .into_iter()196 .map(eth::Property::try_into)197 .collect::<Result<Vec<_>>>()?;198199 <Pallet<T>>::set_token_properties(200 self,201 &caller,202 TokenId(token_id),203 properties.into_iter(),204 &nesting_budget(&self.recorder),205 )206 .map_err(dispatch_to_evm::<T>)207 }208209 /// @notice Delete token property value.210 /// @dev Throws error if `msg.sender` has no permission to edit the property.211 /// @param tokenId ID of the token.212 /// @param key Property key.213 #[solidity(hide)]214 #[weight(<CommonWeights<T>>::delete_token_properties(1))]215 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {216 let caller = T::CrossAccountId::from_eth(caller);217 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;218 let key = <Vec<u8>>::from(key)219 .try_into()220 .map_err(|_| "key too long")?;221222 <Pallet<T>>::delete_token_property(223 self,224 &caller,225 TokenId(token_id),226 key,227 &nesting_budget(&self.recorder),228 )229 .map_err(dispatch_to_evm::<T>)230 }231232 /// @notice Delete token properties value.233 /// @dev Throws error if `msg.sender` has no permission to edit the property.234 /// @param tokenId ID of the token.235 /// @param keys Properties key.236 #[weight(<CommonWeights<T>>::delete_token_properties(keys.len() as u32))]237 fn delete_properties(238 &mut self,239 token_id: U256,240 caller: Caller,241 keys: Vec<String>,242 ) -> Result<()> {243 let caller = T::CrossAccountId::from_eth(caller);244 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;245 let keys = keys246 .into_iter()247 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))248 .collect::<Result<Vec<_>>>()?;249250 <Pallet<T>>::delete_token_properties(251 self,252 &caller,253 TokenId(token_id),254 keys.into_iter(),255 &nesting_budget(&self.recorder),256 )257 .map_err(dispatch_to_evm::<T>)258 }259260 /// @notice Get token property value.261 /// @dev Throws error if key not found262 /// @param tokenId ID of the token.263 /// @param key Property key.264 /// @return Property value bytes265 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {266 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;267 let key = <Vec<u8>>::from(key)268 .try_into()269 .map_err(|_| "key too long")?;270271 let props =272 <TokenProperties<T>>::get((self.id, token_id)).ok_or("token properties not found")?;273 let prop = props.get(&key).ok_or("key not found")?;274275 Ok(prop.to_vec().into())276 }277}278279#[derive(ToLog)]280pub enum ERC721Events {281 /// @dev This emits when ownership of any NFT changes by any mechanism.282 /// This event emits when NFTs are created (`from` == 0) and destroyed283 /// (`to` == 0). Exception: during contract creation, any number of NFTs284 /// may be created and assigned without emitting Transfer. At the time of285 /// any transfer, the approved address for that NFT (if any) is reset to none.286 Transfer {287 #[indexed]288 from: Address,289 #[indexed]290 to: Address,291 #[indexed]292 token_id: U256,293 },294 /// @dev This emits when the approved address for an NFT is changed or295 /// reaffirmed. The zero address indicates there is no approved address.296 /// When a Transfer event emits, this also indicates that the approved297 /// address for that NFT (if any) is reset to none.298 Approval {299 #[indexed]300 owner: Address,301 #[indexed]302 approved: Address,303 #[indexed]304 token_id: U256,305 },306 /// @dev This emits when an operator is enabled or disabled for an owner.307 /// The operator can manage all NFTs of the owner.308 #[allow(dead_code)]309 ApprovalForAll {310 #[indexed]311 owner: Address,312 #[indexed]313 operator: Address,314 approved: bool,315 },316}317318/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension319/// @dev See https://eips.ethereum.org/EIPS/eip-721320#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]321impl<T: Config> NonfungibleHandle<T>322where323 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,324{325 /// @notice A descriptive name for a collection of NFTs in this contract326 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`327 #[solidity(hide, rename_selector = "name")]328 fn name_proxy(&self) -> String {329 self.name()330 }331332 /// @notice An abbreviated name for NFTs in this contract333 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`334 #[solidity(hide, rename_selector = "symbol")]335 fn symbol_proxy(&self) -> String {336 self.symbol()337 }338339 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.340 ///341 /// @dev If the token has a `url` property and it is not empty, it is returned.342 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.343 /// If the collection property `baseURI` is empty or absent, return "" (empty string)344 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix345 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).346 ///347 /// @return token's const_metadata348 #[solidity(rename_selector = "tokenURI")]349 fn token_uri(&self, token_id: U256) -> Result<String> {350 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;351352 match get_token_property(self, token_id_u32, &key::url()).as_deref() {353 Err(_) | Ok("") => (),354 Ok(url) => {355 return Ok(url.into());356 }357 };358359 let base_uri =360 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())361 .map(BoundedVec::into_inner)362 .map(String::from_utf8)363 .transpose()364 .map_err(|e| {365 Error::Revert(alloc::format!(366 "can not convert value \"baseURI\" to string with error \"{e}\""367 ))368 })?;369370 let base_uri = match base_uri.as_deref() {371 None | Some("") => {372 return Ok("".into());373 }374 Some(base_uri) => base_uri.into(),375 };376377 Ok(378 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {379 Err(_) | Ok("") => base_uri,380 Ok(suffix) => base_uri + suffix,381 },382 )383 }384}385386/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension387/// @dev See https://eips.ethereum.org/EIPS/eip-721388#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]389impl<T: Config> NonfungibleHandle<T> {390 /// @notice Enumerate valid NFTs391 /// @param index A counter less than `totalSupply()`392 /// @return The token identifier for the `index`th NFT,393 /// (sort order not specified)394 fn token_by_index(&self, index: U256) -> U256 {395 index396 }397398 /// @dev Not implemented399 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {400 // TODO: Not implemetable401 Err("not implemented".into())402 }403404 /// @notice Count NFTs tracked by this contract405 /// @return A count of valid NFTs tracked by this contract, where each one of406 /// them has an assigned and queryable owner not equal to the zero address407 fn total_supply(&self) -> Result<U256> {408 self.consume_store_reads(1)?;409 Ok(<Pallet<T>>::total_supply(self).into())410 }411}412413/// @title ERC-721 Non-Fungible Token Standard414/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md415#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]416impl<T: Config> NonfungibleHandle<T> {417 /// @notice Count all NFTs assigned to an owner418 /// @dev NFTs assigned to the zero address are considered invalid, and this419 /// function throws for queries about the zero address.420 /// @param owner An address for whom to query the balance421 /// @return The number of NFTs owned by `owner`, possibly zero422 fn balance_of(&self, owner: Address) -> Result<U256> {423 self.consume_store_reads(1)?;424 let owner = T::CrossAccountId::from_eth(owner);425 let balance = <AccountBalance<T>>::get((self.id, owner));426 Ok(balance.into())427 }428 /// @notice Find the owner of an NFT429 /// @dev NFTs assigned to zero address are considered invalid, and queries430 /// about them do throw.431 /// @param tokenId The identifier for an NFT432 /// @return The address of the owner of the NFT433 fn owner_of(&self, token_id: U256) -> Result<Address> {434 self.consume_store_reads(1)?;435 let token: TokenId = token_id.try_into()?;436 Ok(*<TokenData<T>>::get((self.id, token))437 .ok_or("token not found")?438 .owner439 .as_eth())440 }441 /// @dev Not implemented442 #[solidity(rename_selector = "safeTransferFrom")]443 fn safe_transfer_from_with_data(444 &mut self,445 _from: Address,446 _to: Address,447 _token_id: U256,448 _data: Bytes,449 ) -> Result<()> {450 // TODO: Not implemetable451 Err("not implemented".into())452 }453 /// @dev Not implemented454 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {455 // TODO: Not implemetable456 Err("not implemented".into())457 }458459 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE460 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE461 /// THEY MAY BE PERMANENTLY LOST462 /// @dev Throws unless `msg.sender` is the current owner or an authorized463 /// operator for this NFT. Throws if `from` is not the current owner. Throws464 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.465 /// @param from The current owner of the NFT466 /// @param to The new owner467 /// @param tokenId The NFT to transfer468 #[weight(<CommonWeights<T>>::transfer_from())]469 fn transfer_from(470 &mut self,471 caller: Caller,472 from: Address,473 to: Address,474 token_id: U256,475 ) -> Result<()> {476 let caller = T::CrossAccountId::from_eth(caller);477 let from = T::CrossAccountId::from_eth(from);478 let to = T::CrossAccountId::from_eth(to);479 let token = token_id.try_into()?;480481 <Pallet<T>>::transfer_from(482 self,483 &caller,484 &from,485 &to,486 token,487 &nesting_budget(&self.recorder),488 )489 .map_err(|e| dispatch_to_evm::<T>(e.error))?;490 Ok(())491 }492493 /// @notice Set or reaffirm the approved address for an NFT494 /// @dev The zero address indicates there is no approved address.495 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized496 /// operator of the current owner.497 /// @param approved The new approved NFT controller498 /// @param tokenId The NFT to approve499 #[weight(<SelfWeightOf<T>>::approve())]500 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {501 let caller = T::CrossAccountId::from_eth(caller);502 let approved = T::CrossAccountId::from_eth(approved);503 let token = token_id.try_into()?;504505 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))506 .map_err(dispatch_to_evm::<T>)?;507 Ok(())508 }509510 /// @notice Sets or unsets the approval of a given operator.511 /// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.512 /// @param operator Operator513 /// @param approved Should operator status be granted or revoked?514 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]515 fn set_approval_for_all(516 &mut self,517 caller: Caller,518 operator: Address,519 approved: bool,520 ) -> Result<()> {521 let caller = T::CrossAccountId::from_eth(caller);522 let operator = T::CrossAccountId::from_eth(operator);523524 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)525 .map_err(dispatch_to_evm::<T>)?;526 Ok(())527 }528529 /// @notice Get the approved address for a single NFT530 /// @dev Throws if `tokenId` is not a valid NFT531 /// @param tokenId The NFT to find the approved address for532 /// @return The approved address for this NFT, or the zero address if there is none533 fn get_approved(&self, token_id: U256) -> Result<Address> {534 let token_id = token_id.try_into()?;535 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;536 Ok(if let Some(operator) = operator {537 *operator.as_eth()538 } else {539 Address::zero()540 })541 }542543 /// @notice Tells whether the given `owner` approves the `operator`.544 #[weight(<SelfWeightOf<T>>::allowance_for_all())]545 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {546 let owner = T::CrossAccountId::from_eth(owner);547 let operator = T::CrossAccountId::from_eth(operator);548549 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))550 }551}552553/// @title ERC721 Token that can be irreversibly burned (destroyed).554#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]555impl<T: Config> NonfungibleHandle<T> {556 /// @notice Burns a specific ERC721 token.557 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized558 /// operator of the current owner.559 /// @param tokenId The NFT to approve560 #[weight(<SelfWeightOf<T>>::burn_item())]561 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {562 let caller = T::CrossAccountId::from_eth(caller);563 let token = token_id.try_into()?;564565 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;566 Ok(())567 }568}569570/// @title ERC721 minting logic.571#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]572impl<T: Config> NonfungibleHandle<T> {573 /// @notice Function to mint a token.574 /// @param to The new owner575 /// @return uint256 The id of the newly minted token576 #[weight(<SelfWeightOf<T>>::create_item())]577 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {578 let token_id: U256 = <TokensMinted<T>>::get(self.id)579 .checked_add(1)580 .ok_or("item id overflow")?581 .into();582 self.mint_check_id(caller, to, token_id)?;583 Ok(token_id)584 }585586 /// @notice Function to mint a token.587 /// @dev `tokenId` should be obtained with `nextTokenId` method,588 /// unlike standard, you can't specify it manually589 /// @param to The new owner590 /// @param tokenId ID of the minted NFT591 #[solidity(hide, rename_selector = "mint")]592 #[weight(<SelfWeightOf<T>>::create_item())]593 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {594 let caller = T::CrossAccountId::from_eth(caller);595 let to = T::CrossAccountId::from_eth(to);596 let token_id: u32 = token_id.try_into()?;597598 if <TokensMinted<T>>::get(self.id)599 .checked_add(1)600 .ok_or("item id overflow")?601 != token_id602 {603 return Err("item id should be next".into());604 }605606 <Pallet<T>>::create_item(607 self,608 &caller,609 CreateItemData::<T> {610 properties: BoundedVec::default(),611 owner: to,612 },613 &nesting_budget(&self.recorder),614 )615 .map_err(dispatch_to_evm::<T>)?;616617 Ok(true)618 }619620 /// @notice Function to mint token with the given tokenUri.621 /// @param to The new owner622 /// @param tokenUri Token URI that would be stored in the NFT properties623 /// @return uint256 The id of the newly minted token624 #[solidity(rename_selector = "mintWithTokenURI")]625 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]626 fn mint_with_token_uri(627 &mut self,628 caller: Caller,629 to: Address,630 token_uri: String,631 ) -> Result<U256> {632 let token_id: U256 = <TokensMinted<T>>::get(self.id)633 .checked_add(1)634 .ok_or("item id overflow")?635 .into();636 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;637 Ok(token_id)638 }639640 /// @notice Function to mint token with the given tokenUri.641 /// @dev `tokenId` should be obtained with `nextTokenId` method,642 /// unlike standard, you can't specify it manually643 /// @param to The new owner644 /// @param tokenId ID of the minted NFT645 /// @param tokenUri Token URI that would be stored in the NFT properties646 #[solidity(hide, rename_selector = "mintWithTokenURI")]647 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]648 fn mint_with_token_uri_check_id(649 &mut self,650 caller: Caller,651 to: Address,652 token_id: U256,653 token_uri: String,654 ) -> Result<bool> {655 let key = key::url();656 let permission = get_token_permission::<T>(self.id, &key)?;657 if !permission.collection_admin {658 return Err("operation is not allowed".into());659 }660661 let caller = T::CrossAccountId::from_eth(caller);662 let to = T::CrossAccountId::from_eth(to);663 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;664665 if <TokensMinted<T>>::get(self.id)666 .checked_add(1)667 .ok_or("item id overflow")?668 != token_id669 {670 return Err("item id should be next".into());671 }672673 let mut properties = CollectionPropertiesVec::default();674 properties675 .try_push(Property {676 key,677 value: token_uri678 .into_bytes()679 .try_into()680 .map_err(|_| "token uri is too long")?,681 })682 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;683684 <Pallet<T>>::create_item(685 self,686 &caller,687 CreateItemData::<T> {688 properties,689 owner: to,690 },691 &nesting_budget(&self.recorder),692 )693 .map_err(dispatch_to_evm::<T>)?;694 Ok(true)695 }696}697698fn get_token_property<T: Config>(699 collection: &CollectionHandle<T>,700 token_id: u32,701 key: &up_data_structs::PropertyKey,702) -> Result<String> {703 collection.consume_store_reads(1)?;704 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))705 .map_err(|_| Error::Revert("token properties not found".into()))?;706 if let Some(property) = properties.get(key) {707 return Ok(String::from_utf8_lossy(property).into());708 }709710 Err("property tokenURI not found".into())711}712713fn get_token_permission<T: Config>(714 collection_id: CollectionId,715 key: &PropertyKey,716) -> Result<PropertyPermission> {717 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)718 .map_err(|_| Error::Revert("no permissions for collection".into()))?;719 let a = token_property_permissions720 .get(key)721 .map(Clone::clone)722 .ok_or_else(|| {723 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();724 Error::Revert(alloc::format!("no permission for key {key}"))725 })?;726 Ok(a)727}728729/// @title Unique extensions for ERC721.730#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]731impl<T: Config> NonfungibleHandle<T>732where733 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,734{735 /// @notice A descriptive name for a collection of NFTs in this contract736 fn name(&self) -> String {737 decode_utf16(self.name.iter().copied())738 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))739 .collect::<String>()740 }741742 /// @notice An abbreviated name for NFTs in this contract743 fn symbol(&self) -> String {744 String::from_utf8_lossy(&self.token_prefix).into()745 }746747 /// @notice A description for the collection.748 fn description(&self) -> String {749 decode_utf16(self.description.iter().copied())750 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))751 .collect::<String>()752 }753754 /// Returns the owner (in cross format) of the token.755 ///756 /// @param tokenId Id for the token.757 #[solidity(hide)]758 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {759 Self::owner_of_cross(self, token_id)760 }761762 /// Returns the owner (in cross format) of the token.763 ///764 /// @param tokenId Id for the token.765 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {766 Self::token_owner(self, token_id.try_into()?)767 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))768 .map_err(|_| Error::Revert("token not found".into()))769 }770771 /// @notice Count all NFTs assigned to an owner772 /// @param owner An cross address for whom to query the balance773 /// @return The number of NFTs owned by `owner`, possibly zero774 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {775 self.consume_store_reads(1)?;776 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));777 Ok(balance.into())778 }779780 /// Returns the token properties.781 ///782 /// @param tokenId Id for the token.783 /// @param keys Properties keys. Empty keys for all propertyes.784 /// @return Vector of properties key/value pairs.785 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {786 let keys = keys787 .into_iter()788 .map(|key| {789 <Vec<u8>>::from(key)790 .try_into()791 .map_err(|_| Error::Revert("key too large".into()))792 })793 .collect::<Result<Vec<_>>>()?;794795 <Self as CommonCollectionOperations<T>>::token_properties(796 self,797 token_id.try_into()?,798 if keys.is_empty() { None } else { Some(keys) },799 )800 .into_iter()801 .map(eth::Property::try_from)802 .collect::<Result<Vec<_>>>()803 }804805 /// @notice Set or reaffirm the approved address for an NFT806 /// @dev The zero address indicates there is no approved address.807 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized808 /// operator of the current owner.809 /// @param approved The new substrate address approved NFT controller810 /// @param tokenId The NFT to approve811 #[weight(<SelfWeightOf<T>>::approve())]812 fn approve_cross(813 &mut self,814 caller: Caller,815 approved: eth::CrossAddress,816 token_id: U256,817 ) -> Result<()> {818 let caller = T::CrossAccountId::from_eth(caller);819 let approved = approved.into_sub_cross_account::<T>()?;820 let token = token_id.try_into()?;821822 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))823 .map_err(dispatch_to_evm::<T>)?;824 Ok(())825 }826827 /// @notice Transfer ownership of an NFT828 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`829 /// is the zero address. Throws if `tokenId` is not a valid NFT.830 /// @param to The new owner831 /// @param tokenId The NFT to transfer832 #[weight(<CommonWeights<T>>::transfer())]833 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {834 let caller = T::CrossAccountId::from_eth(caller);835 let to = T::CrossAccountId::from_eth(to);836 let token = token_id.try_into()?;837838 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))839 .map_err(|e| dispatch_to_evm::<T>(e.error))?;840 Ok(())841 }842843 /// @notice Transfer ownership of an NFT844 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`845 /// is the zero address. Throws if `tokenId` is not a valid NFT.846 /// @param to The new owner847 /// @param tokenId The NFT to transfer848 #[weight(<CommonWeights<T>>::transfer())]849 fn transfer_cross(850 &mut self,851 caller: Caller,852 to: eth::CrossAddress,853 token_id: U256,854 ) -> Result<()> {855 let caller = T::CrossAccountId::from_eth(caller);856 let to = to.into_sub_cross_account::<T>()?;857 let token = token_id.try_into()?;858859 <Pallet<T>>::transfer(self, &caller, &to, token, &nesting_budget(&self.recorder))860 .map_err(|e| dispatch_to_evm::<T>(e.error))?;861 Ok(())862 }863864 /// @notice Transfer ownership of an NFT from cross account address to cross account address865 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`866 /// is the zero address. Throws if `tokenId` is not a valid NFT.867 /// @param from Cross acccount address of current owner868 /// @param to Cross acccount address of new owner869 /// @param tokenId The NFT to transfer870 #[weight(<CommonWeights<T>>::transfer_from())]871 fn transfer_from_cross(872 &mut self,873 caller: Caller,874 from: eth::CrossAddress,875 to: eth::CrossAddress,876 token_id: U256,877 ) -> Result<()> {878 let caller = T::CrossAccountId::from_eth(caller);879 let from = from.into_sub_cross_account::<T>()?;880 let to = to.into_sub_cross_account::<T>()?;881 let token_id = token_id.try_into()?;882883 Pallet::<T>::transfer_from(884 self,885 &caller,886 &from,887 &to,888 token_id,889 &nesting_budget(&self.recorder),890 )891 .map_err(|e| dispatch_to_evm::<T>(e.error))?;892 Ok(())893 }894895 /// @notice Burns a specific ERC721 token.896 /// @dev Throws unless `msg.sender` is the current owner or an authorized897 /// operator for this NFT. Throws if `from` is not the current owner. Throws898 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.899 /// @param from The current owner of the NFT900 /// @param tokenId The NFT to transfer901 #[solidity(hide)]902 #[weight(<SelfWeightOf<T>>::burn_from())]903 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {904 let caller = T::CrossAccountId::from_eth(caller);905 let from = T::CrossAccountId::from_eth(from);906 let token = token_id.try_into()?;907908 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))909 .map_err(dispatch_to_evm::<T>)?;910 Ok(())911 }912913 /// @notice Burns a specific ERC721 token.914 /// @dev Throws unless `msg.sender` is the current owner or an authorized915 /// operator for this NFT. Throws if `from` is not the current owner. Throws916 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.917 /// @param from The current owner of the NFT918 /// @param tokenId The NFT to transfer919 #[weight(<SelfWeightOf<T>>::burn_from())]920 fn burn_from_cross(921 &mut self,922 caller: Caller,923 from: eth::CrossAddress,924 token_id: U256,925 ) -> Result<()> {926 let caller = T::CrossAccountId::from_eth(caller);927 let from = from.into_sub_cross_account::<T>()?;928 let token = token_id.try_into()?;929930 <Pallet<T>>::burn_from(self, &caller, &from, token, &nesting_budget(&self.recorder))931 .map_err(dispatch_to_evm::<T>)?;932 Ok(())933 }934935 /// @notice Returns next free NFT ID.936 fn next_token_id(&self) -> Result<U256> {937 self.consume_store_reads(1)?;938 Ok(<Pallet<T>>::next_token_id(self)939 .map_err(dispatch_to_evm::<T>)?940 .into())941 }942943 /// @notice Function to mint multiple tokens.944 /// @dev `tokenIds` should be an array of consecutive numbers and first number945 /// should be obtained with `nextTokenId` method946 /// @param to The new owner947 /// @param tokenIds IDs of the minted NFTs948 #[solidity(hide)]949 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]950 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {951 let caller = T::CrossAccountId::from_eth(caller);952 let to = T::CrossAccountId::from_eth(to);953 let mut expected_index = <TokensMinted<T>>::get(self.id)954 .checked_add(1)955 .ok_or("item id overflow")?;956957 let total_tokens = token_ids.len();958 for id in token_ids.into_iter() {959 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;960 if id != expected_index {961 return Err("item id should be next".into());962 }963 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;964 }965 let data = (0..total_tokens)966 .map(|_| CreateItemData::<T> {967 properties: BoundedVec::default(),968 owner: to.clone(),969 })970 .collect();971972 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))973 .map_err(dispatch_to_evm::<T>)?;974 Ok(true)975 }976977 /// @notice Function to mint a token.978 /// @param data Array of pairs of token owner and token's properties for minted token979 #[weight(980 mint_with_props_weight::<T>(981 <SelfWeightOf<T>>::create_multiple_items_ex(data.len() as u32),982 data.iter().map(|d| d.properties.len() as u32),983 )984 )]985 fn mint_bulk_cross(&mut self, caller: Caller, data: Vec<MintTokenData>) -> Result<bool> {986 let caller = T::CrossAccountId::from_eth(caller);987988 let mut create_nft_data = Vec::with_capacity(data.len());989 for MintTokenData { owner, properties } in data {990 let owner = owner.into_sub_cross_account::<T>()?;991 create_nft_data.push(CreateItemData::<T> {992 properties: properties993 .into_iter()994 .map(|property| property.try_into())995 .collect::<Result<Vec<_>>>()?996 .try_into()997 .map_err(|_| "too many properties")?,998 owner,999 });1000 }10011002 <Pallet<T>>::create_multiple_items(1003 self,1004 &caller,1005 create_nft_data,1006 &nesting_budget(&self.recorder),1007 )1008 .map_err(dispatch_to_evm::<T>)?;1009 Ok(true)1010 }10111012 /// @notice Function to mint multiple tokens with the given tokenUris.1013 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1014 /// numbers and first number should be obtained with `nextTokenId` method1015 /// @param to The new owner1016 /// @param tokens array of pairs of token ID and token URI for minted tokens1017 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1018 #[weight(1019 mint_with_props_weight::<T>(1020 <SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32),1021 tokens.iter().map(|_| 1),1022 )1023 )]1024 fn mint_bulk_with_token_uri(1025 &mut self,1026 caller: Caller,1027 to: Address,1028 tokens: Vec<TokenUri>,1029 ) -> Result<bool> {1030 let key = key::url();1031 let caller = T::CrossAccountId::from_eth(caller);1032 let to = T::CrossAccountId::from_eth(to);1033 let mut expected_index = <TokensMinted<T>>::get(self.id)1034 .checked_add(1)1035 .ok_or("item id overflow")?;10361037 let mut data = Vec::with_capacity(tokens.len());1038 for TokenUri { id, uri } in tokens {1039 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1040 if id != expected_index {1041 return Err("item id should be next".into());1042 }1043 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10441045 let mut properties = CollectionPropertiesVec::default();1046 properties1047 .try_push(Property {1048 key: key.clone(),1049 value: uri1050 .into_bytes()1051 .try_into()1052 .map_err(|_| "token uri is too long")?,1053 })1054 .map_err(|e| Error::Revert(alloc::format!("can't add property: {e:?}")))?;10551056 data.push(CreateItemData::<T> {1057 properties,1058 owner: to.clone(),1059 });1060 }10611062 <Pallet<T>>::create_multiple_items(self, &caller, data, &nesting_budget(&self.recorder))1063 .map_err(dispatch_to_evm::<T>)?;1064 Ok(true)1065 }10661067 /// @notice Function to mint a token.1068 /// @param to The new owner crossAccountId1069 /// @param properties Properties of minted token1070 /// @return uint256 The id of the newly minted token1071 #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [properties.len() as u32].into_iter()))]1072 fn mint_cross(1073 &mut self,1074 caller: Caller,1075 to: eth::CrossAddress,1076 properties: Vec<eth::Property>,1077 ) -> Result<U256> {1078 let token_id = <TokensMinted<T>>::get(self.id)1079 .checked_add(1)1080 .ok_or("item id overflow")?;10811082 let to = to.into_sub_cross_account::<T>()?;10831084 let properties = properties1085 .into_iter()1086 .map(eth::Property::try_into)1087 .collect::<Result<Vec<_>>>()?1088 .try_into()1089 .map_err(|_| Error::Revert("too many properties".to_string()))?;10901091 let caller = T::CrossAccountId::from_eth(caller);10921093 <Pallet<T>>::create_item(1094 self,1095 &caller,1096 CreateItemData::<T> {1097 properties,1098 owner: to,1099 },1100 &nesting_budget(&self.recorder),1101 )1102 .map_err(dispatch_to_evm::<T>)?;11031104 Ok(token_id.into())1105 }11061107 /// @notice Returns collection helper contract address1108 fn collection_helper_address(&self) -> Address {1109 T::ContractAddress::get()1110 }1111}11121113#[solidity_interface(1114 name = UniqueNFT,1115 is(1116 ERC721,1117 ERC721Enumerable,1118 ERC721UniqueExtensions,1119 ERC721UniqueMintable,1120 ERC721Burnable,1121 ERC721Metadata(if(this.flags.erc721metadata)),1122 Collection(via(common_mut returns CollectionHandle<T>)),1123 TokenProperties,1124 ),1125 enum(derive(PreDispatch)),1126)]1127impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11281129// Not a tests, but code generators1130generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1131generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11321133impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1134where1135 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1136{1137 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11381139 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1140 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1141 }1142}pallets/refungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/refungible/src/benchmarking.rs
+++ b/pallets/refungible/src/benchmarking.rs
@@ -421,114 +421,30 @@
Ok(())
}
- // set_token_properties {
- // let b in 0..MAX_PROPERTIES_PER_ITEM;
- // bench_init!{
- // owner: sub; collection: collection(owner);
- // owner: cross_from_sub;
- // };
- // let perms = (0..b).map(|k| PropertyKeyPermission {
- // key: property_key(k as usize),
- // permission: PropertyPermission {
- // mutable: false,
- // collection_admin: true,
- // token_owner: true,
- // },
- // }).collect::<Vec<_>>();
- // <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
- // let props = (0..b).map(|k| Property {
- // key: property_key(k as usize),
- // value: property_value(),
- // }).collect::<Vec<_>>();
- // let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
- // }: {<Pallet<T>>::set_token_properties(&collection, &owner, item, props.into_iter(), &Unlimited)?}
-
- // load_token_properties {
- // bench_init!{
- // owner: sub; collection: collection(owner);
- // owner: cross_from_sub;
- // };
-
- // let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
- // }: {
- // pallet_common::BenchmarkPropertyWriter::<T>::load_token_properties(
- // &collection,
- // item,
- // )
- // }
-
- // write_token_properties {
- // let b in 0..MAX_PROPERTIES_PER_ITEM;
- // bench_init!{
- // owner: sub; collection: collection(owner);
- // owner: cross_from_sub;
- // };
-
- // let perms = (0..b).map(|k| PropertyKeyPermission {
- // key: property_key(k as usize),
- // permission: PropertyPermission {
- // mutable: false,
- // collection_admin: true,
- // token_owner: true,
- // },
- // }).collect::<Vec<_>>();
- // <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
- // let props = (0..b).map(|k| Property {
- // key: property_key(k as usize),
- // value: property_value(),
- // }).collect::<Vec<_>>();
- // let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
-
- // let lazy_collection_info = pallet_common::BenchmarkPropertyWriter::<T>::load_collection_info(
- // &collection,
- // &owner,
- // );
- // }: {
- // let mut property_writer = pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
-
- // property_writer.write_token_properties(
- // item,
- // props.into_iter(),
- // crate::erc::ERC721TokenEvent::TokenChanged {
- // token_id: item.into(),
- // }
- // .to_log(T::ContractAddress::get()),
- // )?
- // }
-
#[benchmark]
- fn set_token_property_permissions(
- b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
- ) -> Result<(), BenchmarkError> {
+ fn load_token_properties() -> Result<(), BenchmarkError> {
bench_init! {
owner: sub; collection: collection(owner);
owner: cross_from_sub;
};
- let perms = (0..b)
- .map(|k| PropertyKeyPermission {
- key: property_key(k as usize),
- permission: PropertyPermission {
- mutable: false,
- collection_admin: false,
- token_owner: false,
- },
- })
- .collect::<Vec<_>>();
+ let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+
#[block]
{
- <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
+ pallet_common::BenchmarkPropertyWriter::<T>::load_token_properties(&collection, item);
}
Ok(())
}
#[benchmark]
- fn set_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
+ fn write_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
bench_init! {
owner: sub; collection: collection(owner);
owner: cross_from_sub;
};
+
let perms = (0..b)
.map(|k| PropertyKeyPermission {
key: property_key(k as usize),
@@ -548,73 +464,29 @@
.collect::<Vec<_>>();
let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
+ let lazy_collection_info =
+ pallet_common::BenchmarkPropertyWriter::<T>::load_collection_info(&collection, &owner);
+
#[block]
{
- <Pallet<T>>::set_token_properties(
- &collection,
- &owner,
+ let mut property_writer =
+ pallet_common::BenchmarkPropertyWriter::new(&collection, lazy_collection_info);
+
+ property_writer.write_token_properties(
item,
props.into_iter(),
- &Unlimited,
+ crate::erc::ERC721TokenEvent::TokenChanged {
+ token_id: item.into(),
+ }
+ .to_log(T::ContractAddress::get()),
)?;
}
Ok(())
}
- // TODO:
#[benchmark]
- fn init_token_properties(b: Linear<0, MAX_PROPERTIES_PER_ITEM>) -> Result<(), BenchmarkError> {
- // bench_init! {
- // owner: sub; collection: collection(owner);
- // owner: cross_from_sub;
- // };
-
- // let perms = (0..b)
- // .map(|k| PropertyKeyPermission {
- // key: property_key(k as usize),
- // permission: PropertyPermission {
- // mutable: false,
- // collection_admin: true,
- // token_owner: true,
- // },
- // })
- // .collect::<Vec<_>>();
- // <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
-
- #[block]
- {}
- // let props = (0..b).map(|k| Property {
- // key: property_key(k as usize),
- // value: property_value(),
- // }).collect::<Vec<_>>();
- // let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
-
- // let (is_collection_admin, property_permissions) = load_is_admin_and_property_permissions(&collection, &owner)
- // let mut property_writer = pallet_common::collection_info_loaded_property_writer(
- // &collection,
- // is_collection_admin,
- // property_permissions,
- // );
-
- // #[block]
- // {
- // property_writer.write_token_properties(
- // true,
- // item,
- // props.into_iter(),
- // crate::erc::ERC721TokenEvent::TokenChanged {
- // token_id: item.into(),
- // }
- // .to_log(T::ContractAddress::get()),
- // )?;
- // }
-
- Ok(())
- }
-
- #[benchmark]
- fn delete_token_properties(
+ fn set_token_property_permissions(
b: Linear<0, MAX_PROPERTIES_PER_ITEM>,
) -> Result<(), BenchmarkError> {
bench_init! {
@@ -625,38 +497,16 @@
.map(|k| PropertyKeyPermission {
key: property_key(k as usize),
permission: PropertyPermission {
- mutable: true,
- collection_admin: true,
- token_owner: true,
+ mutable: false,
+ collection_admin: false,
+ token_owner: false,
},
- })
- .collect::<Vec<_>>();
- <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
- let props = (0..b)
- .map(|k| Property {
- key: property_key(k as usize),
- value: property_value(),
})
.collect::<Vec<_>>();
- let item = create_max_item(&collection, &owner, [(owner.clone(), 200)])?;
- <Pallet<T>>::set_token_properties(
- &collection,
- &owner,
- item,
- props.into_iter(),
- &Unlimited,
- )?;
- let to_delete = (0..b).map(|k| property_key(k as usize)).collect::<Vec<_>>();
#[block]
{
- <Pallet<T>>::delete_token_properties(
- &collection,
- &owner,
- item,
- to_delete.into_iter(),
- &Unlimited,
- )?;
+ <Pallet<T>>::set_token_property_permissions(&collection, &owner, perms)?;
}
Ok(())
@@ -673,22 +523,6 @@
#[block]
{
<Pallet<T>>::repartition(&collection, &owner, item, 200)?;
- }
-
- Ok(())
- }
-
- #[benchmark]
- fn token_owner() -> Result<(), BenchmarkError> {
- bench_init! {
- owner: sub; collection: collection(owner);
- sender: cross_from_sub(owner); owner: cross_sub;
- };
- let item = create_max_item(&collection, &sender, [(owner, 100)])?;
-
- #[block]
- {
- <Pallet<T>>::token_owner(collection.id, item).unwrap();
}
Ok(())
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -47,35 +47,27 @@
pub struct CommonWeights<T: Config>(PhantomData<T>);
impl<T: Config> CommonWeightInfo<T::CrossAccountId> for CommonWeights<T> {
fn create_multiple_items(data: &[up_data_structs::CreateItemData]) -> Weight {
- <SelfWeightOf<T>>::create_multiple_items(data.len() as u32).saturating_add(
- write_token_properties_total_weight::<T, _>(
- data.iter().map(|data| match data {
- up_data_structs::CreateItemData::ReFungible(rft_data) => {
- rft_data.properties.len() as u32
- }
- _ => 0,
- }),
- <SelfWeightOf<T>>::write_token_properties,
- ),
+ mint_with_props_weight::<T>(
+ <SelfWeightOf<T>>::create_multiple_items(data.len() as u32),
+ data.iter().map(|data| match data {
+ up_data_structs::CreateItemData::ReFungible(rft_data) => {
+ rft_data.properties.len() as u32
+ }
+ _ => 0,
+ }),
)
}
fn create_multiple_items_ex(call: &CreateItemExData<T::CrossAccountId>) -> Weight {
match call {
- CreateItemExData::RefungibleMultipleOwners(i) => {
- <SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32)
- .saturating_add(write_token_properties_total_weight::<T, _>(
- [i.properties.len() as u32].into_iter(),
- <SelfWeightOf<T>>::write_token_properties,
- ))
- }
- CreateItemExData::RefungibleMultipleItems(i) => {
- <SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32)
- .saturating_add(write_token_properties_total_weight::<T, _>(
- i.iter().map(|d| d.properties.len() as u32),
- <SelfWeightOf<T>>::write_token_properties,
- ))
- }
+ CreateItemExData::RefungibleMultipleOwners(i) => mint_with_props_weight::<T>(
+ <SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(i.users.len() as u32),
+ [i.properties.len() as u32].into_iter(),
+ ),
+ CreateItemExData::RefungibleMultipleItems(i) => mint_with_props_weight::<T>(
+ <SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(i.len() as u32),
+ i.iter().map(|d| d.properties.len() as u32),
+ ),
_ => Weight::zero(),
}
}
@@ -138,6 +130,16 @@
}
}
+pub(crate) fn mint_with_props_weight<T: Config>(
+ create_no_data_weight: Weight,
+ tokens: impl Iterator<Item = u32> + Clone,
+) -> Weight {
+ create_no_data_weight.saturating_add(write_token_properties_total_weight::<T, _>(
+ tokens,
+ <SelfWeightOf<T>>::write_token_properties,
+ ))
+}
+
fn map_create_data<T: Config>(
data: up_data_structs::CreateItemData,
to: &T::CrossAccountId,
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -50,8 +50,10 @@
};
use crate::{
- common::CommonWeights, weights::WeightInfo, AccountBalance, Balance, Config, CreateItemData,
- Pallet, RefungibleHandle, SelfWeightOf, TokenProperties, TokensMinted, TotalSupply,
+ common::{mint_with_props_weight, CommonWeights},
+ weights::WeightInfo,
+ AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,
+ TokenProperties, TokensMinted, TotalSupply,
};
frontier_contract! {
@@ -661,7 +663,7 @@
/// @param tokenUri Token URI that would be stored in the NFT properties
/// @return uint256 The id of the newly minted token
#[solidity(rename_selector = "mintWithTokenURI")]
- #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]
+ #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]
fn mint_with_token_uri(
&mut self,
caller: Caller,
@@ -683,7 +685,7 @@
/// @param tokenId ID of the minted RFT
/// @param tokenUri Token URI that would be stored in the RFT properties
#[solidity(hide, rename_selector = "mintWithTokenURI")]
- #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]
+ #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [1].into_iter()))]
fn mint_with_token_uri_check_id(
&mut self,
caller: Caller,
@@ -1052,22 +1054,26 @@
}
/// @notice Function to mint a token.
- /// @param tokenProperties Properties of minted token
- #[weight(if token_properties.len() == 1 {
- <SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(token_properties.iter().next().unwrap().owners.len() as u32)
+ /// @param tokensData Data of minted token(s)
+ #[weight(if tokens_data.len() == 1 {
+ let token_data = tokens_data.first().unwrap();
+
+ mint_with_props_weight::<T>(
+ <SelfWeightOf<T>>::create_multiple_items_ex_multiple_owners(token_data.owners.len() as u32),
+ [token_data.properties.len() as u32].into_iter(),
+ )
} else {
- <SelfWeightOf<T>>::create_multiple_items_ex_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<MintTokenData>,
- ) -> Result<bool> {
+ mint_with_props_weight::<T>(
+ <SelfWeightOf<T>>::create_multiple_items_ex_multiple_items(tokens_data.len() as u32),
+ tokens_data.iter().map(|d| d.properties.len() as u32),
+ )
+ })]
+ fn mint_bulk_cross(&mut self, caller: Caller, tokens_data: Vec<MintTokenData>) -> Result<bool> {
let caller = T::CrossAccountId::from_eth(caller);
- let has_multiple_tokens = token_properties.len() > 1;
+ let has_multiple_tokens = tokens_data.len() > 1;
- let mut create_rft_data = Vec::with_capacity(token_properties.len());
- for MintTokenData { owners, properties } in token_properties {
+ let mut create_rft_data = Vec::with_capacity(tokens_data.len());
+ for MintTokenData { owners, properties } in tokens_data {
let has_multiple_owners = owners.len() > 1;
if has_multiple_tokens & has_multiple_owners {
return Err(
@@ -1108,7 +1114,12 @@
/// @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(
+ mint_with_props_weight::<T>(
+ <SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32),
+ tokens.iter().map(|_| 1),
+ )
+ )]
fn mint_bulk_with_token_uri(
&mut self,
caller: Caller,
@@ -1162,7 +1173,7 @@
/// @param to The new owner crossAccountId
/// @param properties Properties of minted token
/// @return uint256 The id of the newly minted token
- #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]
+ #[weight(mint_with_props_weight::<T>(<SelfWeightOf<T>>::create_item(), [properties.len() as u32].into_iter()))]
fn mint_cross(
&mut self,
caller: Caller,