difftreelog
feat calculate signature in compile time
in: master
11 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1096,6 +1096,26 @@
checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
[[package]]
+name = "const_format"
+version = "0.2.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2353,6 +2373,7 @@
version = "0.1.3"
dependencies = [
"concat-idents",
+ "const_format",
"ethereum",
"evm-coder-procedural",
"evm-core 0.35.0 (git+https://github.com/uniquenetwork/evm?branch=unique-polkadot-v0.9.30)",
@@ -2362,6 +2383,7 @@
"impl-trait-for-tuples",
"pallet-evm 6.0.0-dev (git+https://github.com/uniquenetwork/frontier?branch=unique-polkadot-v0.9.27-fee-limit)",
"primitive-types",
+ "sha3-const",
"similar-asserts",
"sp-std 4.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.30)",
]
@@ -11085,6 +11107,12 @@
]
[[package]]
+name = "sha3-const"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af9625d558d174dbdc711248b479271c0fdca39e9d3d67f6e890b3d4251fbf8e"
+
+[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
crates/evm-coder/Cargo.tomldiffbeforeafterboth--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -5,6 +5,10 @@
edition = "2021"
[dependencies]
+const_format = { version = "0.2.26", default-features = false }
+sha3-const = { version = "0.1.0", default-features = false }
+# Ethereum uses keccak (=sha3) for selectors
+# sha3 = "0.10.1"
# evm-coder reexports those proc-macro
evm-coder-procedural = { path = "./procedural" }
# Evm uses primitive-types for H160, H256 and others
crates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth--- a/crates/evm-coder/procedural/src/solidity_interface.rs
+++ b/crates/evm-coder/procedural/src/solidity_interface.rs
@@ -20,6 +20,7 @@
// about Procedural Macros in Rust book:
// https://doc.rust-lang.org/reference/procedural-macros.html
+use proc_macro2::{TokenStream, Group};
use quote::{quote, ToTokens};
use inflector::cases;
use std::fmt::Write;
@@ -328,6 +329,7 @@
}
}
+#[derive(Debug)]
enum AbiType {
// type
Plain(Ident),
@@ -473,6 +475,7 @@
}
}
+#[derive(Debug)]
struct MethodArg {
name: Ident,
camel_name: String,
@@ -722,13 +725,25 @@
}
}
+ fn expand_selector(&self) -> proc_macro2::TokenStream {
+ let custom_signature = self.expand_custom_signature();
+ quote! {
+ {
+ let a = ::evm_coder::sha3_const::Keccak256::new()
+ .update(#custom_signature.as_bytes())
+ .finalize();
+ [a[0], a[1], a[2], a[3]]
+ }
+ }
+ }
+
fn expand_const(&self) -> proc_macro2::TokenStream {
let screaming_name = &self.screaming_name;
- let selector = u32::to_be_bytes(self.selector);
let selector_str = &self.selector_str;
+ let selector = &self.expand_selector();
quote! {
#[doc = #selector_str]
- const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*];
+ const #screaming_name: ::evm_coder::types::bytes4 = #selector;
}
}
@@ -831,6 +846,59 @@
}
}
+ fn expand_custom_signature(&self) -> proc_macro2::TokenStream {
+ let mut first_comma = true;
+ let mut custom_signature = TokenStream::new();
+ let mut template = self.camel_name.clone() + "(";
+ self.args
+ .iter()
+ .filter_map(|a| {
+ if a.is_special() {
+ return None;
+ };
+
+ match a.ty {
+ AbiType::Plain(ref ident) => Some(ident),
+ _ => None,
+ }
+ })
+ .for_each(|ident| {
+ if !first_comma {
+ custom_signature.extend(quote!(,));
+ template.push(',');
+ } else {
+ first_comma = false;
+ };
+ template.push_str("{}");
+ let ident_str = ident.to_string();
+ match ident_str.as_str() {
+ "address" | "uint8" | "uint16" | "uint32" | "uint64" | "uint128"
+ | "uint256" | "bytes4" | "topic" | "string" | "bytes" | "void" | "caller"
+ | "bool" | "" => {
+ custom_signature.extend(quote!(#ident_str));
+ }
+ _ => {
+ custom_signature.extend(quote! {
+ #ident::SIGNATURE_STRING
+ });
+ }
+ }
+ });
+
+ template.push(')');
+ let mut template = quote!(#template);
+ template.extend(quote!(,));
+ template.extend(custom_signature);
+ let custom_signature_group = Group::new(proc_macro2::Delimiter::Parenthesis, template);
+ let mut custom_signature = quote! {
+ ::evm_coder::const_format::formatcp!
+ };
+ custom_signature.extend(custom_signature_group.to_token_stream());
+
+ // println!("!!!!! {}", custom_signature);
+ custom_signature
+ }
+
fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
let camel_name = &self.camel_name;
let mutability = match self.mutability {
@@ -847,15 +915,17 @@
.map(MethodArg::expand_solidity_argument);
let docs = &self.docs;
let selector_str = &self.selector_str;
- let selector = self.selector;
+ let selector = &self.expand_selector();
let hide = self.hide;
+ let custom_signature = self.expand_custom_signature();
let is_payable = self.has_value_args;
quote! {
SolidityFunction {
docs: &[#(#docs),*],
selector_str: #selector_str,
- selector: #selector,
hide: #hide,
+ selector: u32::from_be_bytes(#selector),
+ custom_signature: #custom_signature,
name: #camel_name,
mutability: #mutability,
is_payable: #is_payable,
crates/evm-coder/src/lib.rsdiffbeforeafterboth--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -90,6 +90,8 @@
pub use evm_coder_procedural::solidity;
/// See [`solidity_interface`]
pub use evm_coder_procedural::weight;
+pub use const_format;
+pub use sha3_const;
/// Derives [`ToLog`] for enum
///
@@ -227,6 +229,14 @@
}
}
+ impl SignatureString for EthCrossAccount {
+ const SIGNATURE_STRING: &'static str = "(address,uint256)";
+ }
+
+ pub trait SignatureString {
+ const SIGNATURE_STRING: &'static str;
+ }
+
/// Convert `CrossAccountId` to `uint256`.
pub fn convert_cross_account_to_uint256<T: pallet_evm::account::Config>(
from: &T::CrossAccountId,
@@ -348,6 +358,18 @@
assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);
}
+ // #[test]
+ // fn function_selector_generation_1() {
+ // assert_eq!(
+ // fn_selector!(transferFromCrossAccountToCrossAccount(
+ // EthCrossAccount,
+ // EthCrossAccount,
+ // uint256
+ // )),
+ // 2543295963
+ // );
+ // }
+
#[test]
fn event_topic_generation() {
assert_eq!(
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -35,6 +35,12 @@
use crate::types::*;
#[derive(Default)]
+pub struct FunctionSelectorMaker {
+ pub name: string,
+ pub args: Vec<fn() -> string>,
+}
+
+#[derive(Default)]
pub struct TypeCollector {
/// Code => id
/// id ordering is required to perform topo-sort on the resulting data
@@ -482,11 +488,25 @@
View,
Mutable,
}
+
+// fn fn_selector_str(input: &str) -> u32 {
+// use sha3::Digest;
+// let mut hasher = sha3::Keccak256::new();
+// hasher.update(input.as_bytes());
+// let result = hasher.finalize();
+
+// let mut selector_bytes = [0; 4];
+// selector_bytes.copy_from_slice(&result[0..4]);
+
+// u32::from_be_bytes(selector_bytes)
+// }
+
pub struct SolidityFunction<A, R> {
pub docs: &'static [&'static str],
pub selector_str: &'static str,
pub selector: u32,
pub hide: bool,
+ pub custom_signature: &'static str,
pub name: &'static str,
pub args: A,
pub result: R,
@@ -512,7 +532,7 @@
writeln!(
writer,
"\t{hide_comment}/// or in textual repr: {}",
- self.selector_str
+ self.custom_signature
)?;
write!(writer, "\t{hide_comment}function {}(", self.name)?;
self.args.solidity_name(writer, tc)?;
pallets/evm-contract-helpers/Cargo.tomldiffbeforeafterboth--- a/pallets/evm-contract-helpers/Cargo.toml
+++ b/pallets/evm-contract-helpers/Cargo.toml
@@ -27,7 +27,7 @@
evm-coder = { default-features = false, path = '../../crates/evm-coder' }
pallet-common = { default-features = false, path = '../../pallets/common' }
pallet-evm-coder-substrate = { default-features = false, path = '../../pallets/evm-coder-substrate' }
-pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' }
+pallet-evm-transaction-payment = { default-features = false, path = '../../pallets/evm-transaction-payment' }
up-data-structs = { default-features = false, path = '../../primitives/data-structs', features = [
'serde1',
] }
pallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -16,6 +16,8 @@
//! Implementation of magic contract
+extern crate alloc;
+use alloc::{format, string::ToString};
use core::marker::PhantomData;
use evm_coder::{
abi::AbiWriter, execution::Result, generate_stubgen, solidity_interface, types::*, ToLog,
pallets/fungible/src/erc.rsdiffbeforeafterboth--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -16,6 +16,8 @@
//! ERC-20 standart support implementation.
+extern crate alloc;
+use alloc::{format, string::ToString};
use core::char::{REPLACEMENT_CHARACTER, decode_utf16};
use core::convert::TryInto;
use evm_coder::{ToLog, execution::*, generate_stubgen, solidity_interface, types::*, weight};
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::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::dispatch_to_evm;34use sp_std::vec::Vec;35use pallet_common::{36 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},37 CollectionHandle, CollectionPropertyPermissions,38 eth::convert_tuple_to_cross_account,39};40use pallet_evm::{account::CrossAccountId, PrecompileHandle};41use pallet_evm_coder_substrate::call;42use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4344use crate::{45 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,46 SelfWeightOf, weights::WeightInfo, TokenProperties,47};4849/// @title A contract that allows to set and delete token properties and change token property permissions.50#[solidity_interface(name = TokenProperties)]51impl<T: Config> NonfungibleHandle<T> {52 /// @notice Set permissions for token property.53 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.54 /// @param key Property key.55 /// @param isMutable Permission to mutate property.56 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.57 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.58 fn set_token_property_permission(59 &mut self,60 caller: caller,61 key: string,62 is_mutable: bool,63 collection_admin: bool,64 token_owner: bool,65 ) -> Result<()> {66 let caller = T::CrossAccountId::from_eth(caller);67 <Pallet<T>>::set_property_permission(68 self,69 &caller,70 PropertyKeyPermission {71 key: <Vec<u8>>::from(key)72 .try_into()73 .map_err(|_| "too long key")?,74 permission: PropertyPermission {75 mutable: is_mutable,76 collection_admin,77 token_owner,78 },79 },80 )81 .map_err(dispatch_to_evm::<T>)82 }8384 /// @notice Set token property value.85 /// @dev Throws error if `msg.sender` has no permission to edit the property.86 /// @param tokenId ID of the token.87 /// @param key Property key.88 /// @param value Property value.89 fn set_property(90 &mut self,91 caller: caller,92 token_id: uint256,93 key: string,94 value: bytes,95 ) -> Result<()> {96 let caller = T::CrossAccountId::from_eth(caller);97 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;98 let key = <Vec<u8>>::from(key)99 .try_into()100 .map_err(|_| "key too long")?;101 let value = value.0.try_into().map_err(|_| "value too long")?;102103 let nesting_budget = self104 .recorder105 .weight_calls_budget(<StructureWeight<T>>::find_parent());106107 <Pallet<T>>::set_token_property(108 self,109 &caller,110 TokenId(token_id),111 Property { key, value },112 &nesting_budget,113 )114 .map_err(dispatch_to_evm::<T>)115 }116117 /// @notice Set token properties value.118 /// @dev Throws error if `msg.sender` has no permission to edit the property.119 /// @param tokenId ID of the token.120 /// @param properties settable properties121 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]122 fn set_properties(123 &mut self,124 caller: caller,125 token_id: uint256,126 properties: Vec<(string, bytes)>,127 ) -> Result<()> {128 let caller = T::CrossAccountId::from_eth(caller);129 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;130131 let nesting_budget = self132 .recorder133 .weight_calls_budget(<StructureWeight<T>>::find_parent());134135 let properties = properties136 .into_iter()137 .map(|(key, value)| {138 let key = <Vec<u8>>::from(key)139 .try_into()140 .map_err(|_| "key too large")?;141142 let value = value.0.try_into().map_err(|_| "value too large")?;143144 Ok(Property { key, value })145 })146 .collect::<Result<Vec<_>>>()?;147148 <Pallet<T>>::set_token_properties(149 self,150 &caller,151 TokenId(token_id),152 properties.into_iter(),153 <Pallet<T>>::token_exists(&self, TokenId(token_id)),154 &nesting_budget,155 )156 .map_err(dispatch_to_evm::<T>)157 }158159 /// @notice Delete token property value.160 /// @dev Throws error if `msg.sender` has no permission to edit the property.161 /// @param tokenId ID of the token.162 /// @param key Property key.163 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {164 let caller = T::CrossAccountId::from_eth(caller);165 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;166 let key = <Vec<u8>>::from(key)167 .try_into()168 .map_err(|_| "key too long")?;169170 let nesting_budget = self171 .recorder172 .weight_calls_budget(<StructureWeight<T>>::find_parent());173174 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)175 .map_err(dispatch_to_evm::<T>)176 }177178 /// @notice Get token property value.179 /// @dev Throws error if key not found180 /// @param tokenId ID of the token.181 /// @param key Property key.182 /// @return Property value bytes183 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {184 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;185 let key = <Vec<u8>>::from(key)186 .try_into()187 .map_err(|_| "key too long")?;188189 let props = <TokenProperties<T>>::get((self.id, token_id));190 let prop = props.get(&key).ok_or("key not found")?;191192 Ok(prop.to_vec().into())193 }194}195196#[derive(ToLog)]197pub enum ERC721Events {198 /// @dev This emits when ownership of any NFT changes by any mechanism.199 /// This event emits when NFTs are created (`from` == 0) and destroyed200 /// (`to` == 0). Exception: during contract creation, any number of NFTs201 /// may be created and assigned without emitting Transfer. At the time of202 /// any transfer, the approved address for that NFT (if any) is reset to none.203 Transfer {204 #[indexed]205 from: address,206 #[indexed]207 to: address,208 #[indexed]209 token_id: uint256,210 },211 /// @dev This emits when the approved address for an NFT is changed or212 /// reaffirmed. The zero address indicates there is no approved address.213 /// When a Transfer event emits, this also indicates that the approved214 /// address for that NFT (if any) is reset to none.215 Approval {216 #[indexed]217 owner: address,218 #[indexed]219 approved: address,220 #[indexed]221 token_id: uint256,222 },223 /// @dev This emits when an operator is enabled or disabled for an owner.224 /// The operator can manage all NFTs of the owner.225 #[allow(dead_code)]226 ApprovalForAll {227 #[indexed]228 owner: address,229 #[indexed]230 operator: address,231 approved: bool,232 },233}234235#[derive(ToLog)]236pub enum ERC721UniqueMintableEvents {237 #[allow(dead_code)]238 MintingFinished {},239}240241/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension242/// @dev See https://eips.ethereum.org/EIPS/eip-721243#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]244impl<T: Config> NonfungibleHandle<T>245where246 T::AccountId: From<[u8; 32]>,247{248 /// @notice A descriptive name for a collection of NFTs in this contract249 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`250 #[solidity(hide, rename_selector = "name")]251 fn name_proxy(&self) -> Result<string> {252 self.name()253 }254255 /// @notice An abbreviated name for NFTs in this contract256 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`257 #[solidity(hide, rename_selector = "symbol")]258 fn symbol_proxy(&self) -> Result<string> {259 self.symbol()260 }261262 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.263 ///264 /// @dev If the token has a `url` property and it is not empty, it is returned.265 /// 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`.266 /// If the collection property `baseURI` is empty or absent, return "" (empty string)267 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix268 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).269 ///270 /// @return token's const_metadata271 #[solidity(rename_selector = "tokenURI")]272 fn token_uri(&self, token_id: uint256) -> Result<string> {273 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;274275 match get_token_property(self, token_id_u32, &key::url()).as_deref() {276 Err(_) | Ok("") => (),277 Ok(url) => {278 return Ok(url.into());279 }280 };281282 let base_uri =283 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())284 .map(BoundedVec::into_inner)285 .map(string::from_utf8)286 .transpose()287 .map_err(|e| {288 Error::Revert(alloc::format!(289 "Can not convert value \"baseURI\" to string with error \"{}\"",290 e291 ))292 })?;293294 let base_uri = match base_uri.as_deref() {295 None | Some("") => {296 return Ok("".into());297 }298 Some(base_uri) => base_uri.into(),299 };300301 Ok(302 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {303 Err(_) | Ok("") => base_uri,304 Ok(suffix) => base_uri + suffix,305 },306 )307 }308}309310/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension311/// @dev See https://eips.ethereum.org/EIPS/eip-721312#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]313impl<T: Config> NonfungibleHandle<T> {314 /// @notice Enumerate valid NFTs315 /// @param index A counter less than `totalSupply()`316 /// @return The token identifier for the `index`th NFT,317 /// (sort order not specified)318 fn token_by_index(&self, index: uint256) -> Result<uint256> {319 Ok(index)320 }321322 /// @dev Not implemented323 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {324 // TODO: Not implemetable325 Err("not implemented".into())326 }327328 /// @notice Count NFTs tracked by this contract329 /// @return A count of valid NFTs tracked by this contract, where each one of330 /// them has an assigned and queryable owner not equal to the zero address331 fn total_supply(&self) -> Result<uint256> {332 self.consume_store_reads(1)?;333 Ok(<Pallet<T>>::total_supply(self).into())334 }335}336337/// @title ERC-721 Non-Fungible Token Standard338/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md339#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]340impl<T: Config> NonfungibleHandle<T> {341 /// @notice Count all NFTs assigned to an owner342 /// @dev NFTs assigned to the zero address are considered invalid, and this343 /// function throws for queries about the zero address.344 /// @param owner An address for whom to query the balance345 /// @return The number of NFTs owned by `owner`, possibly zero346 fn balance_of(&self, owner: address) -> Result<uint256> {347 self.consume_store_reads(1)?;348 let owner = T::CrossAccountId::from_eth(owner);349 let balance = <AccountBalance<T>>::get((self.id, owner));350 Ok(balance.into())351 }352 /// @notice Find the owner of an NFT353 /// @dev NFTs assigned to zero address are considered invalid, and queries354 /// about them do throw.355 /// @param tokenId The identifier for an NFT356 /// @return The address of the owner of the NFT357 fn owner_of(&self, token_id: uint256) -> Result<address> {358 self.consume_store_reads(1)?;359 let token: TokenId = token_id.try_into()?;360 Ok(*<TokenData<T>>::get((self.id, token))361 .ok_or("token not found")?362 .owner363 .as_eth())364 }365 /// @dev Not implemented366 #[solidity(rename_selector = "safeTransferFrom")]367 fn safe_transfer_from_with_data(368 &mut self,369 _from: address,370 _to: address,371 _token_id: uint256,372 _data: bytes,373 ) -> Result<void> {374 // TODO: Not implemetable375 Err("not implemented".into())376 }377 /// @dev Not implemented378 fn safe_transfer_from(379 &mut self,380 _from: address,381 _to: address,382 _token_id: uint256,383 ) -> Result<void> {384 // TODO: Not implemetable385 Err("not implemented".into())386 }387388 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE389 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE390 /// THEY MAY BE PERMANENTLY LOST391 /// @dev Throws unless `msg.sender` is the current owner or an authorized392 /// operator for this NFT. Throws if `from` is not the current owner. Throws393 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.394 /// @param from The current owner of the NFT395 /// @param to The new owner396 /// @param tokenId The NFT to transfer397 #[weight(<SelfWeightOf<T>>::transfer_from())]398 fn transfer_from(399 &mut self,400 caller: caller,401 from: address,402 to: address,403 token_id: uint256,404 ) -> Result<void> {405 let caller = T::CrossAccountId::from_eth(caller);406 let from = T::CrossAccountId::from_eth(from);407 let to = T::CrossAccountId::from_eth(to);408 let token = token_id.try_into()?;409 let budget = self410 .recorder411 .weight_calls_budget(<StructureWeight<T>>::find_parent());412413 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)414 .map_err(dispatch_to_evm::<T>)?;415 Ok(())416 }417418 /// @notice Set or reaffirm the approved address for an NFT419 /// @dev The zero address indicates there is no approved address.420 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized421 /// operator of the current owner.422 /// @param approved The new approved NFT controller423 /// @param tokenId The NFT to approve424 #[weight(<SelfWeightOf<T>>::approve())]425 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {426 let caller = T::CrossAccountId::from_eth(caller);427 let approved = T::CrossAccountId::from_eth(approved);428 let token = token_id.try_into()?;429430 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))431 .map_err(dispatch_to_evm::<T>)?;432 Ok(())433 }434435 /// @dev Not implemented436 fn set_approval_for_all(437 &mut self,438 _caller: caller,439 _operator: address,440 _approved: bool,441 ) -> Result<void> {442 // TODO: Not implemetable443 Err("not implemented".into())444 }445446 /// @dev Not implemented447 fn get_approved(&self, _token_id: uint256) -> Result<address> {448 // TODO: Not implemetable449 Err("not implemented".into())450 }451452 /// @dev Not implemented453 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {454 // TODO: Not implemetable455 Err("not implemented".into())456 }457}458459/// @title ERC721 Token that can be irreversibly burned (destroyed).460#[solidity_interface(name = ERC721Burnable)]461impl<T: Config> NonfungibleHandle<T> {462 /// @notice Burns a specific ERC721 token.463 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized464 /// operator of the current owner.465 /// @param tokenId The NFT to approve466 #[weight(<SelfWeightOf<T>>::burn_item())]467 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {468 let caller = T::CrossAccountId::from_eth(caller);469 let token = token_id.try_into()?;470471 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;472 Ok(())473 }474}475476/// @title ERC721 minting logic.477#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]478impl<T: Config> NonfungibleHandle<T> {479 fn minting_finished(&self) -> Result<bool> {480 Ok(false)481 }482483 /// @notice Function to mint token.484 /// @param to The new owner485 /// @return uint256 The id of the newly minted token486 #[weight(<SelfWeightOf<T>>::create_item())]487 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {488 let token_id: uint256 = <TokensMinted<T>>::get(self.id)489 .checked_add(1)490 .ok_or("item id overflow")?491 .into();492 self.mint_check_id(caller, to, token_id)?;493 Ok(token_id)494 }495496 /// @notice Function to mint token.497 /// @dev `tokenId` should be obtained with `nextTokenId` method,498 /// unlike standard, you can't specify it manually499 /// @param to The new owner500 /// @param tokenId ID of the minted NFT501 #[solidity(hide, rename_selector = "mint")]502 #[weight(<SelfWeightOf<T>>::create_item())]503 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {504 let caller = T::CrossAccountId::from_eth(caller);505 let to = T::CrossAccountId::from_eth(to);506 let token_id: u32 = token_id.try_into()?;507 let budget = self508 .recorder509 .weight_calls_budget(<StructureWeight<T>>::find_parent());510511 if <TokensMinted<T>>::get(self.id)512 .checked_add(1)513 .ok_or("item id overflow")?514 != token_id515 {516 return Err("item id should be next".into());517 }518519 <Pallet<T>>::create_item(520 self,521 &caller,522 CreateItemData::<T> {523 properties: BoundedVec::default(),524 owner: to,525 },526 &budget,527 )528 .map_err(dispatch_to_evm::<T>)?;529530 Ok(true)531 }532533 /// @notice Function to mint token with the given tokenUri.534 /// @param to The new owner535 /// @param tokenUri Token URI that would be stored in the NFT properties536 /// @return uint256 The id of the newly minted token537 #[solidity(rename_selector = "mintWithTokenURI")]538 #[weight(<SelfWeightOf<T>>::create_item())]539 fn mint_with_token_uri(540 &mut self,541 caller: caller,542 to: address,543 token_uri: string,544 ) -> Result<uint256> {545 let token_id: uint256 = <TokensMinted<T>>::get(self.id)546 .checked_add(1)547 .ok_or("item id overflow")?548 .into();549 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;550 Ok(token_id)551 }552553 /// @notice Function to mint token with the given tokenUri.554 /// @dev `tokenId` should be obtained with `nextTokenId` method,555 /// unlike standard, you can't specify it manually556 /// @param to The new owner557 /// @param tokenId ID of the minted NFT558 /// @param tokenUri Token URI that would be stored in the NFT properties559 #[solidity(hide, rename_selector = "mintWithTokenURI")]560 #[weight(<SelfWeightOf<T>>::create_item())]561 fn mint_with_token_uri_check_id(562 &mut self,563 caller: caller,564 to: address,565 token_id: uint256,566 token_uri: string,567 ) -> Result<bool> {568 let key = key::url();569 let permission = get_token_permission::<T>(self.id, &key)?;570 if !permission.collection_admin {571 return Err("Operation is not allowed".into());572 }573574 let caller = T::CrossAccountId::from_eth(caller);575 let to = T::CrossAccountId::from_eth(to);576 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;577 let budget = self578 .recorder579 .weight_calls_budget(<StructureWeight<T>>::find_parent());580581 if <TokensMinted<T>>::get(self.id)582 .checked_add(1)583 .ok_or("item id overflow")?584 != token_id585 {586 return Err("item id should be next".into());587 }588589 let mut properties = CollectionPropertiesVec::default();590 properties591 .try_push(Property {592 key,593 value: token_uri594 .into_bytes()595 .try_into()596 .map_err(|_| "token uri is too long")?,597 })598 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;599600 <Pallet<T>>::create_item(601 self,602 &caller,603 CreateItemData::<T> {604 properties,605 owner: to,606 },607 &budget,608 )609 .map_err(dispatch_to_evm::<T>)?;610 Ok(true)611 }612613 /// @dev Not implemented614 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {615 Err("not implementable".into())616 }617}618619fn get_token_property<T: Config>(620 collection: &CollectionHandle<T>,621 token_id: u32,622 key: &up_data_structs::PropertyKey,623) -> Result<string> {624 collection.consume_store_reads(1)?;625 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))626 .map_err(|_| Error::Revert("Token properties not found".into()))?;627 if let Some(property) = properties.get(key) {628 return Ok(string::from_utf8_lossy(property).into());629 }630631 Err("Property tokenURI not found".into())632}633634fn get_token_permission<T: Config>(635 collection_id: CollectionId,636 key: &PropertyKey,637) -> Result<PropertyPermission> {638 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)639 .map_err(|_| Error::Revert("No permissions for collection".into()))?;640 let a = token_property_permissions641 .get(key)642 .map(Clone::clone)643 .ok_or_else(|| {644 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();645 Error::Revert(alloc::format!("No permission for key {}", key))646 })?;647 Ok(a)648}649650/// @title Unique extensions for ERC721.651#[solidity_interface(name = ERC721UniqueExtensions)]652impl<T: Config> NonfungibleHandle<T>653where654 T::AccountId: From<[u8; 32]>,655{656 /// @notice A descriptive name for a collection of NFTs in this contract657 fn name(&self) -> Result<string> {658 Ok(decode_utf16(self.name.iter().copied())659 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))660 .collect::<string>())661 }662663 /// @notice An abbreviated name for NFTs in this contract664 fn symbol(&self) -> Result<string> {665 Ok(string::from_utf8_lossy(&self.token_prefix).into())666 }667668 /// @notice Set or reaffirm the approved address for an NFT669 /// @dev The zero address indicates there is no approved address.670 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized671 /// operator of the current owner.672 /// @param approved The new substrate address approved NFT controller673 /// @param tokenId The NFT to approve674 #[weight(<SelfWeightOf<T>>::approve())]675 fn approve_cross(676 &mut self,677 caller: caller,678 approved: (address, uint256),679 token_id: uint256,680 ) -> Result<void> {681 let caller = T::CrossAccountId::from_eth(caller);682 let approved = convert_tuple_to_cross_account::<T>(approved)?;683 let token = token_id.try_into()?;684685 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))686 .map_err(dispatch_to_evm::<T>)?;687 Ok(())688 }689690 /// @notice Transfer ownership of an NFT691 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`692 /// is the zero address. Throws if `tokenId` is not a valid NFT.693 /// @param to The new owner694 /// @param tokenId The NFT to transfer695 #[weight(<SelfWeightOf<T>>::transfer())]696 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {697 let caller = T::CrossAccountId::from_eth(caller);698 let to = T::CrossAccountId::from_eth(to);699 let token = token_id.try_into()?;700 let budget = self701 .recorder702 .weight_calls_budget(<StructureWeight<T>>::find_parent());703704 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;705 Ok(())706 }707708 /// @notice Transfer ownership of an NFT from cross account address to cross account address709 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`710 /// is the zero address. Throws if `tokenId` is not a valid NFT.711 /// @param from Cross acccount address of current owner712 /// @param to Cross acccount address of new owner713 /// @param tokenId The NFT to transfer714 #[weight(<SelfWeightOf<T>>::transfer())]715 fn transfer_from_cross(716 &mut self,717 caller: caller,718 from: EthCrossAccount,719 to: EthCrossAccount,720 token_id: uint256,721 ) -> Result<void> {722 let caller = T::CrossAccountId::from_eth(caller);723 let from = from.into_sub_cross_account::<T>()?;724 let to = to.into_sub_cross_account::<T>()?;725 let token_id = token_id.try_into()?;726 let budget = self727 .recorder728 .weight_calls_budget(<StructureWeight<T>>::find_parent());729 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)730 .map_err(dispatch_to_evm::<T>)?;731 Ok(())732 }733734 /// @notice Burns a specific ERC721 token.735 /// @dev Throws unless `msg.sender` is the current owner or an authorized736 /// operator for this NFT. Throws if `from` is not the current owner. Throws737 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.738 /// @param from The current owner of the NFT739 /// @param tokenId The NFT to transfer740 #[weight(<SelfWeightOf<T>>::burn_from())]741 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {742 let caller = T::CrossAccountId::from_eth(caller);743 let from = T::CrossAccountId::from_eth(from);744 let token = token_id.try_into()?;745 let budget = self746 .recorder747 .weight_calls_budget(<StructureWeight<T>>::find_parent());748749 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)750 .map_err(dispatch_to_evm::<T>)?;751 Ok(())752 }753754 /// @notice Burns a specific ERC721 token.755 /// @dev Throws unless `msg.sender` is the current owner or an authorized756 /// operator for this NFT. Throws if `from` is not the current owner. Throws757 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.758 /// @param from The current owner of the NFT759 /// @param tokenId The NFT to transfer760 #[weight(<SelfWeightOf<T>>::burn_from())]761 fn burn_from_cross(762 &mut self,763 caller: caller,764 from: (address, uint256),765 token_id: uint256,766 ) -> Result<void> {767 let caller = T::CrossAccountId::from_eth(caller);768 let from = convert_tuple_to_cross_account::<T>(from)?;769 let token = token_id.try_into()?;770 let budget = self771 .recorder772 .weight_calls_budget(<StructureWeight<T>>::find_parent());773774 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)775 .map_err(dispatch_to_evm::<T>)?;776 Ok(())777 }778779 /// @notice Returns next free NFT ID.780 fn next_token_id(&self) -> Result<uint256> {781 self.consume_store_reads(1)?;782 Ok(<TokensMinted<T>>::get(self.id)783 .checked_add(1)784 .ok_or("item id overflow")?785 .into())786 }787788 /// @notice Function to mint multiple tokens.789 /// @dev `tokenIds` should be an array of consecutive numbers and first number790 /// should be obtained with `nextTokenId` method791 /// @param to The new owner792 /// @param tokenIds IDs of the minted NFTs793 #[solidity(hide)]794 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]795 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {796 let caller = T::CrossAccountId::from_eth(caller);797 let to = T::CrossAccountId::from_eth(to);798 let mut expected_index = <TokensMinted<T>>::get(self.id)799 .checked_add(1)800 .ok_or("item id overflow")?;801 let budget = self802 .recorder803 .weight_calls_budget(<StructureWeight<T>>::find_parent());804805 let total_tokens = token_ids.len();806 for id in token_ids.into_iter() {807 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;808 if id != expected_index {809 return Err("item id should be next".into());810 }811 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;812 }813 let data = (0..total_tokens)814 .map(|_| CreateItemData::<T> {815 properties: BoundedVec::default(),816 owner: to.clone(),817 })818 .collect();819820 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)821 .map_err(dispatch_to_evm::<T>)?;822 Ok(true)823 }824825 /// @notice Function to mint multiple tokens with the given tokenUris.826 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive827 /// numbers and first number should be obtained with `nextTokenId` method828 /// @param to The new owner829 /// @param tokens array of pairs of token ID and token URI for minted tokens830 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]831 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]832 fn mint_bulk_with_token_uri(833 &mut self,834 caller: caller,835 to: address,836 tokens: Vec<(uint256, string)>,837 ) -> Result<bool> {838 let key = key::url();839 let caller = T::CrossAccountId::from_eth(caller);840 let to = T::CrossAccountId::from_eth(to);841 let mut expected_index = <TokensMinted<T>>::get(self.id)842 .checked_add(1)843 .ok_or("item id overflow")?;844 let budget = self845 .recorder846 .weight_calls_budget(<StructureWeight<T>>::find_parent());847848 let mut data = Vec::with_capacity(tokens.len());849 for (id, token_uri) in tokens {850 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;851 if id != expected_index {852 return Err("item id should be next".into());853 }854 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;855856 let mut properties = CollectionPropertiesVec::default();857 properties858 .try_push(Property {859 key: key.clone(),860 value: token_uri861 .into_bytes()862 .try_into()863 .map_err(|_| "token uri is too long")?,864 })865 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;866867 data.push(CreateItemData::<T> {868 properties,869 owner: to.clone(),870 });871 }872873 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)874 .map_err(dispatch_to_evm::<T>)?;875 Ok(true)876 }877}878879#[solidity_interface(880 name = UniqueNFT,881 is(882 ERC721,883 ERC721Enumerable,884 ERC721UniqueExtensions,885 ERC721UniqueMintable,886 ERC721Burnable,887 ERC721Metadata(if(this.flags.erc721metadata)),888 Collection(via(common_mut returns CollectionHandle<T>)),889 TokenProperties,890 )891)]892impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}893894// Not a tests, but code generators895generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);896generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);897898impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>899where900 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,901{902 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");903904 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {905 call::<T, UniqueNFTCall<T>, _, _>(handle, self)906 }907}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 alloc::{format, string::ToString};24use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};29use frame_support::BoundedVec;30use up_data_structs::{31 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,32 CollectionPropertiesVec,33};34use pallet_evm_coder_substrate::dispatch_to_evm;35use sp_std::vec::Vec;36use pallet_common::{37 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},38 CollectionHandle, CollectionPropertyPermissions,39 eth::convert_tuple_to_cross_account,40};41use pallet_evm::{account::CrossAccountId, PrecompileHandle};42use pallet_evm_coder_substrate::call;43use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};4445use crate::{46 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,47 SelfWeightOf, weights::WeightInfo, TokenProperties,48};4950/// @title A contract that allows to set and delete token properties and change token property permissions.51#[solidity_interface(name = TokenProperties)]52impl<T: Config> NonfungibleHandle<T> {53 /// @notice Set permissions for token property.54 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.55 /// @param key Property key.56 /// @param isMutable Permission to mutate property.57 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.58 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.59 fn set_token_property_permission(60 &mut self,61 caller: caller,62 key: string,63 is_mutable: bool,64 collection_admin: bool,65 token_owner: bool,66 ) -> Result<()> {67 let caller = T::CrossAccountId::from_eth(caller);68 <Pallet<T>>::set_property_permission(69 self,70 &caller,71 PropertyKeyPermission {72 key: <Vec<u8>>::from(key)73 .try_into()74 .map_err(|_| "too long key")?,75 permission: PropertyPermission {76 mutable: is_mutable,77 collection_admin,78 token_owner,79 },80 },81 )82 .map_err(dispatch_to_evm::<T>)83 }8485 /// @notice Set token property value.86 /// @dev Throws error if `msg.sender` has no permission to edit the property.87 /// @param tokenId ID of the token.88 /// @param key Property key.89 /// @param value Property value.90 fn set_property(91 &mut self,92 caller: caller,93 token_id: uint256,94 key: string,95 value: bytes,96 ) -> Result<()> {97 let caller = T::CrossAccountId::from_eth(caller);98 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;99 let key = <Vec<u8>>::from(key)100 .try_into()101 .map_err(|_| "key too long")?;102 let value = value.0.try_into().map_err(|_| "value too long")?;103104 let nesting_budget = self105 .recorder106 .weight_calls_budget(<StructureWeight<T>>::find_parent());107108 <Pallet<T>>::set_token_property(109 self,110 &caller,111 TokenId(token_id),112 Property { key, value },113 &nesting_budget,114 )115 .map_err(dispatch_to_evm::<T>)116 }117118 /// @notice Set token properties value.119 /// @dev Throws error if `msg.sender` has no permission to edit the property.120 /// @param tokenId ID of the token.121 /// @param properties settable properties122 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]123 fn set_properties(124 &mut self,125 caller: caller,126 token_id: uint256,127 properties: Vec<(string, bytes)>,128 ) -> Result<()> {129 let caller = T::CrossAccountId::from_eth(caller);130 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;131132 let nesting_budget = self133 .recorder134 .weight_calls_budget(<StructureWeight<T>>::find_parent());135136 let properties = properties137 .into_iter()138 .map(|(key, value)| {139 let key = <Vec<u8>>::from(key)140 .try_into()141 .map_err(|_| "key too large")?;142143 let value = value.0.try_into().map_err(|_| "value too large")?;144145 Ok(Property { key, value })146 })147 .collect::<Result<Vec<_>>>()?;148149 <Pallet<T>>::set_token_properties(150 self,151 &caller,152 TokenId(token_id),153 properties.into_iter(),154 <Pallet<T>>::token_exists(&self, TokenId(token_id)),155 &nesting_budget,156 )157 .map_err(dispatch_to_evm::<T>)158 }159160 /// @notice Delete token property value.161 /// @dev Throws error if `msg.sender` has no permission to edit the property.162 /// @param tokenId ID of the token.163 /// @param key Property key.164 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {165 let caller = T::CrossAccountId::from_eth(caller);166 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;167 let key = <Vec<u8>>::from(key)168 .try_into()169 .map_err(|_| "key too long")?;170171 let nesting_budget = self172 .recorder173 .weight_calls_budget(<StructureWeight<T>>::find_parent());174175 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)176 .map_err(dispatch_to_evm::<T>)177 }178179 /// @notice Get token property value.180 /// @dev Throws error if key not found181 /// @param tokenId ID of the token.182 /// @param key Property key.183 /// @return Property value bytes184 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {185 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;186 let key = <Vec<u8>>::from(key)187 .try_into()188 .map_err(|_| "key too long")?;189190 let props = <TokenProperties<T>>::get((self.id, token_id));191 let prop = props.get(&key).ok_or("key not found")?;192193 Ok(prop.to_vec().into())194 }195}196197#[derive(ToLog)]198pub enum ERC721Events {199 /// @dev This emits when ownership of any NFT changes by any mechanism.200 /// This event emits when NFTs are created (`from` == 0) and destroyed201 /// (`to` == 0). Exception: during contract creation, any number of NFTs202 /// may be created and assigned without emitting Transfer. At the time of203 /// any transfer, the approved address for that NFT (if any) is reset to none.204 Transfer {205 #[indexed]206 from: address,207 #[indexed]208 to: address,209 #[indexed]210 token_id: uint256,211 },212 /// @dev This emits when the approved address for an NFT is changed or213 /// reaffirmed. The zero address indicates there is no approved address.214 /// When a Transfer event emits, this also indicates that the approved215 /// address for that NFT (if any) is reset to none.216 Approval {217 #[indexed]218 owner: address,219 #[indexed]220 approved: address,221 #[indexed]222 token_id: uint256,223 },224 /// @dev This emits when an operator is enabled or disabled for an owner.225 /// The operator can manage all NFTs of the owner.226 #[allow(dead_code)]227 ApprovalForAll {228 #[indexed]229 owner: address,230 #[indexed]231 operator: address,232 approved: bool,233 },234}235236#[derive(ToLog)]237pub enum ERC721UniqueMintableEvents {238 #[allow(dead_code)]239 MintingFinished {},240}241242/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension243/// @dev See https://eips.ethereum.org/EIPS/eip-721244#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]245impl<T: Config> NonfungibleHandle<T>246where247 T::AccountId: From<[u8; 32]>,248{249 /// @notice A descriptive name for a collection of NFTs in this contract250 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`251 #[solidity(hide, rename_selector = "name")]252 fn name_proxy(&self) -> Result<string> {253 self.name()254 }255256 /// @notice An abbreviated name for NFTs in this contract257 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`258 #[solidity(hide, rename_selector = "symbol")]259 fn symbol_proxy(&self) -> Result<string> {260 self.symbol()261 }262263 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.264 ///265 /// @dev If the token has a `url` property and it is not empty, it is returned.266 /// 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`.267 /// If the collection property `baseURI` is empty or absent, return "" (empty string)268 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix269 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).270 ///271 /// @return token's const_metadata272 #[solidity(rename_selector = "tokenURI")]273 fn token_uri(&self, token_id: uint256) -> Result<string> {274 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;275276 match get_token_property(self, token_id_u32, &key::url()).as_deref() {277 Err(_) | Ok("") => (),278 Ok(url) => {279 return Ok(url.into());280 }281 };282283 let base_uri =284 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())285 .map(BoundedVec::into_inner)286 .map(string::from_utf8)287 .transpose()288 .map_err(|e| {289 Error::Revert(alloc::format!(290 "Can not convert value \"baseURI\" to string with error \"{}\"",291 e292 ))293 })?;294295 let base_uri = match base_uri.as_deref() {296 None | Some("") => {297 return Ok("".into());298 }299 Some(base_uri) => base_uri.into(),300 };301302 Ok(303 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {304 Err(_) | Ok("") => base_uri,305 Ok(suffix) => base_uri + suffix,306 },307 )308 }309}310311/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension312/// @dev See https://eips.ethereum.org/EIPS/eip-721313#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]314impl<T: Config> NonfungibleHandle<T> {315 /// @notice Enumerate valid NFTs316 /// @param index A counter less than `totalSupply()`317 /// @return The token identifier for the `index`th NFT,318 /// (sort order not specified)319 fn token_by_index(&self, index: uint256) -> Result<uint256> {320 Ok(index)321 }322323 /// @dev Not implemented324 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {325 // TODO: Not implemetable326 Err("not implemented".into())327 }328329 /// @notice Count NFTs tracked by this contract330 /// @return A count of valid NFTs tracked by this contract, where each one of331 /// them has an assigned and queryable owner not equal to the zero address332 fn total_supply(&self) -> Result<uint256> {333 self.consume_store_reads(1)?;334 Ok(<Pallet<T>>::total_supply(self).into())335 }336}337338/// @title ERC-721 Non-Fungible Token Standard339/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md340#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]341impl<T: Config> NonfungibleHandle<T> {342 /// @notice Count all NFTs assigned to an owner343 /// @dev NFTs assigned to the zero address are considered invalid, and this344 /// function throws for queries about the zero address.345 /// @param owner An address for whom to query the balance346 /// @return The number of NFTs owned by `owner`, possibly zero347 fn balance_of(&self, owner: address) -> Result<uint256> {348 self.consume_store_reads(1)?;349 let owner = T::CrossAccountId::from_eth(owner);350 let balance = <AccountBalance<T>>::get((self.id, owner));351 Ok(balance.into())352 }353 /// @notice Find the owner of an NFT354 /// @dev NFTs assigned to zero address are considered invalid, and queries355 /// about them do throw.356 /// @param tokenId The identifier for an NFT357 /// @return The address of the owner of the NFT358 fn owner_of(&self, token_id: uint256) -> Result<address> {359 self.consume_store_reads(1)?;360 let token: TokenId = token_id.try_into()?;361 Ok(*<TokenData<T>>::get((self.id, token))362 .ok_or("token not found")?363 .owner364 .as_eth())365 }366 /// @dev Not implemented367 #[solidity(rename_selector = "safeTransferFrom")]368 fn safe_transfer_from_with_data(369 &mut self,370 _from: address,371 _to: address,372 _token_id: uint256,373 _data: bytes,374 ) -> Result<void> {375 // TODO: Not implemetable376 Err("not implemented".into())377 }378 /// @dev Not implemented379 fn safe_transfer_from(380 &mut self,381 _from: address,382 _to: address,383 _token_id: uint256,384 ) -> Result<void> {385 // TODO: Not implemetable386 Err("not implemented".into())387 }388389 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE390 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE391 /// THEY MAY BE PERMANENTLY LOST392 /// @dev Throws unless `msg.sender` is the current owner or an authorized393 /// operator for this NFT. Throws if `from` is not the current owner. Throws394 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.395 /// @param from The current owner of the NFT396 /// @param to The new owner397 /// @param tokenId The NFT to transfer398 #[weight(<SelfWeightOf<T>>::transfer_from())]399 fn transfer_from(400 &mut self,401 caller: caller,402 from: address,403 to: address,404 token_id: uint256,405 ) -> Result<void> {406 let caller = T::CrossAccountId::from_eth(caller);407 let from = T::CrossAccountId::from_eth(from);408 let to = T::CrossAccountId::from_eth(to);409 let token = token_id.try_into()?;410 let budget = self411 .recorder412 .weight_calls_budget(<StructureWeight<T>>::find_parent());413414 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)415 .map_err(dispatch_to_evm::<T>)?;416 Ok(())417 }418419 /// @notice Set or reaffirm the approved address for an NFT420 /// @dev The zero address indicates there is no approved address.421 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized422 /// operator of the current owner.423 /// @param approved The new approved NFT controller424 /// @param tokenId The NFT to approve425 #[weight(<SelfWeightOf<T>>::approve())]426 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {427 let caller = T::CrossAccountId::from_eth(caller);428 let approved = T::CrossAccountId::from_eth(approved);429 let token = token_id.try_into()?;430431 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))432 .map_err(dispatch_to_evm::<T>)?;433 Ok(())434 }435436 /// @dev Not implemented437 fn set_approval_for_all(438 &mut self,439 _caller: caller,440 _operator: address,441 _approved: bool,442 ) -> Result<void> {443 // TODO: Not implemetable444 Err("not implemented".into())445 }446447 /// @dev Not implemented448 fn get_approved(&self, _token_id: uint256) -> Result<address> {449 // TODO: Not implemetable450 Err("not implemented".into())451 }452453 /// @dev Not implemented454 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {455 // TODO: Not implemetable456 Err("not implemented".into())457 }458}459460/// @title ERC721 Token that can be irreversibly burned (destroyed).461#[solidity_interface(name = ERC721Burnable)]462impl<T: Config> NonfungibleHandle<T> {463 /// @notice Burns a specific ERC721 token.464 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized465 /// operator of the current owner.466 /// @param tokenId The NFT to approve467 #[weight(<SelfWeightOf<T>>::burn_item())]468 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {469 let caller = T::CrossAccountId::from_eth(caller);470 let token = token_id.try_into()?;471472 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;473 Ok(())474 }475}476477/// @title ERC721 minting logic.478#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]479impl<T: Config> NonfungibleHandle<T> {480 fn minting_finished(&self) -> Result<bool> {481 Ok(false)482 }483484 /// @notice Function to mint token.485 /// @param to The new owner486 /// @return uint256 The id of the newly minted token487 #[weight(<SelfWeightOf<T>>::create_item())]488 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {489 let token_id: uint256 = <TokensMinted<T>>::get(self.id)490 .checked_add(1)491 .ok_or("item id overflow")?492 .into();493 self.mint_check_id(caller, to, token_id)?;494 Ok(token_id)495 }496497 /// @notice Function to mint token.498 /// @dev `tokenId` should be obtained with `nextTokenId` method,499 /// unlike standard, you can't specify it manually500 /// @param to The new owner501 /// @param tokenId ID of the minted NFT502 #[solidity(hide, rename_selector = "mint")]503 #[weight(<SelfWeightOf<T>>::create_item())]504 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {505 let caller = T::CrossAccountId::from_eth(caller);506 let to = T::CrossAccountId::from_eth(to);507 let token_id: u32 = token_id.try_into()?;508 let budget = self509 .recorder510 .weight_calls_budget(<StructureWeight<T>>::find_parent());511512 if <TokensMinted<T>>::get(self.id)513 .checked_add(1)514 .ok_or("item id overflow")?515 != token_id516 {517 return Err("item id should be next".into());518 }519520 <Pallet<T>>::create_item(521 self,522 &caller,523 CreateItemData::<T> {524 properties: BoundedVec::default(),525 owner: to,526 },527 &budget,528 )529 .map_err(dispatch_to_evm::<T>)?;530531 Ok(true)532 }533534 /// @notice Function to mint token with the given tokenUri.535 /// @param to The new owner536 /// @param tokenUri Token URI that would be stored in the NFT properties537 /// @return uint256 The id of the newly minted token538 #[solidity(rename_selector = "mintWithTokenURI")]539 #[weight(<SelfWeightOf<T>>::create_item())]540 fn mint_with_token_uri(541 &mut self,542 caller: caller,543 to: address,544 token_uri: string,545 ) -> Result<uint256> {546 let token_id: uint256 = <TokensMinted<T>>::get(self.id)547 .checked_add(1)548 .ok_or("item id overflow")?549 .into();550 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;551 Ok(token_id)552 }553554 /// @notice Function to mint token with the given tokenUri.555 /// @dev `tokenId` should be obtained with `nextTokenId` method,556 /// unlike standard, you can't specify it manually557 /// @param to The new owner558 /// @param tokenId ID of the minted NFT559 /// @param tokenUri Token URI that would be stored in the NFT properties560 #[solidity(hide, rename_selector = "mintWithTokenURI")]561 #[weight(<SelfWeightOf<T>>::create_item())]562 fn mint_with_token_uri_check_id(563 &mut self,564 caller: caller,565 to: address,566 token_id: uint256,567 token_uri: string,568 ) -> Result<bool> {569 let key = key::url();570 let permission = get_token_permission::<T>(self.id, &key)?;571 if !permission.collection_admin {572 return Err("Operation is not allowed".into());573 }574575 let caller = T::CrossAccountId::from_eth(caller);576 let to = T::CrossAccountId::from_eth(to);577 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;578 let budget = self579 .recorder580 .weight_calls_budget(<StructureWeight<T>>::find_parent());581582 if <TokensMinted<T>>::get(self.id)583 .checked_add(1)584 .ok_or("item id overflow")?585 != token_id586 {587 return Err("item id should be next".into());588 }589590 let mut properties = CollectionPropertiesVec::default();591 properties592 .try_push(Property {593 key,594 value: token_uri595 .into_bytes()596 .try_into()597 .map_err(|_| "token uri is too long")?,598 })599 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;600601 <Pallet<T>>::create_item(602 self,603 &caller,604 CreateItemData::<T> {605 properties,606 owner: to,607 },608 &budget,609 )610 .map_err(dispatch_to_evm::<T>)?;611 Ok(true)612 }613614 /// @dev Not implemented615 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {616 Err("not implementable".into())617 }618}619620fn get_token_property<T: Config>(621 collection: &CollectionHandle<T>,622 token_id: u32,623 key: &up_data_structs::PropertyKey,624) -> Result<string> {625 collection.consume_store_reads(1)?;626 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))627 .map_err(|_| Error::Revert("Token properties not found".into()))?;628 if let Some(property) = properties.get(key) {629 return Ok(string::from_utf8_lossy(property).into());630 }631632 Err("Property tokenURI not found".into())633}634635fn get_token_permission<T: Config>(636 collection_id: CollectionId,637 key: &PropertyKey,638) -> Result<PropertyPermission> {639 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)640 .map_err(|_| Error::Revert("No permissions for collection".into()))?;641 let a = token_property_permissions642 .get(key)643 .map(Clone::clone)644 .ok_or_else(|| {645 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();646 Error::Revert(alloc::format!("No permission for key {}", key))647 })?;648 Ok(a)649}650651/// @title Unique extensions for ERC721.652#[solidity_interface(name = ERC721UniqueExtensions)]653impl<T: Config> NonfungibleHandle<T>654where655 T::AccountId: From<[u8; 32]>,656{657 /// @notice A descriptive name for a collection of NFTs in this contract658 fn name(&self) -> Result<string> {659 Ok(decode_utf16(self.name.iter().copied())660 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))661 .collect::<string>())662 }663664 /// @notice An abbreviated name for NFTs in this contract665 fn symbol(&self) -> Result<string> {666 Ok(string::from_utf8_lossy(&self.token_prefix).into())667 }668669 /// @notice Set or reaffirm the approved address for an NFT670 /// @dev The zero address indicates there is no approved address.671 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized672 /// operator of the current owner.673 /// @param approved The new substrate address approved NFT controller674 /// @param tokenId The NFT to approve675 #[weight(<SelfWeightOf<T>>::approve())]676 fn approve_cross(677 &mut self,678 caller: caller,679 approved: (address, uint256),680 token_id: uint256,681 ) -> Result<void> {682 let caller = T::CrossAccountId::from_eth(caller);683 let approved = convert_tuple_to_cross_account::<T>(approved)?;684 let token = token_id.try_into()?;685686 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))687 .map_err(dispatch_to_evm::<T>)?;688 Ok(())689 }690691 /// @notice Transfer ownership of an NFT692 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`693 /// is the zero address. Throws if `tokenId` is not a valid NFT.694 /// @param to The new owner695 /// @param tokenId The NFT to transfer696 #[weight(<SelfWeightOf<T>>::transfer())]697 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {698 let caller = T::CrossAccountId::from_eth(caller);699 let to = T::CrossAccountId::from_eth(to);700 let token = token_id.try_into()?;701 let budget = self702 .recorder703 .weight_calls_budget(<StructureWeight<T>>::find_parent());704705 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;706 Ok(())707 }708709 /// @notice Transfer ownership of an NFT from cross account address to cross account address710 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`711 /// is the zero address. Throws if `tokenId` is not a valid NFT.712 /// @param from Cross acccount address of current owner713 /// @param to Cross acccount address of new owner714 /// @param tokenId The NFT to transfer715 #[weight(<SelfWeightOf<T>>::transfer())]716 fn transfer_from_cross(717 &mut self,718 caller: caller,719 from: EthCrossAccount,720 to: EthCrossAccount,721 token_id: uint256,722 ) -> Result<void> {723 let caller = T::CrossAccountId::from_eth(caller);724 let from = from.into_sub_cross_account::<T>()?;725 let to = to.into_sub_cross_account::<T>()?;726 let token_id = token_id.try_into()?;727 let budget = self728 .recorder729 .weight_calls_budget(<StructureWeight<T>>::find_parent());730 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)731 .map_err(dispatch_to_evm::<T>)?;732 Ok(())733 }734735 /// @notice Burns a specific ERC721 token.736 /// @dev Throws unless `msg.sender` is the current owner or an authorized737 /// operator for this NFT. Throws if `from` is not the current owner. Throws738 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.739 /// @param from The current owner of the NFT740 /// @param tokenId The NFT to transfer741 #[weight(<SelfWeightOf<T>>::burn_from())]742 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {743 let caller = T::CrossAccountId::from_eth(caller);744 let from = T::CrossAccountId::from_eth(from);745 let token = token_id.try_into()?;746 let budget = self747 .recorder748 .weight_calls_budget(<StructureWeight<T>>::find_parent());749750 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)751 .map_err(dispatch_to_evm::<T>)?;752 Ok(())753 }754755 /// @notice Burns a specific ERC721 token.756 /// @dev Throws unless `msg.sender` is the current owner or an authorized757 /// operator for this NFT. Throws if `from` is not the current owner. Throws758 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.759 /// @param from The current owner of the NFT760 /// @param tokenId The NFT to transfer761 #[weight(<SelfWeightOf<T>>::burn_from())]762 fn burn_from_cross(763 &mut self,764 caller: caller,765 from: (address, uint256),766 token_id: uint256,767 ) -> Result<void> {768 let caller = T::CrossAccountId::from_eth(caller);769 let from = convert_tuple_to_cross_account::<T>(from)?;770 let token = token_id.try_into()?;771 let budget = self772 .recorder773 .weight_calls_budget(<StructureWeight<T>>::find_parent());774775 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)776 .map_err(dispatch_to_evm::<T>)?;777 Ok(())778 }779780 /// @notice Returns next free NFT ID.781 fn next_token_id(&self) -> Result<uint256> {782 self.consume_store_reads(1)?;783 Ok(<TokensMinted<T>>::get(self.id)784 .checked_add(1)785 .ok_or("item id overflow")?786 .into())787 }788789 /// @notice Function to mint multiple tokens.790 /// @dev `tokenIds` should be an array of consecutive numbers and first number791 /// should be obtained with `nextTokenId` method792 /// @param to The new owner793 /// @param tokenIds IDs of the minted NFTs794 #[solidity(hide)]795 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]796 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {797 let caller = T::CrossAccountId::from_eth(caller);798 let to = T::CrossAccountId::from_eth(to);799 let mut expected_index = <TokensMinted<T>>::get(self.id)800 .checked_add(1)801 .ok_or("item id overflow")?;802 let budget = self803 .recorder804 .weight_calls_budget(<StructureWeight<T>>::find_parent());805806 let total_tokens = token_ids.len();807 for id in token_ids.into_iter() {808 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;809 if id != expected_index {810 return Err("item id should be next".into());811 }812 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;813 }814 let data = (0..total_tokens)815 .map(|_| CreateItemData::<T> {816 properties: BoundedVec::default(),817 owner: to.clone(),818 })819 .collect();820821 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)822 .map_err(dispatch_to_evm::<T>)?;823 Ok(true)824 }825826 /// @notice Function to mint multiple tokens with the given tokenUris.827 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive828 /// numbers and first number should be obtained with `nextTokenId` method829 /// @param to The new owner830 /// @param tokens array of pairs of token ID and token URI for minted tokens831 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]832 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]833 fn mint_bulk_with_token_uri(834 &mut self,835 caller: caller,836 to: address,837 tokens: Vec<(uint256, string)>,838 ) -> Result<bool> {839 let key = key::url();840 let caller = T::CrossAccountId::from_eth(caller);841 let to = T::CrossAccountId::from_eth(to);842 let mut expected_index = <TokensMinted<T>>::get(self.id)843 .checked_add(1)844 .ok_or("item id overflow")?;845 let budget = self846 .recorder847 .weight_calls_budget(<StructureWeight<T>>::find_parent());848849 let mut data = Vec::with_capacity(tokens.len());850 for (id, token_uri) in tokens {851 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;852 if id != expected_index {853 return Err("item id should be next".into());854 }855 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;856857 let mut properties = CollectionPropertiesVec::default();858 properties859 .try_push(Property {860 key: key.clone(),861 value: token_uri862 .into_bytes()863 .try_into()864 .map_err(|_| "token uri is too long")?,865 })866 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;867868 data.push(CreateItemData::<T> {869 properties,870 owner: to.clone(),871 });872 }873874 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)875 .map_err(dispatch_to_evm::<T>)?;876 Ok(true)877 }878}879880#[solidity_interface(881 name = UniqueNFT,882 is(883 ERC721,884 ERC721Enumerable,885 ERC721UniqueExtensions,886 ERC721UniqueMintable,887 ERC721Burnable,888 ERC721Metadata(if(this.flags.erc721metadata)),889 Collection(via(common_mut returns CollectionHandle<T>)),890 TokenProperties,891 )892)]893impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}894895// Not a tests, but code generators896generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);897generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);898899impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>900where901 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,902{903 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");904905 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {906 call::<T, UniqueNFTCall<T>, _, _>(handle, self)907 }908}pallets/refungible/Cargo.tomldiffbeforeafterboth--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -26,7 +26,9 @@
struct-versioning = { path = "../../crates/struct-versioning" }
up-data-structs = { default-features = false, path = '../../primitives/data-structs' }
ethereum = { version = "0.12.0", default-features = false }
-scale-info = { version = "2.0.1", default-features = false, features = ["derive",] }
+scale-info = { version = "2.0.1", default-features = false, features = [
+ "derive",
+] }
derivative = { version = "2.2.0", features = ["use_core"] }
[features]
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -21,6 +21,7 @@
extern crate alloc;
+use alloc::{format, string::ToString};
use core::{
char::{REPLACEMENT_CHARACTER, decode_utf16},
convert::TryInto,