difftreelog
fix PR
in: master
10 files changed
crates/evm-coder/procedural/src/abi_derive/derive_enum.rsdiffbeforeafterboth--- a/crates/evm-coder/procedural/src/abi_derive/derive_enum.rs
+++ b/crates/evm-coder/procedural/src/abi_derive/derive_enum.rs
@@ -47,11 +47,10 @@
)
}
-pub fn impl_enum_abi_type(name: &syn::Ident, option_count: usize) -> proc_macro2::TokenStream {
+pub fn impl_enum_abi_type(name: &syn::Ident) -> proc_macro2::TokenStream {
quote! {
impl ::evm_coder::abi::AbiType for #name {
const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <u8 as ::evm_coder::abi::AbiType>::SIGNATURE;
- const FIELDS_COUNT: usize = #option_count;
fn is_dynamic() -> bool {
<u8 as ::evm_coder::abi::AbiType>::is_dynamic()
crates/evm-coder/procedural/src/abi_derive/derive_struct.rsdiffbeforeafterboth--- a/crates/evm-coder/procedural/src/abi_derive/derive_struct.rs
+++ b/crates/evm-coder/procedural/src/abi_derive/derive_struct.rs
@@ -100,12 +100,10 @@
pub fn impl_struct_abi_type(
name: &syn::Ident,
tuple_type: proc_macro2::TokenStream,
- fields_count: usize,
) -> proc_macro2::TokenStream {
quote! {
impl ::evm_coder::abi::AbiType for #name {
const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <#tuple_type as ::evm_coder::abi::AbiType>::SIGNATURE;
- const FIELDS_COUNT: usize = #fields_count;
fn is_dynamic() -> bool {
<#tuple_type as ::evm_coder::abi::AbiType>::is_dynamic()
}
crates/evm-coder/procedural/src/abi_derive/mod.rsdiffbeforeafterboth--- a/crates/evm-coder/procedural/src/abi_derive/mod.rs
+++ b/crates/evm-coder/procedural/src/abi_derive/mod.rs
@@ -49,7 +49,7 @@
let struct_from_tuple = struct_from_tuple(name, is_named_fields, field_names.clone());
let can_be_plcaed_in_vec = impl_can_be_placed_in_vec(name);
- let abi_type = impl_struct_abi_type(name, tuple_type.clone(), params_count);
+ let abi_type = impl_struct_abi_type(name, tuple_type.clone());
let abi_read = impl_struct_abi_read(name, tuple_type, tuple_names, struct_from_tuple);
let abi_write = impl_struct_abi_write(name, is_named_fields, tuple_ref_type, tuple_data);
let solidity_type = impl_struct_solidity_type(name, field_types.clone(), params_count);
@@ -83,7 +83,7 @@
let from = impl_enum_from_u8(name, enum_options.clone());
let solidity_option = impl_solidity_option(name, enum_options.clone());
let can_be_plcaed_in_vec = impl_can_be_placed_in_vec(name);
- let abi_type = impl_enum_abi_type(name, option_count);
+ let abi_type = impl_enum_abi_type(name);
let abi_read = impl_enum_abi_read(name);
let abi_write = impl_enum_abi_write(name);
let solidity_type = impl_enum_solidity_type(name);
crates/evm-coder/src/abi/impls.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi/impls.rs
+++ b/crates/evm-coder/src/abi/impls.rs
@@ -16,7 +16,6 @@
impl AbiType for $ty {
const SIGNATURE: SignatureUnit = make_signature!(new fixed(stringify!($name)));
- const FIELDS_COUNT: usize = 1;
fn is_dynamic() -> bool {
$dynamic
@@ -97,7 +96,6 @@
impl<T: AbiType> AbiType for &T {
const SIGNATURE: SignatureUnit = T::SIGNATURE;
- const FIELDS_COUNT: usize = T::FIELDS_COUNT;
fn is_dynamic() -> bool {
T::is_dynamic()
@@ -127,7 +125,6 @@
impl<T: AbiType> AbiType for Vec<T> {
const SIGNATURE: SignatureUnit = make_signature!(new nameof(T::SIGNATURE) fixed("[]"));
- const FIELDS_COUNT: usize = 1;
fn is_dynamic() -> bool {
true
@@ -203,7 +200,6 @@
shift_left(1)
fixed(")")
);
- const FIELDS_COUNT: usize = count!($($ident)*);
fn is_dynamic() -> bool {
false
crates/evm-coder/src/abi/traits.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi/traits.rs
+++ b/crates/evm-coder/src/abi/traits.rs
@@ -10,9 +10,6 @@
/// Signature for Etherium ABI.
const SIGNATURE: SignatureUnit;
- /// Count of enum variants or struct fields.
- const FIELDS_COUNT: usize;
-
/// Signature as str.
fn as_str() -> &'static str {
from_utf8(&Self::SIGNATURE.data[..Self::SIGNATURE.len]).expect("bad utf-8")
crates/evm-coder/tests/abi_derive_generation.rsdiffbeforeafterboth--- a/crates/evm-coder/tests/abi_derive_generation.rs
+++ b/crates/evm-coder/tests/abi_derive_generation.rs
@@ -173,50 +173,6 @@
}
#[test]
- fn impl_abi_type_fields_count() {
- assert_eq!(
- <TypeStruct1SimpleParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 1
- );
- assert_eq!(
- <TypeStruct1DynamicParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 1
- );
- assert_eq!(
- <TypeStruct2SimpleParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 2
- );
- assert_eq!(
- <TypeStruct2DynamicParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 2
- );
- assert_eq!(
- <TypeStruct2MixedParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 2
- );
- assert_eq!(
- <TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 1
- );
- assert_eq!(
- <TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 2
- );
- assert_eq!(
- <TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 1
- );
- assert_eq!(
- <TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 2
- );
- assert_eq!(
- <TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::FIELDS_COUNT,
- 3
- );
- }
-
- #[test]
fn impl_abi_type_is_dynamic() {
assert_eq!(
<TypeStruct1SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -347,6 +347,10 @@
) -> Result<void> {
self.consume_store_reads_and_writes(1, 1)?;
+ if !limit.has_value() {
+ return Err(Error::Revert("user can't disable limits".into()));
+ }
+
let caller = T::CrossAccountId::from_eth(caller);
<Pallet<T>>::update_limits(&caller, self, limit.try_into()?).map_err(dispatch_to_evm::<T>)
}
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -264,16 +264,17 @@
value: value.into(),
}
}
+
+ /// Whether the field contains a value.
+ pub fn has_value(&self) -> bool {
+ self.value.status
+ }
}
impl TryInto<up_data_structs::CollectionLimits> for CollectionLimit {
type Error = evm_coder::execution::Error;
fn try_into(self) -> Result<up_data_structs::CollectionLimits, Self::Error> {
- if !self.value.status {
- return Err(Self::Error::Revert("user can't disable limits".into()));
- }
-
let value = self.value.value.try_into().map_err(|error| {
Self::Error::Revert(format!(
"can't convert value to u32 \"{}\" because: \"{error}\"",
@@ -433,17 +434,6 @@
let mut perms = Vec::new();
for TokenPropertyPermission { key, permissions } in permissions {
- if permissions.len() > <TokenPermissionField as evm_coder::abi::AbiType>::FIELDS_COUNT {
- return Err(alloc::format!(
- "Actual number of fields {} for {}, which exceeds the maximum value of {}",
- permissions.len(),
- stringify!(EthTokenPermissions),
- <TokenPermissionField as evm_coder::abi::AbiType>::FIELDS_COUNT
- )
- .as_str()
- .into());
- }
-
let token_permission = PropertyPermission::from_vec(permissions);
perms.push(up_data_structs::PropertyKeyPermission {
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -38,6 +38,7 @@
use pallet_common::{
CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},
+ eth,
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::call;
@@ -93,25 +94,21 @@
fn set_token_property_permissions(
&mut self,
caller: caller,
- permissions: Vec<pallet_common::eth::TokenPropertyPermission>,
+ permissions: Vec<eth::TokenPropertyPermission>,
) -> Result<()> {
let caller = T::CrossAccountId::from_eth(caller);
- let perms = pallet_common::eth::TokenPropertyPermission::into_property_key_permissions(
- permissions,
- )?;
+ let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;
<Pallet<T>>::set_token_property_permissions(self, &caller, perms)
.map_err(dispatch_to_evm::<T>)
}
/// @notice Get permissions for token properties.
- fn token_property_permissions(
- &self,
- ) -> Result<Vec<pallet_common::eth::TokenPropertyPermission>> {
+ fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {
let perms = <Pallet<T>>::token_property_permission(self.id);
Ok(perms
.into_iter()
- .map(pallet_common::eth::TokenPropertyPermission::from)
+ .map(eth::TokenPropertyPermission::from)
.collect())
}
@@ -159,7 +156,7 @@
&mut self,
caller: caller,
token_id: uint256,
- properties: Vec<pallet_common::eth::Property>,
+ properties: Vec<eth::Property>,
) -> Result<()> {
let caller = T::CrossAccountId::from_eth(caller);
let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
@@ -170,7 +167,7 @@
let properties = properties
.into_iter()
- .map(pallet_common::eth::Property::try_into)
+ .map(eth::Property::try_into)
.collect::<Result<Vec<_>>>()?;
<Pallet<T>>::set_token_properties(
@@ -753,9 +750,9 @@
/// Returns the owner (in cross format) of the token.
///
/// @param tokenId Id for the token.
- fn cross_owner_of(&self, token_id: uint256) -> Result<pallet_common::eth::CrossAddress> {
+ fn cross_owner_of(&self, token_id: uint256) -> Result<eth::CrossAddress> {
Self::token_owner(&self, token_id.try_into()?)
- .map(|o| pallet_common::eth::CrossAddress::from_sub_cross_account::<T>(&o))
+ .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))
.ok_or(Error::Revert("key too large".into()))
}
@@ -764,11 +761,7 @@
/// @param tokenId Id for the token.
/// @param keys Properties keys. Empty keys for all propertyes.
/// @return Vector of properties key/value pairs.
- fn properties(
- &self,
- token_id: uint256,
- keys: Vec<string>,
- ) -> Result<Vec<pallet_common::eth::Property>> {
+ fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<eth::Property>> {
let keys = keys
.into_iter()
.map(|key| {
@@ -784,7 +777,7 @@
if keys.is_empty() { None } else { Some(keys) },
)
.into_iter()
- .map(pallet_common::eth::Property::try_from)
+ .map(eth::Property::try_from)
.collect::<Result<Vec<_>>>()
}
@@ -798,7 +791,7 @@
fn approve_cross(
&mut self,
caller: caller,
- approved: pallet_common::eth::CrossAddress,
+ approved: eth::CrossAddress,
token_id: uint256,
) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -837,7 +830,7 @@
fn transfer_cross(
&mut self,
caller: caller,
- to: pallet_common::eth::CrossAddress,
+ to: eth::CrossAddress,
token_id: uint256,
) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -861,8 +854,8 @@
fn transfer_from_cross(
&mut self,
caller: caller,
- from: pallet_common::eth::CrossAddress,
- to: pallet_common::eth::CrossAddress,
+ from: eth::CrossAddress,
+ to: eth::CrossAddress,
token_id: uint256,
) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -908,7 +901,7 @@
fn burn_from_cross(
&mut self,
caller: caller,
- from: pallet_common::eth::CrossAddress,
+ from: eth::CrossAddress,
token_id: uint256,
) -> Result<void> {
let caller = T::CrossAccountId::from_eth(caller);
@@ -1030,8 +1023,8 @@
fn mint_cross(
&mut self,
caller: caller,
- to: pallet_common::eth::CrossAddress,
- properties: Vec<pallet_common::eth::Property>,
+ to: eth::CrossAddress,
+ properties: Vec<eth::Property>,
) -> Result<uint256> {
let token_id = <TokensMinted<T>>::get(self.id)
.checked_add(1)
@@ -1041,7 +1034,7 @@
let properties = properties
.into_iter()
- .map(pallet_common::eth::Property::try_into)
+ .map(eth::Property::try_into)
.collect::<Result<Vec<_>>>()?
.try_into()
.map_err(|_| Error::Revert(alloc::format!("too many properties")))?;
pallets/refungible/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//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{29 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,30 weight,31};32use frame_support::{BoundedBTreeMap, BoundedVec};33use pallet_common::{34 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,35 erc::{CommonEvmHandler, CollectionCall, static_property::key},36 Error as CommonError,37};38use pallet_evm::{account::CrossAccountId, PrecompileHandle};39use pallet_evm_coder_substrate::{call, dispatch_to_evm};40use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};41use sp_core::{H160, Get};42use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};43use up_data_structs::{44 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,45 PropertyKeyPermission, PropertyPermission, TokenId,46};4748use crate::{49 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,50 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,51};5253pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5455/// @title A contract that allows to set and delete token properties and change token property permissions.56#[solidity_interface(name = TokenProperties)]57impl<T: Config> RefungibleHandle<T> {58 /// @notice Set permissions for token property.59 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.60 /// @param key Property key.61 /// @param isMutable Permission to mutate property.62 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.63 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.64 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]65 #[solidity(hide)]66 fn set_token_property_permission(67 &mut self,68 caller: caller,69 key: string,70 is_mutable: bool,71 collection_admin: bool,72 token_owner: bool,73 ) -> Result<()> {74 let caller = T::CrossAccountId::from_eth(caller);75 <Pallet<T>>::set_token_property_permissions(76 self,77 &caller,78 vec![PropertyKeyPermission {79 key: <Vec<u8>>::from(key)80 .try_into()81 .map_err(|_| "too long key")?,82 permission: PropertyPermission {83 mutable: is_mutable,84 collection_admin,85 token_owner,86 },87 }],88 )89 .map_err(dispatch_to_evm::<T>)90 }9192 /// @notice Set permissions for token property.93 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.94 /// @param permissions Permissions for keys.95 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]96 fn set_token_property_permissions(97 &mut self,98 caller: caller,99 permissions: Vec<pallet_common::eth::TokenPropertyPermission>,100 ) -> Result<()> {101 let caller = T::CrossAccountId::from_eth(caller);102 let perms = pallet_common::eth::TokenPropertyPermission::into_property_key_permissions(103 permissions,104 )?;105106 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)107 .map_err(dispatch_to_evm::<T>)108 }109110 /// @notice Get permissions for token properties.111 fn token_property_permissions(112 &self,113 ) -> Result<Vec<pallet_common::eth::TokenPropertyPermission>> {114 let perms = <Pallet<T>>::token_property_permission(self.id);115 Ok(perms116 .into_iter()117 .map(pallet_common::eth::TokenPropertyPermission::from)118 .collect())119 }120121 /// @notice Set token property value.122 /// @dev Throws error if `msg.sender` has no permission to edit the property.123 /// @param tokenId ID of the token.124 /// @param key Property key.125 /// @param value Property value.126 #[solidity(hide)]127 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]128 fn set_property(129 &mut self,130 caller: caller,131 token_id: uint256,132 key: string,133 value: bytes,134 ) -> Result<()> {135 let caller = T::CrossAccountId::from_eth(caller);136 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;137 let key = <Vec<u8>>::from(key)138 .try_into()139 .map_err(|_| "key too long")?;140 let value = value.0.try_into().map_err(|_| "value too long")?;141142 let nesting_budget = self143 .recorder144 .weight_calls_budget(<StructureWeight<T>>::find_parent());145146 <Pallet<T>>::set_token_property(147 self,148 &caller,149 TokenId(token_id),150 Property { key, value },151 &nesting_budget,152 )153 .map_err(dispatch_to_evm::<T>)154 }155156 /// @notice Set token properties value.157 /// @dev Throws error if `msg.sender` has no permission to edit the property.158 /// @param tokenId ID of the token.159 /// @param properties settable properties160 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]161 fn set_properties(162 &mut self,163 caller: caller,164 token_id: uint256,165 properties: Vec<pallet_common::eth::Property>,166 ) -> Result<()> {167 let caller = T::CrossAccountId::from_eth(caller);168 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;169170 let nesting_budget = self171 .recorder172 .weight_calls_budget(<StructureWeight<T>>::find_parent());173174 let properties = properties175 .into_iter()176 .map(pallet_common::eth::Property::try_into)177 .collect::<Result<Vec<_>>>()?;178179 <Pallet<T>>::set_token_properties(180 self,181 &caller,182 TokenId(token_id),183 properties.into_iter(),184 false,185 &nesting_budget,186 )187 .map_err(dispatch_to_evm::<T>)188 }189190 /// @notice Delete token property value.191 /// @dev Throws error if `msg.sender` has no permission to edit the property.192 /// @param tokenId ID of the token.193 /// @param key Property key.194 #[solidity(hide)]195 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]196 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {197 let caller = T::CrossAccountId::from_eth(caller);198 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;199 let key = <Vec<u8>>::from(key)200 .try_into()201 .map_err(|_| "key too long")?;202203 let nesting_budget = self204 .recorder205 .weight_calls_budget(<StructureWeight<T>>::find_parent());206207 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)208 .map_err(dispatch_to_evm::<T>)209 }210211 /// @notice Delete token properties value.212 /// @dev Throws error if `msg.sender` has no permission to edit the property.213 /// @param tokenId ID of the token.214 /// @param keys Properties key.215 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]216 fn delete_properties(217 &mut self,218 token_id: uint256,219 caller: caller,220 keys: Vec<string>,221 ) -> Result<()> {222 let caller = T::CrossAccountId::from_eth(caller);223 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;224 let keys = keys225 .into_iter()226 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))227 .collect::<Result<Vec<_>>>()?;228229 let nesting_budget = self230 .recorder231 .weight_calls_budget(<StructureWeight<T>>::find_parent());232233 <Pallet<T>>::delete_token_properties(234 self,235 &caller,236 TokenId(token_id),237 keys.into_iter(),238 &nesting_budget,239 )240 .map_err(dispatch_to_evm::<T>)241 }242243 /// @notice Get token property value.244 /// @dev Throws error if key not found245 /// @param tokenId ID of the token.246 /// @param key Property key.247 /// @return Property value bytes248 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {249 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;250 let key = <Vec<u8>>::from(key)251 .try_into()252 .map_err(|_| "key too long")?;253254 let props = <TokenProperties<T>>::get((self.id, token_id));255 let prop = props.get(&key).ok_or("key not found")?;256257 Ok(prop.to_vec().into())258 }259}260261#[derive(ToLog)]262pub enum ERC721Events {263 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed264 /// (`to` == 0). Exception: during contract creation, any number of RFTs265 /// may be created and assigned without emitting Transfer.266 Transfer {267 #[indexed]268 from: address,269 #[indexed]270 to: address,271 #[indexed]272 token_id: uint256,273 },274 /// @dev Not supported275 Approval {276 #[indexed]277 owner: address,278 #[indexed]279 approved: address,280 #[indexed]281 token_id: uint256,282 },283 /// @dev Not supported284 #[allow(dead_code)]285 ApprovalForAll {286 #[indexed]287 owner: address,288 #[indexed]289 operator: address,290 approved: bool,291 },292}293294#[derive(ToLog)]295pub enum ERC721UniqueMintableEvents {296 /// @dev Not supported297 #[allow(dead_code)]298 MintingFinished {},299}300301#[solidity_interface(name = ERC721Metadata)]302impl<T: Config> RefungibleHandle<T>303where304 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,305{306 /// @notice A descriptive name for a collection of NFTs in this contract307 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`308 #[solidity(hide, rename_selector = "name")]309 fn name_proxy(&self) -> Result<string> {310 self.name()311 }312313 /// @notice An abbreviated name for NFTs in this contract314 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`315 #[solidity(hide, rename_selector = "symbol")]316 fn symbol_proxy(&self) -> Result<string> {317 self.symbol()318 }319320 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.321 ///322 /// @dev If the token has a `url` property and it is not empty, it is returned.323 /// 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`.324 /// If the collection property `baseURI` is empty or absent, return "" (empty string)325 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix326 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).327 ///328 /// @return token's const_metadata329 #[solidity(rename_selector = "tokenURI")]330 fn token_uri(&self, token_id: uint256) -> Result<string> {331 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;332333 match get_token_property(self, token_id_u32, &key::url()).as_deref() {334 Err(_) | Ok("") => (),335 Ok(url) => {336 return Ok(url.into());337 }338 };339340 let base_uri =341 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())342 .map(BoundedVec::into_inner)343 .map(string::from_utf8)344 .transpose()345 .map_err(|e| {346 Error::Revert(alloc::format!(347 "Can not convert value \"baseURI\" to string with error \"{}\"",348 e349 ))350 })?;351352 let base_uri = match base_uri.as_deref() {353 None | Some("") => {354 return Ok("".into());355 }356 Some(base_uri) => base_uri.into(),357 };358359 Ok(360 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {361 Err(_) | Ok("") => base_uri,362 Ok(suffix) => base_uri + suffix,363 },364 )365 }366}367368/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension369/// @dev See https://eips.ethereum.org/EIPS/eip-721370#[solidity_interface(name = ERC721Enumerable)]371impl<T: Config> RefungibleHandle<T> {372 /// @notice Enumerate valid RFTs373 /// @param index A counter less than `totalSupply()`374 /// @return The token identifier for the `index`th NFT,375 /// (sort order not specified)376 fn token_by_index(&self, index: uint256) -> Result<uint256> {377 Ok(index)378 }379380 /// Not implemented381 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {382 // TODO: Not implemetable383 Err("not implemented".into())384 }385386 /// @notice Count RFTs tracked by this contract387 /// @return A count of valid RFTs tracked by this contract, where each one of388 /// them has an assigned and queryable owner not equal to the zero address389 fn total_supply(&self) -> Result<uint256> {390 self.consume_store_reads(1)?;391 Ok(<Pallet<T>>::total_supply(self).into())392 }393}394395/// @title ERC-721 Non-Fungible Token Standard396/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md397#[solidity_interface(name = ERC721, events(ERC721Events))]398impl<T: Config> RefungibleHandle<T> {399 /// @notice Count all RFTs assigned to an owner400 /// @dev RFTs assigned to the zero address are considered invalid, and this401 /// function throws for queries about the zero address.402 /// @param owner An address for whom to query the balance403 /// @return The number of RFTs owned by `owner`, possibly zero404 fn balance_of(&self, owner: address) -> Result<uint256> {405 self.consume_store_reads(1)?;406 let owner = T::CrossAccountId::from_eth(owner);407 let balance = <AccountBalance<T>>::get((self.id, owner));408 Ok(balance.into())409 }410411 /// @notice Find the owner of an RFT412 /// @dev RFTs assigned to zero address are considered invalid, and queries413 /// about them do throw.414 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for415 /// the tokens that are partially owned.416 /// @param tokenId The identifier for an RFT417 /// @return The address of the owner of the RFT418 fn owner_of(&self, token_id: uint256) -> Result<address> {419 self.consume_store_reads(2)?;420 let token = token_id.try_into()?;421 let owner = <Pallet<T>>::token_owner(self.id, token);422 Ok(owner423 .map(|address| *address.as_eth())424 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))425 }426427 /// @dev Not implemented428 fn safe_transfer_from_with_data(429 &mut self,430 _from: address,431 _to: address,432 _token_id: uint256,433 _data: bytes,434 ) -> Result<void> {435 // TODO: Not implemetable436 Err("not implemented".into())437 }438439 /// @dev Not implemented440 fn safe_transfer_from(441 &mut self,442 _from: address,443 _to: address,444 _token_id: uint256,445 ) -> Result<void> {446 // TODO: Not implemetable447 Err("not implemented".into())448 }449450 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE451 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE452 /// THEY MAY BE PERMANENTLY LOST453 /// @dev Throws unless `msg.sender` is the current owner or an authorized454 /// operator for this RFT. Throws if `from` is not the current owner. Throws455 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.456 /// Throws if RFT pieces have multiple owners.457 /// @param from The current owner of the NFT458 /// @param to The new owner459 /// @param tokenId The NFT to transfer460 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]461 fn transfer_from(462 &mut self,463 caller: caller,464 from: address,465 to: address,466 token_id: uint256,467 ) -> Result<void> {468 let caller = T::CrossAccountId::from_eth(caller);469 let from = T::CrossAccountId::from_eth(from);470 let to = T::CrossAccountId::from_eth(to);471 let token = token_id.try_into()?;472 let budget = self473 .recorder474 .weight_calls_budget(<StructureWeight<T>>::find_parent());475476 let balance = balance(&self, token, &from)?;477 ensure_single_owner(&self, token, balance)?;478479 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)480 .map_err(dispatch_to_evm::<T>)?;481482 Ok(())483 }484485 /// @dev Not implemented486 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {487 Err("not implemented".into())488 }489490 /// @notice Sets or unsets the approval of a given operator.491 /// The `operator` is allowed to transfer all token pieces of the `caller` on their behalf.492 /// @param operator Operator493 /// @param approved Should operator status be granted or revoked?494 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]495 fn set_approval_for_all(496 &mut self,497 caller: caller,498 operator: address,499 approved: bool,500 ) -> Result<void> {501 let caller = T::CrossAccountId::from_eth(caller);502 let operator = T::CrossAccountId::from_eth(operator);503504 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)505 .map_err(dispatch_to_evm::<T>)?;506 Ok(())507 }508509 /// @dev Not implemented510 fn get_approved(&self, _token_id: uint256) -> Result<address> {511 // TODO: Not implemetable512 Err("not implemented".into())513 }514515 /// @notice Tells whether the given `owner` approves the `operator`.516 #[weight(<SelfWeightOf<T>>::allowance_for_all())]517 fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {518 let owner = T::CrossAccountId::from_eth(owner);519 let operator = T::CrossAccountId::from_eth(operator);520521 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))522 }523524 /// @notice Returns collection helper contract address525 fn collection_helper_address(&self) -> Result<address> {526 Ok(T::ContractAddress::get())527 }528}529530/// Returns amount of pieces of `token` that `owner` have531pub fn balance<T: Config>(532 collection: &RefungibleHandle<T>,533 token: TokenId,534 owner: &T::CrossAccountId,535) -> Result<u128> {536 collection.consume_store_reads(1)?;537 let balance = <Balance<T>>::get((collection.id, token, &owner));538 Ok(balance)539}540541/// Throws if `owner_balance` is lower than total amount of `token` pieces542pub fn ensure_single_owner<T: Config>(543 collection: &RefungibleHandle<T>,544 token: TokenId,545 owner_balance: u128,546) -> Result<()> {547 collection.consume_store_reads(1)?;548 let total_supply = <TotalSupply<T>>::get((collection.id, token));549550 if owner_balance == 0 {551 return Err(dispatch_to_evm::<T>(552 <CommonError<T>>::MustBeTokenOwner.into(),553 ));554 }555556 if total_supply != owner_balance {557 return Err("token has multiple owners".into());558 }559 Ok(())560}561562/// @title ERC721 Token that can be irreversibly burned (destroyed).563#[solidity_interface(name = ERC721Burnable)]564impl<T: Config> RefungibleHandle<T> {565 /// @notice Burns a specific ERC721 token.566 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized567 /// operator of the current owner.568 /// @param tokenId The RFT to approve569 #[weight(<SelfWeightOf<T>>::burn_item_fully())]570 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {571 let caller = T::CrossAccountId::from_eth(caller);572 let token = token_id.try_into()?;573574 let balance = balance(&self, token, &caller)?;575 ensure_single_owner(&self, token, balance)?;576577 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;578 Ok(())579 }580}581582/// @title ERC721 minting logic.583#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]584impl<T: Config> RefungibleHandle<T> {585 fn minting_finished(&self) -> Result<bool> {586 Ok(false)587 }588589 /// @notice Function to mint a token.590 /// @param to The new owner591 /// @return uint256 The id of the newly minted token592 #[weight(<SelfWeightOf<T>>::create_item())]593 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {594 let token_id: uint256 = <TokensMinted<T>>::get(self.id)595 .checked_add(1)596 .ok_or("item id overflow")?597 .into();598 self.mint_check_id(caller, to, token_id)?;599 Ok(token_id)600 }601602 /// @notice Function to mint a token.603 /// @dev `tokenId` should be obtained with `nextTokenId` method,604 /// unlike standard, you can't specify it manually605 /// @param to The new owner606 /// @param tokenId ID of the minted RFT607 #[solidity(hide, rename_selector = "mint")]608 #[weight(<SelfWeightOf<T>>::create_item())]609 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {610 let caller = T::CrossAccountId::from_eth(caller);611 let to = T::CrossAccountId::from_eth(to);612 let token_id: u32 = token_id.try_into()?;613 let budget = self614 .recorder615 .weight_calls_budget(<StructureWeight<T>>::find_parent());616617 if <TokensMinted<T>>::get(self.id)618 .checked_add(1)619 .ok_or("item id overflow")?620 != token_id621 {622 return Err("item id should be next".into());623 }624625 let users = [(to.clone(), 1)]626 .into_iter()627 .collect::<BTreeMap<_, _>>()628 .try_into()629 .unwrap();630 <Pallet<T>>::create_item(631 self,632 &caller,633 CreateItemData::<T> {634 users,635 properties: CollectionPropertiesVec::default(),636 },637 &budget,638 )639 .map_err(dispatch_to_evm::<T>)?;640641 Ok(true)642 }643644 /// @notice Function to mint token with the given tokenUri.645 /// @param to The new owner646 /// @param tokenUri Token URI that would be stored in the NFT properties647 /// @return uint256 The id of the newly minted token648 #[solidity(rename_selector = "mintWithTokenURI")]649 #[weight(<SelfWeightOf<T>>::create_item())]650 fn mint_with_token_uri(651 &mut self,652 caller: caller,653 to: address,654 token_uri: string,655 ) -> Result<uint256> {656 let token_id: uint256 = <TokensMinted<T>>::get(self.id)657 .checked_add(1)658 .ok_or("item id overflow")?659 .into();660 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;661 Ok(token_id)662 }663664 /// @notice Function to mint token with the given tokenUri.665 /// @dev `tokenId` should be obtained with `nextTokenId` method,666 /// unlike standard, you can't specify it manually667 /// @param to The new owner668 /// @param tokenId ID of the minted RFT669 /// @param tokenUri Token URI that would be stored in the RFT properties670 #[solidity(hide, rename_selector = "mintWithTokenURI")]671 #[weight(<SelfWeightOf<T>>::create_item())]672 fn mint_with_token_uri_check_id(673 &mut self,674 caller: caller,675 to: address,676 token_id: uint256,677 token_uri: string,678 ) -> Result<bool> {679 let key = key::url();680 let permission = get_token_permission::<T>(self.id, &key)?;681 if !permission.collection_admin {682 return Err("Operation is not allowed".into());683 }684685 let caller = T::CrossAccountId::from_eth(caller);686 let to = T::CrossAccountId::from_eth(to);687 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;688 let budget = self689 .recorder690 .weight_calls_budget(<StructureWeight<T>>::find_parent());691692 if <TokensMinted<T>>::get(self.id)693 .checked_add(1)694 .ok_or("item id overflow")?695 != token_id696 {697 return Err("item id should be next".into());698 }699700 let mut properties = CollectionPropertiesVec::default();701 properties702 .try_push(Property {703 key,704 value: token_uri705 .into_bytes()706 .try_into()707 .map_err(|_| "token uri is too long")?,708 })709 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;710711 let users = [(to.clone(), 1)]712 .into_iter()713 .collect::<BTreeMap<_, _>>()714 .try_into()715 .unwrap();716 <Pallet<T>>::create_item(717 self,718 &caller,719 CreateItemData::<T> { users, properties },720 &budget,721 )722 .map_err(dispatch_to_evm::<T>)?;723 Ok(true)724 }725726 /// @dev Not implemented727 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {728 Err("not implementable".into())729 }730}731732fn get_token_property<T: Config>(733 collection: &CollectionHandle<T>,734 token_id: u32,735 key: &up_data_structs::PropertyKey,736) -> Result<string> {737 collection.consume_store_reads(1)?;738 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))739 .map_err(|_| Error::Revert("Token properties not found".into()))?;740 if let Some(property) = properties.get(key) {741 return Ok(string::from_utf8_lossy(property).into());742 }743744 Err("Property tokenURI not found".into())745}746747fn get_token_permission<T: Config>(748 collection_id: CollectionId,749 key: &PropertyKey,750) -> Result<PropertyPermission> {751 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)752 .map_err(|_| Error::Revert("No permissions for collection".into()))?;753 let a = token_property_permissions754 .get(key)755 .map(Clone::clone)756 .ok_or_else(|| {757 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();758 Error::Revert(alloc::format!("No permission for key {}", key))759 })?;760 Ok(a)761}762763/// @title Unique extensions for ERC721.764#[solidity_interface(name = ERC721UniqueExtensions)]765impl<T: Config> RefungibleHandle<T>766where767 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,768{769 /// @notice A descriptive name for a collection of NFTs in this contract770 fn name(&self) -> Result<string> {771 Ok(decode_utf16(self.name.iter().copied())772 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))773 .collect::<string>())774 }775776 /// @notice An abbreviated name for NFTs in this contract777 fn symbol(&self) -> Result<string> {778 Ok(string::from_utf8_lossy(&self.token_prefix).into())779 }780781 /// @notice A description for the collection.782 fn description(&self) -> Result<string> {783 Ok(decode_utf16(self.description.iter().copied())784 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))785 .collect::<string>())786 }787788 /// Returns the owner (in cross format) of the token.789 ///790 /// @param tokenId Id for the token.791 fn cross_owner_of(&self, token_id: uint256) -> Result<pallet_common::eth::CrossAddress> {792 Self::token_owner(&self, token_id.try_into()?)793 .map(|o| pallet_common::eth::CrossAddress::from_sub_cross_account::<T>(&o))794 .ok_or(Error::Revert("key too large".into()))795 }796797 /// Returns the token properties.798 ///799 /// @param tokenId Id for the token.800 /// @param keys Properties keys. Empty keys for all propertyes.801 /// @return Vector of properties key/value pairs.802 fn properties(803 &self,804 token_id: uint256,805 keys: Vec<string>,806 ) -> Result<Vec<pallet_common::eth::Property>> {807 let keys = keys808 .into_iter()809 .map(|key| {810 <Vec<u8>>::from(key)811 .try_into()812 .map_err(|_| Error::Revert("key too large".into()))813 })814 .collect::<Result<Vec<_>>>()?;815816 <Self as CommonCollectionOperations<T>>::token_properties(817 &self,818 token_id.try_into()?,819 if keys.is_empty() { None } else { Some(keys) },820 )821 .into_iter()822 .map(pallet_common::eth::Property::try_from)823 .collect::<Result<Vec<_>>>()824 }825 /// @notice Transfer ownership of an RFT826 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`827 /// is the zero address. Throws if `tokenId` is not a valid RFT.828 /// Throws if RFT pieces have multiple owners.829 /// @param to The new owner830 /// @param tokenId The RFT to transfer831 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]832 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {833 let caller = T::CrossAccountId::from_eth(caller);834 let to = T::CrossAccountId::from_eth(to);835 let token = token_id.try_into()?;836 let budget = self837 .recorder838 .weight_calls_budget(<StructureWeight<T>>::find_parent());839840 let balance = balance(self, token, &caller)?;841 ensure_single_owner(self, token, balance)?;842843 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)844 .map_err(dispatch_to_evm::<T>)?;845 Ok(())846 }847848 /// @notice Transfer ownership of an RFT849 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`850 /// is the zero address. Throws if `tokenId` is not a valid RFT.851 /// Throws if RFT pieces have multiple owners.852 /// @param to The new owner853 /// @param tokenId The RFT to transfer854 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]855 fn transfer_cross(856 &mut self,857 caller: caller,858 to: pallet_common::eth::CrossAddress,859 token_id: uint256,860 ) -> Result<void> {861 let caller = T::CrossAccountId::from_eth(caller);862 let to = to.into_sub_cross_account::<T>()?;863 let token = token_id.try_into()?;864 let budget = self865 .recorder866 .weight_calls_budget(<StructureWeight<T>>::find_parent());867868 let balance = balance(self, token, &caller)?;869 ensure_single_owner(self, token, balance)?;870871 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)872 .map_err(dispatch_to_evm::<T>)?;873 Ok(())874 }875876 /// @notice Transfer ownership of an RFT877 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`878 /// is the zero address. Throws if `tokenId` is not a valid RFT.879 /// Throws if RFT pieces have multiple owners.880 /// @param to The new owner881 /// @param tokenId The RFT to transfer882 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]883 fn transfer_from_cross(884 &mut self,885 caller: caller,886 from: pallet_common::eth::CrossAddress,887 to: pallet_common::eth::CrossAddress,888 token_id: uint256,889 ) -> Result<void> {890 let caller = T::CrossAccountId::from_eth(caller);891 let from = from.into_sub_cross_account::<T>()?;892 let to = to.into_sub_cross_account::<T>()?;893 let token_id = token_id.try_into()?;894 let budget = self895 .recorder896 .weight_calls_budget(<StructureWeight<T>>::find_parent());897898 let balance = balance(self, token_id, &from)?;899 ensure_single_owner(self, token_id, balance)?;900901 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)902 .map_err(dispatch_to_evm::<T>)?;903 Ok(())904 }905906 /// @notice Burns a specific ERC721 token.907 /// @dev Throws unless `msg.sender` is the current owner or an authorized908 /// operator for this RFT. Throws if `from` is not the current owner. Throws909 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.910 /// Throws if RFT pieces have multiple owners.911 /// @param from The current owner of the RFT912 /// @param tokenId The RFT to transfer913 #[solidity(hide)]914 #[weight(<SelfWeightOf<T>>::burn_from())]915 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {916 let caller = T::CrossAccountId::from_eth(caller);917 let from = T::CrossAccountId::from_eth(from);918 let token = token_id.try_into()?;919 let budget = self920 .recorder921 .weight_calls_budget(<StructureWeight<T>>::find_parent());922923 let balance = balance(self, token, &from)?;924 ensure_single_owner(self, token, balance)?;925926 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)927 .map_err(dispatch_to_evm::<T>)?;928 Ok(())929 }930931 /// @notice Burns a specific ERC721 token.932 /// @dev Throws unless `msg.sender` is the current owner or an authorized933 /// operator for this RFT. Throws if `from` is not the current owner. Throws934 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.935 /// Throws if RFT pieces have multiple owners.936 /// @param from The current owner of the RFT937 /// @param tokenId The RFT to transfer938 #[weight(<SelfWeightOf<T>>::burn_from())]939 fn burn_from_cross(940 &mut self,941 caller: caller,942 from: pallet_common::eth::CrossAddress,943 token_id: uint256,944 ) -> Result<void> {945 let caller = T::CrossAccountId::from_eth(caller);946 let from = from.into_sub_cross_account::<T>()?;947 let token = token_id.try_into()?;948 let budget = self949 .recorder950 .weight_calls_budget(<StructureWeight<T>>::find_parent());951952 let balance = balance(self, token, &from)?;953 ensure_single_owner(self, token, balance)?;954955 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)956 .map_err(dispatch_to_evm::<T>)?;957 Ok(())958 }959960 /// @notice Returns next free RFT ID.961 fn next_token_id(&self) -> Result<uint256> {962 self.consume_store_reads(1)?;963 Ok(<TokensMinted<T>>::get(self.id)964 .checked_add(1)965 .ok_or("item id overflow")?966 .into())967 }968969 /// @notice Function to mint multiple tokens.970 /// @dev `tokenIds` should be an array of consecutive numbers and first number971 /// should be obtained with `nextTokenId` method972 /// @param to The new owner973 /// @param tokenIds IDs of the minted RFTs974 #[solidity(hide)]975 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]976 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {977 let caller = T::CrossAccountId::from_eth(caller);978 let to = T::CrossAccountId::from_eth(to);979 let mut expected_index = <TokensMinted<T>>::get(self.id)980 .checked_add(1)981 .ok_or("item id overflow")?;982 let budget = self983 .recorder984 .weight_calls_budget(<StructureWeight<T>>::find_parent());985986 let total_tokens = token_ids.len();987 for id in token_ids.into_iter() {988 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;989 if id != expected_index {990 return Err("item id should be next".into());991 }992 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;993 }994 let users = [(to.clone(), 1)]995 .into_iter()996 .collect::<BTreeMap<_, _>>()997 .try_into()998 .unwrap();999 let create_item_data = CreateItemData::<T> {1000 users,1001 properties: CollectionPropertiesVec::default(),1002 };1003 let data = (0..total_tokens)1004 .map(|_| create_item_data.clone())1005 .collect();10061007 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)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(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]1019 fn mint_bulk_with_token_uri(1020 &mut self,1021 caller: caller,1022 to: address,1023 tokens: Vec<(uint256, string)>,1024 ) -> Result<bool> {1025 let key = key::url();1026 let caller = T::CrossAccountId::from_eth(caller);1027 let to = T::CrossAccountId::from_eth(to);1028 let mut expected_index = <TokensMinted<T>>::get(self.id)1029 .checked_add(1)1030 .ok_or("item id overflow")?;1031 let budget = self1032 .recorder1033 .weight_calls_budget(<StructureWeight<T>>::find_parent());10341035 let mut data = Vec::with_capacity(tokens.len());1036 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]1037 .into_iter()1038 .collect::<BTreeMap<_, _>>()1039 .try_into()1040 .unwrap();1041 for (id, token_uri) in tokens {1042 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1043 if id != expected_index {1044 return Err("item id should be next".into());1045 }1046 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10471048 let mut properties = CollectionPropertiesVec::default();1049 properties1050 .try_push(Property {1051 key: key.clone(),1052 value: token_uri1053 .into_bytes()1054 .try_into()1055 .map_err(|_| "token uri is too long")?,1056 })1057 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10581059 let create_item_data = CreateItemData::<T> {1060 users: users.clone(),1061 properties,1062 };1063 data.push(create_item_data);1064 }10651066 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1067 .map_err(dispatch_to_evm::<T>)?;1068 Ok(true)1069 }10701071 /// @notice Function to mint a token.1072 /// @param to The new owner crossAccountId1073 /// @param properties Properties of minted token1074 /// @return uint256 The id of the newly minted token1075 #[weight(<SelfWeightOf<T>>::create_item())]1076 fn mint_cross(1077 &mut self,1078 caller: caller,1079 to: pallet_common::eth::CrossAddress,1080 properties: Vec<pallet_common::eth::Property>,1081 ) -> Result<uint256> {1082 let token_id = <TokensMinted<T>>::get(self.id)1083 .checked_add(1)1084 .ok_or("item id overflow")?;10851086 let to = to.into_sub_cross_account::<T>()?;10871088 let properties = properties1089 .into_iter()1090 .map(pallet_common::eth::Property::try_into)1091 .collect::<Result<Vec<_>>>()?1092 .try_into()1093 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10941095 let caller = T::CrossAccountId::from_eth(caller);10961097 let budget = self1098 .recorder1099 .weight_calls_budget(<StructureWeight<T>>::find_parent());11001101 let users = [(to, 1)]1102 .into_iter()1103 .collect::<BTreeMap<_, _>>()1104 .try_into()1105 .unwrap();1106 <Pallet<T>>::create_item(1107 self,1108 &caller,1109 CreateItemData::<T> { users, properties },1110 &budget,1111 )1112 .map_err(dispatch_to_evm::<T>)?;11131114 Ok(token_id.into())1115 }11161117 /// Returns EVM address for refungible token1118 ///1119 /// @param token ID of the token1120 fn token_contract_address(&self, token: uint256) -> Result<address> {1121 Ok(T::EvmTokenAddressMapping::token_to_address(1122 self.id,1123 token.try_into().map_err(|_| "token id overflow")?,1124 ))1125 }1126}11271128#[solidity_interface(1129 name = UniqueRefungible,1130 is(1131 ERC721,1132 ERC721Enumerable,1133 ERC721UniqueExtensions,1134 ERC721UniqueMintable,1135 ERC721Burnable,1136 ERC721Metadata(if(this.flags.erc721metadata)),1137 Collection(via(common_mut returns CollectionHandle<T>)),1138 TokenProperties,1139 )1140)]1141impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11421143// Not a tests, but code generators1144generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);1145generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);11461147impl<T: Config> CommonEvmHandler for RefungibleHandle<T>1148where1149 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1150{1151 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");1152 fn call(1153 self,1154 handle: &mut impl PrecompileHandle,1155 ) -> Option<pallet_common::erc::PrecompileResult> {1156 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)1157 }1158}