difftreelog
refactor Make implementations of Abi* for EthCrossAccount via AbiCoder macro
in: master
9 files changed
.maintain/scripts/generate_abi.shdiffbeforeafterboth--- a/.maintain/scripts/generate_abi.sh
+++ b/.maintain/scripts/generate_abi.sh
@@ -4,6 +4,7 @@
dir=$PWD
tmp=$(mktemp -d)
+echo "Tmp file: $tmp/input.sol"
cd $tmp
cp $dir/$INPUT input.sol
solcjs --abi -p input.sol
crates/evm-coder/src/abi/impls.rsdiffbeforeafterboth--- a/crates/evm-coder/src/abi/impls.rs
+++ b/crates/evm-coder/src/abi/impls.rs
@@ -120,42 +120,6 @@
}
}
-impl sealed::CanBePlacedInVec for EthCrossAccount {}
-
-impl AbiType for EthCrossAccount {
- const SIGNATURE: SignatureUnit = make_signature!(new fixed("(address,uint256)"));
-
- fn is_dynamic() -> bool {
- address::is_dynamic() || uint256::is_dynamic()
- }
-
- fn size() -> usize {
- <address as AbiType>::size() + <uint256 as AbiType>::size()
- }
-}
-
-impl AbiRead for EthCrossAccount {
- fn abi_read(reader: &mut AbiReader) -> Result<EthCrossAccount> {
- let size = if !EthCrossAccount::is_dynamic() {
- Some(<EthCrossAccount as AbiType>::size())
- } else {
- None
- };
- let mut subresult = reader.subresult(size)?;
- let eth = <address>::abi_read(&mut subresult)?;
- let sub = <uint256>::abi_read(&mut subresult)?;
-
- Ok(EthCrossAccount { eth, sub })
- }
-}
-
-impl AbiWrite for EthCrossAccount {
- fn abi_write(&self, writer: &mut AbiWriter) {
- self.eth.abi_write(writer);
- self.sub.abi_write(writer);
- }
-}
-
impl sealed::CanBePlacedInVec for Property {}
impl AbiType for Property {
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -93,6 +93,7 @@
pub use evm_coder_procedural::solidity;
/// See [`solidity_interface`]
pub use evm_coder_procedural::weight;
+pub use evm_coder_procedural::AbiCoder;
pub use sha3_const;
/// Derives [`ToLog`] for enum
@@ -119,7 +120,6 @@
#[cfg(not(feature = "std"))]
use alloc::{vec::Vec};
- use pallet_evm::account::CrossAccountId;
use primitive_types::{U256, H160, H256};
pub type address = H160;
@@ -185,73 +185,7 @@
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
- }
- }
-
- #[derive(Debug, Default)]
- pub struct EthCrossAccount {
- pub(crate) eth: address,
- pub(crate) sub: uint256,
- }
-
- impl EthCrossAccount {
- pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self
- where
- T: pallet_evm::Config,
- T::AccountId: AsRef<[u8; 32]>,
- {
- if cross_account_id.is_canonical_substrate() {
- Self {
- eth: Default::default(),
- sub: convert_cross_account_to_uint256::<T>(cross_account_id),
- }
- } else {
- Self {
- eth: *cross_account_id.as_eth(),
- sub: Default::default(),
- }
- }
- }
-
- pub fn into_sub_cross_account<T>(&self) -> crate::execution::Result<T::CrossAccountId>
- where
- T: pallet_evm::Config,
- T::AccountId: From<[u8; 32]>,
- {
- if self.eth == Default::default() && self.sub == Default::default() {
- Err("All fields of cross account is zeroed".into())
- } else if self.eth == Default::default() {
- Ok(convert_uint256_to_cross_account::<T>(self.sub))
- } else if self.sub == Default::default() {
- Ok(T::CrossAccountId::from_eth(self.eth))
- } else {
- Err("All fields of cross account is non zeroed".into())
- }
}
- }
-
- /// Convert `CrossAccountId` to `uint256`.
- pub fn convert_cross_account_to_uint256<T: pallet_evm::Config>(
- from: &T::CrossAccountId,
- ) -> uint256
- where
- T::AccountId: AsRef<[u8; 32]>,
- {
- let slice = from.as_sub().as_ref();
- uint256::from_big_endian(slice)
- }
-
- /// Convert `uint256` to `CrossAccountId`.
- pub fn convert_uint256_to_cross_account<T: pallet_evm::Config>(
- from: uint256,
- ) -> T::CrossAccountId
- where
- T::AccountId: From<[u8; 32]>,
- {
- let mut new_admin_arr = [0_u8; 32];
- from.to_big_endian(&mut new_admin_arr);
- let account_id = T::AccountId::from(new_admin_arr);
- T::CrossAccountId::from_sub(account_id)
}
#[derive(Debug, Default)]
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -145,7 +145,7 @@
}
}
-mod sealed {
+pub mod sealed {
/// Not every type should be directly placed in vec.
/// Vec encoding is not memory efficient, as every item will be padded
/// to 32 bytes.
@@ -156,7 +156,6 @@
impl sealed::CanBePlacedInVec for uint256 {}
impl sealed::CanBePlacedInVec for string {}
impl sealed::CanBePlacedInVec for address {}
-impl sealed::CanBePlacedInVec for EthCrossAccount {}
impl sealed::CanBePlacedInVec for Property {}
impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {
@@ -171,61 +170,6 @@
write!(writer, "new ")?;
T::solidity_name(writer, tc)?;
write!(writer, "[](0)")
- }
-}
-
-impl SolidityTupleType for EthCrossAccount {
- fn names(tc: &TypeCollector) -> Vec<string> {
- let mut collected = Vec::with_capacity(Self::len());
- {
- let mut out = string::new();
- address::solidity_name(&mut out, tc).expect("no fmt error");
- collected.push(out);
- }
- {
- let mut out = string::new();
- uint256::solidity_name(&mut out, tc).expect("no fmt error");
- collected.push(out);
- }
- collected
- }
-
- fn len() -> usize {
- 2
- }
-}
-
-impl SolidityTypeName for EthCrossAccount {
- fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
- write!(writer, "{}", tc.collect_struct::<Self>())
- }
-
- fn is_simple() -> bool {
- false
- }
-
- fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
- write!(writer, "{}(", tc.collect_struct::<Self>())?;
- address::solidity_default(writer, tc)?;
- write!(writer, ",")?;
- uint256::solidity_default(writer, tc)?;
- write!(writer, ")")
- }
-}
-
-impl StructCollect for EthCrossAccount {
- fn name() -> String {
- "EthCrossAccount".into()
- }
-
- fn declaration() -> String {
- let mut str = String::new();
- writeln!(str, "/// @dev Cross account struct").unwrap();
- writeln!(str, "struct {} {{", Self::name()).unwrap();
- writeln!(str, "\taddress eth;").unwrap();
- writeln!(str, "\tuint256 sub;").unwrap();
- writeln!(str, "}}").unwrap();
- str
}
}
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -16,6 +16,7 @@
//! This module contains the implementation of pallet methods for evm.
+pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};
use evm_coder::{
abi::AbiType,
solidity_interface, solidity, ToLog,
@@ -24,7 +25,6 @@
execution::{Result, Error},
weight,
};
-pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};
use pallet_evm_coder_substrate::dispatch_to_evm;
use sp_std::vec::Vec;
use up_data_structs::{
@@ -35,7 +35,8 @@
use crate::{
Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,
- eth::convert_cross_account_to_uint256, weights::WeightInfo,
+ eth::{EthCrossAccount, convert_cross_account_to_uint256},
+ weights::WeightInfo,
};
/// Events for ethereum collection helper.
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -16,7 +16,10 @@
//! The module contains a number of functions for converting and checking ethereum identifiers.
-use evm_coder::types::{uint256, address};
+use evm_coder::{
+ AbiCoder,
+ types::{uint256, address},
+};
pub use pallet_evm::{Config, account::CrossAccountId};
use sp_core::H160;
use up_data_structs::CollectionId;
@@ -109,3 +112,111 @@
Err("All fields of cross account is non zeroed".into())
}
}
+
+#[derive(Debug, Default, AbiCoder)]
+pub struct EthCrossAccount {
+ pub(crate) eth: address,
+ pub(crate) sub: uint256,
+}
+
+impl EthCrossAccount {
+ pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self
+ where
+ T: pallet_evm::account::Config,
+ T::AccountId: AsRef<[u8; 32]>,
+ {
+ if cross_account_id.is_canonical_substrate() {
+ Self {
+ eth: Default::default(),
+ sub: convert_cross_account_to_uint256::<T>(cross_account_id),
+ }
+ } else {
+ Self {
+ eth: *cross_account_id.as_eth(),
+ sub: Default::default(),
+ }
+ }
+ }
+
+ pub fn into_sub_cross_account<T>(&self) -> evm_coder::execution::Result<T::CrossAccountId>
+ where
+ T: pallet_evm::account::Config,
+ T::AccountId: From<[u8; 32]>,
+ {
+ if self.eth == Default::default() && self.sub == Default::default() {
+ Err("All fields of cross account is zeroed".into())
+ } else if self.eth == Default::default() {
+ Ok(convert_uint256_to_cross_account::<T>(self.sub))
+ } else if self.sub == Default::default() {
+ Ok(T::CrossAccountId::from_eth(self.eth))
+ } else {
+ Err("All fields of cross account is non zeroed".into())
+ }
+ }
+}
+
+impl ::evm_coder::solidity::sealed::CanBePlacedInVec for EthCrossAccount {}
+impl ::evm_coder::solidity::SolidityTupleType for EthCrossAccount {
+ fn names(tc: &::evm_coder::solidity::TypeCollector) -> Vec<String> {
+ let mut collected =
+ Vec::with_capacity(<Self as ::evm_coder::solidity::SolidityTupleType>::len());
+ {
+ let mut out = String::new();
+ <address as ::evm_coder::solidity::SolidityTypeName>::solidity_name(&mut out, tc)
+ .expect("no fmt error");
+ collected.push(out);
+ }
+ {
+ let mut out = String::new();
+ <uint256 as ::evm_coder::solidity::SolidityTypeName>::solidity_name(&mut out, tc)
+ .expect("no fmt error");
+ collected.push(out);
+ }
+ collected
+ }
+
+ fn len() -> usize {
+ 2
+ }
+}
+impl ::evm_coder::solidity::SolidityTypeName for EthCrossAccount {
+ fn solidity_name(
+ writer: &mut impl ::core::fmt::Write,
+ tc: &::evm_coder::solidity::TypeCollector,
+ ) -> ::core::fmt::Result {
+ write!(writer, "{}", tc.collect_struct::<Self>())
+ }
+
+ fn is_simple() -> bool {
+ false
+ }
+
+ fn solidity_default(
+ writer: &mut impl ::core::fmt::Write,
+ tc: &::evm_coder::solidity::TypeCollector,
+ ) -> ::core::fmt::Result {
+ write!(writer, "{}(", tc.collect_struct::<Self>())?;
+ address::solidity_default(writer, tc)?;
+ write!(writer, ",")?;
+ uint256::solidity_default(writer, tc)?;
+ write!(writer, ")")
+ }
+}
+
+impl ::evm_coder::solidity::StructCollect for EthCrossAccount {
+ fn name() -> String {
+ "EthCrossAccount".into()
+ }
+
+ fn declaration() -> String {
+ use std::fmt::Write;
+
+ let mut str = String::new();
+ writeln!(str, "/// @dev Cross account struct").unwrap();
+ writeln!(str, "struct {} {{", Self::name()).unwrap();
+ writeln!(str, "\taddress eth;").unwrap();
+ writeln!(str, "\tuint256 sub;").unwrap();
+ writeln!(str, "}}").unwrap();
+ str
+ }
+}
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -24,12 +24,15 @@
weight,
};
use up_data_structs::CollectionMode;
-use pallet_common::erc::{CommonEvmHandler, PrecompileResult};
+use pallet_common::{
+ CollectionHandle,
+ erc::{CommonEvmHandler, PrecompileResult, CollectionCall},
+ eth::EthCrossAccount,
+};
use sp_std::vec::Vec;
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::{call, dispatch_to_evm};
use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
-use pallet_common::{CollectionHandle, erc::CollectionCall};
use sp_core::Get;
use crate::{
pallets/nonfungible/src/erc.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29 types::Property as PropertyStruct, weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34 CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},40 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};45use sp_core::Get;4647use crate::{48 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,49 SelfWeightOf, weights::WeightInfo, TokenProperties,50};5152/// @title A contract that allows to set and delete token properties and change token property permissions.53#[solidity_interface(name = TokenProperties)]54impl<T: Config> NonfungibleHandle<T> {55 /// @notice Set permissions for token property.56 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.57 /// @param key Property key.58 /// @param isMutable Permission to mutate property.59 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.60 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.61 fn set_token_property_permission(62 &mut self,63 caller: caller,64 key: string,65 is_mutable: bool,66 collection_admin: bool,67 token_owner: bool,68 ) -> Result<()> {69 let caller = T::CrossAccountId::from_eth(caller);70 <Pallet<T>>::set_property_permission(71 self,72 &caller,73 PropertyKeyPermission {74 key: <Vec<u8>>::from(key)75 .try_into()76 .map_err(|_| "too long key")?,77 permission: PropertyPermission {78 mutable: is_mutable,79 collection_admin,80 token_owner,81 },82 },83 )84 .map_err(dispatch_to_evm::<T>)85 }8687 /// @notice Set token property value.88 /// @dev Throws error if `msg.sender` has no permission to edit the property.89 /// @param tokenId ID of the token.90 /// @param key Property key.91 /// @param value Property value.92 #[solidity(hide)]93 fn set_property(94 &mut self,95 caller: caller,96 token_id: uint256,97 key: string,98 value: bytes,99 ) -> Result<()> {100 let caller = T::CrossAccountId::from_eth(caller);101 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;102 let key = <Vec<u8>>::from(key)103 .try_into()104 .map_err(|_| "key too long")?;105 let value = value.0.try_into().map_err(|_| "value too long")?;106107 let nesting_budget = self108 .recorder109 .weight_calls_budget(<StructureWeight<T>>::find_parent());110111 <Pallet<T>>::set_token_property(112 self,113 &caller,114 TokenId(token_id),115 Property { key, value },116 &nesting_budget,117 )118 .map_err(dispatch_to_evm::<T>)119 }120121 /// @notice Set token properties value.122 /// @dev Throws error if `msg.sender` has no permission to edit the property.123 /// @param tokenId ID of the token.124 /// @param properties settable properties125 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]126 fn set_properties(127 &mut self,128 caller: caller,129 token_id: uint256,130 properties: Vec<PropertyStruct>,131 ) -> Result<()> {132 let caller = T::CrossAccountId::from_eth(caller);133 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;134135 let nesting_budget = self136 .recorder137 .weight_calls_budget(<StructureWeight<T>>::find_parent());138139 let properties = properties140 .into_iter()141 .map(|PropertyStruct { key, value }| {142 let key = <Vec<u8>>::from(key)143 .try_into()144 .map_err(|_| "key too large")?;145146 let value = value.0.try_into().map_err(|_| "value too large")?;147148 Ok(Property { key, value })149 })150 .collect::<Result<Vec<_>>>()?;151152 <Pallet<T>>::set_token_properties(153 self,154 &caller,155 TokenId(token_id),156 properties.into_iter(),157 <Pallet<T>>::token_exists(&self, TokenId(token_id)),158 &nesting_budget,159 )160 .map_err(dispatch_to_evm::<T>)161 }162163 /// @notice Delete token property value.164 /// @dev Throws error if `msg.sender` has no permission to edit the property.165 /// @param tokenId ID of the token.166 /// @param key Property key.167 #[solidity(hide)]168 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {169 let caller = T::CrossAccountId::from_eth(caller);170 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;171 let key = <Vec<u8>>::from(key)172 .try_into()173 .map_err(|_| "key too long")?;174175 let nesting_budget = self176 .recorder177 .weight_calls_budget(<StructureWeight<T>>::find_parent());178179 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)180 .map_err(dispatch_to_evm::<T>)181 }182183 /// @notice Delete token properties value.184 /// @dev Throws error if `msg.sender` has no permission to edit the property.185 /// @param tokenId ID of the token.186 /// @param keys Properties key.187 fn delete_properties(188 &mut self,189 token_id: uint256,190 caller: caller,191 keys: Vec<string>,192 ) -> Result<()> {193 let caller = T::CrossAccountId::from_eth(caller);194 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;195 let keys = keys196 .into_iter()197 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))198 .collect::<Result<Vec<_>>>()?;199200 let nesting_budget = self201 .recorder202 .weight_calls_budget(<StructureWeight<T>>::find_parent());203204 <Pallet<T>>::delete_token_properties(205 self,206 &caller,207 TokenId(token_id),208 keys.into_iter(),209 &nesting_budget,210 )211 .map_err(dispatch_to_evm::<T>)212 }213214 /// @notice Get token property value.215 /// @dev Throws error if key not found216 /// @param tokenId ID of the token.217 /// @param key Property key.218 /// @return Property value bytes219 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {220 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;221 let key = <Vec<u8>>::from(key)222 .try_into()223 .map_err(|_| "key too long")?;224225 let props = <TokenProperties<T>>::get((self.id, token_id));226 let prop = props.get(&key).ok_or("key not found")?;227228 Ok(prop.to_vec().into())229 }230}231232#[derive(ToLog)]233pub enum ERC721Events {234 /// @dev This emits when ownership of any NFT changes by any mechanism.235 /// This event emits when NFTs are created (`from` == 0) and destroyed236 /// (`to` == 0). Exception: during contract creation, any number of NFTs237 /// may be created and assigned without emitting Transfer. At the time of238 /// any transfer, the approved address for that NFT (if any) is reset to none.239 Transfer {240 #[indexed]241 from: address,242 #[indexed]243 to: address,244 #[indexed]245 token_id: uint256,246 },247 /// @dev This emits when the approved address for an NFT is changed or248 /// reaffirmed. The zero address indicates there is no approved address.249 /// When a Transfer event emits, this also indicates that the approved250 /// address for that NFT (if any) is reset to none.251 Approval {252 #[indexed]253 owner: address,254 #[indexed]255 approved: address,256 #[indexed]257 token_id: uint256,258 },259 /// @dev This emits when an operator is enabled or disabled for an owner.260 /// The operator can manage all NFTs of the owner.261 #[allow(dead_code)]262 ApprovalForAll {263 #[indexed]264 owner: address,265 #[indexed]266 operator: address,267 approved: bool,268 },269}270271#[derive(ToLog)]272pub enum ERC721UniqueMintableEvents {273 #[allow(dead_code)]274 MintingFinished {},275}276277/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension278/// @dev See https://eips.ethereum.org/EIPS/eip-721279#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]280impl<T: Config> NonfungibleHandle<T>281where282 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,283{284 /// @notice A descriptive name for a collection of NFTs in this contract285 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`286 #[solidity(hide, rename_selector = "name")]287 fn name_proxy(&self) -> Result<string> {288 self.name()289 }290291 /// @notice An abbreviated name for NFTs in this contract292 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`293 #[solidity(hide, rename_selector = "symbol")]294 fn symbol_proxy(&self) -> Result<string> {295 self.symbol()296 }297298 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.299 ///300 /// @dev If the token has a `url` property and it is not empty, it is returned.301 /// 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`.302 /// If the collection property `baseURI` is empty or absent, return "" (empty string)303 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix304 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).305 ///306 /// @return token's const_metadata307 #[solidity(rename_selector = "tokenURI")]308 fn token_uri(&self, token_id: uint256) -> Result<string> {309 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;310311 match get_token_property(self, token_id_u32, &key::url()).as_deref() {312 Err(_) | Ok("") => (),313 Ok(url) => {314 return Ok(url.into());315 }316 };317318 let base_uri =319 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())320 .map(BoundedVec::into_inner)321 .map(string::from_utf8)322 .transpose()323 .map_err(|e| {324 Error::Revert(alloc::format!(325 "Can not convert value \"baseURI\" to string with error \"{}\"",326 e327 ))328 })?;329330 let base_uri = match base_uri.as_deref() {331 None | Some("") => {332 return Ok("".into());333 }334 Some(base_uri) => base_uri.into(),335 };336337 Ok(338 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {339 Err(_) | Ok("") => base_uri,340 Ok(suffix) => base_uri + suffix,341 },342 )343 }344}345346/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension347/// @dev See https://eips.ethereum.org/EIPS/eip-721348#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]349impl<T: Config> NonfungibleHandle<T> {350 /// @notice Enumerate valid NFTs351 /// @param index A counter less than `totalSupply()`352 /// @return The token identifier for the `index`th NFT,353 /// (sort order not specified)354 fn token_by_index(&self, index: uint256) -> Result<uint256> {355 Ok(index)356 }357358 /// @dev Not implemented359 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {360 // TODO: Not implemetable361 Err("not implemented".into())362 }363364 /// @notice Count NFTs tracked by this contract365 /// @return A count of valid NFTs tracked by this contract, where each one of366 /// them has an assigned and queryable owner not equal to the zero address367 fn total_supply(&self) -> Result<uint256> {368 self.consume_store_reads(1)?;369 Ok(<Pallet<T>>::total_supply(self).into())370 }371}372373/// @title ERC-721 Non-Fungible Token Standard374/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md375#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]376impl<T: Config> NonfungibleHandle<T> {377 /// @notice Count all NFTs assigned to an owner378 /// @dev NFTs assigned to the zero address are considered invalid, and this379 /// function throws for queries about the zero address.380 /// @param owner An address for whom to query the balance381 /// @return The number of NFTs owned by `owner`, possibly zero382 fn balance_of(&self, owner: address) -> Result<uint256> {383 self.consume_store_reads(1)?;384 let owner = T::CrossAccountId::from_eth(owner);385 let balance = <AccountBalance<T>>::get((self.id, owner));386 Ok(balance.into())387 }388 /// @notice Find the owner of an NFT389 /// @dev NFTs assigned to zero address are considered invalid, and queries390 /// about them do throw.391 /// @param tokenId The identifier for an NFT392 /// @return The address of the owner of the NFT393 fn owner_of(&self, token_id: uint256) -> Result<address> {394 self.consume_store_reads(1)?;395 let token: TokenId = token_id.try_into()?;396 Ok(*<TokenData<T>>::get((self.id, token))397 .ok_or("token not found")?398 .owner399 .as_eth())400 }401 /// @dev Not implemented402 #[solidity(rename_selector = "safeTransferFrom")]403 fn safe_transfer_from_with_data(404 &mut self,405 _from: address,406 _to: address,407 _token_id: uint256,408 _data: bytes,409 ) -> Result<void> {410 // TODO: Not implemetable411 Err("not implemented".into())412 }413 /// @dev Not implemented414 fn safe_transfer_from(415 &mut self,416 _from: address,417 _to: address,418 _token_id: uint256,419 ) -> Result<void> {420 // TODO: Not implemetable421 Err("not implemented".into())422 }423424 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE425 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE426 /// THEY MAY BE PERMANENTLY LOST427 /// @dev Throws unless `msg.sender` is the current owner or an authorized428 /// operator for this NFT. Throws if `from` is not the current owner. Throws429 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.430 /// @param from The current owner of the NFT431 /// @param to The new owner432 /// @param tokenId The NFT to transfer433 #[weight(<SelfWeightOf<T>>::transfer_from())]434 fn transfer_from(435 &mut self,436 caller: caller,437 from: address,438 to: address,439 token_id: uint256,440 ) -> Result<void> {441 let caller = T::CrossAccountId::from_eth(caller);442 let from = T::CrossAccountId::from_eth(from);443 let to = T::CrossAccountId::from_eth(to);444 let token = token_id.try_into()?;445 let budget = self446 .recorder447 .weight_calls_budget(<StructureWeight<T>>::find_parent());448449 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)450 .map_err(dispatch_to_evm::<T>)?;451 Ok(())452 }453454 /// @notice Set or reaffirm the approved address for an NFT455 /// @dev The zero address indicates there is no approved address.456 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized457 /// operator of the current owner.458 /// @param approved The new approved NFT controller459 /// @param tokenId The NFT to approve460 #[weight(<SelfWeightOf<T>>::approve())]461 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {462 let caller = T::CrossAccountId::from_eth(caller);463 let approved = T::CrossAccountId::from_eth(approved);464 let token = token_id.try_into()?;465466 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))467 .map_err(dispatch_to_evm::<T>)?;468 Ok(())469 }470471 /// @dev Not implemented472 fn set_approval_for_all(473 &mut self,474 _caller: caller,475 _operator: address,476 _approved: bool,477 ) -> Result<void> {478 // TODO: Not implemetable479 Err("not implemented".into())480 }481482 /// @dev Not implemented483 fn get_approved(&self, _token_id: uint256) -> Result<address> {484 // TODO: Not implemetable485 Err("not implemented".into())486 }487488 /// @dev Not implemented489 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {490 // TODO: Not implemetable491 Err("not implemented".into())492 }493494 /// @notice Returns collection helper contract address495 fn collection_helper_address(&self) -> Result<address> {496 Ok(T::ContractAddress::get())497 }498}499500/// @title ERC721 Token that can be irreversibly burned (destroyed).501#[solidity_interface(name = ERC721Burnable)]502impl<T: Config> NonfungibleHandle<T> {503 /// @notice Burns a specific ERC721 token.504 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized505 /// operator of the current owner.506 /// @param tokenId The NFT to approve507 #[weight(<SelfWeightOf<T>>::burn_item())]508 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {509 let caller = T::CrossAccountId::from_eth(caller);510 let token = token_id.try_into()?;511512 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;513 Ok(())514 }515}516517/// @title ERC721 minting logic.518#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]519impl<T: Config> NonfungibleHandle<T> {520 fn minting_finished(&self) -> Result<bool> {521 Ok(false)522 }523524 /// @notice Function to mint token.525 /// @param to The new owner526 /// @return uint256 The id of the newly minted token527 #[weight(<SelfWeightOf<T>>::create_item())]528 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {529 let token_id: uint256 = <TokensMinted<T>>::get(self.id)530 .checked_add(1)531 .ok_or("item id overflow")?532 .into();533 self.mint_check_id(caller, to, token_id)?;534 Ok(token_id)535 }536537 /// @notice Function to mint token.538 /// @dev `tokenId` should be obtained with `nextTokenId` method,539 /// unlike standard, you can't specify it manually540 /// @param to The new owner541 /// @param tokenId ID of the minted NFT542 #[solidity(hide, rename_selector = "mint")]543 #[weight(<SelfWeightOf<T>>::create_item())]544 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {545 let caller = T::CrossAccountId::from_eth(caller);546 let to = T::CrossAccountId::from_eth(to);547 let token_id: u32 = token_id.try_into()?;548 let budget = self549 .recorder550 .weight_calls_budget(<StructureWeight<T>>::find_parent());551552 if <TokensMinted<T>>::get(self.id)553 .checked_add(1)554 .ok_or("item id overflow")?555 != token_id556 {557 return Err("item id should be next".into());558 }559560 <Pallet<T>>::create_item(561 self,562 &caller,563 CreateItemData::<T> {564 properties: BoundedVec::default(),565 owner: to,566 },567 &budget,568 )569 .map_err(dispatch_to_evm::<T>)?;570571 Ok(true)572 }573574 /// @notice Function to mint token with the given tokenUri.575 /// @param to The new owner576 /// @param tokenUri Token URI that would be stored in the NFT properties577 /// @return uint256 The id of the newly minted token578 #[solidity(rename_selector = "mintWithTokenURI")]579 #[weight(<SelfWeightOf<T>>::create_item())]580 fn mint_with_token_uri(581 &mut self,582 caller: caller,583 to: address,584 token_uri: string,585 ) -> Result<uint256> {586 let token_id: uint256 = <TokensMinted<T>>::get(self.id)587 .checked_add(1)588 .ok_or("item id overflow")?589 .into();590 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;591 Ok(token_id)592 }593594 /// @notice Function to mint token with the given tokenUri.595 /// @dev `tokenId` should be obtained with `nextTokenId` method,596 /// unlike standard, you can't specify it manually597 /// @param to The new owner598 /// @param tokenId ID of the minted NFT599 /// @param tokenUri Token URI that would be stored in the NFT properties600 #[solidity(hide, rename_selector = "mintWithTokenURI")]601 #[weight(<SelfWeightOf<T>>::create_item())]602 fn mint_with_token_uri_check_id(603 &mut self,604 caller: caller,605 to: address,606 token_id: uint256,607 token_uri: string,608 ) -> Result<bool> {609 let key = key::url();610 let permission = get_token_permission::<T>(self.id, &key)?;611 if !permission.collection_admin {612 return Err("Operation is not allowed".into());613 }614615 let caller = T::CrossAccountId::from_eth(caller);616 let to = T::CrossAccountId::from_eth(to);617 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;618 let budget = self619 .recorder620 .weight_calls_budget(<StructureWeight<T>>::find_parent());621622 if <TokensMinted<T>>::get(self.id)623 .checked_add(1)624 .ok_or("item id overflow")?625 != token_id626 {627 return Err("item id should be next".into());628 }629630 let mut properties = CollectionPropertiesVec::default();631 properties632 .try_push(Property {633 key,634 value: token_uri635 .into_bytes()636 .try_into()637 .map_err(|_| "token uri is too long")?,638 })639 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;640641 <Pallet<T>>::create_item(642 self,643 &caller,644 CreateItemData::<T> {645 properties,646 owner: to,647 },648 &budget,649 )650 .map_err(dispatch_to_evm::<T>)?;651 Ok(true)652 }653654 /// @dev Not implemented655 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {656 Err("not implementable".into())657 }658}659660fn get_token_property<T: Config>(661 collection: &CollectionHandle<T>,662 token_id: u32,663 key: &up_data_structs::PropertyKey,664) -> Result<string> {665 collection.consume_store_reads(1)?;666 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))667 .map_err(|_| Error::Revert("Token properties not found".into()))?;668 if let Some(property) = properties.get(key) {669 return Ok(string::from_utf8_lossy(property).into());670 }671672 Err("Property tokenURI not found".into())673}674675fn get_token_permission<T: Config>(676 collection_id: CollectionId,677 key: &PropertyKey,678) -> Result<PropertyPermission> {679 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)680 .map_err(|_| Error::Revert("No permissions for collection".into()))?;681 let a = token_property_permissions682 .get(key)683 .map(Clone::clone)684 .ok_or_else(|| {685 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();686 Error::Revert(alloc::format!("No permission for key {}", key))687 })?;688 Ok(a)689}690691/// @title Unique extensions for ERC721.692#[solidity_interface(name = ERC721UniqueExtensions)]693impl<T: Config> NonfungibleHandle<T>694where695 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,696{697 /// @notice A descriptive name for a collection of NFTs in this contract698 fn name(&self) -> Result<string> {699 Ok(decode_utf16(self.name.iter().copied())700 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))701 .collect::<string>())702 }703704 /// @notice An abbreviated name for NFTs in this contract705 fn symbol(&self) -> Result<string> {706 Ok(string::from_utf8_lossy(&self.token_prefix).into())707 }708709 /// @notice A description for the collection.710 fn description(&self) -> Result<string> {711 Ok(decode_utf16(self.description.iter().copied())712 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))713 .collect::<string>())714 }715716 /// Returns the owner (in cross format) of the token.717 ///718 /// @param tokenId Id for the token.719 fn cross_owner_of(&self, token_id: uint256) -> Result<EthCrossAccount> {720 Self::token_owner(&self, token_id.try_into()?)721 .map(|o| EthCrossAccount::from_sub_cross_account::<T>(&o))722 .ok_or(Error::Revert("key too large".into()))723 }724725 /// Returns the token properties.726 ///727 /// @param tokenId Id for the token.728 /// @param keys Properties keys. Empty keys for all propertyes.729 /// @return Vector of properties key/value pairs.730 fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {731 let keys = keys732 .into_iter()733 .map(|key| {734 <Vec<u8>>::from(key)735 .try_into()736 .map_err(|_| Error::Revert("key too large".into()))737 })738 .collect::<Result<Vec<_>>>()?;739740 <Self as CommonCollectionOperations<T>>::token_properties(741 &self,742 token_id.try_into()?,743 if keys.is_empty() { None } else { Some(keys) },744 )745 .into_iter()746 .map(|p| {747 let key = string::from_utf8(p.key.to_vec())748 .map_err(|e| Error::Revert(alloc::format!("{}", e)))?;749 let value = bytes(p.value.to_vec());750 Ok(PropertyStruct { key, value })751 })752 .collect::<Result<Vec<_>>>()753 }754755 /// @notice Set or reaffirm the approved address for an NFT756 /// @dev The zero address indicates there is no approved address.757 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized758 /// operator of the current owner.759 /// @param approved The new substrate address approved NFT controller760 /// @param tokenId The NFT to approve761 #[weight(<SelfWeightOf<T>>::approve())]762 fn approve_cross(763 &mut self,764 caller: caller,765 approved: EthCrossAccount,766 token_id: uint256,767 ) -> Result<void> {768 let caller = T::CrossAccountId::from_eth(caller);769 let approved = approved.into_sub_cross_account::<T>()?;770 let token = token_id.try_into()?;771772 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))773 .map_err(dispatch_to_evm::<T>)?;774 Ok(())775 }776777 /// @notice Transfer ownership of an NFT778 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`779 /// is the zero address. Throws if `tokenId` is not a valid NFT.780 /// @param to The new owner781 /// @param tokenId The NFT to transfer782 #[weight(<SelfWeightOf<T>>::transfer())]783 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {784 let caller = T::CrossAccountId::from_eth(caller);785 let to = T::CrossAccountId::from_eth(to);786 let token = token_id.try_into()?;787 let budget = self788 .recorder789 .weight_calls_budget(<StructureWeight<T>>::find_parent());790791 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;792 Ok(())793 }794795 /// @notice Transfer ownership of an NFT796 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`797 /// is the zero address. Throws if `tokenId` is not a valid NFT.798 /// @param to The new owner799 /// @param tokenId The NFT to transfer800 #[weight(<SelfWeightOf<T>>::transfer())]801 fn transfer_cross(802 &mut self,803 caller: caller,804 to: EthCrossAccount,805 token_id: uint256,806 ) -> Result<void> {807 let caller = T::CrossAccountId::from_eth(caller);808 let to = to.into_sub_cross_account::<T>()?;809 let token = token_id.try_into()?;810 let budget = self811 .recorder812 .weight_calls_budget(<StructureWeight<T>>::find_parent());813814 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;815 Ok(())816 }817818 /// @notice Transfer ownership of an NFT from cross account address to cross account address819 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`820 /// is the zero address. Throws if `tokenId` is not a valid NFT.821 /// @param from Cross acccount address of current owner822 /// @param to Cross acccount address of new owner823 /// @param tokenId The NFT to transfer824 #[weight(<SelfWeightOf<T>>::transfer())]825 fn transfer_from_cross(826 &mut self,827 caller: caller,828 from: EthCrossAccount,829 to: EthCrossAccount,830 token_id: uint256,831 ) -> Result<void> {832 let caller = T::CrossAccountId::from_eth(caller);833 let from = from.into_sub_cross_account::<T>()?;834 let to = to.into_sub_cross_account::<T>()?;835 let token_id = token_id.try_into()?;836 let budget = self837 .recorder838 .weight_calls_budget(<StructureWeight<T>>::find_parent());839 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)840 .map_err(dispatch_to_evm::<T>)?;841 Ok(())842 }843844 /// @notice Burns a specific ERC721 token.845 /// @dev Throws unless `msg.sender` is the current owner or an authorized846 /// operator for this NFT. Throws if `from` is not the current owner. Throws847 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.848 /// @param from The current owner of the NFT849 /// @param tokenId The NFT to transfer850 #[solidity(hide)]851 #[weight(<SelfWeightOf<T>>::burn_from())]852 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {853 let caller = T::CrossAccountId::from_eth(caller);854 let from = T::CrossAccountId::from_eth(from);855 let token = token_id.try_into()?;856 let budget = self857 .recorder858 .weight_calls_budget(<StructureWeight<T>>::find_parent());859860 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)861 .map_err(dispatch_to_evm::<T>)?;862 Ok(())863 }864865 /// @notice Burns a specific ERC721 token.866 /// @dev Throws unless `msg.sender` is the current owner or an authorized867 /// operator for this NFT. Throws if `from` is not the current owner. Throws868 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.869 /// @param from The current owner of the NFT870 /// @param tokenId The NFT to transfer871 #[weight(<SelfWeightOf<T>>::burn_from())]872 fn burn_from_cross(873 &mut self,874 caller: caller,875 from: EthCrossAccount,876 token_id: uint256,877 ) -> Result<void> {878 let caller = T::CrossAccountId::from_eth(caller);879 let from = from.into_sub_cross_account::<T>()?;880 let token = token_id.try_into()?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884885 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)886 .map_err(dispatch_to_evm::<T>)?;887 Ok(())888 }889890 /// @notice Returns next free NFT ID.891 fn next_token_id(&self) -> Result<uint256> {892 self.consume_store_reads(1)?;893 Ok(<TokensMinted<T>>::get(self.id)894 .checked_add(1)895 .ok_or("item id overflow")?896 .into())897 }898899 /// @notice Function to mint multiple tokens.900 /// @dev `tokenIds` should be an array of consecutive numbers and first number901 /// should be obtained with `nextTokenId` method902 /// @param to The new owner903 /// @param tokenIds IDs of the minted NFTs904 #[solidity(hide)]905 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]906 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {907 let caller = T::CrossAccountId::from_eth(caller);908 let to = T::CrossAccountId::from_eth(to);909 let mut expected_index = <TokensMinted<T>>::get(self.id)910 .checked_add(1)911 .ok_or("item id overflow")?;912 let budget = self913 .recorder914 .weight_calls_budget(<StructureWeight<T>>::find_parent());915916 let total_tokens = token_ids.len();917 for id in token_ids.into_iter() {918 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;919 if id != expected_index {920 return Err("item id should be next".into());921 }922 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;923 }924 let data = (0..total_tokens)925 .map(|_| CreateItemData::<T> {926 properties: BoundedVec::default(),927 owner: to.clone(),928 })929 .collect();930931 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)932 .map_err(dispatch_to_evm::<T>)?;933 Ok(true)934 }935936 /// @notice Function to mint multiple tokens with the given tokenUris.937 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive938 /// numbers and first number should be obtained with `nextTokenId` method939 /// @param to The new owner940 /// @param tokens array of pairs of token ID and token URI for minted tokens941 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]942 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]943 fn mint_bulk_with_token_uri(944 &mut self,945 caller: caller,946 to: address,947 tokens: Vec<(uint256, string)>,948 ) -> Result<bool> {949 let key = key::url();950 let caller = T::CrossAccountId::from_eth(caller);951 let to = T::CrossAccountId::from_eth(to);952 let mut expected_index = <TokensMinted<T>>::get(self.id)953 .checked_add(1)954 .ok_or("item id overflow")?;955 let budget = self956 .recorder957 .weight_calls_budget(<StructureWeight<T>>::find_parent());958959 let mut data = Vec::with_capacity(tokens.len());960 for (id, token_uri) in tokens {961 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;962 if id != expected_index {963 return Err("item id should be next".into());964 }965 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;966967 let mut properties = CollectionPropertiesVec::default();968 properties969 .try_push(Property {970 key: key.clone(),971 value: token_uri972 .into_bytes()973 .try_into()974 .map_err(|_| "token uri is too long")?,975 })976 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;977978 data.push(CreateItemData::<T> {979 properties,980 owner: to.clone(),981 });982 }983984 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)985 .map_err(dispatch_to_evm::<T>)?;986 Ok(true)987 }988}989990#[solidity_interface(991 name = UniqueNFT,992 is(993 ERC721,994 ERC721Enumerable,995 ERC721UniqueExtensions,996 ERC721UniqueMintable,997 ERC721Burnable,998 ERC721Metadata(if(this.flags.erc721metadata)),999 Collection(via(common_mut returns CollectionHandle<T>)),1000 TokenProperties,1001 )1002)]1003impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}10041005// Not a tests, but code generators1006generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1007generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10081009impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1010where1011 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1012{1013 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10141015 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1016 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1017 }1018}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29 types::Property as PropertyStruct, weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34 CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,40 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},41 eth::EthCrossAccount,42};43use pallet_evm::{account::CrossAccountId, PrecompileHandle};44use pallet_evm_coder_substrate::call;45use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};46use sp_core::Get;4748use crate::{49 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,50 SelfWeightOf, weights::WeightInfo, TokenProperties,51};5253/// @title A contract that allows to set and delete token properties and change token property permissions.54#[solidity_interface(name = TokenProperties)]55impl<T: Config> NonfungibleHandle<T> {56 /// @notice Set permissions for token property.57 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.58 /// @param key Property key.59 /// @param isMutable Permission to mutate property.60 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.61 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.62 fn set_token_property_permission(63 &mut self,64 caller: caller,65 key: string,66 is_mutable: bool,67 collection_admin: bool,68 token_owner: bool,69 ) -> Result<()> {70 let caller = T::CrossAccountId::from_eth(caller);71 <Pallet<T>>::set_property_permission(72 self,73 &caller,74 PropertyKeyPermission {75 key: <Vec<u8>>::from(key)76 .try_into()77 .map_err(|_| "too long key")?,78 permission: PropertyPermission {79 mutable: is_mutable,80 collection_admin,81 token_owner,82 },83 },84 )85 .map_err(dispatch_to_evm::<T>)86 }8788 /// @notice Set token property value.89 /// @dev Throws error if `msg.sender` has no permission to edit the property.90 /// @param tokenId ID of the token.91 /// @param key Property key.92 /// @param value Property value.93 #[solidity(hide)]94 fn set_property(95 &mut self,96 caller: caller,97 token_id: uint256,98 key: string,99 value: bytes,100 ) -> Result<()> {101 let caller = T::CrossAccountId::from_eth(caller);102 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;103 let key = <Vec<u8>>::from(key)104 .try_into()105 .map_err(|_| "key too long")?;106 let value = value.0.try_into().map_err(|_| "value too long")?;107108 let nesting_budget = self109 .recorder110 .weight_calls_budget(<StructureWeight<T>>::find_parent());111112 <Pallet<T>>::set_token_property(113 self,114 &caller,115 TokenId(token_id),116 Property { key, value },117 &nesting_budget,118 )119 .map_err(dispatch_to_evm::<T>)120 }121122 /// @notice Set token properties value.123 /// @dev Throws error if `msg.sender` has no permission to edit the property.124 /// @param tokenId ID of the token.125 /// @param properties settable properties126 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]127 fn set_properties(128 &mut self,129 caller: caller,130 token_id: uint256,131 properties: Vec<PropertyStruct>,132 ) -> Result<()> {133 let caller = T::CrossAccountId::from_eth(caller);134 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;135136 let nesting_budget = self137 .recorder138 .weight_calls_budget(<StructureWeight<T>>::find_parent());139140 let properties = properties141 .into_iter()142 .map(|PropertyStruct { key, value }| {143 let key = <Vec<u8>>::from(key)144 .try_into()145 .map_err(|_| "key too large")?;146147 let value = value.0.try_into().map_err(|_| "value too large")?;148149 Ok(Property { key, value })150 })151 .collect::<Result<Vec<_>>>()?;152153 <Pallet<T>>::set_token_properties(154 self,155 &caller,156 TokenId(token_id),157 properties.into_iter(),158 <Pallet<T>>::token_exists(&self, TokenId(token_id)),159 &nesting_budget,160 )161 .map_err(dispatch_to_evm::<T>)162 }163164 /// @notice Delete token property value.165 /// @dev Throws error if `msg.sender` has no permission to edit the property.166 /// @param tokenId ID of the token.167 /// @param key Property key.168 #[solidity(hide)]169 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {170 let caller = T::CrossAccountId::from_eth(caller);171 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;172 let key = <Vec<u8>>::from(key)173 .try_into()174 .map_err(|_| "key too long")?;175176 let nesting_budget = self177 .recorder178 .weight_calls_budget(<StructureWeight<T>>::find_parent());179180 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)181 .map_err(dispatch_to_evm::<T>)182 }183184 /// @notice Delete token properties value.185 /// @dev Throws error if `msg.sender` has no permission to edit the property.186 /// @param tokenId ID of the token.187 /// @param keys Properties key.188 fn delete_properties(189 &mut self,190 token_id: uint256,191 caller: caller,192 keys: Vec<string>,193 ) -> 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 keys = keys197 .into_iter()198 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))199 .collect::<Result<Vec<_>>>()?;200201 let nesting_budget = self202 .recorder203 .weight_calls_budget(<StructureWeight<T>>::find_parent());204205 <Pallet<T>>::delete_token_properties(206 self,207 &caller,208 TokenId(token_id),209 keys.into_iter(),210 &nesting_budget,211 )212 .map_err(dispatch_to_evm::<T>)213 }214215 /// @notice Get token property value.216 /// @dev Throws error if key not found217 /// @param tokenId ID of the token.218 /// @param key Property key.219 /// @return Property value bytes220 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {221 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;222 let key = <Vec<u8>>::from(key)223 .try_into()224 .map_err(|_| "key too long")?;225226 let props = <TokenProperties<T>>::get((self.id, token_id));227 let prop = props.get(&key).ok_or("key not found")?;228229 Ok(prop.to_vec().into())230 }231}232233#[derive(ToLog)]234pub enum ERC721Events {235 /// @dev This emits when ownership of any NFT changes by any mechanism.236 /// This event emits when NFTs are created (`from` == 0) and destroyed237 /// (`to` == 0). Exception: during contract creation, any number of NFTs238 /// may be created and assigned without emitting Transfer. At the time of239 /// any transfer, the approved address for that NFT (if any) is reset to none.240 Transfer {241 #[indexed]242 from: address,243 #[indexed]244 to: address,245 #[indexed]246 token_id: uint256,247 },248 /// @dev This emits when the approved address for an NFT is changed or249 /// reaffirmed. The zero address indicates there is no approved address.250 /// When a Transfer event emits, this also indicates that the approved251 /// address for that NFT (if any) is reset to none.252 Approval {253 #[indexed]254 owner: address,255 #[indexed]256 approved: address,257 #[indexed]258 token_id: uint256,259 },260 /// @dev This emits when an operator is enabled or disabled for an owner.261 /// The operator can manage all NFTs of the owner.262 #[allow(dead_code)]263 ApprovalForAll {264 #[indexed]265 owner: address,266 #[indexed]267 operator: address,268 approved: bool,269 },270}271272#[derive(ToLog)]273pub enum ERC721UniqueMintableEvents {274 #[allow(dead_code)]275 MintingFinished {},276}277278/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension279/// @dev See https://eips.ethereum.org/EIPS/eip-721280#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]281impl<T: Config> NonfungibleHandle<T>282where283 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,284{285 /// @notice A descriptive name for a collection of NFTs in this contract286 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`287 #[solidity(hide, rename_selector = "name")]288 fn name_proxy(&self) -> Result<string> {289 self.name()290 }291292 /// @notice An abbreviated name for NFTs in this contract293 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`294 #[solidity(hide, rename_selector = "symbol")]295 fn symbol_proxy(&self) -> Result<string> {296 self.symbol()297 }298299 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.300 ///301 /// @dev If the token has a `url` property and it is not empty, it is returned.302 /// 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`.303 /// If the collection property `baseURI` is empty or absent, return "" (empty string)304 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix305 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).306 ///307 /// @return token's const_metadata308 #[solidity(rename_selector = "tokenURI")]309 fn token_uri(&self, token_id: uint256) -> Result<string> {310 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;311312 match get_token_property(self, token_id_u32, &key::url()).as_deref() {313 Err(_) | Ok("") => (),314 Ok(url) => {315 return Ok(url.into());316 }317 };318319 let base_uri =320 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())321 .map(BoundedVec::into_inner)322 .map(string::from_utf8)323 .transpose()324 .map_err(|e| {325 Error::Revert(alloc::format!(326 "Can not convert value \"baseURI\" to string with error \"{}\"",327 e328 ))329 })?;330331 let base_uri = match base_uri.as_deref() {332 None | Some("") => {333 return Ok("".into());334 }335 Some(base_uri) => base_uri.into(),336 };337338 Ok(339 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {340 Err(_) | Ok("") => base_uri,341 Ok(suffix) => base_uri + suffix,342 },343 )344 }345}346347/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension348/// @dev See https://eips.ethereum.org/EIPS/eip-721349#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]350impl<T: Config> NonfungibleHandle<T> {351 /// @notice Enumerate valid NFTs352 /// @param index A counter less than `totalSupply()`353 /// @return The token identifier for the `index`th NFT,354 /// (sort order not specified)355 fn token_by_index(&self, index: uint256) -> Result<uint256> {356 Ok(index)357 }358359 /// @dev Not implemented360 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {361 // TODO: Not implemetable362 Err("not implemented".into())363 }364365 /// @notice Count NFTs tracked by this contract366 /// @return A count of valid NFTs tracked by this contract, where each one of367 /// them has an assigned and queryable owner not equal to the zero address368 fn total_supply(&self) -> Result<uint256> {369 self.consume_store_reads(1)?;370 Ok(<Pallet<T>>::total_supply(self).into())371 }372}373374/// @title ERC-721 Non-Fungible Token Standard375/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md376#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]377impl<T: Config> NonfungibleHandle<T> {378 /// @notice Count all NFTs assigned to an owner379 /// @dev NFTs assigned to the zero address are considered invalid, and this380 /// function throws for queries about the zero address.381 /// @param owner An address for whom to query the balance382 /// @return The number of NFTs owned by `owner`, possibly zero383 fn balance_of(&self, owner: address) -> Result<uint256> {384 self.consume_store_reads(1)?;385 let owner = T::CrossAccountId::from_eth(owner);386 let balance = <AccountBalance<T>>::get((self.id, owner));387 Ok(balance.into())388 }389 /// @notice Find the owner of an NFT390 /// @dev NFTs assigned to zero address are considered invalid, and queries391 /// about them do throw.392 /// @param tokenId The identifier for an NFT393 /// @return The address of the owner of the NFT394 fn owner_of(&self, token_id: uint256) -> Result<address> {395 self.consume_store_reads(1)?;396 let token: TokenId = token_id.try_into()?;397 Ok(*<TokenData<T>>::get((self.id, token))398 .ok_or("token not found")?399 .owner400 .as_eth())401 }402 /// @dev Not implemented403 #[solidity(rename_selector = "safeTransferFrom")]404 fn safe_transfer_from_with_data(405 &mut self,406 _from: address,407 _to: address,408 _token_id: uint256,409 _data: bytes,410 ) -> Result<void> {411 // TODO: Not implemetable412 Err("not implemented".into())413 }414 /// @dev Not implemented415 fn safe_transfer_from(416 &mut self,417 _from: address,418 _to: address,419 _token_id: uint256,420 ) -> Result<void> {421 // TODO: Not implemetable422 Err("not implemented".into())423 }424425 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE426 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE427 /// THEY MAY BE PERMANENTLY LOST428 /// @dev Throws unless `msg.sender` is the current owner or an authorized429 /// operator for this NFT. Throws if `from` is not the current owner. Throws430 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.431 /// @param from The current owner of the NFT432 /// @param to The new owner433 /// @param tokenId The NFT to transfer434 #[weight(<SelfWeightOf<T>>::transfer_from())]435 fn transfer_from(436 &mut self,437 caller: caller,438 from: address,439 to: address,440 token_id: uint256,441 ) -> Result<void> {442 let caller = T::CrossAccountId::from_eth(caller);443 let from = T::CrossAccountId::from_eth(from);444 let to = T::CrossAccountId::from_eth(to);445 let token = token_id.try_into()?;446 let budget = self447 .recorder448 .weight_calls_budget(<StructureWeight<T>>::find_parent());449450 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)451 .map_err(dispatch_to_evm::<T>)?;452 Ok(())453 }454455 /// @notice Set or reaffirm the approved address for an NFT456 /// @dev The zero address indicates there is no approved address.457 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized458 /// operator of the current owner.459 /// @param approved The new approved NFT controller460 /// @param tokenId The NFT to approve461 #[weight(<SelfWeightOf<T>>::approve())]462 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {463 let caller = T::CrossAccountId::from_eth(caller);464 let approved = T::CrossAccountId::from_eth(approved);465 let token = token_id.try_into()?;466467 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))468 .map_err(dispatch_to_evm::<T>)?;469 Ok(())470 }471472 /// @dev Not implemented473 fn set_approval_for_all(474 &mut self,475 _caller: caller,476 _operator: address,477 _approved: bool,478 ) -> Result<void> {479 // TODO: Not implemetable480 Err("not implemented".into())481 }482483 /// @dev Not implemented484 fn get_approved(&self, _token_id: uint256) -> Result<address> {485 // TODO: Not implemetable486 Err("not implemented".into())487 }488489 /// @dev Not implemented490 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {491 // TODO: Not implemetable492 Err("not implemented".into())493 }494495 /// @notice Returns collection helper contract address496 fn collection_helper_address(&self) -> Result<address> {497 Ok(T::ContractAddress::get())498 }499}500501/// @title ERC721 Token that can be irreversibly burned (destroyed).502#[solidity_interface(name = ERC721Burnable)]503impl<T: Config> NonfungibleHandle<T> {504 /// @notice Burns a specific ERC721 token.505 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized506 /// operator of the current owner.507 /// @param tokenId The NFT to approve508 #[weight(<SelfWeightOf<T>>::burn_item())]509 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {510 let caller = T::CrossAccountId::from_eth(caller);511 let token = token_id.try_into()?;512513 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;514 Ok(())515 }516}517518/// @title ERC721 minting logic.519#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]520impl<T: Config> NonfungibleHandle<T> {521 fn minting_finished(&self) -> Result<bool> {522 Ok(false)523 }524525 /// @notice Function to mint token.526 /// @param to The new owner527 /// @return uint256 The id of the newly minted token528 #[weight(<SelfWeightOf<T>>::create_item())]529 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {530 let token_id: uint256 = <TokensMinted<T>>::get(self.id)531 .checked_add(1)532 .ok_or("item id overflow")?533 .into();534 self.mint_check_id(caller, to, token_id)?;535 Ok(token_id)536 }537538 /// @notice Function to mint token.539 /// @dev `tokenId` should be obtained with `nextTokenId` method,540 /// unlike standard, you can't specify it manually541 /// @param to The new owner542 /// @param tokenId ID of the minted NFT543 #[solidity(hide, rename_selector = "mint")]544 #[weight(<SelfWeightOf<T>>::create_item())]545 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {546 let caller = T::CrossAccountId::from_eth(caller);547 let to = T::CrossAccountId::from_eth(to);548 let token_id: u32 = token_id.try_into()?;549 let budget = self550 .recorder551 .weight_calls_budget(<StructureWeight<T>>::find_parent());552553 if <TokensMinted<T>>::get(self.id)554 .checked_add(1)555 .ok_or("item id overflow")?556 != token_id557 {558 return Err("item id should be next".into());559 }560561 <Pallet<T>>::create_item(562 self,563 &caller,564 CreateItemData::<T> {565 properties: BoundedVec::default(),566 owner: to,567 },568 &budget,569 )570 .map_err(dispatch_to_evm::<T>)?;571572 Ok(true)573 }574575 /// @notice Function to mint token with the given tokenUri.576 /// @param to The new owner577 /// @param tokenUri Token URI that would be stored in the NFT properties578 /// @return uint256 The id of the newly minted token579 #[solidity(rename_selector = "mintWithTokenURI")]580 #[weight(<SelfWeightOf<T>>::create_item())]581 fn mint_with_token_uri(582 &mut self,583 caller: caller,584 to: address,585 token_uri: string,586 ) -> Result<uint256> {587 let token_id: uint256 = <TokensMinted<T>>::get(self.id)588 .checked_add(1)589 .ok_or("item id overflow")?590 .into();591 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;592 Ok(token_id)593 }594595 /// @notice Function to mint token with the given tokenUri.596 /// @dev `tokenId` should be obtained with `nextTokenId` method,597 /// unlike standard, you can't specify it manually598 /// @param to The new owner599 /// @param tokenId ID of the minted NFT600 /// @param tokenUri Token URI that would be stored in the NFT properties601 #[solidity(hide, rename_selector = "mintWithTokenURI")]602 #[weight(<SelfWeightOf<T>>::create_item())]603 fn mint_with_token_uri_check_id(604 &mut self,605 caller: caller,606 to: address,607 token_id: uint256,608 token_uri: string,609 ) -> Result<bool> {610 let key = key::url();611 let permission = get_token_permission::<T>(self.id, &key)?;612 if !permission.collection_admin {613 return Err("Operation is not allowed".into());614 }615616 let caller = T::CrossAccountId::from_eth(caller);617 let to = T::CrossAccountId::from_eth(to);618 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;619 let budget = self620 .recorder621 .weight_calls_budget(<StructureWeight<T>>::find_parent());622623 if <TokensMinted<T>>::get(self.id)624 .checked_add(1)625 .ok_or("item id overflow")?626 != token_id627 {628 return Err("item id should be next".into());629 }630631 let mut properties = CollectionPropertiesVec::default();632 properties633 .try_push(Property {634 key,635 value: token_uri636 .into_bytes()637 .try_into()638 .map_err(|_| "token uri is too long")?,639 })640 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;641642 <Pallet<T>>::create_item(643 self,644 &caller,645 CreateItemData::<T> {646 properties,647 owner: to,648 },649 &budget,650 )651 .map_err(dispatch_to_evm::<T>)?;652 Ok(true)653 }654655 /// @dev Not implemented656 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {657 Err("not implementable".into())658 }659}660661fn get_token_property<T: Config>(662 collection: &CollectionHandle<T>,663 token_id: u32,664 key: &up_data_structs::PropertyKey,665) -> Result<string> {666 collection.consume_store_reads(1)?;667 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))668 .map_err(|_| Error::Revert("Token properties not found".into()))?;669 if let Some(property) = properties.get(key) {670 return Ok(string::from_utf8_lossy(property).into());671 }672673 Err("Property tokenURI not found".into())674}675676fn get_token_permission<T: Config>(677 collection_id: CollectionId,678 key: &PropertyKey,679) -> Result<PropertyPermission> {680 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)681 .map_err(|_| Error::Revert("No permissions for collection".into()))?;682 let a = token_property_permissions683 .get(key)684 .map(Clone::clone)685 .ok_or_else(|| {686 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();687 Error::Revert(alloc::format!("No permission for key {}", key))688 })?;689 Ok(a)690}691692/// @title Unique extensions for ERC721.693#[solidity_interface(name = ERC721UniqueExtensions)]694impl<T: Config> NonfungibleHandle<T>695where696 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,697{698 /// @notice A descriptive name for a collection of NFTs in this contract699 fn name(&self) -> Result<string> {700 Ok(decode_utf16(self.name.iter().copied())701 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))702 .collect::<string>())703 }704705 /// @notice An abbreviated name for NFTs in this contract706 fn symbol(&self) -> Result<string> {707 Ok(string::from_utf8_lossy(&self.token_prefix).into())708 }709710 /// @notice A description for the collection.711 fn description(&self) -> Result<string> {712 Ok(decode_utf16(self.description.iter().copied())713 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))714 .collect::<string>())715 }716717 /// Returns the owner (in cross format) of the token.718 ///719 /// @param tokenId Id for the token.720 fn cross_owner_of(&self, token_id: uint256) -> Result<EthCrossAccount> {721 Self::token_owner(&self, token_id.try_into()?)722 .map(|o| EthCrossAccount::from_sub_cross_account::<T>(&o))723 .ok_or(Error::Revert("key too large".into()))724 }725726 /// Returns the token properties.727 ///728 /// @param tokenId Id for the token.729 /// @param keys Properties keys. Empty keys for all propertyes.730 /// @return Vector of properties key/value pairs.731 fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {732 let keys = keys733 .into_iter()734 .map(|key| {735 <Vec<u8>>::from(key)736 .try_into()737 .map_err(|_| Error::Revert("key too large".into()))738 })739 .collect::<Result<Vec<_>>>()?;740741 <Self as CommonCollectionOperations<T>>::token_properties(742 &self,743 token_id.try_into()?,744 if keys.is_empty() { None } else { Some(keys) },745 )746 .into_iter()747 .map(|p| {748 let key = string::from_utf8(p.key.to_vec())749 .map_err(|e| Error::Revert(alloc::format!("{}", e)))?;750 let value = bytes(p.value.to_vec());751 Ok(PropertyStruct { key, value })752 })753 .collect::<Result<Vec<_>>>()754 }755756 /// @notice Set or reaffirm the approved address for an NFT757 /// @dev The zero address indicates there is no approved address.758 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized759 /// operator of the current owner.760 /// @param approved The new substrate address approved NFT controller761 /// @param tokenId The NFT to approve762 #[weight(<SelfWeightOf<T>>::approve())]763 fn approve_cross(764 &mut self,765 caller: caller,766 approved: EthCrossAccount,767 token_id: uint256,768 ) -> Result<void> {769 let caller = T::CrossAccountId::from_eth(caller);770 let approved = approved.into_sub_cross_account::<T>()?;771 let token = token_id.try_into()?;772773 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))774 .map_err(dispatch_to_evm::<T>)?;775 Ok(())776 }777778 /// @notice Transfer ownership of an NFT779 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`780 /// is the zero address. Throws if `tokenId` is not a valid NFT.781 /// @param to The new owner782 /// @param tokenId The NFT to transfer783 #[weight(<SelfWeightOf<T>>::transfer())]784 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {785 let caller = T::CrossAccountId::from_eth(caller);786 let to = T::CrossAccountId::from_eth(to);787 let token = token_id.try_into()?;788 let budget = self789 .recorder790 .weight_calls_budget(<StructureWeight<T>>::find_parent());791792 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;793 Ok(())794 }795796 /// @notice Transfer ownership of an NFT797 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`798 /// is the zero address. Throws if `tokenId` is not a valid NFT.799 /// @param to The new owner800 /// @param tokenId The NFT to transfer801 #[weight(<SelfWeightOf<T>>::transfer())]802 fn transfer_cross(803 &mut self,804 caller: caller,805 to: EthCrossAccount,806 token_id: uint256,807 ) -> Result<void> {808 let caller = T::CrossAccountId::from_eth(caller);809 let to = to.into_sub_cross_account::<T>()?;810 let token = token_id.try_into()?;811 let budget = self812 .recorder813 .weight_calls_budget(<StructureWeight<T>>::find_parent());814815 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;816 Ok(())817 }818819 /// @notice Transfer ownership of an NFT from cross account address to cross account address820 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`821 /// is the zero address. Throws if `tokenId` is not a valid NFT.822 /// @param from Cross acccount address of current owner823 /// @param to Cross acccount address of new owner824 /// @param tokenId The NFT to transfer825 #[weight(<SelfWeightOf<T>>::transfer())]826 fn transfer_from_cross(827 &mut self,828 caller: caller,829 from: EthCrossAccount,830 to: EthCrossAccount,831 token_id: uint256,832 ) -> Result<void> {833 let caller = T::CrossAccountId::from_eth(caller);834 let from = from.into_sub_cross_account::<T>()?;835 let to = to.into_sub_cross_account::<T>()?;836 let token_id = token_id.try_into()?;837 let budget = self838 .recorder839 .weight_calls_budget(<StructureWeight<T>>::find_parent());840 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)841 .map_err(dispatch_to_evm::<T>)?;842 Ok(())843 }844845 /// @notice Burns a specific ERC721 token.846 /// @dev Throws unless `msg.sender` is the current owner or an authorized847 /// operator for this NFT. Throws if `from` is not the current owner. Throws848 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.849 /// @param from The current owner of the NFT850 /// @param tokenId The NFT to transfer851 #[solidity(hide)]852 #[weight(<SelfWeightOf<T>>::burn_from())]853 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {854 let caller = T::CrossAccountId::from_eth(caller);855 let from = T::CrossAccountId::from_eth(from);856 let token = token_id.try_into()?;857 let budget = self858 .recorder859 .weight_calls_budget(<StructureWeight<T>>::find_parent());860861 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)862 .map_err(dispatch_to_evm::<T>)?;863 Ok(())864 }865866 /// @notice Burns a specific ERC721 token.867 /// @dev Throws unless `msg.sender` is the current owner or an authorized868 /// operator for this NFT. Throws if `from` is not the current owner. Throws869 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.870 /// @param from The current owner of the NFT871 /// @param tokenId The NFT to transfer872 #[weight(<SelfWeightOf<T>>::burn_from())]873 fn burn_from_cross(874 &mut self,875 caller: caller,876 from: EthCrossAccount,877 token_id: uint256,878 ) -> Result<void> {879 let caller = T::CrossAccountId::from_eth(caller);880 let from = from.into_sub_cross_account::<T>()?;881 let token = token_id.try_into()?;882 let budget = self883 .recorder884 .weight_calls_budget(<StructureWeight<T>>::find_parent());885886 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)887 .map_err(dispatch_to_evm::<T>)?;888 Ok(())889 }890891 /// @notice Returns next free NFT ID.892 fn next_token_id(&self) -> Result<uint256> {893 self.consume_store_reads(1)?;894 Ok(<TokensMinted<T>>::get(self.id)895 .checked_add(1)896 .ok_or("item id overflow")?897 .into())898 }899900 /// @notice Function to mint multiple tokens.901 /// @dev `tokenIds` should be an array of consecutive numbers and first number902 /// should be obtained with `nextTokenId` method903 /// @param to The new owner904 /// @param tokenIds IDs of the minted NFTs905 #[solidity(hide)]906 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]907 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {908 let caller = T::CrossAccountId::from_eth(caller);909 let to = T::CrossAccountId::from_eth(to);910 let mut expected_index = <TokensMinted<T>>::get(self.id)911 .checked_add(1)912 .ok_or("item id overflow")?;913 let budget = self914 .recorder915 .weight_calls_budget(<StructureWeight<T>>::find_parent());916917 let total_tokens = token_ids.len();918 for id in token_ids.into_iter() {919 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;920 if id != expected_index {921 return Err("item id should be next".into());922 }923 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;924 }925 let data = (0..total_tokens)926 .map(|_| CreateItemData::<T> {927 properties: BoundedVec::default(),928 owner: to.clone(),929 })930 .collect();931932 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)933 .map_err(dispatch_to_evm::<T>)?;934 Ok(true)935 }936937 /// @notice Function to mint multiple tokens with the given tokenUris.938 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive939 /// numbers and first number should be obtained with `nextTokenId` method940 /// @param to The new owner941 /// @param tokens array of pairs of token ID and token URI for minted tokens942 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]943 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]944 fn mint_bulk_with_token_uri(945 &mut self,946 caller: caller,947 to: address,948 tokens: Vec<(uint256, string)>,949 ) -> Result<bool> {950 let key = key::url();951 let caller = T::CrossAccountId::from_eth(caller);952 let to = T::CrossAccountId::from_eth(to);953 let mut expected_index = <TokensMinted<T>>::get(self.id)954 .checked_add(1)955 .ok_or("item id overflow")?;956 let budget = self957 .recorder958 .weight_calls_budget(<StructureWeight<T>>::find_parent());959960 let mut data = Vec::with_capacity(tokens.len());961 for (id, token_uri) in tokens {962 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;963 if id != expected_index {964 return Err("item id should be next".into());965 }966 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;967968 let mut properties = CollectionPropertiesVec::default();969 properties970 .try_push(Property {971 key: key.clone(),972 value: token_uri973 .into_bytes()974 .try_into()975 .map_err(|_| "token uri is too long")?,976 })977 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;978979 data.push(CreateItemData::<T> {980 properties,981 owner: to.clone(),982 });983 }984985 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)986 .map_err(dispatch_to_evm::<T>)?;987 Ok(true)988 }989}990991#[solidity_interface(992 name = UniqueNFT,993 is(994 ERC721,995 ERC721Enumerable,996 ERC721UniqueExtensions,997 ERC721UniqueMintable,998 ERC721Burnable,999 ERC721Metadata(if(this.flags.erc721metadata)),1000 Collection(via(common_mut returns CollectionHandle<T>)),1001 TokenProperties,1002 )1003)]1004impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}10051006// Not a tests, but code generators1007generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1008generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10091010impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1011where1012 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1013{1014 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10151016 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1017 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1018 }1019}pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -33,6 +33,7 @@
use pallet_common::{
CollectionHandle, CollectionPropertyPermissions,
erc::{CommonEvmHandler, CollectionCall, static_property::key},
+ eth::EthCrossAccount,
CommonCollectionOperations,
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};