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}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};