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 Error as CommonError,36 erc::{CommonEvmHandler, CollectionCall, static_property::key},37 eth,38};39use pallet_evm::{account::CrossAccountId, PrecompileHandle};40use pallet_evm_coder_substrate::{call, dispatch_to_evm};41use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};42use sp_core::{H160, Get};43use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};44use up_data_structs::{45 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,46 PropertyKeyPermission, PropertyPermission, TokenId,47};4849use crate::{50 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,51 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,52};5354pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5556/// @title A contract that allows to set and delete token properties and change token property permissions.57#[solidity_interface(name = TokenProperties)]58impl<T: Config> RefungibleHandle<T> {59 /// @notice Set permissions for token property.60 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.61 /// @param key Property key.62 /// @param isMutable Permission to mutate property.63 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.64 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.65 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]66 #[solidity(hide)]67 fn set_token_property_permission(68 &mut self,69 caller: caller,70 key: string,71 is_mutable: bool,72 collection_admin: bool,73 token_owner: bool,74 ) -> Result<()> {75 let caller = T::CrossAccountId::from_eth(caller);76 <Pallet<T>>::set_token_property_permissions(77 self,78 &caller,79 vec![PropertyKeyPermission {80 key: <Vec<u8>>::from(key)81 .try_into()82 .map_err(|_| "too long key")?,83 permission: PropertyPermission {84 mutable: is_mutable,85 collection_admin,86 token_owner,87 },88 }],89 )90 .map_err(dispatch_to_evm::<T>)91 }9293 /// @notice Set permissions for token property.94 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.95 /// @param permissions Permissions for keys.96 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]97 fn set_token_property_permissions(98 &mut self,99 caller: caller,100 permissions: Vec<eth::TokenPropertyPermission>,101 ) -> Result<()> {102 let caller = T::CrossAccountId::from_eth(caller);103 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;104105 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)106 .map_err(dispatch_to_evm::<T>)107 }108109 /// @notice Get permissions for token properties.110 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {111 let perms = <Pallet<T>>::token_property_permission(self.id);112 Ok(perms113 .into_iter()114 .map(eth::TokenPropertyPermission::from)115 .collect())116 }117118 /// @notice Set token property value.119 /// @dev Throws error if `msg.sender` has no permission to edit the property.120 /// @param tokenId ID of the token.121 /// @param key Property key.122 /// @param value Property value.123 #[solidity(hide)]124 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]125 fn set_property(126 &mut self,127 caller: caller,128 token_id: uint256,129 key: string,130 value: bytes,131 ) -> Result<()> {132 let caller = T::CrossAccountId::from_eth(caller);133 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;134 let key = <Vec<u8>>::from(key)135 .try_into()136 .map_err(|_| "key too long")?;137 let value = value.0.try_into().map_err(|_| "value too long")?;138139 let nesting_budget = self140 .recorder141 .weight_calls_budget(<StructureWeight<T>>::find_parent());142143 <Pallet<T>>::set_token_property(144 self,145 &caller,146 TokenId(token_id),147 Property { key, value },148 &nesting_budget,149 )150 .map_err(dispatch_to_evm::<T>)151 }152153 /// @notice Set token properties value.154 /// @dev Throws error if `msg.sender` has no permission to edit the property.155 /// @param tokenId ID of the token.156 /// @param properties settable properties157 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]158 fn set_properties(159 &mut self,160 caller: caller,161 token_id: uint256,162 properties: Vec<eth::Property>,163 ) -> Result<()> {164 let caller = T::CrossAccountId::from_eth(caller);165 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;166167 let nesting_budget = self168 .recorder169 .weight_calls_budget(<StructureWeight<T>>::find_parent());170171 let properties = properties172 .into_iter()173 .map(eth::Property::try_into)174 .collect::<Result<Vec<_>>>()?;175176 <Pallet<T>>::set_token_properties(177 self,178 &caller,179 TokenId(token_id),180 properties.into_iter(),181 false,182 &nesting_budget,183 )184 .map_err(dispatch_to_evm::<T>)185 }186187 /// @notice Delete token property value.188 /// @dev Throws error if `msg.sender` has no permission to edit the property.189 /// @param tokenId ID of the token.190 /// @param key Property key.191 #[solidity(hide)]192 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]193 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {194 let caller = T::CrossAccountId::from_eth(caller);195 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;196 let key = <Vec<u8>>::from(key)197 .try_into()198 .map_err(|_| "key too long")?;199200 let nesting_budget = self201 .recorder202 .weight_calls_budget(<StructureWeight<T>>::find_parent());203204 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)205 .map_err(dispatch_to_evm::<T>)206 }207208 /// @notice Delete token properties value.209 /// @dev Throws error if `msg.sender` has no permission to edit the property.210 /// @param tokenId ID of the token.211 /// @param keys Properties key.212 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]213 fn delete_properties(214 &mut self,215 token_id: uint256,216 caller: caller,217 keys: Vec<string>,218 ) -> Result<()> {219 let caller = T::CrossAccountId::from_eth(caller);220 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;221 let keys = keys222 .into_iter()223 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))224 .collect::<Result<Vec<_>>>()?;225226 let nesting_budget = self227 .recorder228 .weight_calls_budget(<StructureWeight<T>>::find_parent());229230 <Pallet<T>>::delete_token_properties(231 self,232 &caller,233 TokenId(token_id),234 keys.into_iter(),235 &nesting_budget,236 )237 .map_err(dispatch_to_evm::<T>)238 }239240 /// @notice Get token property value.241 /// @dev Throws error if key not found242 /// @param tokenId ID of the token.243 /// @param key Property key.244 /// @return Property value bytes245 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {246 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;247 let key = <Vec<u8>>::from(key)248 .try_into()249 .map_err(|_| "key too long")?;250251 let props = <TokenProperties<T>>::get((self.id, token_id));252 let prop = props.get(&key).ok_or("key not found")?;253254 Ok(prop.to_vec().into())255 }256}257258#[derive(ToLog)]259pub enum ERC721Events {260 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed261 /// (`to` == 0). Exception: during contract creation, any number of RFTs262 /// may be created and assigned without emitting Transfer.263 Transfer {264 #[indexed]265 from: address,266 #[indexed]267 to: address,268 #[indexed]269 token_id: uint256,270 },271 /// @dev Not supported272 Approval {273 #[indexed]274 owner: address,275 #[indexed]276 approved: address,277 #[indexed]278 token_id: uint256,279 },280 /// @dev Not supported281 #[allow(dead_code)]282 ApprovalForAll {283 #[indexed]284 owner: address,285 #[indexed]286 operator: address,287 approved: bool,288 },289}290291#[derive(ToLog)]292pub enum ERC721UniqueMintableEvents {293 /// @dev Not supported294 #[allow(dead_code)]295 MintingFinished {},296}297298#[solidity_interface(name = ERC721Metadata)]299impl<T: Config> RefungibleHandle<T>300where301 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,302{303 /// @notice A descriptive name for a collection of NFTs in this contract304 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`305 #[solidity(hide, rename_selector = "name")]306 fn name_proxy(&self) -> Result<string> {307 self.name()308 }309310 /// @notice An abbreviated name for NFTs in this contract311 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`312 #[solidity(hide, rename_selector = "symbol")]313 fn symbol_proxy(&self) -> Result<string> {314 self.symbol()315 }316317 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.318 ///319 /// @dev If the token has a `url` property and it is not empty, it is returned.320 /// 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`.321 /// If the collection property `baseURI` is empty or absent, return "" (empty string)322 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix323 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).324 ///325 /// @return token's const_metadata326 #[solidity(rename_selector = "tokenURI")]327 fn token_uri(&self, token_id: uint256) -> Result<string> {328 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;329330 match get_token_property(self, token_id_u32, &key::url()).as_deref() {331 Err(_) | Ok("") => (),332 Ok(url) => {333 return Ok(url.into());334 }335 };336337 let base_uri =338 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())339 .map(BoundedVec::into_inner)340 .map(string::from_utf8)341 .transpose()342 .map_err(|e| {343 Error::Revert(alloc::format!(344 "Can not convert value \"baseURI\" to string with error \"{}\"",345 e346 ))347 })?;348349 let base_uri = match base_uri.as_deref() {350 None | Some("") => {351 return Ok("".into());352 }353 Some(base_uri) => base_uri.into(),354 };355356 Ok(357 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {358 Err(_) | Ok("") => base_uri,359 Ok(suffix) => base_uri + suffix,360 },361 )362 }363}364365/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension366/// @dev See https://eips.ethereum.org/EIPS/eip-721367#[solidity_interface(name = ERC721Enumerable)]368impl<T: Config> RefungibleHandle<T> {369 /// @notice Enumerate valid RFTs370 /// @param index A counter less than `totalSupply()`371 /// @return The token identifier for the `index`th NFT,372 /// (sort order not specified)373 fn token_by_index(&self, index: uint256) -> Result<uint256> {374 Ok(index)375 }376377 /// Not implemented378 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {379 // TODO: Not implemetable380 Err("not implemented".into())381 }382383 /// @notice Count RFTs tracked by this contract384 /// @return A count of valid RFTs tracked by this contract, where each one of385 /// them has an assigned and queryable owner not equal to the zero address386 fn total_supply(&self) -> Result<uint256> {387 self.consume_store_reads(1)?;388 Ok(<Pallet<T>>::total_supply(self).into())389 }390}391392/// @title ERC-721 Non-Fungible Token Standard393/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md394#[solidity_interface(name = ERC721, events(ERC721Events))]395impl<T: Config> RefungibleHandle<T> {396 /// @notice Count all RFTs assigned to an owner397 /// @dev RFTs assigned to the zero address are considered invalid, and this398 /// function throws for queries about the zero address.399 /// @param owner An address for whom to query the balance400 /// @return The number of RFTs owned by `owner`, possibly zero401 fn balance_of(&self, owner: address) -> Result<uint256> {402 self.consume_store_reads(1)?;403 let owner = T::CrossAccountId::from_eth(owner);404 let balance = <AccountBalance<T>>::get((self.id, owner));405 Ok(balance.into())406 }407408 /// @notice Find the owner of an RFT409 /// @dev RFTs assigned to zero address are considered invalid, and queries410 /// about them do throw.411 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for412 /// the tokens that are partially owned.413 /// @param tokenId The identifier for an RFT414 /// @return The address of the owner of the RFT415 fn owner_of(&self, token_id: uint256) -> Result<address> {416 self.consume_store_reads(2)?;417 let token = token_id.try_into()?;418 let owner = <Pallet<T>>::token_owner(self.id, token);419 Ok(owner420 .map(|address| *address.as_eth())421 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))422 }423424 /// @dev Not implemented425 fn safe_transfer_from_with_data(426 &mut self,427 _from: address,428 _to: address,429 _token_id: uint256,430 _data: bytes,431 ) -> Result<void> {432 // TODO: Not implemetable433 Err("not implemented".into())434 }435436 /// @dev Not implemented437 fn safe_transfer_from(438 &mut self,439 _from: address,440 _to: address,441 _token_id: uint256,442 ) -> Result<void> {443 // TODO: Not implemetable444 Err("not implemented".into())445 }446447 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE448 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE449 /// THEY MAY BE PERMANENTLY LOST450 /// @dev Throws unless `msg.sender` is the current owner or an authorized451 /// operator for this RFT. Throws if `from` is not the current owner. Throws452 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.453 /// Throws if RFT pieces have multiple owners.454 /// @param from The current owner of the NFT455 /// @param to The new owner456 /// @param tokenId The NFT to transfer457 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]458 fn transfer_from(459 &mut self,460 caller: caller,461 from: address,462 to: address,463 token_id: uint256,464 ) -> Result<void> {465 let caller = T::CrossAccountId::from_eth(caller);466 let from = T::CrossAccountId::from_eth(from);467 let to = T::CrossAccountId::from_eth(to);468 let token = token_id.try_into()?;469 let budget = self470 .recorder471 .weight_calls_budget(<StructureWeight<T>>::find_parent());472473 let balance = balance(&self, token, &from)?;474 ensure_single_owner(&self, token, balance)?;475476 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)477 .map_err(dispatch_to_evm::<T>)?;478479 Ok(())480 }481482 /// @dev Not implemented483 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {484 Err("not implemented".into())485 }486487 /// @notice Sets or unsets the approval of a given operator.488 /// The `operator` is allowed to transfer all token pieces of the `caller` on their behalf.489 /// @param operator Operator490 /// @param approved Should operator status be granted or revoked?491 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]492 fn set_approval_for_all(493 &mut self,494 caller: caller,495 operator: address,496 approved: bool,497 ) -> Result<void> {498 let caller = T::CrossAccountId::from_eth(caller);499 let operator = T::CrossAccountId::from_eth(operator);500501 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)502 .map_err(dispatch_to_evm::<T>)?;503 Ok(())504 }505506 /// @dev Not implemented507 fn get_approved(&self, _token_id: uint256) -> Result<address> {508 // TODO: Not implemetable509 Err("not implemented".into())510 }511512 /// @notice Tells whether the given `owner` approves the `operator`.513 #[weight(<SelfWeightOf<T>>::allowance_for_all())]514 fn is_approved_for_all(&self, owner: address, operator: address) -> Result<bool> {515 let owner = T::CrossAccountId::from_eth(owner);516 let operator = T::CrossAccountId::from_eth(operator);517518 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))519 }520521 /// @notice Returns collection helper contract address522 fn collection_helper_address(&self) -> Result<address> {523 Ok(T::ContractAddress::get())524 }525}526527/// Returns amount of pieces of `token` that `owner` have528pub fn balance<T: Config>(529 collection: &RefungibleHandle<T>,530 token: TokenId,531 owner: &T::CrossAccountId,532) -> Result<u128> {533 collection.consume_store_reads(1)?;534 let balance = <Balance<T>>::get((collection.id, token, &owner));535 Ok(balance)536}537538/// Throws if `owner_balance` is lower than total amount of `token` pieces539pub fn ensure_single_owner<T: Config>(540 collection: &RefungibleHandle<T>,541 token: TokenId,542 owner_balance: u128,543) -> Result<()> {544 collection.consume_store_reads(1)?;545 let total_supply = <TotalSupply<T>>::get((collection.id, token));546547 if owner_balance == 0 {548 return Err(dispatch_to_evm::<T>(549 <CommonError<T>>::MustBeTokenOwner.into(),550 ));551 }552553 if total_supply != owner_balance {554 return Err("token has multiple owners".into());555 }556 Ok(())557}558559/// @title ERC721 Token that can be irreversibly burned (destroyed).560#[solidity_interface(name = ERC721Burnable)]561impl<T: Config> RefungibleHandle<T> {562 /// @notice Burns a specific ERC721 token.563 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized564 /// operator of the current owner.565 /// @param tokenId The RFT to approve566 #[weight(<SelfWeightOf<T>>::burn_item_fully())]567 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {568 let caller = T::CrossAccountId::from_eth(caller);569 let token = token_id.try_into()?;570571 let balance = balance(&self, token, &caller)?;572 ensure_single_owner(&self, token, balance)?;573574 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;575 Ok(())576 }577}578579/// @title ERC721 minting logic.580#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]581impl<T: Config> RefungibleHandle<T> {582 fn minting_finished(&self) -> Result<bool> {583 Ok(false)584 }585586 /// @notice Function to mint a token.587 /// @param to The new owner588 /// @return uint256 The id of the newly minted token589 #[weight(<SelfWeightOf<T>>::create_item())]590 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {591 let token_id: uint256 = <TokensMinted<T>>::get(self.id)592 .checked_add(1)593 .ok_or("item id overflow")?594 .into();595 self.mint_check_id(caller, to, token_id)?;596 Ok(token_id)597 }598599 /// @notice Function to mint a token.600 /// @dev `tokenId` should be obtained with `nextTokenId` method,601 /// unlike standard, you can't specify it manually602 /// @param to The new owner603 /// @param tokenId ID of the minted RFT604 #[solidity(hide, rename_selector = "mint")]605 #[weight(<SelfWeightOf<T>>::create_item())]606 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {607 let caller = T::CrossAccountId::from_eth(caller);608 let to = T::CrossAccountId::from_eth(to);609 let token_id: u32 = token_id.try_into()?;610 let budget = self611 .recorder612 .weight_calls_budget(<StructureWeight<T>>::find_parent());613614 if <TokensMinted<T>>::get(self.id)615 .checked_add(1)616 .ok_or("item id overflow")?617 != token_id618 {619 return Err("item id should be next".into());620 }621622 let users = [(to.clone(), 1)]623 .into_iter()624 .collect::<BTreeMap<_, _>>()625 .try_into()626 .unwrap();627 <Pallet<T>>::create_item(628 self,629 &caller,630 CreateItemData::<T> {631 users,632 properties: CollectionPropertiesVec::default(),633 },634 &budget,635 )636 .map_err(dispatch_to_evm::<T>)?;637638 Ok(true)639 }640641 /// @notice Function to mint token with the given tokenUri.642 /// @param to The new owner643 /// @param tokenUri Token URI that would be stored in the NFT properties644 /// @return uint256 The id of the newly minted token645 #[solidity(rename_selector = "mintWithTokenURI")]646 #[weight(<SelfWeightOf<T>>::create_item())]647 fn mint_with_token_uri(648 &mut self,649 caller: caller,650 to: address,651 token_uri: string,652 ) -> Result<uint256> {653 let token_id: uint256 = <TokensMinted<T>>::get(self.id)654 .checked_add(1)655 .ok_or("item id overflow")?656 .into();657 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;658 Ok(token_id)659 }660661 /// @notice Function to mint token with the given tokenUri.662 /// @dev `tokenId` should be obtained with `nextTokenId` method,663 /// unlike standard, you can't specify it manually664 /// @param to The new owner665 /// @param tokenId ID of the minted RFT666 /// @param tokenUri Token URI that would be stored in the RFT properties667 #[solidity(hide, rename_selector = "mintWithTokenURI")]668 #[weight(<SelfWeightOf<T>>::create_item())]669 fn mint_with_token_uri_check_id(670 &mut self,671 caller: caller,672 to: address,673 token_id: uint256,674 token_uri: string,675 ) -> Result<bool> {676 let key = key::url();677 let permission = get_token_permission::<T>(self.id, &key)?;678 if !permission.collection_admin {679 return Err("Operation is not allowed".into());680 }681682 let caller = T::CrossAccountId::from_eth(caller);683 let to = T::CrossAccountId::from_eth(to);684 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;685 let budget = self686 .recorder687 .weight_calls_budget(<StructureWeight<T>>::find_parent());688689 if <TokensMinted<T>>::get(self.id)690 .checked_add(1)691 .ok_or("item id overflow")?692 != token_id693 {694 return Err("item id should be next".into());695 }696697 let mut properties = CollectionPropertiesVec::default();698 properties699 .try_push(Property {700 key,701 value: token_uri702 .into_bytes()703 .try_into()704 .map_err(|_| "token uri is too long")?,705 })706 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;707708 let users = [(to.clone(), 1)]709 .into_iter()710 .collect::<BTreeMap<_, _>>()711 .try_into()712 .unwrap();713 <Pallet<T>>::create_item(714 self,715 &caller,716 CreateItemData::<T> { users, properties },717 &budget,718 )719 .map_err(dispatch_to_evm::<T>)?;720 Ok(true)721 }722723 /// @dev Not implemented724 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {725 Err("not implementable".into())726 }727}728729fn get_token_property<T: Config>(730 collection: &CollectionHandle<T>,731 token_id: u32,732 key: &up_data_structs::PropertyKey,733) -> Result<string> {734 collection.consume_store_reads(1)?;735 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))736 .map_err(|_| Error::Revert("Token properties not found".into()))?;737 if let Some(property) = properties.get(key) {738 return Ok(string::from_utf8_lossy(property).into());739 }740741 Err("Property tokenURI not found".into())742}743744fn get_token_permission<T: Config>(745 collection_id: CollectionId,746 key: &PropertyKey,747) -> Result<PropertyPermission> {748 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)749 .map_err(|_| Error::Revert("No permissions for collection".into()))?;750 let a = token_property_permissions751 .get(key)752 .map(Clone::clone)753 .ok_or_else(|| {754 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();755 Error::Revert(alloc::format!("No permission for key {}", key))756 })?;757 Ok(a)758}759760/// @title Unique extensions for ERC721.761#[solidity_interface(name = ERC721UniqueExtensions)]762impl<T: Config> RefungibleHandle<T>763where764 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,765{766 /// @notice A descriptive name for a collection of NFTs in this contract767 fn name(&self) -> Result<string> {768 Ok(decode_utf16(self.name.iter().copied())769 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))770 .collect::<string>())771 }772773 /// @notice An abbreviated name for NFTs in this contract774 fn symbol(&self) -> Result<string> {775 Ok(string::from_utf8_lossy(&self.token_prefix).into())776 }777778 /// @notice A description for the collection.779 fn description(&self) -> Result<string> {780 Ok(decode_utf16(self.description.iter().copied())781 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))782 .collect::<string>())783 }784785 /// Returns the owner (in cross format) of the token.786 ///787 /// @param tokenId Id for the token.788 fn cross_owner_of(&self, token_id: uint256) -> Result<eth::CrossAddress> {789 Self::token_owner(&self, token_id.try_into()?)790 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))791 .ok_or(Error::Revert("key too large".into()))792 }793794 /// Returns the token properties.795 ///796 /// @param tokenId Id for the token.797 /// @param keys Properties keys. Empty keys for all propertyes.798 /// @return Vector of properties key/value pairs.799 fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<eth::Property>> {800 let keys = keys801 .into_iter()802 .map(|key| {803 <Vec<u8>>::from(key)804 .try_into()805 .map_err(|_| Error::Revert("key too large".into()))806 })807 .collect::<Result<Vec<_>>>()?;808809 <Self as CommonCollectionOperations<T>>::token_properties(810 &self,811 token_id.try_into()?,812 if keys.is_empty() { None } else { Some(keys) },813 )814 .into_iter()815 .map(eth::Property::try_from)816 .collect::<Result<Vec<_>>>()817 }818 /// @notice Transfer ownership of an RFT819 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`820 /// is the zero address. Throws if `tokenId` is not a valid RFT.821 /// Throws if RFT pieces have multiple owners.822 /// @param to The new owner823 /// @param tokenId The RFT to transfer824 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]825 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {826 let caller = T::CrossAccountId::from_eth(caller);827 let to = T::CrossAccountId::from_eth(to);828 let token = token_id.try_into()?;829 let budget = self830 .recorder831 .weight_calls_budget(<StructureWeight<T>>::find_parent());832833 let balance = balance(self, token, &caller)?;834 ensure_single_owner(self, token, balance)?;835836 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)837 .map_err(dispatch_to_evm::<T>)?;838 Ok(())839 }840841 /// @notice Transfer ownership of an RFT842 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`843 /// is the zero address. Throws if `tokenId` is not a valid RFT.844 /// Throws if RFT pieces have multiple owners.845 /// @param to The new owner846 /// @param tokenId The RFT to transfer847 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]848 fn transfer_cross(849 &mut self,850 caller: caller,851 to: eth::CrossAddress,852 token_id: uint256,853 ) -> Result<void> {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 let balance = balance(self, token, &caller)?;862 ensure_single_owner(self, token, balance)?;863864 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)865 .map_err(dispatch_to_evm::<T>)?;866 Ok(())867 }868869 /// @notice Transfer ownership of an RFT870 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`871 /// is the zero address. Throws if `tokenId` is not a valid RFT.872 /// Throws if RFT pieces have multiple owners.873 /// @param to The new owner874 /// @param tokenId The RFT to transfer875 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]876 fn transfer_from_cross(877 &mut self,878 caller: caller,879 from: eth::CrossAddress,880 to: eth::CrossAddress,881 token_id: uint256,882 ) -> Result<void> {883 let caller = T::CrossAccountId::from_eth(caller);884 let from = from.into_sub_cross_account::<T>()?;885 let to = to.into_sub_cross_account::<T>()?;886 let token_id = token_id.try_into()?;887 let budget = self888 .recorder889 .weight_calls_budget(<StructureWeight<T>>::find_parent());890891 let balance = balance(self, token_id, &from)?;892 ensure_single_owner(self, token_id, balance)?;893894 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, balance, &budget)895 .map_err(dispatch_to_evm::<T>)?;896 Ok(())897 }898899 /// @notice Burns a specific ERC721 token.900 /// @dev Throws unless `msg.sender` is the current owner or an authorized901 /// operator for this RFT. Throws if `from` is not the current owner. Throws902 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.903 /// Throws if RFT pieces have multiple owners.904 /// @param from The current owner of the RFT905 /// @param tokenId The RFT to transfer906 #[solidity(hide)]907 #[weight(<SelfWeightOf<T>>::burn_from())]908 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {909 let caller = T::CrossAccountId::from_eth(caller);910 let from = T::CrossAccountId::from_eth(from);911 let token = token_id.try_into()?;912 let budget = self913 .recorder914 .weight_calls_budget(<StructureWeight<T>>::find_parent());915916 let balance = balance(self, token, &from)?;917 ensure_single_owner(self, token, balance)?;918919 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)920 .map_err(dispatch_to_evm::<T>)?;921 Ok(())922 }923924 /// @notice Burns a specific ERC721 token.925 /// @dev Throws unless `msg.sender` is the current owner or an authorized926 /// operator for this RFT. Throws if `from` is not the current owner. Throws927 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.928 /// Throws if RFT pieces have multiple owners.929 /// @param from The current owner of the RFT930 /// @param tokenId The RFT to transfer931 #[weight(<SelfWeightOf<T>>::burn_from())]932 fn burn_from_cross(933 &mut self,934 caller: caller,935 from: eth::CrossAddress,936 token_id: uint256,937 ) -> Result<void> {938 let caller = T::CrossAccountId::from_eth(caller);939 let from = from.into_sub_cross_account::<T>()?;940 let token = token_id.try_into()?;941 let budget = self942 .recorder943 .weight_calls_budget(<StructureWeight<T>>::find_parent());944945 let balance = balance(self, token, &from)?;946 ensure_single_owner(self, token, balance)?;947948 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)949 .map_err(dispatch_to_evm::<T>)?;950 Ok(())951 }952953 /// @notice Returns next free RFT ID.954 fn next_token_id(&self) -> Result<uint256> {955 self.consume_store_reads(1)?;956 Ok(<TokensMinted<T>>::get(self.id)957 .checked_add(1)958 .ok_or("item id overflow")?959 .into())960 }961962 /// @notice Function to mint multiple tokens.963 /// @dev `tokenIds` should be an array of consecutive numbers and first number964 /// should be obtained with `nextTokenId` method965 /// @param to The new owner966 /// @param tokenIds IDs of the minted RFTs967 #[solidity(hide)]968 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]969 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {970 let caller = T::CrossAccountId::from_eth(caller);971 let to = T::CrossAccountId::from_eth(to);972 let mut expected_index = <TokensMinted<T>>::get(self.id)973 .checked_add(1)974 .ok_or("item id overflow")?;975 let budget = self976 .recorder977 .weight_calls_budget(<StructureWeight<T>>::find_parent());978979 let total_tokens = token_ids.len();980 for id in token_ids.into_iter() {981 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;982 if id != expected_index {983 return Err("item id should be next".into());984 }985 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;986 }987 let users = [(to.clone(), 1)]988 .into_iter()989 .collect::<BTreeMap<_, _>>()990 .try_into()991 .unwrap();992 let create_item_data = CreateItemData::<T> {993 users,994 properties: CollectionPropertiesVec::default(),995 };996 let data = (0..total_tokens)997 .map(|_| create_item_data.clone())998 .collect();9991000 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1001 .map_err(dispatch_to_evm::<T>)?;1002 Ok(true)1003 }10041005 /// @notice Function to mint multiple tokens with the given tokenUris.1006 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive1007 /// numbers and first number should be obtained with `nextTokenId` method1008 /// @param to The new owner1009 /// @param tokens array of pairs of token ID and token URI for minted tokens1010 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]1011 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]1012 fn mint_bulk_with_token_uri(1013 &mut self,1014 caller: caller,1015 to: address,1016 tokens: Vec<(uint256, string)>,1017 ) -> Result<bool> {1018 let key = key::url();1019 let caller = T::CrossAccountId::from_eth(caller);1020 let to = T::CrossAccountId::from_eth(to);1021 let mut expected_index = <TokensMinted<T>>::get(self.id)1022 .checked_add(1)1023 .ok_or("item id overflow")?;1024 let budget = self1025 .recorder1026 .weight_calls_budget(<StructureWeight<T>>::find_parent());10271028 let mut data = Vec::with_capacity(tokens.len());1029 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]1030 .into_iter()1031 .collect::<BTreeMap<_, _>>()1032 .try_into()1033 .unwrap();1034 for (id, token_uri) in tokens {1035 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;1036 if id != expected_index {1037 return Err("item id should be next".into());1038 }1039 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;10401041 let mut properties = CollectionPropertiesVec::default();1042 properties1043 .try_push(Property {1044 key: key.clone(),1045 value: token_uri1046 .into_bytes()1047 .try_into()1048 .map_err(|_| "token uri is too long")?,1049 })1050 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;10511052 let create_item_data = CreateItemData::<T> {1053 users: users.clone(),1054 properties,1055 };1056 data.push(create_item_data);1057 }10581059 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1060 .map_err(dispatch_to_evm::<T>)?;1061 Ok(true)1062 }10631064 /// @notice Function to mint a token.1065 /// @param to The new owner crossAccountId1066 /// @param properties Properties of minted token1067 /// @return uint256 The id of the newly minted token1068 #[weight(<SelfWeightOf<T>>::create_item())]1069 fn mint_cross(1070 &mut self,1071 caller: caller,1072 to: eth::CrossAddress,1073 properties: Vec<eth::Property>,1074 ) -> Result<uint256> {1075 let token_id = <TokensMinted<T>>::get(self.id)1076 .checked_add(1)1077 .ok_or("item id overflow")?;10781079 let to = to.into_sub_cross_account::<T>()?;10801081 let properties = properties1082 .into_iter()1083 .map(eth::Property::try_into)1084 .collect::<Result<Vec<_>>>()?1085 .try_into()1086 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10871088 let caller = T::CrossAccountId::from_eth(caller);10891090 let budget = self1091 .recorder1092 .weight_calls_budget(<StructureWeight<T>>::find_parent());10931094 let users = [(to, 1)]1095 .into_iter()1096 .collect::<BTreeMap<_, _>>()1097 .try_into()1098 .unwrap();1099 <Pallet<T>>::create_item(1100 self,1101 &caller,1102 CreateItemData::<T> { users, properties },1103 &budget,1104 )1105 .map_err(dispatch_to_evm::<T>)?;11061107 Ok(token_id.into())1108 }11091110 /// Returns EVM address for refungible token1111 ///1112 /// @param token ID of the token1113 fn token_contract_address(&self, token: uint256) -> Result<address> {1114 Ok(T::EvmTokenAddressMapping::token_to_address(1115 self.id,1116 token.try_into().map_err(|_| "token id overflow")?,1117 ))1118 }1119}11201121#[solidity_interface(1122 name = UniqueRefungible,1123 is(1124 ERC721,1125 ERC721Enumerable,1126 ERC721UniqueExtensions,1127 ERC721UniqueMintable,1128 ERC721Burnable,1129 ERC721Metadata(if(this.flags.erc721metadata)),1130 Collection(via(common_mut returns CollectionHandle<T>)),1131 TokenProperties,1132 )1133)]1134impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}11351136// Not a tests, but code generators1137generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);1138generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);11391140impl<T: Config> CommonEvmHandler for RefungibleHandle<T>1141where1142 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1143{1144 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");1145 fn call(1146 self,1147 handle: &mut impl PrecompileHandle,1148 ) -> Option<pallet_common::erc::PrecompileResult> {1149 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)1150 }1151}