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}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # 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}