difftreelog
feat(weight) added benchs for decompose weight Changed weight calculation system for `transfer` & `transfer_from` (NFT)
in: master
13 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6262,7 +6262,7 @@
[[package]]
name = "pallet-common"
-version = "0.1.13"
+version = "0.1.14"
dependencies = [
"ethereum",
"evm-coder",
@@ -6814,7 +6814,7 @@
[[package]]
name = "pallet-nonfungible"
-version = "0.1.13"
+version = "0.1.14"
dependencies = [
"evm-coder",
"frame-benchmarking",
pallets/app-promotion/src/lib.rsdiffbeforeafterboth--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -845,7 +845,6 @@
/// - `staker`: staker account.
pub fn total_staked_by_id(staker: impl EncodeLike<T::AccountId>) -> Option<BalanceOf<T>> {
let staked = Staked::<T>::iter_prefix((staker,))
- .into_iter()
.fold(<BalanceOf<T>>::default(), |acc, (_, (amount, _))| {
acc + amount
});
@@ -864,7 +863,6 @@
staker: impl EncodeLike<T::AccountId>,
) -> Option<Vec<(T::BlockNumber, BalanceOf<T>)>> {
let mut staked = Staked::<T>::iter_prefix((staker,))
- .into_iter()
.map(|(block, (amount, _))| (block, amount))
.collect::<Vec<_>>();
staked.sort_by_key(|(block, _)| *block);
@@ -883,12 +881,6 @@
Self::total_staked_by_id(s.as_sub())
})
}
-
- // pub fn cross_id_locked_balance(staker: T::CrossAccountId) -> BalanceOf<T> {
- // Self::get_locked_balance(staker.as_sub())
- // .map(|l| l.amount)
- // .unwrap_or_default()
- // }
/// Returns all relay block numbers when stake was made,
/// the amount of the stake.
pallets/common/CHANGELOG.mddiffbeforeafterboth--- a/pallets/common/CHANGELOG.md
+++ b/pallets/common/CHANGELOG.md
@@ -4,6 +4,12 @@
<!-- bureaucrate goes here -->
+## [0.1.14] - 2023-03-28
+
+### Added
+
+- Added benchmark to check if user is contained in AllowList (`check_accesslist()`).
+
## [0.1.13] - 2023-01-20
### Changed
pallets/common/Cargo.tomldiffbeforeafterboth--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -2,7 +2,7 @@
edition = "2021"
license = "GPLv3"
name = "pallet-common"
-version = "0.1.13"
+version = "0.1.14"
[dependencies]
# Note: `package = "parity-scale-codec"` must be supplied since the `Encode` macro searches for it.
pallets/common/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -22,8 +22,9 @@
use frame_benchmarking::{benchmarks, account};
use up_data_structs::{
CollectionMode, CollectionFlags, CreateCollectionData, CollectionId, Property, PropertyKey,
- PropertyValue, CollectionPermissions, NestingPermissions, MAX_COLLECTION_NAME_LENGTH,
- MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH, MAX_PROPERTIES_PER_ITEM,
+ PropertyValue, CollectionPermissions, NestingPermissions, AccessMode,
+ MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
+ MAX_PROPERTIES_PER_ITEM,
};
use frame_support::{
traits::{Currency, Get},
@@ -193,4 +194,38 @@
<Pallet<T>>::set_collection_properties(&collection, &owner, props.into_iter())?;
let to_delete = (0..b).map(|p| property_key(p as usize)).collect::<Vec<_>>();
}: {<Pallet<T>>::delete_collection_properties(&collection, &owner, to_delete.into_iter())?}
+
+ check_accesslist{
+ bench_init!{
+ owner: sub; collection: collection(owner);
+ sender: cross_from_sub(owner); receiver: cross_sub;
+ };
+
+ let mut collection_handle = <CollectionHandle<T>>::try_get(collection.id)?;
+ <Pallet<T>>::update_permissions(
+ &sender,
+ &mut collection_handle,
+ CollectionPermissions { access: Some(AccessMode::AllowList), ..Default::default() }
+ )?;
+
+ <Pallet<T>>::toggle_allowlist(
+ &collection,
+ &sender,
+ &sender,
+ true,
+ )?;
+
+ <Pallet<T>>::toggle_allowlist(
+ &collection,
+ &sender,
+ &receiver,
+ true,
+ )?;
+
+ assert_eq!(collection_handle.permissions.access(), AccessMode::AllowList);
+
+ collection_handle.check_allowlist(&sender)?;
+ collection_handle.check_allowlist(&receiver)?;
+
+ }: {collection_handle.check_allowlist(&sender)?;}
}
pallets/common/src/weights.rsdiffbeforeafterboth--- a/pallets/common/src/weights.rs
+++ b/pallets/common/src/weights.rs
@@ -36,6 +36,7 @@
pub trait WeightInfo {
fn set_collection_properties(b: u32, ) -> Weight;
fn delete_collection_properties(b: u32, ) -> Weight;
+ fn check_accesslist() -> Weight;
}
/// Weights for pallet_common using the Substrate node and recommended hardware.
@@ -66,11 +67,8 @@
Weight::from_parts(6_684_000, 43467)
// Standard Error: 67_984
.saturating_add(Weight::from_parts(21_502_829, 0).saturating_mul(b.into()))
- .saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
-}
-
// For backwards compatibility and tests
impl WeightInfo for () {
/// Storage: Common CollectionProperties (r:1 w:1)
@@ -98,8 +96,5 @@
Weight::from_parts(6_684_000, 43467)
// Standard Error: 67_984
.saturating_add(Weight::from_parts(21_502_829, 0).saturating_mul(b.into()))
- .saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
-}
-
pallets/nonfungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/nonfungible/CHANGELOG.md
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -4,6 +4,12 @@
<!-- bureaucrate goes here -->
+## [0.1.14] - 2023-03-28
+
+### Fixed
+
+- The weight of `transfer` and `transfer_from`.
+
## [0.1.13] - 2023-01-20
### Fixed
pallets/nonfungible/Cargo.tomldiffbeforeafterboth--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -2,7 +2,7 @@
edition = "2021"
license = "GPLv3"
name = "pallet-nonfungible"
-version = "0.1.13"
+version = "0.1.14"
[dependencies]
# Note: `package = "parity-scale-codec"` must be supplied since the `Encode` macro searches for it.
pallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/benchmarking.rs
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -146,14 +146,14 @@
let item = create_max_item(&collection, &owner, owner_eth.clone())?;
}: {<Pallet<T>>::set_allowance_from(&collection, &sender, &owner_eth, item, Some(&spender))?}
- transfer_from {
+ checks_for_transfer_from {
bench_init!{
owner: sub; collection: collection(owner);
owner: cross_from_sub; sender: cross_sub; spender: cross_sub; receiver: cross_sub;
};
let item = create_max_item(&collection, &owner, sender.clone())?;
<Pallet<T>>::set_allowance(&collection, &sender, item, Some(&spender))?;
- }: {<Pallet<T>>::transfer_from(&collection, &spender, &sender, &receiver, item, &Unlimited)?}
+ }: {<Pallet<T>>::check_allowed(&collection, &spender, &sender, item, &Unlimited)?}
burn_from {
bench_init!{
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -23,7 +23,7 @@
};
use pallet_common::{
CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
- weights::WeightInfo as _,
+ weights::WeightInfo as _, SelfWeightOf as PalletCommonWeightOf,
};
use sp_runtime::DispatchError;
use sp_std::{vec::Vec, vec};
@@ -91,7 +91,7 @@
}
fn transfer() -> Weight {
- <SelfWeightOf<T>>::transfer()
+ <SelfWeightOf<T>>::transfer() + <PalletCommonWeightOf<T>>::check_accesslist() * 2
}
fn approve() -> Weight {
@@ -103,7 +103,7 @@
}
fn transfer_from() -> Weight {
- <SelfWeightOf<T>>::transfer_from()
+ Self::transfer() + <SelfWeightOf<T>>::checks_for_transfer_from()
}
fn burn_from() -> Weight {
@@ -325,10 +325,7 @@
) -> DispatchResultWithPostInfo {
ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
if amount == 1 {
- with_weight(
- <Pallet<T>>::transfer(self, &from, &to, token, nesting_budget),
- <CommonWeights<T>>::transfer(),
- )
+ <Pallet<T>>::transfer(self, &from, &to, token, nesting_budget)
} else {
<Pallet<T>>::check_token_immediate_ownership(self, token, &from)?;
Ok(().into())
@@ -386,10 +383,7 @@
ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
if amount == 1 {
- with_weight(
- <Pallet<T>>::transfer_from(self, &sender, &from, &to, token, nesting_budget),
- <CommonWeights<T>>::transfer_from(),
- )
+ <Pallet<T>>::transfer_from(self, &sender, &from, &to, token, nesting_budget)
} else {
<Pallet<T>>::check_allowed(self, &sender, &from, token, nesting_budget)?;
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;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::{34 dispatch_to_evm, frontier_contract,35 execution::{Result, PreDispatch, Error},36};37use sp_std::{vec::Vec, vec};38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,40 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},41 eth::{self, TokenUri},42};43use pallet_evm::{account::CrossAccountId, PrecompileHandle};44use pallet_evm_coder_substrate::call;45use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};46use sp_core::{U256, Get};4748use crate::{49 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,50 TokenProperties, SelfWeightOf, weights::WeightInfo,51};5253/// Nft events.54#[derive(ToLog)]55pub enum ERC721TokenEvent {56 /// The token has been changed.57 TokenChanged {58 /// Token ID.59 #[indexed]60 token_id: U256,61 },62}6364frontier_contract! {65 macro_rules! NonfungibleHandle_result {...}66 impl<T: Config> Contract for NonfungibleHandle<T> {...}67}6869/// @title A contract that allows to set and delete token properties and change token property permissions.70#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]71impl<T: Config> NonfungibleHandle<T> {72 /// @notice Set permissions for token property.73 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.74 /// @param key Property key.75 /// @param isMutable Permission to mutate property.76 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.77 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.78 #[solidity(hide)]79 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]80 fn set_token_property_permission(81 &mut self,82 caller: Caller,83 key: String,84 is_mutable: bool,85 collection_admin: bool,86 token_owner: bool,87 ) -> Result<()> {88 let caller = T::CrossAccountId::from_eth(caller);89 <Pallet<T>>::set_token_property_permissions(90 self,91 &caller,92 vec![PropertyKeyPermission {93 key: <Vec<u8>>::from(key)94 .try_into()95 .map_err(|_| "too long key")?,96 permission: PropertyPermission {97 mutable: is_mutable,98 collection_admin,99 token_owner,100 },101 }],102 )103 .map_err(dispatch_to_evm::<T>)104 }105106 /// @notice Set permissions for token property.107 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.108 /// @param permissions Permissions for keys.109 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]110 fn set_token_property_permissions(111 &mut self,112 caller: Caller,113 permissions: Vec<eth::TokenPropertyPermission>,114 ) -> Result<()> {115 let caller = T::CrossAccountId::from_eth(caller);116 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;117118 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)119 .map_err(dispatch_to_evm::<T>)120 }121122 /// @notice Get permissions for token properties.123 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {124 let perms = <Pallet<T>>::token_property_permission(self.id);125 Ok(perms126 .into_iter()127 .map(eth::TokenPropertyPermission::from)128 .collect())129 }130131 /// @notice Set token property value.132 /// @dev Throws error if `msg.sender` has no permission to edit the property.133 /// @param tokenId ID of the token.134 /// @param key Property key.135 /// @param value Property value.136 #[solidity(hide)]137 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]138 fn set_property(139 &mut self,140 caller: Caller,141 token_id: U256,142 key: String,143 value: Bytes,144 ) -> Result<()> {145 let caller = T::CrossAccountId::from_eth(caller);146 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;147 let key = <Vec<u8>>::from(key)148 .try_into()149 .map_err(|_| "key too long")?;150 let value = value.0.try_into().map_err(|_| "value too long")?;151152 let nesting_budget = self153 .recorder154 .weight_calls_budget(<StructureWeight<T>>::find_parent());155156 <Pallet<T>>::set_token_property(157 self,158 &caller,159 TokenId(token_id),160 Property { key, value },161 &nesting_budget,162 )163 .map_err(dispatch_to_evm::<T>)164 }165166 /// @notice Set token properties value.167 /// @dev Throws error if `msg.sender` has no permission to edit the property.168 /// @param tokenId ID of the token.169 /// @param properties settable properties170 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]171 fn set_properties(172 &mut self,173 caller: Caller,174 token_id: U256,175 properties: Vec<eth::Property>,176 ) -> Result<()> {177 let caller = T::CrossAccountId::from_eth(caller);178 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;179180 let nesting_budget = self181 .recorder182 .weight_calls_budget(<StructureWeight<T>>::find_parent());183184 let properties = properties185 .into_iter()186 .map(eth::Property::try_into)187 .collect::<Result<Vec<_>>>()?;188189 <Pallet<T>>::set_token_properties(190 self,191 &caller,192 TokenId(token_id),193 properties.into_iter(),194 false,195 &nesting_budget,196 )197 .map_err(dispatch_to_evm::<T>)198 }199200 /// @notice Delete token property value.201 /// @dev Throws error if `msg.sender` has no permission to edit the property.202 /// @param tokenId ID of the token.203 /// @param key Property key.204 #[solidity(hide)]205 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]206 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {207 let caller = T::CrossAccountId::from_eth(caller);208 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;209 let key = <Vec<u8>>::from(key)210 .try_into()211 .map_err(|_| "key too long")?;212213 let nesting_budget = self214 .recorder215 .weight_calls_budget(<StructureWeight<T>>::find_parent());216217 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)218 .map_err(dispatch_to_evm::<T>)219 }220221 /// @notice Delete token properties value.222 /// @dev Throws error if `msg.sender` has no permission to edit the property.223 /// @param tokenId ID of the token.224 /// @param keys Properties key.225 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]226 fn delete_properties(227 &mut self,228 token_id: U256,229 caller: Caller,230 keys: Vec<String>,231 ) -> Result<()> {232 let caller = T::CrossAccountId::from_eth(caller);233 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;234 let keys = keys235 .into_iter()236 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))237 .collect::<Result<Vec<_>>>()?;238239 let nesting_budget = self240 .recorder241 .weight_calls_budget(<StructureWeight<T>>::find_parent());242243 <Pallet<T>>::delete_token_properties(244 self,245 &caller,246 TokenId(token_id),247 keys.into_iter(),248 &nesting_budget,249 )250 .map_err(dispatch_to_evm::<T>)251 }252253 /// @notice Get token property value.254 /// @dev Throws error if key not found255 /// @param tokenId ID of the token.256 /// @param key Property key.257 /// @return Property value bytes258 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {259 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;260 let key = <Vec<u8>>::from(key)261 .try_into()262 .map_err(|_| "key too long")?;263264 let props = <TokenProperties<T>>::get((self.id, token_id));265 let prop = props.get(&key).ok_or("key not found")?;266267 Ok(prop.to_vec().into())268 }269}270271#[derive(ToLog)]272pub enum ERC721Events {273 /// @dev This emits when ownership of any NFT changes by any mechanism.274 /// This event emits when NFTs are created (`from` == 0) and destroyed275 /// (`to` == 0). Exception: during contract creation, any number of NFTs276 /// may be created and assigned without emitting Transfer. At the time of277 /// any transfer, the approved address for that NFT (if any) is reset to none.278 Transfer {279 #[indexed]280 from: Address,281 #[indexed]282 to: Address,283 #[indexed]284 token_id: U256,285 },286 /// @dev This emits when the approved address for an NFT is changed or287 /// reaffirmed. The zero address indicates there is no approved address.288 /// When a Transfer event emits, this also indicates that the approved289 /// address for that NFT (if any) is reset to none.290 Approval {291 #[indexed]292 owner: Address,293 #[indexed]294 approved: Address,295 #[indexed]296 token_id: U256,297 },298 /// @dev This emits when an operator is enabled or disabled for an owner.299 /// The operator can manage all NFTs of the owner.300 #[allow(dead_code)]301 ApprovalForAll {302 #[indexed]303 owner: Address,304 #[indexed]305 operator: Address,306 approved: bool,307 },308}309310/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension311/// @dev See https://eips.ethereum.org/EIPS/eip-721312#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]313impl<T: Config> NonfungibleHandle<T>314where315 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,316{317 /// @notice A descriptive name for a collection of NFTs in this contract318 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`319 #[solidity(hide, rename_selector = "name")]320 fn name_proxy(&self) -> String {321 self.name()322 }323324 /// @notice An abbreviated name for NFTs in this contract325 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`326 #[solidity(hide, rename_selector = "symbol")]327 fn symbol_proxy(&self) -> String {328 self.symbol()329 }330331 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.332 ///333 /// @dev If the token has a `url` property and it is not empty, it is returned.334 /// 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`.335 /// If the collection property `baseURI` is empty or absent, return "" (empty string)336 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix337 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).338 ///339 /// @return token's const_metadata340 #[solidity(rename_selector = "tokenURI")]341 fn token_uri(&self, token_id: U256) -> Result<String> {342 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;343344 match get_token_property(self, token_id_u32, &key::url()).as_deref() {345 Err(_) | Ok("") => (),346 Ok(url) => {347 return Ok(url.into());348 }349 };350351 let base_uri =352 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())353 .map(BoundedVec::into_inner)354 .map(String::from_utf8)355 .transpose()356 .map_err(|e| {357 Error::Revert(alloc::format!(358 "Can not convert value \"baseURI\" to string with error \"{}\"",359 e360 ))361 })?;362363 let base_uri = match base_uri.as_deref() {364 None | Some("") => {365 return Ok("".into());366 }367 Some(base_uri) => base_uri.into(),368 };369370 Ok(371 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {372 Err(_) | Ok("") => base_uri,373 Ok(suffix) => base_uri + suffix,374 },375 )376 }377}378379/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension380/// @dev See https://eips.ethereum.org/EIPS/eip-721381#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]382impl<T: Config> NonfungibleHandle<T> {383 /// @notice Enumerate valid NFTs384 /// @param index A counter less than `totalSupply()`385 /// @return The token identifier for the `index`th NFT,386 /// (sort order not specified)387 fn token_by_index(&self, index: U256) -> U256 {388 index389 }390391 /// @dev Not implemented392 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {393 // TODO: Not implemetable394 Err("not implemented".into())395 }396397 /// @notice Count NFTs tracked by this contract398 /// @return A count of valid NFTs tracked by this contract, where each one of399 /// them has an assigned and queryable owner not equal to the zero address400 fn total_supply(&self) -> Result<U256> {401 self.consume_store_reads(1)?;402 Ok(<Pallet<T>>::total_supply(self).into())403 }404}405406/// @title ERC-721 Non-Fungible Token Standard407/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md408#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]409impl<T: Config> NonfungibleHandle<T> {410 /// @notice Count all NFTs assigned to an owner411 /// @dev NFTs assigned to the zero address are considered invalid, and this412 /// function throws for queries about the zero address.413 /// @param owner An address for whom to query the balance414 /// @return The number of NFTs owned by `owner`, possibly zero415 fn balance_of(&self, owner: Address) -> Result<U256> {416 self.consume_store_reads(1)?;417 let owner = T::CrossAccountId::from_eth(owner);418 let balance = <AccountBalance<T>>::get((self.id, owner));419 Ok(balance.into())420 }421 /// @notice Find the owner of an NFT422 /// @dev NFTs assigned to zero address are considered invalid, and queries423 /// about them do throw.424 /// @param tokenId The identifier for an NFT425 /// @return The address of the owner of the NFT426 fn owner_of(&self, token_id: U256) -> Result<Address> {427 self.consume_store_reads(1)?;428 let token: TokenId = token_id.try_into()?;429 Ok(*<TokenData<T>>::get((self.id, token))430 .ok_or("token not found")?431 .owner432 .as_eth())433 }434 /// @dev Not implemented435 #[solidity(rename_selector = "safeTransferFrom")]436 fn safe_transfer_from_with_data(437 &mut self,438 _from: Address,439 _to: Address,440 _token_id: U256,441 _data: Bytes,442 ) -> Result<()> {443 // TODO: Not implemetable444 Err("not implemented".into())445 }446 /// @dev Not implemented447 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {448 // TODO: Not implemetable449 Err("not implemented".into())450 }451452 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE453 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE454 /// THEY MAY BE PERMANENTLY LOST455 /// @dev Throws unless `msg.sender` is the current owner or an authorized456 /// operator for this NFT. Throws if `from` is not the current owner. Throws457 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.458 /// @param from The current owner of the NFT459 /// @param to The new owner460 /// @param tokenId The NFT to transfer461 #[weight(<SelfWeightOf<T>>::transfer_from())]462 fn transfer_from(463 &mut self,464 caller: Caller,465 from: Address,466 to: Address,467 token_id: U256,468 ) -> Result<()> {469 let caller = T::CrossAccountId::from_eth(caller);470 let from = T::CrossAccountId::from_eth(from);471 let to = T::CrossAccountId::from_eth(to);472 let token = token_id.try_into()?;473 let budget = self474 .recorder475 .weight_calls_budget(<StructureWeight<T>>::find_parent());476477 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)478 .map_err(dispatch_to_evm::<T>)?;479 Ok(())480 }481482 /// @notice Set or reaffirm the approved address for an NFT483 /// @dev The zero address indicates there is no approved address.484 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized485 /// operator of the current owner.486 /// @param approved The new approved NFT controller487 /// @param tokenId The NFT to approve488 #[weight(<SelfWeightOf<T>>::approve())]489 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {490 let caller = T::CrossAccountId::from_eth(caller);491 let approved = T::CrossAccountId::from_eth(approved);492 let token = token_id.try_into()?;493494 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))495 .map_err(dispatch_to_evm::<T>)?;496 Ok(())497 }498499 /// @notice Sets or unsets the approval of a given operator.500 /// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.501 /// @param operator Operator502 /// @param approved Should operator status be granted or revoked?503 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]504 fn set_approval_for_all(505 &mut self,506 caller: Caller,507 operator: Address,508 approved: bool,509 ) -> Result<()> {510 let caller = T::CrossAccountId::from_eth(caller);511 let operator = T::CrossAccountId::from_eth(operator);512513 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)514 .map_err(dispatch_to_evm::<T>)?;515 Ok(())516 }517518 /// @notice Get the approved address for a single NFT519 /// @dev Throws if `tokenId` is not a valid NFT520 /// @param tokenId The NFT to find the approved address for521 /// @return The approved address for this NFT, or the zero address if there is none522 fn get_approved(&self, token_id: U256) -> Result<Address> {523 let token_id = token_id.try_into()?;524 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;525 Ok(if let Some(operator) = operator {526 *operator.as_eth()527 } else {528 Address::zero()529 })530 }531532 /// @notice Tells whether the given `owner` approves the `operator`.533 #[weight(<SelfWeightOf<T>>::allowance_for_all())]534 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {535 let owner = T::CrossAccountId::from_eth(owner);536 let operator = T::CrossAccountId::from_eth(operator);537538 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))539 }540}541542/// @title ERC721 Token that can be irreversibly burned (destroyed).543#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]544impl<T: Config> NonfungibleHandle<T> {545 /// @notice Burns a specific ERC721 token.546 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized547 /// operator of the current owner.548 /// @param tokenId The NFT to approve549 #[weight(<SelfWeightOf<T>>::burn_item())]550 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {551 let caller = T::CrossAccountId::from_eth(caller);552 let token = token_id.try_into()?;553554 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;555 Ok(())556 }557}558559/// @title ERC721 minting logic.560#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]561impl<T: Config> NonfungibleHandle<T> {562 /// @notice Function to mint a token.563 /// @param to The new owner564 /// @return uint256 The id of the newly minted token565 #[weight(<SelfWeightOf<T>>::create_item())]566 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {567 let token_id: U256 = <TokensMinted<T>>::get(self.id)568 .checked_add(1)569 .ok_or("item id overflow")?570 .into();571 self.mint_check_id(caller, to, token_id)?;572 Ok(token_id)573 }574575 /// @notice Function to mint a token.576 /// @dev `tokenId` should be obtained with `nextTokenId` method,577 /// unlike standard, you can't specify it manually578 /// @param to The new owner579 /// @param tokenId ID of the minted NFT580 #[solidity(hide, rename_selector = "mint")]581 #[weight(<SelfWeightOf<T>>::create_item())]582 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {583 let caller = T::CrossAccountId::from_eth(caller);584 let to = T::CrossAccountId::from_eth(to);585 let token_id: u32 = token_id.try_into()?;586 let budget = self587 .recorder588 .weight_calls_budget(<StructureWeight<T>>::find_parent());589590 if <TokensMinted<T>>::get(self.id)591 .checked_add(1)592 .ok_or("item id overflow")?593 != token_id594 {595 return Err("item id should be next".into());596 }597598 <Pallet<T>>::create_item(599 self,600 &caller,601 CreateItemData::<T> {602 properties: BoundedVec::default(),603 owner: to,604 },605 &budget,606 )607 .map_err(dispatch_to_evm::<T>)?;608609 Ok(true)610 }611612 /// @notice Function to mint token with the given tokenUri.613 /// @param to The new owner614 /// @param tokenUri Token URI that would be stored in the NFT properties615 /// @return uint256 The id of the newly minted token616 #[solidity(rename_selector = "mintWithTokenURI")]617 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]618 fn mint_with_token_uri(619 &mut self,620 caller: Caller,621 to: Address,622 token_uri: String,623 ) -> Result<U256> {624 let token_id: U256 = <TokensMinted<T>>::get(self.id)625 .checked_add(1)626 .ok_or("item id overflow")?627 .into();628 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;629 Ok(token_id)630 }631632 /// @notice Function to mint token with the given tokenUri.633 /// @dev `tokenId` should be obtained with `nextTokenId` method,634 /// unlike standard, you can't specify it manually635 /// @param to The new owner636 /// @param tokenId ID of the minted NFT637 /// @param tokenUri Token URI that would be stored in the NFT properties638 #[solidity(hide, rename_selector = "mintWithTokenURI")]639 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]640 fn mint_with_token_uri_check_id(641 &mut self,642 caller: Caller,643 to: Address,644 token_id: U256,645 token_uri: String,646 ) -> Result<bool> {647 let key = key::url();648 let permission = get_token_permission::<T>(self.id, &key)?;649 if !permission.collection_admin {650 return Err("Operation is not allowed".into());651 }652653 let caller = T::CrossAccountId::from_eth(caller);654 let to = T::CrossAccountId::from_eth(to);655 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;656 let budget = self657 .recorder658 .weight_calls_budget(<StructureWeight<T>>::find_parent());659660 if <TokensMinted<T>>::get(self.id)661 .checked_add(1)662 .ok_or("item id overflow")?663 != token_id664 {665 return Err("item id should be next".into());666 }667668 let mut properties = CollectionPropertiesVec::default();669 properties670 .try_push(Property {671 key,672 value: token_uri673 .into_bytes()674 .try_into()675 .map_err(|_| "token uri is too long")?,676 })677 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;678679 <Pallet<T>>::create_item(680 self,681 &caller,682 CreateItemData::<T> {683 properties,684 owner: to,685 },686 &budget,687 )688 .map_err(dispatch_to_evm::<T>)?;689 Ok(true)690 }691}692693fn get_token_property<T: Config>(694 collection: &CollectionHandle<T>,695 token_id: u32,696 key: &up_data_structs::PropertyKey,697) -> Result<String> {698 collection.consume_store_reads(1)?;699 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))700 .map_err(|_| Error::Revert("Token properties not found".into()))?;701 if let Some(property) = properties.get(key) {702 return Ok(String::from_utf8_lossy(property).into());703 }704705 Err("Property tokenURI not found".into())706}707708fn get_token_permission<T: Config>(709 collection_id: CollectionId,710 key: &PropertyKey,711) -> Result<PropertyPermission> {712 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)713 .map_err(|_| Error::Revert("No permissions for collection".into()))?;714 let a = token_property_permissions715 .get(key)716 .map(Clone::clone)717 .ok_or_else(|| {718 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();719 Error::Revert(alloc::format!("No permission for key {}", key))720 })?;721 Ok(a)722}723724/// @title Unique extensions for ERC721.725#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]726impl<T: Config> NonfungibleHandle<T>727where728 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,729{730 /// @notice A descriptive name for a collection of NFTs in this contract731 fn name(&self) -> String {732 decode_utf16(self.name.iter().copied())733 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))734 .collect::<String>()735 }736737 /// @notice An abbreviated name for NFTs in this contract738 fn symbol(&self) -> String {739 String::from_utf8_lossy(&self.token_prefix).into()740 }741742 /// @notice A description for the collection.743 fn description(&self) -> String {744 decode_utf16(self.description.iter().copied())745 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))746 .collect::<String>()747 }748749 /// Returns the owner (in cross format) of the token.750 ///751 /// @param tokenId Id for the token.752 #[solidity(hide)]753 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {754 Self::owner_of_cross(&self, token_id)755 }756757 /// Returns the owner (in cross format) of the token.758 ///759 /// @param tokenId Id for the token.760 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {761 Self::token_owner(&self, token_id.try_into()?)762 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))763 .map_err(|_| Error::Revert("token not found".into()))764 }765766 /// @notice Count all NFTs assigned to an owner767 /// @param owner An cross address for whom to query the balance768 /// @return The number of NFTs owned by `owner`, possibly zero769 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {770 self.consume_store_reads(1)?;771 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));772 Ok(balance.into())773 }774775 /// Returns the token properties.776 ///777 /// @param tokenId Id for the token.778 /// @param keys Properties keys. Empty keys for all propertyes.779 /// @return Vector of properties key/value pairs.780 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {781 let keys = keys782 .into_iter()783 .map(|key| {784 <Vec<u8>>::from(key)785 .try_into()786 .map_err(|_| Error::Revert("key too large".into()))787 })788 .collect::<Result<Vec<_>>>()?;789790 <Self as CommonCollectionOperations<T>>::token_properties(791 &self,792 token_id.try_into()?,793 if keys.is_empty() { None } else { Some(keys) },794 )795 .into_iter()796 .map(eth::Property::try_from)797 .collect::<Result<Vec<_>>>()798 }799800 /// @notice Set or reaffirm the approved address for an NFT801 /// @dev The zero address indicates there is no approved address.802 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized803 /// operator of the current owner.804 /// @param approved The new substrate address approved NFT controller805 /// @param tokenId The NFT to approve806 #[weight(<SelfWeightOf<T>>::approve())]807 fn approve_cross(808 &mut self,809 caller: Caller,810 approved: eth::CrossAddress,811 token_id: U256,812 ) -> Result<()> {813 let caller = T::CrossAccountId::from_eth(caller);814 let approved = approved.into_sub_cross_account::<T>()?;815 let token = token_id.try_into()?;816817 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))818 .map_err(dispatch_to_evm::<T>)?;819 Ok(())820 }821822 /// @notice Transfer ownership of an NFT823 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`824 /// is the zero address. Throws if `tokenId` is not a valid NFT.825 /// @param to The new owner826 /// @param tokenId The NFT to transfer827 #[weight(<SelfWeightOf<T>>::transfer())]828 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {829 let caller = T::CrossAccountId::from_eth(caller);830 let to = T::CrossAccountId::from_eth(to);831 let token = token_id.try_into()?;832 let budget = self833 .recorder834 .weight_calls_budget(<StructureWeight<T>>::find_parent());835836 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;837 Ok(())838 }839840 /// @notice Transfer ownership of an NFT841 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`842 /// is the zero address. Throws if `tokenId` is not a valid NFT.843 /// @param to The new owner844 /// @param tokenId The NFT to transfer845 #[weight(<SelfWeightOf<T>>::transfer())]846 fn transfer_cross(847 &mut self,848 caller: Caller,849 to: eth::CrossAddress,850 token_id: U256,851 ) -> Result<()> {852 let caller = T::CrossAccountId::from_eth(caller);853 let to = to.into_sub_cross_account::<T>()?;854 let token = token_id.try_into()?;855 let budget = self856 .recorder857 .weight_calls_budget(<StructureWeight<T>>::find_parent());858859 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;860 Ok(())861 }862863 /// @notice Transfer ownership of an NFT from cross account address to cross account address864 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`865 /// is the zero address. Throws if `tokenId` is not a valid NFT.866 /// @param from Cross acccount address of current owner867 /// @param to Cross acccount address of new owner868 /// @param tokenId The NFT to transfer869 #[weight(<SelfWeightOf<T>>::transfer())]870 fn transfer_from_cross(871 &mut self,872 caller: Caller,873 from: eth::CrossAddress,874 to: eth::CrossAddress,875 token_id: U256,876 ) -> Result<()> {877 let caller = T::CrossAccountId::from_eth(caller);878 let from = from.into_sub_cross_account::<T>()?;879 let to = to.into_sub_cross_account::<T>()?;880 let token_id = token_id.try_into()?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)885 .map_err(dispatch_to_evm::<T>)?;886 Ok(())887 }888889 /// @notice Burns a specific ERC721 token.890 /// @dev Throws unless `msg.sender` is the current owner or an authorized891 /// operator for this NFT. Throws if `from` is not the current owner. Throws892 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.893 /// @param from The current owner of the NFT894 /// @param tokenId The NFT to transfer895 #[solidity(hide)]896 #[weight(<SelfWeightOf<T>>::burn_from())]897 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {898 let caller = T::CrossAccountId::from_eth(caller);899 let from = T::CrossAccountId::from_eth(from);900 let token = token_id.try_into()?;901 let budget = self902 .recorder903 .weight_calls_budget(<StructureWeight<T>>::find_parent());904905 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)906 .map_err(dispatch_to_evm::<T>)?;907 Ok(())908 }909910 /// @notice Burns a specific ERC721 token.911 /// @dev Throws unless `msg.sender` is the current owner or an authorized912 /// operator for this NFT. Throws if `from` is not the current owner. Throws913 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.914 /// @param from The current owner of the NFT915 /// @param tokenId The NFT to transfer916 #[weight(<SelfWeightOf<T>>::burn_from())]917 fn burn_from_cross(918 &mut self,919 caller: Caller,920 from: eth::CrossAddress,921 token_id: U256,922 ) -> Result<()> {923 let caller = T::CrossAccountId::from_eth(caller);924 let from = from.into_sub_cross_account::<T>()?;925 let token = token_id.try_into()?;926 let budget = self927 .recorder928 .weight_calls_budget(<StructureWeight<T>>::find_parent());929930 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)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(<TokensMinted<T>>::get(self.id)939 .checked_add(1)940 .ok_or("item id overflow")?941 .into())942 }943944 /// @notice Function to mint multiple tokens.945 /// @dev `tokenIds` should be an array of consecutive numbers and first number946 /// should be obtained with `nextTokenId` method947 /// @param to The new owner948 /// @param tokenIds IDs of the minted NFTs949 #[solidity(hide)]950 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]951 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {952 let caller = T::CrossAccountId::from_eth(caller);953 let to = T::CrossAccountId::from_eth(to);954 let mut expected_index = <TokensMinted<T>>::get(self.id)955 .checked_add(1)956 .ok_or("item id overflow")?;957 let budget = self958 .recorder959 .weight_calls_budget(<StructureWeight<T>>::find_parent());960961 let total_tokens = token_ids.len();962 for id in token_ids.into_iter() {963 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;964 if id != expected_index {965 return Err("item id should be next".into());966 }967 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;968 }969 let data = (0..total_tokens)970 .map(|_| CreateItemData::<T> {971 properties: BoundedVec::default(),972 owner: to.clone(),973 })974 .collect();975976 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)977 .map_err(dispatch_to_evm::<T>)?;978 Ok(true)979 }980981 /// @notice Function to mint multiple tokens with the given tokenUris.982 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive983 /// numbers and first number should be obtained with `nextTokenId` method984 /// @param to The new owner985 /// @param tokens array of pairs of token ID and token URI for minted tokens986 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]987 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]988 fn mint_bulk_with_token_uri(989 &mut self,990 caller: Caller,991 to: Address,992 tokens: Vec<TokenUri>,993 ) -> Result<bool> {994 let key = key::url();995 let caller = T::CrossAccountId::from_eth(caller);996 let to = T::CrossAccountId::from_eth(to);997 let mut expected_index = <TokensMinted<T>>::get(self.id)998 .checked_add(1)999 .ok_or("item id overflow")?;1000 let budget = self1001 .recorder1002 .weight_calls_budget(<StructureWeight<T>>::find_parent());10031004 let mut data = Vec::with_capacity(tokens.len());1005 for TokenUri { id, uri } in tokens {1006 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1007 if id != expected_index {1008 return Err("item id should be next".into());1009 }1010 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10111012 let mut properties = CollectionPropertiesVec::default();1013 properties1014 .try_push(Property {1015 key: key.clone(),1016 value: uri1017 .into_bytes()1018 .try_into()1019 .map_err(|_| "token uri is too long")?,1020 })1021 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10221023 data.push(CreateItemData::<T> {1024 properties,1025 owner: to.clone(),1026 });1027 }10281029 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1030 .map_err(dispatch_to_evm::<T>)?;1031 Ok(true)1032 }10331034 /// @notice Function to mint a token.1035 /// @param to The new owner crossAccountId1036 /// @param properties Properties of minted token1037 /// @return uint256 The id of the newly minted token1038 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1039 fn mint_cross(1040 &mut self,1041 caller: Caller,1042 to: eth::CrossAddress,1043 properties: Vec<eth::Property>,1044 ) -> Result<U256> {1045 let token_id = <TokensMinted<T>>::get(self.id)1046 .checked_add(1)1047 .ok_or("item id overflow")?;10481049 let to = to.into_sub_cross_account::<T>()?;10501051 let properties = properties1052 .into_iter()1053 .map(eth::Property::try_into)1054 .collect::<Result<Vec<_>>>()?1055 .try_into()1056 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10571058 let caller = T::CrossAccountId::from_eth(caller);10591060 let budget = self1061 .recorder1062 .weight_calls_budget(<StructureWeight<T>>::find_parent());10631064 <Pallet<T>>::create_item(1065 self,1066 &caller,1067 CreateItemData::<T> {1068 properties,1069 owner: to,1070 },1071 &budget,1072 )1073 .map_err(dispatch_to_evm::<T>)?;10741075 Ok(token_id.into())1076 }10771078 /// @notice Returns collection helper contract address1079 fn collection_helper_address(&self) -> Address {1080 T::ContractAddress::get()1081 }1082}10831084#[solidity_interface(1085 name = UniqueNFT,1086 is(1087 ERC721,1088 ERC721Enumerable,1089 ERC721UniqueExtensions,1090 ERC721UniqueMintable,1091 ERC721Burnable,1092 ERC721Metadata(if(this.flags.erc721metadata)),1093 Collection(via(common_mut returns CollectionHandle<T>)),1094 TokenProperties,1095 ),1096 enum(derive(PreDispatch)),1097)]1098impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}10991100// Not a tests, but code generators1101generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1102generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11031104impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1105where1106 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1107{1108 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11091110 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1111 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1112 }1113}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;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::{34 dispatch_to_evm, frontier_contract,35 execution::{Result, PreDispatch, Error},36};37use sp_std::{vec::Vec, vec};38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,40 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},41 eth::{self, TokenUri},42 CommonWeightInfo,43};44use pallet_evm::{account::CrossAccountId, PrecompileHandle};45use pallet_evm_coder_substrate::call;46use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};47use sp_core::{U256, Get};4849use crate::{50 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,51 TokenProperties, SelfWeightOf, weights::WeightInfo, common::CommonWeights,52};5354/// Nft events.55#[derive(ToLog)]56pub enum ERC721TokenEvent {57 /// The token has been changed.58 TokenChanged {59 /// Token ID.60 #[indexed]61 token_id: U256,62 },63}6465frontier_contract! {66 macro_rules! NonfungibleHandle_result {...}67 impl<T: Config> Contract for NonfungibleHandle<T> {...}68}6970/// @title A contract that allows to set and delete token properties and change token property permissions.71#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]72impl<T: Config> NonfungibleHandle<T> {73 /// @notice Set permissions for token property.74 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.75 /// @param key Property key.76 /// @param isMutable Permission to mutate property.77 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.78 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.79 #[solidity(hide)]80 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]81 fn set_token_property_permission(82 &mut self,83 caller: Caller,84 key: String,85 is_mutable: bool,86 collection_admin: bool,87 token_owner: bool,88 ) -> Result<()> {89 let caller = T::CrossAccountId::from_eth(caller);90 <Pallet<T>>::set_token_property_permissions(91 self,92 &caller,93 vec![PropertyKeyPermission {94 key: <Vec<u8>>::from(key)95 .try_into()96 .map_err(|_| "too long key")?,97 permission: PropertyPermission {98 mutable: is_mutable,99 collection_admin,100 token_owner,101 },102 }],103 )104 .map_err(dispatch_to_evm::<T>)105 }106107 /// @notice Set permissions for token property.108 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.109 /// @param permissions Permissions for keys.110 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]111 fn set_token_property_permissions(112 &mut self,113 caller: Caller,114 permissions: Vec<eth::TokenPropertyPermission>,115 ) -> Result<()> {116 let caller = T::CrossAccountId::from_eth(caller);117 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;118119 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)120 .map_err(dispatch_to_evm::<T>)121 }122123 /// @notice Get permissions for token properties.124 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {125 let perms = <Pallet<T>>::token_property_permission(self.id);126 Ok(perms127 .into_iter()128 .map(eth::TokenPropertyPermission::from)129 .collect())130 }131132 /// @notice Set token property value.133 /// @dev Throws error if `msg.sender` has no permission to edit the property.134 /// @param tokenId ID of the token.135 /// @param key Property key.136 /// @param value Property value.137 #[solidity(hide)]138 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]139 fn set_property(140 &mut self,141 caller: Caller,142 token_id: U256,143 key: String,144 value: Bytes,145 ) -> Result<()> {146 let caller = T::CrossAccountId::from_eth(caller);147 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;148 let key = <Vec<u8>>::from(key)149 .try_into()150 .map_err(|_| "key too long")?;151 let value = value.0.try_into().map_err(|_| "value too long")?;152153 let nesting_budget = self154 .recorder155 .weight_calls_budget(<StructureWeight<T>>::find_parent());156157 <Pallet<T>>::set_token_property(158 self,159 &caller,160 TokenId(token_id),161 Property { key, value },162 &nesting_budget,163 )164 .map_err(dispatch_to_evm::<T>)165 }166167 /// @notice Set token properties value.168 /// @dev Throws error if `msg.sender` has no permission to edit the property.169 /// @param tokenId ID of the token.170 /// @param properties settable properties171 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]172 fn set_properties(173 &mut self,174 caller: Caller,175 token_id: U256,176 properties: Vec<eth::Property>,177 ) -> Result<()> {178 let caller = T::CrossAccountId::from_eth(caller);179 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;180181 let nesting_budget = self182 .recorder183 .weight_calls_budget(<StructureWeight<T>>::find_parent());184185 let properties = properties186 .into_iter()187 .map(eth::Property::try_into)188 .collect::<Result<Vec<_>>>()?;189190 <Pallet<T>>::set_token_properties(191 self,192 &caller,193 TokenId(token_id),194 properties.into_iter(),195 false,196 &nesting_budget,197 )198 .map_err(dispatch_to_evm::<T>)199 }200201 /// @notice Delete token property value.202 /// @dev Throws error if `msg.sender` has no permission to edit the property.203 /// @param tokenId ID of the token.204 /// @param key Property key.205 #[solidity(hide)]206 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]207 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {208 let caller = T::CrossAccountId::from_eth(caller);209 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;210 let key = <Vec<u8>>::from(key)211 .try_into()212 .map_err(|_| "key too long")?;213214 let nesting_budget = self215 .recorder216 .weight_calls_budget(<StructureWeight<T>>::find_parent());217218 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)219 .map_err(dispatch_to_evm::<T>)220 }221222 /// @notice Delete token properties value.223 /// @dev Throws error if `msg.sender` has no permission to edit the property.224 /// @param tokenId ID of the token.225 /// @param keys Properties key.226 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]227 fn delete_properties(228 &mut self,229 token_id: U256,230 caller: Caller,231 keys: Vec<String>,232 ) -> Result<()> {233 let caller = T::CrossAccountId::from_eth(caller);234 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;235 let keys = keys236 .into_iter()237 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))238 .collect::<Result<Vec<_>>>()?;239240 let nesting_budget = self241 .recorder242 .weight_calls_budget(<StructureWeight<T>>::find_parent());243244 <Pallet<T>>::delete_token_properties(245 self,246 &caller,247 TokenId(token_id),248 keys.into_iter(),249 &nesting_budget,250 )251 .map_err(dispatch_to_evm::<T>)252 }253254 /// @notice Get token property value.255 /// @dev Throws error if key not found256 /// @param tokenId ID of the token.257 /// @param key Property key.258 /// @return Property value bytes259 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {260 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;261 let key = <Vec<u8>>::from(key)262 .try_into()263 .map_err(|_| "key too long")?;264265 let props = <TokenProperties<T>>::get((self.id, token_id));266 let prop = props.get(&key).ok_or("key not found")?;267268 Ok(prop.to_vec().into())269 }270}271272#[derive(ToLog)]273pub enum ERC721Events {274 /// @dev This emits when ownership of any NFT changes by any mechanism.275 /// This event emits when NFTs are created (`from` == 0) and destroyed276 /// (`to` == 0). Exception: during contract creation, any number of NFTs277 /// may be created and assigned without emitting Transfer. At the time of278 /// any transfer, the approved address for that NFT (if any) is reset to none.279 Transfer {280 #[indexed]281 from: Address,282 #[indexed]283 to: Address,284 #[indexed]285 token_id: U256,286 },287 /// @dev This emits when the approved address for an NFT is changed or288 /// reaffirmed. The zero address indicates there is no approved address.289 /// When a Transfer event emits, this also indicates that the approved290 /// address for that NFT (if any) is reset to none.291 Approval {292 #[indexed]293 owner: Address,294 #[indexed]295 approved: Address,296 #[indexed]297 token_id: U256,298 },299 /// @dev This emits when an operator is enabled or disabled for an owner.300 /// The operator can manage all NFTs of the owner.301 #[allow(dead_code)]302 ApprovalForAll {303 #[indexed]304 owner: Address,305 #[indexed]306 operator: Address,307 approved: bool,308 },309}310311/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension312/// @dev See https://eips.ethereum.org/EIPS/eip-721313#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]314impl<T: Config> NonfungibleHandle<T>315where316 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,317{318 /// @notice A descriptive name for a collection of NFTs in this contract319 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`320 #[solidity(hide, rename_selector = "name")]321 fn name_proxy(&self) -> String {322 self.name()323 }324325 /// @notice An abbreviated name for NFTs in this contract326 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`327 #[solidity(hide, rename_selector = "symbol")]328 fn symbol_proxy(&self) -> String {329 self.symbol()330 }331332 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.333 ///334 /// @dev If the token has a `url` property and it is not empty, it is returned.335 /// 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`.336 /// If the collection property `baseURI` is empty or absent, return "" (empty string)337 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix338 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).339 ///340 /// @return token's const_metadata341 #[solidity(rename_selector = "tokenURI")]342 fn token_uri(&self, token_id: U256) -> Result<String> {343 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;344345 match get_token_property(self, token_id_u32, &key::url()).as_deref() {346 Err(_) | Ok("") => (),347 Ok(url) => {348 return Ok(url.into());349 }350 };351352 let base_uri =353 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())354 .map(BoundedVec::into_inner)355 .map(String::from_utf8)356 .transpose()357 .map_err(|e| {358 Error::Revert(alloc::format!(359 "Can not convert value \"baseURI\" to string with error \"{}\"",360 e361 ))362 })?;363364 let base_uri = match base_uri.as_deref() {365 None | Some("") => {366 return Ok("".into());367 }368 Some(base_uri) => base_uri.into(),369 };370371 Ok(372 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {373 Err(_) | Ok("") => base_uri,374 Ok(suffix) => base_uri + suffix,375 },376 )377 }378}379380/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension381/// @dev See https://eips.ethereum.org/EIPS/eip-721382#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]383impl<T: Config> NonfungibleHandle<T> {384 /// @notice Enumerate valid NFTs385 /// @param index A counter less than `totalSupply()`386 /// @return The token identifier for the `index`th NFT,387 /// (sort order not specified)388 fn token_by_index(&self, index: U256) -> U256 {389 index390 }391392 /// @dev Not implemented393 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {394 // TODO: Not implemetable395 Err("not implemented".into())396 }397398 /// @notice Count NFTs tracked by this contract399 /// @return A count of valid NFTs tracked by this contract, where each one of400 /// them has an assigned and queryable owner not equal to the zero address401 fn total_supply(&self) -> Result<U256> {402 self.consume_store_reads(1)?;403 Ok(<Pallet<T>>::total_supply(self).into())404 }405}406407/// @title ERC-721 Non-Fungible Token Standard408/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md409#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]410impl<T: Config> NonfungibleHandle<T> {411 /// @notice Count all NFTs assigned to an owner412 /// @dev NFTs assigned to the zero address are considered invalid, and this413 /// function throws for queries about the zero address.414 /// @param owner An address for whom to query the balance415 /// @return The number of NFTs owned by `owner`, possibly zero416 fn balance_of(&self, owner: Address) -> Result<U256> {417 self.consume_store_reads(1)?;418 let owner = T::CrossAccountId::from_eth(owner);419 let balance = <AccountBalance<T>>::get((self.id, owner));420 Ok(balance.into())421 }422 /// @notice Find the owner of an NFT423 /// @dev NFTs assigned to zero address are considered invalid, and queries424 /// about them do throw.425 /// @param tokenId The identifier for an NFT426 /// @return The address of the owner of the NFT427 fn owner_of(&self, token_id: U256) -> Result<Address> {428 self.consume_store_reads(1)?;429 let token: TokenId = token_id.try_into()?;430 Ok(*<TokenData<T>>::get((self.id, token))431 .ok_or("token not found")?432 .owner433 .as_eth())434 }435 /// @dev Not implemented436 #[solidity(rename_selector = "safeTransferFrom")]437 fn safe_transfer_from_with_data(438 &mut self,439 _from: Address,440 _to: Address,441 _token_id: U256,442 _data: Bytes,443 ) -> Result<()> {444 // TODO: Not implemetable445 Err("not implemented".into())446 }447 /// @dev Not implemented448 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {449 // TODO: Not implemetable450 Err("not implemented".into())451 }452453 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE454 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE455 /// THEY MAY BE PERMANENTLY LOST456 /// @dev Throws unless `msg.sender` is the current owner or an authorized457 /// operator for this NFT. Throws if `from` is not the current owner. Throws458 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.459 /// @param from The current owner of the NFT460 /// @param to The new owner461 /// @param tokenId The NFT to transfer462 #[weight(<CommonWeights<T>>::transfer_from())]463 fn transfer_from(464 &mut self,465 caller: Caller,466 from: Address,467 to: Address,468 token_id: U256,469 ) -> Result<()> {470 let caller = T::CrossAccountId::from_eth(caller);471 let from = T::CrossAccountId::from_eth(from);472 let to = T::CrossAccountId::from_eth(to);473 let token = token_id.try_into()?;474 let budget = self475 .recorder476 .weight_calls_budget(<StructureWeight<T>>::find_parent());477478 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)479 .map_err(|e| dispatch_to_evm::<T>(e.error))?;480 Ok(())481 }482483 /// @notice Set or reaffirm the approved address for an NFT484 /// @dev The zero address indicates there is no approved address.485 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized486 /// operator of the current owner.487 /// @param approved The new approved NFT controller488 /// @param tokenId The NFT to approve489 #[weight(<SelfWeightOf<T>>::approve())]490 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {491 let caller = T::CrossAccountId::from_eth(caller);492 let approved = T::CrossAccountId::from_eth(approved);493 let token = token_id.try_into()?;494495 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))496 .map_err(dispatch_to_evm::<T>)?;497 Ok(())498 }499500 /// @notice Sets or unsets the approval of a given operator.501 /// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.502 /// @param operator Operator503 /// @param approved Should operator status be granted or revoked?504 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]505 fn set_approval_for_all(506 &mut self,507 caller: Caller,508 operator: Address,509 approved: bool,510 ) -> Result<()> {511 let caller = T::CrossAccountId::from_eth(caller);512 let operator = T::CrossAccountId::from_eth(operator);513514 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)515 .map_err(dispatch_to_evm::<T>)?;516 Ok(())517 }518519 /// @notice Get the approved address for a single NFT520 /// @dev Throws if `tokenId` is not a valid NFT521 /// @param tokenId The NFT to find the approved address for522 /// @return The approved address for this NFT, or the zero address if there is none523 fn get_approved(&self, token_id: U256) -> Result<Address> {524 let token_id = token_id.try_into()?;525 let operator = <Pallet<T>>::get_allowance(self, token_id).map_err(dispatch_to_evm::<T>)?;526 Ok(if let Some(operator) = operator {527 *operator.as_eth()528 } else {529 Address::zero()530 })531 }532533 /// @notice Tells whether the given `owner` approves the `operator`.534 #[weight(<SelfWeightOf<T>>::allowance_for_all())]535 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {536 let owner = T::CrossAccountId::from_eth(owner);537 let operator = T::CrossAccountId::from_eth(operator);538539 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))540 }541}542543/// @title ERC721 Token that can be irreversibly burned (destroyed).544#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]545impl<T: Config> NonfungibleHandle<T> {546 /// @notice Burns a specific ERC721 token.547 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized548 /// operator of the current owner.549 /// @param tokenId The NFT to approve550 #[weight(<SelfWeightOf<T>>::burn_item())]551 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {552 let caller = T::CrossAccountId::from_eth(caller);553 let token = token_id.try_into()?;554555 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;556 Ok(())557 }558}559560/// @title ERC721 minting logic.561#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]562impl<T: Config> NonfungibleHandle<T> {563 /// @notice Function to mint a token.564 /// @param to The new owner565 /// @return uint256 The id of the newly minted token566 #[weight(<SelfWeightOf<T>>::create_item())]567 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {568 let token_id: U256 = <TokensMinted<T>>::get(self.id)569 .checked_add(1)570 .ok_or("item id overflow")?571 .into();572 self.mint_check_id(caller, to, token_id)?;573 Ok(token_id)574 }575576 /// @notice Function to mint a token.577 /// @dev `tokenId` should be obtained with `nextTokenId` method,578 /// unlike standard, you can't specify it manually579 /// @param to The new owner580 /// @param tokenId ID of the minted NFT581 #[solidity(hide, rename_selector = "mint")]582 #[weight(<SelfWeightOf<T>>::create_item())]583 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {584 let caller = T::CrossAccountId::from_eth(caller);585 let to = T::CrossAccountId::from_eth(to);586 let token_id: u32 = token_id.try_into()?;587 let budget = self588 .recorder589 .weight_calls_budget(<StructureWeight<T>>::find_parent());590591 if <TokensMinted<T>>::get(self.id)592 .checked_add(1)593 .ok_or("item id overflow")?594 != token_id595 {596 return Err("item id should be next".into());597 }598599 <Pallet<T>>::create_item(600 self,601 &caller,602 CreateItemData::<T> {603 properties: BoundedVec::default(),604 owner: to,605 },606 &budget,607 )608 .map_err(dispatch_to_evm::<T>)?;609610 Ok(true)611 }612613 /// @notice Function to mint token with the given tokenUri.614 /// @param to The new owner615 /// @param tokenUri Token URI that would be stored in the NFT properties616 /// @return uint256 The id of the newly minted token617 #[solidity(rename_selector = "mintWithTokenURI")]618 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]619 fn mint_with_token_uri(620 &mut self,621 caller: Caller,622 to: Address,623 token_uri: String,624 ) -> Result<U256> {625 let token_id: U256 = <TokensMinted<T>>::get(self.id)626 .checked_add(1)627 .ok_or("item id overflow")?628 .into();629 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;630 Ok(token_id)631 }632633 /// @notice Function to mint token with the given tokenUri.634 /// @dev `tokenId` should be obtained with `nextTokenId` method,635 /// unlike standard, you can't specify it manually636 /// @param to The new owner637 /// @param tokenId ID of the minted NFT638 /// @param tokenUri Token URI that would be stored in the NFT properties639 #[solidity(hide, rename_selector = "mintWithTokenURI")]640 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]641 fn mint_with_token_uri_check_id(642 &mut self,643 caller: Caller,644 to: Address,645 token_id: U256,646 token_uri: String,647 ) -> Result<bool> {648 let key = key::url();649 let permission = get_token_permission::<T>(self.id, &key)?;650 if !permission.collection_admin {651 return Err("Operation is not allowed".into());652 }653654 let caller = T::CrossAccountId::from_eth(caller);655 let to = T::CrossAccountId::from_eth(to);656 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;657 let budget = self658 .recorder659 .weight_calls_budget(<StructureWeight<T>>::find_parent());660661 if <TokensMinted<T>>::get(self.id)662 .checked_add(1)663 .ok_or("item id overflow")?664 != token_id665 {666 return Err("item id should be next".into());667 }668669 let mut properties = CollectionPropertiesVec::default();670 properties671 .try_push(Property {672 key,673 value: token_uri674 .into_bytes()675 .try_into()676 .map_err(|_| "token uri is too long")?,677 })678 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;679680 <Pallet<T>>::create_item(681 self,682 &caller,683 CreateItemData::<T> {684 properties,685 owner: to,686 },687 &budget,688 )689 .map_err(dispatch_to_evm::<T>)?;690 Ok(true)691 }692}693694fn get_token_property<T: Config>(695 collection: &CollectionHandle<T>,696 token_id: u32,697 key: &up_data_structs::PropertyKey,698) -> Result<String> {699 collection.consume_store_reads(1)?;700 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))701 .map_err(|_| Error::Revert("Token properties not found".into()))?;702 if let Some(property) = properties.get(key) {703 return Ok(String::from_utf8_lossy(property).into());704 }705706 Err("Property tokenURI not found".into())707}708709fn get_token_permission<T: Config>(710 collection_id: CollectionId,711 key: &PropertyKey,712) -> Result<PropertyPermission> {713 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)714 .map_err(|_| Error::Revert("No permissions for collection".into()))?;715 let a = token_property_permissions716 .get(key)717 .map(Clone::clone)718 .ok_or_else(|| {719 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();720 Error::Revert(alloc::format!("No permission for key {}", key))721 })?;722 Ok(a)723}724725/// @title Unique extensions for ERC721.726#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]727impl<T: Config> NonfungibleHandle<T>728where729 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,730{731 /// @notice A descriptive name for a collection of NFTs in this contract732 fn name(&self) -> String {733 decode_utf16(self.name.iter().copied())734 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))735 .collect::<String>()736 }737738 /// @notice An abbreviated name for NFTs in this contract739 fn symbol(&self) -> String {740 String::from_utf8_lossy(&self.token_prefix).into()741 }742743 /// @notice A description for the collection.744 fn description(&self) -> String {745 decode_utf16(self.description.iter().copied())746 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))747 .collect::<String>()748 }749750 /// Returns the owner (in cross format) of the token.751 ///752 /// @param tokenId Id for the token.753 #[solidity(hide)]754 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {755 Self::owner_of_cross(&self, token_id)756 }757758 /// Returns the owner (in cross format) of the token.759 ///760 /// @param tokenId Id for the token.761 fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {762 Self::token_owner(&self, token_id.try_into()?)763 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))764 .map_err(|_| Error::Revert("token not found".into()))765 }766767 /// @notice Count all NFTs assigned to an owner768 /// @param owner An cross address for whom to query the balance769 /// @return The number of NFTs owned by `owner`, possibly zero770 fn balance_of_cross(&self, owner: eth::CrossAddress) -> Result<U256> {771 self.consume_store_reads(1)?;772 let balance = <AccountBalance<T>>::get((self.id, owner.into_sub_cross_account::<T>()?));773 Ok(balance.into())774 }775776 /// Returns the token properties.777 ///778 /// @param tokenId Id for the token.779 /// @param keys Properties keys. Empty keys for all propertyes.780 /// @return Vector of properties key/value pairs.781 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {782 let keys = keys783 .into_iter()784 .map(|key| {785 <Vec<u8>>::from(key)786 .try_into()787 .map_err(|_| Error::Revert("key too large".into()))788 })789 .collect::<Result<Vec<_>>>()?;790791 <Self as CommonCollectionOperations<T>>::token_properties(792 &self,793 token_id.try_into()?,794 if keys.is_empty() { None } else { Some(keys) },795 )796 .into_iter()797 .map(eth::Property::try_from)798 .collect::<Result<Vec<_>>>()799 }800801 /// @notice Set or reaffirm the approved address for an NFT802 /// @dev The zero address indicates there is no approved address.803 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized804 /// operator of the current owner.805 /// @param approved The new substrate address approved NFT controller806 /// @param tokenId The NFT to approve807 #[weight(<SelfWeightOf<T>>::approve())]808 fn approve_cross(809 &mut self,810 caller: Caller,811 approved: eth::CrossAddress,812 token_id: U256,813 ) -> Result<()> {814 let caller = T::CrossAccountId::from_eth(caller);815 let approved = approved.into_sub_cross_account::<T>()?;816 let token = token_id.try_into()?;817818 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))819 .map_err(dispatch_to_evm::<T>)?;820 Ok(())821 }822823 /// @notice Transfer ownership of an NFT824 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`825 /// is the zero address. Throws if `tokenId` is not a valid NFT.826 /// @param to The new owner827 /// @param tokenId The NFT to transfer828 #[weight(<CommonWeights<T>>::transfer())]829 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {830 let caller = T::CrossAccountId::from_eth(caller);831 let to = T::CrossAccountId::from_eth(to);832 let token = token_id.try_into()?;833 let budget = self834 .recorder835 .weight_calls_budget(<StructureWeight<T>>::find_parent());836837 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)838 .map_err(|e| dispatch_to_evm::<T>(e.error))?;839 Ok(())840 }841842 /// @notice Transfer ownership of an NFT843 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`844 /// is the zero address. Throws if `tokenId` is not a valid NFT.845 /// @param to The new owner846 /// @param tokenId The NFT to transfer847 #[weight(<CommonWeights<T>>::transfer())]848 fn transfer_cross(849 &mut self,850 caller: Caller,851 to: eth::CrossAddress,852 token_id: U256,853 ) -> Result<()> {854 let caller = T::CrossAccountId::from_eth(caller);855 let to = to.into_sub_cross_account::<T>()?;856 let token = token_id.try_into()?;857 let budget = self858 .recorder859 .weight_calls_budget(<StructureWeight<T>>::find_parent());860861 <Pallet<T>>::transfer(self, &caller, &to, token, &budget)862 .map_err(|e| dispatch_to_evm::<T>(e.error))?;863 Ok(())864 }865866 /// @notice Transfer ownership of an NFT from cross account address to cross account address867 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`868 /// is the zero address. Throws if `tokenId` is not a valid NFT.869 /// @param from Cross acccount address of current owner870 /// @param to Cross acccount address of new owner871 /// @param tokenId The NFT to transfer872 #[weight(<CommonWeights<T>>::transfer_from())]873 fn transfer_from_cross(874 &mut self,875 caller: Caller,876 from: eth::CrossAddress,877 to: eth::CrossAddress,878 token_id: U256,879 ) -> Result<()> {880 let caller = T::CrossAccountId::from_eth(caller);881 let from = from.into_sub_cross_account::<T>()?;882 let to = to.into_sub_cross_account::<T>()?;883 let token_id = token_id.try_into()?;884 let budget = self885 .recorder886 .weight_calls_budget(<StructureWeight<T>>::find_parent());887 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)888 .map_err(|e| dispatch_to_evm::<T>(e.error))?;889 Ok(())890 }891892 /// @notice Burns a specific ERC721 token.893 /// @dev Throws unless `msg.sender` is the current owner or an authorized894 /// operator for this NFT. Throws if `from` is not the current owner. Throws895 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.896 /// @param from The current owner of the NFT897 /// @param tokenId The NFT to transfer898 #[solidity(hide)]899 #[weight(<SelfWeightOf<T>>::burn_from())]900 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {901 let caller = T::CrossAccountId::from_eth(caller);902 let from = T::CrossAccountId::from_eth(from);903 let token = token_id.try_into()?;904 let budget = self905 .recorder906 .weight_calls_budget(<StructureWeight<T>>::find_parent());907908 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)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()?;929 let budget = self930 .recorder931 .weight_calls_budget(<StructureWeight<T>>::find_parent());932933 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)934 .map_err(dispatch_to_evm::<T>)?;935 Ok(())936 }937938 /// @notice Returns next free NFT ID.939 fn next_token_id(&self) -> Result<U256> {940 self.consume_store_reads(1)?;941 Ok(<TokensMinted<T>>::get(self.id)942 .checked_add(1)943 .ok_or("item id overflow")?944 .into())945 }946947 /// @notice Function to mint multiple tokens.948 /// @dev `tokenIds` should be an array of consecutive numbers and first number949 /// should be obtained with `nextTokenId` method950 /// @param to The new owner951 /// @param tokenIds IDs of the minted NFTs952 #[solidity(hide)]953 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]954 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {955 let caller = T::CrossAccountId::from_eth(caller);956 let to = T::CrossAccountId::from_eth(to);957 let mut expected_index = <TokensMinted<T>>::get(self.id)958 .checked_add(1)959 .ok_or("item id overflow")?;960 let budget = self961 .recorder962 .weight_calls_budget(<StructureWeight<T>>::find_parent());963964 let total_tokens = token_ids.len();965 for id in token_ids.into_iter() {966 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;967 if id != expected_index {968 return Err("item id should be next".into());969 }970 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;971 }972 let data = (0..total_tokens)973 .map(|_| CreateItemData::<T> {974 properties: BoundedVec::default(),975 owner: to.clone(),976 })977 .collect();978979 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)980 .map_err(dispatch_to_evm::<T>)?;981 Ok(true)982 }983984 /// @notice Function to mint multiple tokens with the given tokenUris.985 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive986 /// numbers and first number should be obtained with `nextTokenId` method987 /// @param to The new owner988 /// @param tokens array of pairs of token ID and token URI for minted tokens989 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]990 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]991 fn mint_bulk_with_token_uri(992 &mut self,993 caller: Caller,994 to: Address,995 tokens: Vec<TokenUri>,996 ) -> Result<bool> {997 let key = key::url();998 let caller = T::CrossAccountId::from_eth(caller);999 let to = T::CrossAccountId::from_eth(to);1000 let mut expected_index = <TokensMinted<T>>::get(self.id)1001 .checked_add(1)1002 .ok_or("item id overflow")?;1003 let budget = self1004 .recorder1005 .weight_calls_budget(<StructureWeight<T>>::find_parent());10061007 let mut data = Vec::with_capacity(tokens.len());1008 for TokenUri { id, uri } in tokens {1009 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1010 if id != expected_index {1011 return Err("item id should be next".into());1012 }1013 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10141015 let mut properties = CollectionPropertiesVec::default();1016 properties1017 .try_push(Property {1018 key: key.clone(),1019 value: uri1020 .into_bytes()1021 .try_into()1022 .map_err(|_| "token uri is too long")?,1023 })1024 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10251026 data.push(CreateItemData::<T> {1027 properties,1028 owner: to.clone(),1029 });1030 }10311032 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1033 .map_err(dispatch_to_evm::<T>)?;1034 Ok(true)1035 }10361037 /// @notice Function to mint a token.1038 /// @param to The new owner crossAccountId1039 /// @param properties Properties of minted token1040 /// @return uint256 The id of the newly minted token1041 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1042 fn mint_cross(1043 &mut self,1044 caller: Caller,1045 to: eth::CrossAddress,1046 properties: Vec<eth::Property>,1047 ) -> Result<U256> {1048 let token_id = <TokensMinted<T>>::get(self.id)1049 .checked_add(1)1050 .ok_or("item id overflow")?;10511052 let to = to.into_sub_cross_account::<T>()?;10531054 let properties = properties1055 .into_iter()1056 .map(eth::Property::try_into)1057 .collect::<Result<Vec<_>>>()?1058 .try_into()1059 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10601061 let caller = T::CrossAccountId::from_eth(caller);10621063 let budget = self1064 .recorder1065 .weight_calls_budget(<StructureWeight<T>>::find_parent());10661067 <Pallet<T>>::create_item(1068 self,1069 &caller,1070 CreateItemData::<T> {1071 properties,1072 owner: to,1073 },1074 &budget,1075 )1076 .map_err(dispatch_to_evm::<T>)?;10771078 Ok(token_id.into())1079 }10801081 /// @notice Returns collection helper contract address1082 fn collection_helper_address(&self) -> Address {1083 T::ContractAddress::get()1084 }1085}10861087#[solidity_interface(1088 name = UniqueNFT,1089 is(1090 ERC721,1091 ERC721Enumerable,1092 ERC721UniqueExtensions,1093 ERC721UniqueMintable,1094 ERC721Burnable,1095 ERC721Metadata(if(this.flags.erc721metadata)),1096 Collection(via(common_mut returns CollectionHandle<T>)),1097 TokenProperties,1098 ),1099 enum(derive(PreDispatch)),1100)]1101impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11021103// Not a tests, but code generators1104generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1105generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);11061107impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1108where1109 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1110{1111 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");11121113 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1114 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1115 }1116}pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -108,7 +108,8 @@
use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
use pallet_common::{
Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,
- eth::collection_id_to_address,
+ eth::collection_id_to_address, SelfWeightOf as PalletCommonWeightOf,
+ weights::WeightInfo as CommonWeightInfo,
};
use pallet_structure::{Pallet as PalletStructure, Error as StructureError};
use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};
@@ -802,7 +803,7 @@
to: &T::CrossAccountId,
token: TokenId,
nesting_budget: &dyn Budget,
- ) -> DispatchResult {
+ ) -> DispatchResultWithPostInfo {
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed
@@ -812,7 +813,8 @@
<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;
ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);
- if collection.permissions.access() == AccessMode::AllowList {
+ let is_allow_list_mode = collection.permissions.access() == AccessMode::AllowList;
+ if is_allow_list_mode {
collection.check_allowlist(from)?;
collection.check_allowlist(to)?;
}
@@ -884,7 +886,18 @@
to.clone(),
1,
));
- Ok(())
+
+ let actual_weight = match is_allow_list_mode {
+ true => Some(
+ <SelfWeightOf<T>>::transfer() + <PalletCommonWeightOf<T>>::check_accesslist() * 2,
+ ),
+ false => Some(<SelfWeightOf<T>>::transfer()),
+ };
+
+ Ok(PostDispatchInfo {
+ actual_weight,
+ pays_fee: Pays::Yes,
+ })
}
/// Batch operation to mint multiple NFT tokens.
@@ -1228,13 +1241,18 @@
to: &T::CrossAccountId,
token: TokenId,
nesting_budget: &dyn Budget,
- ) -> DispatchResult {
+ ) -> DispatchResultWithPostInfo {
Self::check_allowed(collection, spender, from, token, nesting_budget)?;
// =========
// Allowance is reset in [`transfer`]
- Self::transfer(collection, from, to, token, nesting_budget)
+ Self::transfer(collection, from, to, token, nesting_budget).map(|mut p| {
+ p.actual_weight = Some(
+ p.actual_weight.unwrap_or_default() + <SelfWeightOf<T>>::checks_for_transfer_from(),
+ );
+ p
+ })
}
/// Burn NFT token for `from` account.
pallets/nonfungible/src/weights.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/weights.rs
+++ b/pallets/nonfungible/src/weights.rs
@@ -43,7 +43,7 @@
fn transfer() -> Weight;
fn approve() -> Weight;
fn approve_from() -> Weight;
- fn transfer_from() -> Weight;
+ fn checks_for_transfer_from() -> Weight;
fn burn_from() -> Weight;
fn set_token_property_permissions(b: u32, ) -> Weight;
fn set_token_properties(b: u32, ) -> Weight;