difftreelog
fix PR
in: master
9 files changed
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::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::{34 dispatch_to_evm, frontier_contract,35 execution::{Result, PreDispatch, Error},36};37use sp_std::{vec::Vec, vec};38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,40 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},41 eth::{self, TokenUri},42};43use pallet_evm::{account::CrossAccountId, PrecompileHandle};44use pallet_evm_coder_substrate::call;45use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};46use sp_core::{U256, Get};4748use crate::{49 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,50 TokenProperties, SelfWeightOf, weights::WeightInfo,51};5253/// Nft events.54#[derive(ToLog)]55pub enum ERC721TokenEvent {56 /// The token has been changed.57 TokenChanged {58 /// Collection ID.59 #[indexed]60 collection_id: Address,61 /// Token ID.62 token_id: U256,63 },64}6566frontier_contract! {67 macro_rules! NonfungibleHandle_result {...}68 impl<T: Config> Contract for NonfungibleHandle<T> {...}69}7071/// @title A contract that allows to set and delete token properties and change token property permissions.72#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]73impl<T: Config> NonfungibleHandle<T> {74 /// @notice Set permissions for token property.75 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.76 /// @param key Property key.77 /// @param isMutable Permission to mutate property.78 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.79 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.80 #[solidity(hide)]81 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]82 fn set_token_property_permission(83 &mut self,84 caller: Caller,85 key: String,86 is_mutable: bool,87 collection_admin: bool,88 token_owner: bool,89 ) -> Result<()> {90 let caller = T::CrossAccountId::from_eth(caller);91 <Pallet<T>>::set_token_property_permissions(92 self,93 &caller,94 vec![PropertyKeyPermission {95 key: <Vec<u8>>::from(key)96 .try_into()97 .map_err(|_| "too long key")?,98 permission: PropertyPermission {99 mutable: is_mutable,100 collection_admin,101 token_owner,102 },103 }],104 )105 .map_err(dispatch_to_evm::<T>)106 }107108 /// @notice Set permissions for token property.109 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.110 /// @param permissions Permissions for keys.111 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]112 fn set_token_property_permissions(113 &mut self,114 caller: Caller,115 permissions: Vec<eth::TokenPropertyPermission>,116 ) -> Result<()> {117 let caller = T::CrossAccountId::from_eth(caller);118 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;119120 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)121 .map_err(dispatch_to_evm::<T>)122 }123124 /// @notice Get permissions for token properties.125 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {126 let perms = <Pallet<T>>::token_property_permission(self.id);127 Ok(perms128 .into_iter()129 .map(eth::TokenPropertyPermission::from)130 .collect())131 }132133 /// @notice Set token property value.134 /// @dev Throws error if `msg.sender` has no permission to edit the property.135 /// @param tokenId ID of the token.136 /// @param key Property key.137 /// @param value Property value.138 #[solidity(hide)]139 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]140 fn set_property(141 &mut self,142 caller: Caller,143 token_id: U256,144 key: String,145 value: Bytes,146 ) -> Result<()> {147 let caller = T::CrossAccountId::from_eth(caller);148 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;149 let key = <Vec<u8>>::from(key)150 .try_into()151 .map_err(|_| "key too long")?;152 let value = value.0.try_into().map_err(|_| "value too long")?;153154 let nesting_budget = self155 .recorder156 .weight_calls_budget(<StructureWeight<T>>::find_parent());157158 <Pallet<T>>::set_token_property(159 self,160 &caller,161 TokenId(token_id),162 Property { key, value },163 &nesting_budget,164 )165 .map_err(dispatch_to_evm::<T>)166 }167168 /// @notice Set token properties value.169 /// @dev Throws error if `msg.sender` has no permission to edit the property.170 /// @param tokenId ID of the token.171 /// @param properties settable properties172 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]173 fn set_properties(174 &mut self,175 caller: Caller,176 token_id: U256,177 properties: Vec<eth::Property>,178 ) -> Result<()> {179 let caller = T::CrossAccountId::from_eth(caller);180 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;181182 let nesting_budget = self183 .recorder184 .weight_calls_budget(<StructureWeight<T>>::find_parent());185186 let properties = properties187 .into_iter()188 .map(eth::Property::try_into)189 .collect::<Result<Vec<_>>>()?;190191 <Pallet<T>>::set_token_properties(192 self,193 &caller,194 TokenId(token_id),195 properties.into_iter(),196 false,197 &nesting_budget,198 )199 .map_err(dispatch_to_evm::<T>)200 }201202 /// @notice Delete token property value.203 /// @dev Throws error if `msg.sender` has no permission to edit the property.204 /// @param tokenId ID of the token.205 /// @param key Property key.206 #[solidity(hide)]207 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]208 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {209 let caller = T::CrossAccountId::from_eth(caller);210 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;211 let key = <Vec<u8>>::from(key)212 .try_into()213 .map_err(|_| "key too long")?;214215 let nesting_budget = self216 .recorder217 .weight_calls_budget(<StructureWeight<T>>::find_parent());218219 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)220 .map_err(dispatch_to_evm::<T>)221 }222223 /// @notice Delete token properties value.224 /// @dev Throws error if `msg.sender` has no permission to edit the property.225 /// @param tokenId ID of the token.226 /// @param keys Properties key.227 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]228 fn delete_properties(229 &mut self,230 token_id: U256,231 caller: Caller,232 keys: Vec<String>,233 ) -> Result<()> {234 let caller = T::CrossAccountId::from_eth(caller);235 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;236 let keys = keys237 .into_iter()238 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))239 .collect::<Result<Vec<_>>>()?;240241 let nesting_budget = self242 .recorder243 .weight_calls_budget(<StructureWeight<T>>::find_parent());244245 <Pallet<T>>::delete_token_properties(246 self,247 &caller,248 TokenId(token_id),249 keys.into_iter(),250 &nesting_budget,251 )252 .map_err(dispatch_to_evm::<T>)253 }254255 /// @notice Get token property value.256 /// @dev Throws error if key not found257 /// @param tokenId ID of the token.258 /// @param key Property key.259 /// @return Property value bytes260 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {261 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;262 let key = <Vec<u8>>::from(key)263 .try_into()264 .map_err(|_| "key too long")?;265266 let props = <TokenProperties<T>>::get((self.id, token_id));267 let prop = props.get(&key).ok_or("key not found")?;268269 Ok(prop.to_vec().into())270 }271}272273#[derive(ToLog)]274pub enum ERC721Events {275 /// @dev This emits when ownership of any NFT changes by any mechanism.276 /// This event emits when NFTs are created (`from` == 0) and destroyed277 /// (`to` == 0). Exception: during contract creation, any number of NFTs278 /// may be created and assigned without emitting Transfer. At the time of279 /// any transfer, the approved address for that NFT (if any) is reset to none.280 Transfer {281 #[indexed]282 from: Address,283 #[indexed]284 to: Address,285 #[indexed]286 token_id: U256,287 },288 /// @dev This emits when the approved address for an NFT is changed or289 /// reaffirmed. The zero address indicates there is no approved address.290 /// When a Transfer event emits, this also indicates that the approved291 /// address for that NFT (if any) is reset to none.292 Approval {293 #[indexed]294 owner: Address,295 #[indexed]296 approved: Address,297 #[indexed]298 token_id: U256,299 },300 /// @dev This emits when an operator is enabled or disabled for an owner.301 /// The operator can manage all NFTs of the owner.302 #[allow(dead_code)]303 ApprovalForAll {304 #[indexed]305 owner: Address,306 #[indexed]307 operator: Address,308 approved: bool,309 },310}311312/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension313/// @dev See https://eips.ethereum.org/EIPS/eip-721314#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]315impl<T: Config> NonfungibleHandle<T>316where317 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,318{319 /// @notice A descriptive name for a collection of NFTs in this contract320 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`321 #[solidity(hide, rename_selector = "name")]322 fn name_proxy(&self) -> String {323 self.name()324 }325326 /// @notice An abbreviated name for NFTs in this contract327 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`328 #[solidity(hide, rename_selector = "symbol")]329 fn symbol_proxy(&self) -> String {330 self.symbol()331 }332333 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.334 ///335 /// @dev If the token has a `url` property and it is not empty, it is returned.336 /// 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`.337 /// If the collection property `baseURI` is empty or absent, return "" (empty string)338 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix339 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).340 ///341 /// @return token's const_metadata342 #[solidity(rename_selector = "tokenURI")]343 fn token_uri(&self, token_id: U256) -> Result<String> {344 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;345346 match get_token_property(self, token_id_u32, &key::url()).as_deref() {347 Err(_) | Ok("") => (),348 Ok(url) => {349 return Ok(url.into());350 }351 };352353 let base_uri =354 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())355 .map(BoundedVec::into_inner)356 .map(String::from_utf8)357 .transpose()358 .map_err(|e| {359 Error::Revert(alloc::format!(360 "Can not convert value \"baseURI\" to string with error \"{}\"",361 e362 ))363 })?;364365 let base_uri = match base_uri.as_deref() {366 None | Some("") => {367 return Ok("".into());368 }369 Some(base_uri) => base_uri.into(),370 };371372 Ok(373 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {374 Err(_) | Ok("") => base_uri,375 Ok(suffix) => base_uri + suffix,376 },377 )378 }379}380381/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension382/// @dev See https://eips.ethereum.org/EIPS/eip-721383#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]384impl<T: Config> NonfungibleHandle<T> {385 /// @notice Enumerate valid NFTs386 /// @param index A counter less than `totalSupply()`387 /// @return The token identifier for the `index`th NFT,388 /// (sort order not specified)389 fn token_by_index(&self, index: U256) -> U256 {390 index391 }392393 /// @dev Not implemented394 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {395 // TODO: Not implemetable396 Err("not implemented".into())397 }398399 /// @notice Count NFTs tracked by this contract400 /// @return A count of valid NFTs tracked by this contract, where each one of401 /// them has an assigned and queryable owner not equal to the zero address402 fn total_supply(&self) -> Result<U256> {403 self.consume_store_reads(1)?;404 Ok(<Pallet<T>>::total_supply(self).into())405 }406}407408/// @title ERC-721 Non-Fungible Token Standard409/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md410#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]411impl<T: Config> NonfungibleHandle<T> {412 /// @notice Count all NFTs assigned to an owner413 /// @dev NFTs assigned to the zero address are considered invalid, and this414 /// function throws for queries about the zero address.415 /// @param owner An address for whom to query the balance416 /// @return The number of NFTs owned by `owner`, possibly zero417 fn balance_of(&self, owner: Address) -> Result<U256> {418 self.consume_store_reads(1)?;419 let owner = T::CrossAccountId::from_eth(owner);420 let balance = <AccountBalance<T>>::get((self.id, owner));421 Ok(balance.into())422 }423 /// @notice Find the owner of an NFT424 /// @dev NFTs assigned to zero address are considered invalid, and queries425 /// about them do throw.426 /// @param tokenId The identifier for an NFT427 /// @return The address of the owner of the NFT428 fn owner_of(&self, token_id: U256) -> Result<Address> {429 self.consume_store_reads(1)?;430 let token: TokenId = token_id.try_into()?;431 Ok(*<TokenData<T>>::get((self.id, token))432 .ok_or("token not found")?433 .owner434 .as_eth())435 }436 /// @dev Not implemented437 #[solidity(rename_selector = "safeTransferFrom")]438 fn safe_transfer_from_with_data(439 &mut self,440 _from: Address,441 _to: Address,442 _token_id: U256,443 _data: Bytes,444 ) -> Result<()> {445 // TODO: Not implemetable446 Err("not implemented".into())447 }448 /// @dev Not implemented449 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {450 // TODO: Not implemetable451 Err("not implemented".into())452 }453454 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE455 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE456 /// THEY MAY BE PERMANENTLY LOST457 /// @dev Throws unless `msg.sender` is the current owner or an authorized458 /// operator for this NFT. Throws if `from` is not the current owner. Throws459 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.460 /// @param from The current owner of the NFT461 /// @param to The new owner462 /// @param tokenId The NFT to transfer463 #[weight(<SelfWeightOf<T>>::transfer_from())]464 fn transfer_from(465 &mut self,466 caller: Caller,467 from: Address,468 to: Address,469 token_id: U256,470 ) -> Result<()> {471 let caller = T::CrossAccountId::from_eth(caller);472 let from = T::CrossAccountId::from_eth(from);473 let to = T::CrossAccountId::from_eth(to);474 let token = token_id.try_into()?;475 let budget = self476 .recorder477 .weight_calls_budget(<StructureWeight<T>>::find_parent());478479 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)480 .map_err(dispatch_to_evm::<T>)?;481 Ok(())482 }483484 /// @notice Set or reaffirm the approved address for an NFT485 /// @dev The zero address indicates there is no approved address.486 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized487 /// operator of the current owner.488 /// @param approved The new approved NFT controller489 /// @param tokenId The NFT to approve490 #[weight(<SelfWeightOf<T>>::approve())]491 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {492 let caller = T::CrossAccountId::from_eth(caller);493 let approved = T::CrossAccountId::from_eth(approved);494 let token = token_id.try_into()?;495496 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))497 .map_err(dispatch_to_evm::<T>)?;498 Ok(())499 }500501 /// @notice Sets or unsets the approval of a given operator.502 /// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.503 /// @param operator Operator504 /// @param approved Should operator status be granted or revoked?505 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]506 fn set_approval_for_all(507 &mut self,508 caller: Caller,509 operator: Address,510 approved: bool,511 ) -> Result<()> {512 let caller = T::CrossAccountId::from_eth(caller);513 let operator = T::CrossAccountId::from_eth(operator);514515 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)516 .map_err(dispatch_to_evm::<T>)?;517 Ok(())518 }519520 /// @dev Not implemented521 fn get_approved(&self, _token_id: U256) -> Result<Address> {522 // TODO: Not implemetable523 Err("not implemented".into())524 }525526 /// @notice Tells whether the given `owner` approves the `operator`.527 #[weight(<SelfWeightOf<T>>::allowance_for_all())]528 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {529 let owner = T::CrossAccountId::from_eth(owner);530 let operator = T::CrossAccountId::from_eth(operator);531532 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))533 }534}535536/// @title ERC721 Token that can be irreversibly burned (destroyed).537#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]538impl<T: Config> NonfungibleHandle<T> {539 /// @notice Burns a specific ERC721 token.540 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized541 /// operator of the current owner.542 /// @param tokenId The NFT to approve543 #[weight(<SelfWeightOf<T>>::burn_item())]544 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {545 let caller = T::CrossAccountId::from_eth(caller);546 let token = token_id.try_into()?;547548 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;549 Ok(())550 }551}552553/// @title ERC721 minting logic.554#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]555impl<T: Config> NonfungibleHandle<T> {556 /// @notice Function to mint a token.557 /// @param to The new owner558 /// @return uint256 The id of the newly minted token559 #[weight(<SelfWeightOf<T>>::create_item())]560 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {561 let token_id: U256 = <TokensMinted<T>>::get(self.id)562 .checked_add(1)563 .ok_or("item id overflow")?564 .into();565 self.mint_check_id(caller, to, token_id)?;566 Ok(token_id)567 }568569 /// @notice Function to mint a token.570 /// @dev `tokenId` should be obtained with `nextTokenId` method,571 /// unlike standard, you can't specify it manually572 /// @param to The new owner573 /// @param tokenId ID of the minted NFT574 #[solidity(hide, rename_selector = "mint")]575 #[weight(<SelfWeightOf<T>>::create_item())]576 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {577 let caller = T::CrossAccountId::from_eth(caller);578 let to = T::CrossAccountId::from_eth(to);579 let token_id: u32 = token_id.try_into()?;580 let budget = self581 .recorder582 .weight_calls_budget(<StructureWeight<T>>::find_parent());583584 if <TokensMinted<T>>::get(self.id)585 .checked_add(1)586 .ok_or("item id overflow")?587 != token_id588 {589 return Err("item id should be next".into());590 }591592 <Pallet<T>>::create_item(593 self,594 &caller,595 CreateItemData::<T> {596 properties: BoundedVec::default(),597 owner: to,598 },599 &budget,600 )601 .map_err(dispatch_to_evm::<T>)?;602603 Ok(true)604 }605606 /// @notice Function to mint token with the given tokenUri.607 /// @param to The new owner608 /// @param tokenUri Token URI that would be stored in the NFT properties609 /// @return uint256 The id of the newly minted token610 #[solidity(rename_selector = "mintWithTokenURI")]611 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]612 fn mint_with_token_uri(613 &mut self,614 caller: Caller,615 to: Address,616 token_uri: String,617 ) -> Result<U256> {618 let token_id: U256 = <TokensMinted<T>>::get(self.id)619 .checked_add(1)620 .ok_or("item id overflow")?621 .into();622 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;623 Ok(token_id)624 }625626 /// @notice Function to mint token with the given tokenUri.627 /// @dev `tokenId` should be obtained with `nextTokenId` method,628 /// unlike standard, you can't specify it manually629 /// @param to The new owner630 /// @param tokenId ID of the minted NFT631 /// @param tokenUri Token URI that would be stored in the NFT properties632 #[solidity(hide, rename_selector = "mintWithTokenURI")]633 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]634 fn mint_with_token_uri_check_id(635 &mut self,636 caller: Caller,637 to: Address,638 token_id: U256,639 token_uri: String,640 ) -> Result<bool> {641 let key = key::url();642 let permission = get_token_permission::<T>(self.id, &key)?;643 if !permission.collection_admin {644 return Err("Operation is not allowed".into());645 }646647 let caller = T::CrossAccountId::from_eth(caller);648 let to = T::CrossAccountId::from_eth(to);649 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;650 let budget = self651 .recorder652 .weight_calls_budget(<StructureWeight<T>>::find_parent());653654 if <TokensMinted<T>>::get(self.id)655 .checked_add(1)656 .ok_or("item id overflow")?657 != token_id658 {659 return Err("item id should be next".into());660 }661662 let mut properties = CollectionPropertiesVec::default();663 properties664 .try_push(Property {665 key,666 value: token_uri667 .into_bytes()668 .try_into()669 .map_err(|_| "token uri is too long")?,670 })671 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;672673 <Pallet<T>>::create_item(674 self,675 &caller,676 CreateItemData::<T> {677 properties,678 owner: to,679 },680 &budget,681 )682 .map_err(dispatch_to_evm::<T>)?;683 Ok(true)684 }685}686687fn get_token_property<T: Config>(688 collection: &CollectionHandle<T>,689 token_id: u32,690 key: &up_data_structs::PropertyKey,691) -> Result<String> {692 collection.consume_store_reads(1)?;693 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))694 .map_err(|_| Error::Revert("Token properties not found".into()))?;695 if let Some(property) = properties.get(key) {696 return Ok(String::from_utf8_lossy(property).into());697 }698699 Err("Property tokenURI not found".into())700}701702fn get_token_permission<T: Config>(703 collection_id: CollectionId,704 key: &PropertyKey,705) -> Result<PropertyPermission> {706 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)707 .map_err(|_| Error::Revert("No permissions for collection".into()))?;708 let a = token_property_permissions709 .get(key)710 .map(Clone::clone)711 .ok_or_else(|| {712 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();713 Error::Revert(alloc::format!("No permission for key {}", key))714 })?;715 Ok(a)716}717718/// @title Unique extensions for ERC721.719#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]720impl<T: Config> NonfungibleHandle<T>721where722 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,723{724 /// @notice A descriptive name for a collection of NFTs in this contract725 fn name(&self) -> String {726 decode_utf16(self.name.iter().copied())727 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))728 .collect::<String>()729 }730731 /// @notice An abbreviated name for NFTs in this contract732 fn symbol(&self) -> String {733 String::from_utf8_lossy(&self.token_prefix).into()734 }735736 /// @notice A description for the collection.737 fn description(&self) -> String {738 decode_utf16(self.description.iter().copied())739 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))740 .collect::<String>()741 }742743 /// Returns the owner (in cross format) of the token.744 ///745 /// @param tokenId Id for the token.746 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {747 Self::token_owner(&self, token_id.try_into()?)748 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))749 .map_err(|_| Error::Revert("token not found".into()))750 }751752 /// Returns the token properties.753 ///754 /// @param tokenId Id for the token.755 /// @param keys Properties keys. Empty keys for all propertyes.756 /// @return Vector of properties key/value pairs.757 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {758 let keys = keys759 .into_iter()760 .map(|key| {761 <Vec<u8>>::from(key)762 .try_into()763 .map_err(|_| Error::Revert("key too large".into()))764 })765 .collect::<Result<Vec<_>>>()?;766767 <Self as CommonCollectionOperations<T>>::token_properties(768 &self,769 token_id.try_into()?,770 if keys.is_empty() { None } else { Some(keys) },771 )772 .into_iter()773 .map(eth::Property::try_from)774 .collect::<Result<Vec<_>>>()775 }776777 /// @notice Set or reaffirm the approved address for an NFT778 /// @dev The zero address indicates there is no approved address.779 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized780 /// operator of the current owner.781 /// @param approved The new substrate address approved NFT controller782 /// @param tokenId The NFT to approve783 #[weight(<SelfWeightOf<T>>::approve())]784 fn approve_cross(785 &mut self,786 caller: Caller,787 approved: eth::CrossAddress,788 token_id: U256,789 ) -> Result<()> {790 let caller = T::CrossAccountId::from_eth(caller);791 let approved = approved.into_sub_cross_account::<T>()?;792 let token = token_id.try_into()?;793794 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))795 .map_err(dispatch_to_evm::<T>)?;796 Ok(())797 }798799 /// @notice Transfer ownership of an NFT800 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`801 /// is the zero address. Throws if `tokenId` is not a valid NFT.802 /// @param to The new owner803 /// @param tokenId The NFT to transfer804 #[weight(<SelfWeightOf<T>>::transfer())]805 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {806 let caller = T::CrossAccountId::from_eth(caller);807 let to = T::CrossAccountId::from_eth(to);808 let token = token_id.try_into()?;809 let budget = self810 .recorder811 .weight_calls_budget(<StructureWeight<T>>::find_parent());812813 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;814 Ok(())815 }816817 /// @notice Transfer ownership of an NFT818 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`819 /// is the zero address. Throws if `tokenId` is not a valid NFT.820 /// @param to The new owner821 /// @param tokenId The NFT to transfer822 #[weight(<SelfWeightOf<T>>::transfer())]823 fn transfer_cross(824 &mut self,825 caller: Caller,826 to: eth::CrossAddress,827 token_id: U256,828 ) -> Result<()> {829 let caller = T::CrossAccountId::from_eth(caller);830 let to = to.into_sub_cross_account::<T>()?;831 let token = token_id.try_into()?;832 let budget = self833 .recorder834 .weight_calls_budget(<StructureWeight<T>>::find_parent());835836 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;837 Ok(())838 }839840 /// @notice Transfer ownership of an NFT from cross account address to cross account address841 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`842 /// is the zero address. Throws if `tokenId` is not a valid NFT.843 /// @param from Cross acccount address of current owner844 /// @param to Cross acccount address of new owner845 /// @param tokenId The NFT to transfer846 #[weight(<SelfWeightOf<T>>::transfer())]847 fn transfer_from_cross(848 &mut self,849 caller: Caller,850 from: eth::CrossAddress,851 to: eth::CrossAddress,852 token_id: U256,853 ) -> Result<()> {854 let caller = T::CrossAccountId::from_eth(caller);855 let from = from.into_sub_cross_account::<T>()?;856 let to = to.into_sub_cross_account::<T>()?;857 let token_id = token_id.try_into()?;858 let budget = self859 .recorder860 .weight_calls_budget(<StructureWeight<T>>::find_parent());861 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)862 .map_err(dispatch_to_evm::<T>)?;863 Ok(())864 }865866 /// @notice Burns a specific ERC721 token.867 /// @dev Throws unless `msg.sender` is the current owner or an authorized868 /// operator for this NFT. Throws if `from` is not the current owner. Throws869 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.870 /// @param from The current owner of the NFT871 /// @param tokenId The NFT to transfer872 #[solidity(hide)]873 #[weight(<SelfWeightOf<T>>::burn_from())]874 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {875 let caller = T::CrossAccountId::from_eth(caller);876 let from = T::CrossAccountId::from_eth(from);877 let token = token_id.try_into()?;878 let budget = self879 .recorder880 .weight_calls_budget(<StructureWeight<T>>::find_parent());881882 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)883 .map_err(dispatch_to_evm::<T>)?;884 Ok(())885 }886887 /// @notice Burns a specific ERC721 token.888 /// @dev Throws unless `msg.sender` is the current owner or an authorized889 /// operator for this NFT. Throws if `from` is not the current owner. Throws890 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.891 /// @param from The current owner of the NFT892 /// @param tokenId The NFT to transfer893 #[weight(<SelfWeightOf<T>>::burn_from())]894 fn burn_from_cross(895 &mut self,896 caller: Caller,897 from: eth::CrossAddress,898 token_id: U256,899 ) -> Result<()> {900 let caller = T::CrossAccountId::from_eth(caller);901 let from = from.into_sub_cross_account::<T>()?;902 let token = token_id.try_into()?;903 let budget = self904 .recorder905 .weight_calls_budget(<StructureWeight<T>>::find_parent());906907 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)908 .map_err(dispatch_to_evm::<T>)?;909 Ok(())910 }911912 /// @notice Returns next free NFT ID.913 fn next_token_id(&self) -> Result<U256> {914 self.consume_store_reads(1)?;915 Ok(<TokensMinted<T>>::get(self.id)916 .checked_add(1)917 .ok_or("item id overflow")?918 .into())919 }920921 /// @notice Function to mint multiple tokens.922 /// @dev `tokenIds` should be an array of consecutive numbers and first number923 /// should be obtained with `nextTokenId` method924 /// @param to The new owner925 /// @param tokenIds IDs of the minted NFTs926 #[solidity(hide)]927 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]928 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {929 let caller = T::CrossAccountId::from_eth(caller);930 let to = T::CrossAccountId::from_eth(to);931 let mut expected_index = <TokensMinted<T>>::get(self.id)932 .checked_add(1)933 .ok_or("item id overflow")?;934 let budget = self935 .recorder936 .weight_calls_budget(<StructureWeight<T>>::find_parent());937938 let total_tokens = token_ids.len();939 for id in token_ids.into_iter() {940 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;941 if id != expected_index {942 return Err("item id should be next".into());943 }944 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;945 }946 let data = (0..total_tokens)947 .map(|_| CreateItemData::<T> {948 properties: BoundedVec::default(),949 owner: to.clone(),950 })951 .collect();952953 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)954 .map_err(dispatch_to_evm::<T>)?;955 Ok(true)956 }957958 /// @notice Function to mint multiple tokens with the given tokenUris.959 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive960 /// numbers and first number should be obtained with `nextTokenId` method961 /// @param to The new owner962 /// @param tokens array of pairs of token ID and token URI for minted tokens963 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]964 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]965 fn mint_bulk_with_token_uri(966 &mut self,967 caller: Caller,968 to: Address,969 tokens: Vec<TokenUri>,970 ) -> Result<bool> {971 let key = key::url();972 let caller = T::CrossAccountId::from_eth(caller);973 let to = T::CrossAccountId::from_eth(to);974 let mut expected_index = <TokensMinted<T>>::get(self.id)975 .checked_add(1)976 .ok_or("item id overflow")?;977 let budget = self978 .recorder979 .weight_calls_budget(<StructureWeight<T>>::find_parent());980981 let mut data = Vec::with_capacity(tokens.len());982 for TokenUri { id, uri } in tokens {983 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;984 if id != expected_index {985 return Err("item id should be next".into());986 }987 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;988989 let mut properties = CollectionPropertiesVec::default();990 properties991 .try_push(Property {992 key: key.clone(),993 value: uri994 .into_bytes()995 .try_into()996 .map_err(|_| "token uri is too long")?,997 })998 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;9991000 data.push(CreateItemData::<T> {1001 properties,1002 owner: to.clone(),1003 });1004 }10051006 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1007 .map_err(dispatch_to_evm::<T>)?;1008 Ok(true)1009 }10101011 /// @notice Function to mint a token.1012 /// @param to The new owner crossAccountId1013 /// @param properties Properties of minted token1014 /// @return uint256 The id of the newly minted token1015 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1016 fn mint_cross(1017 &mut self,1018 caller: Caller,1019 to: eth::CrossAddress,1020 properties: Vec<eth::Property>,1021 ) -> Result<U256> {1022 let token_id = <TokensMinted<T>>::get(self.id)1023 .checked_add(1)1024 .ok_or("item id overflow")?;10251026 let to = to.into_sub_cross_account::<T>()?;10271028 let properties = properties1029 .into_iter()1030 .map(eth::Property::try_into)1031 .collect::<Result<Vec<_>>>()?1032 .try_into()1033 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10341035 let caller = T::CrossAccountId::from_eth(caller);10361037 let budget = self1038 .recorder1039 .weight_calls_budget(<StructureWeight<T>>::find_parent());10401041 <Pallet<T>>::create_item(1042 self,1043 &caller,1044 CreateItemData::<T> {1045 properties,1046 owner: to,1047 },1048 &budget,1049 )1050 .map_err(dispatch_to_evm::<T>)?;10511052 Ok(token_id.into())1053 }10541055 /// @notice Returns collection helper contract address1056 fn collection_helper_address(&self) -> Address {1057 T::ContractAddress::get()1058 }1059}10601061#[solidity_interface(1062 name = UniqueNFT,1063 is(1064 ERC721,1065 ERC721Enumerable,1066 ERC721UniqueExtensions,1067 ERC721UniqueMintable,1068 ERC721Burnable,1069 ERC721Metadata(if(this.flags.erc721metadata)),1070 Collection(via(common_mut returns CollectionHandle<T>)),1071 TokenProperties,1072 ),1073 enum(derive(PreDispatch)),1074)]1075impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}10761077// Not a tests, but code generators1078generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1079generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10801081impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1082where1083 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1084{1085 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10861087 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1088 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1089 }1090}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{abi::AbiType, ToLog, generate_stubgen, solidity_interface, types::*};28use frame_support::BoundedVec;29use up_data_structs::{30 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,31 CollectionPropertiesVec,32};33use pallet_evm_coder_substrate::{34 dispatch_to_evm, frontier_contract,35 execution::{Result, PreDispatch, Error},36};37use sp_std::{vec::Vec, vec};38use pallet_common::{39 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,40 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},41 eth::{self, TokenUri},42};43use pallet_evm::{account::CrossAccountId, PrecompileHandle};44use pallet_evm_coder_substrate::call;45use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};46use sp_core::{U256, Get};4748use crate::{49 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,50 TokenProperties, SelfWeightOf, weights::WeightInfo,51};5253/// Nft events.54#[derive(ToLog)]55pub enum ERC721TokenEvent {56 /// The token has been changed.57 TokenChanged {58 /// Token ID.59 #[indexed]60 token_id: U256,61 },62}6364frontier_contract! {65 macro_rules! NonfungibleHandle_result {...}66 impl<T: Config> Contract for NonfungibleHandle<T> {...}67}6869/// @title A contract that allows to set and delete token properties and change token property permissions.70#[solidity_interface(name = TokenProperties, events(ERC721TokenEvent), enum(derive(PreDispatch)), enum_attr(weight))]71impl<T: Config> NonfungibleHandle<T> {72 /// @notice Set permissions for token property.73 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.74 /// @param key Property key.75 /// @param isMutable Permission to mutate property.76 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.77 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.78 #[solidity(hide)]79 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(1))]80 fn set_token_property_permission(81 &mut self,82 caller: Caller,83 key: String,84 is_mutable: bool,85 collection_admin: bool,86 token_owner: bool,87 ) -> Result<()> {88 let caller = T::CrossAccountId::from_eth(caller);89 <Pallet<T>>::set_token_property_permissions(90 self,91 &caller,92 vec![PropertyKeyPermission {93 key: <Vec<u8>>::from(key)94 .try_into()95 .map_err(|_| "too long key")?,96 permission: PropertyPermission {97 mutable: is_mutable,98 collection_admin,99 token_owner,100 },101 }],102 )103 .map_err(dispatch_to_evm::<T>)104 }105106 /// @notice Set permissions for token property.107 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.108 /// @param permissions Permissions for keys.109 #[weight(<SelfWeightOf<T>>::set_token_property_permissions(permissions.len() as u32))]110 fn set_token_property_permissions(111 &mut self,112 caller: Caller,113 permissions: Vec<eth::TokenPropertyPermission>,114 ) -> Result<()> {115 let caller = T::CrossAccountId::from_eth(caller);116 let perms = eth::TokenPropertyPermission::into_property_key_permissions(permissions)?;117118 <Pallet<T>>::set_token_property_permissions(self, &caller, perms)119 .map_err(dispatch_to_evm::<T>)120 }121122 /// @notice Get permissions for token properties.123 fn token_property_permissions(&self) -> Result<Vec<eth::TokenPropertyPermission>> {124 let perms = <Pallet<T>>::token_property_permission(self.id);125 Ok(perms126 .into_iter()127 .map(eth::TokenPropertyPermission::from)128 .collect())129 }130131 /// @notice Set token property value.132 /// @dev Throws error if `msg.sender` has no permission to edit the property.133 /// @param tokenId ID of the token.134 /// @param key Property key.135 /// @param value Property value.136 #[solidity(hide)]137 #[weight(<SelfWeightOf<T>>::set_token_properties(1))]138 fn set_property(139 &mut self,140 caller: Caller,141 token_id: U256,142 key: String,143 value: Bytes,144 ) -> Result<()> {145 let caller = T::CrossAccountId::from_eth(caller);146 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;147 let key = <Vec<u8>>::from(key)148 .try_into()149 .map_err(|_| "key too long")?;150 let value = value.0.try_into().map_err(|_| "value too long")?;151152 let nesting_budget = self153 .recorder154 .weight_calls_budget(<StructureWeight<T>>::find_parent());155156 <Pallet<T>>::set_token_property(157 self,158 &caller,159 TokenId(token_id),160 Property { key, value },161 &nesting_budget,162 )163 .map_err(dispatch_to_evm::<T>)164 }165166 /// @notice Set token properties value.167 /// @dev Throws error if `msg.sender` has no permission to edit the property.168 /// @param tokenId ID of the token.169 /// @param properties settable properties170 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]171 fn set_properties(172 &mut self,173 caller: Caller,174 token_id: U256,175 properties: Vec<eth::Property>,176 ) -> Result<()> {177 let caller = T::CrossAccountId::from_eth(caller);178 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;179180 let nesting_budget = self181 .recorder182 .weight_calls_budget(<StructureWeight<T>>::find_parent());183184 let properties = properties185 .into_iter()186 .map(eth::Property::try_into)187 .collect::<Result<Vec<_>>>()?;188189 <Pallet<T>>::set_token_properties(190 self,191 &caller,192 TokenId(token_id),193 properties.into_iter(),194 false,195 &nesting_budget,196 )197 .map_err(dispatch_to_evm::<T>)198 }199200 /// @notice Delete token property value.201 /// @dev Throws error if `msg.sender` has no permission to edit the property.202 /// @param tokenId ID of the token.203 /// @param key Property key.204 #[solidity(hide)]205 #[weight(<SelfWeightOf<T>>::delete_token_properties(1))]206 fn delete_property(&mut self, token_id: U256, caller: Caller, key: String) -> Result<()> {207 let caller = T::CrossAccountId::from_eth(caller);208 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;209 let key = <Vec<u8>>::from(key)210 .try_into()211 .map_err(|_| "key too long")?;212213 let nesting_budget = self214 .recorder215 .weight_calls_budget(<StructureWeight<T>>::find_parent());216217 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)218 .map_err(dispatch_to_evm::<T>)219 }220221 /// @notice Delete token properties value.222 /// @dev Throws error if `msg.sender` has no permission to edit the property.223 /// @param tokenId ID of the token.224 /// @param keys Properties key.225 #[weight(<SelfWeightOf<T>>::delete_token_properties(keys.len() as u32))]226 fn delete_properties(227 &mut self,228 token_id: U256,229 caller: Caller,230 keys: Vec<String>,231 ) -> Result<()> {232 let caller = T::CrossAccountId::from_eth(caller);233 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;234 let keys = keys235 .into_iter()236 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))237 .collect::<Result<Vec<_>>>()?;238239 let nesting_budget = self240 .recorder241 .weight_calls_budget(<StructureWeight<T>>::find_parent());242243 <Pallet<T>>::delete_token_properties(244 self,245 &caller,246 TokenId(token_id),247 keys.into_iter(),248 &nesting_budget,249 )250 .map_err(dispatch_to_evm::<T>)251 }252253 /// @notice Get token property value.254 /// @dev Throws error if key not found255 /// @param tokenId ID of the token.256 /// @param key Property key.257 /// @return Property value bytes258 fn property(&self, token_id: U256, key: String) -> Result<Bytes> {259 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;260 let key = <Vec<u8>>::from(key)261 .try_into()262 .map_err(|_| "key too long")?;263264 let props = <TokenProperties<T>>::get((self.id, token_id));265 let prop = props.get(&key).ok_or("key not found")?;266267 Ok(prop.to_vec().into())268 }269}270271#[derive(ToLog)]272pub enum ERC721Events {273 /// @dev This emits when ownership of any NFT changes by any mechanism.274 /// This event emits when NFTs are created (`from` == 0) and destroyed275 /// (`to` == 0). Exception: during contract creation, any number of NFTs276 /// may be created and assigned without emitting Transfer. At the time of277 /// any transfer, the approved address for that NFT (if any) is reset to none.278 Transfer {279 #[indexed]280 from: Address,281 #[indexed]282 to: Address,283 #[indexed]284 token_id: U256,285 },286 /// @dev This emits when the approved address for an NFT is changed or287 /// reaffirmed. The zero address indicates there is no approved address.288 /// When a Transfer event emits, this also indicates that the approved289 /// address for that NFT (if any) is reset to none.290 Approval {291 #[indexed]292 owner: Address,293 #[indexed]294 approved: Address,295 #[indexed]296 token_id: U256,297 },298 /// @dev This emits when an operator is enabled or disabled for an owner.299 /// The operator can manage all NFTs of the owner.300 #[allow(dead_code)]301 ApprovalForAll {302 #[indexed]303 owner: Address,304 #[indexed]305 operator: Address,306 approved: bool,307 },308}309310/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension311/// @dev See https://eips.ethereum.org/EIPS/eip-721312#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f, enum(derive(PreDispatch)), enum_attr(weight))]313impl<T: Config> NonfungibleHandle<T>314where315 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,316{317 /// @notice A descriptive name for a collection of NFTs in this contract318 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`319 #[solidity(hide, rename_selector = "name")]320 fn name_proxy(&self) -> String {321 self.name()322 }323324 /// @notice An abbreviated name for NFTs in this contract325 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`326 #[solidity(hide, rename_selector = "symbol")]327 fn symbol_proxy(&self) -> String {328 self.symbol()329 }330331 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.332 ///333 /// @dev If the token has a `url` property and it is not empty, it is returned.334 /// 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`.335 /// If the collection property `baseURI` is empty or absent, return "" (empty string)336 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix337 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).338 ///339 /// @return token's const_metadata340 #[solidity(rename_selector = "tokenURI")]341 fn token_uri(&self, token_id: U256) -> Result<String> {342 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;343344 match get_token_property(self, token_id_u32, &key::url()).as_deref() {345 Err(_) | Ok("") => (),346 Ok(url) => {347 return Ok(url.into());348 }349 };350351 let base_uri =352 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())353 .map(BoundedVec::into_inner)354 .map(String::from_utf8)355 .transpose()356 .map_err(|e| {357 Error::Revert(alloc::format!(358 "Can not convert value \"baseURI\" to string with error \"{}\"",359 e360 ))361 })?;362363 let base_uri = match base_uri.as_deref() {364 None | Some("") => {365 return Ok("".into());366 }367 Some(base_uri) => base_uri.into(),368 };369370 Ok(371 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {372 Err(_) | Ok("") => base_uri,373 Ok(suffix) => base_uri + suffix,374 },375 )376 }377}378379/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension380/// @dev See https://eips.ethereum.org/EIPS/eip-721381#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63, enum(derive(PreDispatch)), enum_attr(weight))]382impl<T: Config> NonfungibleHandle<T> {383 /// @notice Enumerate valid NFTs384 /// @param index A counter less than `totalSupply()`385 /// @return The token identifier for the `index`th NFT,386 /// (sort order not specified)387 fn token_by_index(&self, index: U256) -> U256 {388 index389 }390391 /// @dev Not implemented392 fn token_of_owner_by_index(&self, _owner: Address, _index: U256) -> Result<U256> {393 // TODO: Not implemetable394 Err("not implemented".into())395 }396397 /// @notice Count NFTs tracked by this contract398 /// @return A count of valid NFTs tracked by this contract, where each one of399 /// them has an assigned and queryable owner not equal to the zero address400 fn total_supply(&self) -> Result<U256> {401 self.consume_store_reads(1)?;402 Ok(<Pallet<T>>::total_supply(self).into())403 }404}405406/// @title ERC-721 Non-Fungible Token Standard407/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md408#[solidity_interface(name = ERC721, events(ERC721Events), enum(derive(PreDispatch)), enum_attr(weight), expect_selector = 0x80ac58cd)]409impl<T: Config> NonfungibleHandle<T> {410 /// @notice Count all NFTs assigned to an owner411 /// @dev NFTs assigned to the zero address are considered invalid, and this412 /// function throws for queries about the zero address.413 /// @param owner An address for whom to query the balance414 /// @return The number of NFTs owned by `owner`, possibly zero415 fn balance_of(&self, owner: Address) -> Result<U256> {416 self.consume_store_reads(1)?;417 let owner = T::CrossAccountId::from_eth(owner);418 let balance = <AccountBalance<T>>::get((self.id, owner));419 Ok(balance.into())420 }421 /// @notice Find the owner of an NFT422 /// @dev NFTs assigned to zero address are considered invalid, and queries423 /// about them do throw.424 /// @param tokenId The identifier for an NFT425 /// @return The address of the owner of the NFT426 fn owner_of(&self, token_id: U256) -> Result<Address> {427 self.consume_store_reads(1)?;428 let token: TokenId = token_id.try_into()?;429 Ok(*<TokenData<T>>::get((self.id, token))430 .ok_or("token not found")?431 .owner432 .as_eth())433 }434 /// @dev Not implemented435 #[solidity(rename_selector = "safeTransferFrom")]436 fn safe_transfer_from_with_data(437 &mut self,438 _from: Address,439 _to: Address,440 _token_id: U256,441 _data: Bytes,442 ) -> Result<()> {443 // TODO: Not implemetable444 Err("not implemented".into())445 }446 /// @dev Not implemented447 fn safe_transfer_from(&mut self, _from: Address, _to: Address, _token_id: U256) -> Result<()> {448 // TODO: Not implemetable449 Err("not implemented".into())450 }451452 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE453 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE454 /// THEY MAY BE PERMANENTLY LOST455 /// @dev Throws unless `msg.sender` is the current owner or an authorized456 /// operator for this NFT. Throws if `from` is not the current owner. Throws457 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.458 /// @param from The current owner of the NFT459 /// @param to The new owner460 /// @param tokenId The NFT to transfer461 #[weight(<SelfWeightOf<T>>::transfer_from())]462 fn transfer_from(463 &mut self,464 caller: Caller,465 from: Address,466 to: Address,467 token_id: U256,468 ) -> Result<()> {469 let caller = T::CrossAccountId::from_eth(caller);470 let from = T::CrossAccountId::from_eth(from);471 let to = T::CrossAccountId::from_eth(to);472 let token = token_id.try_into()?;473 let budget = self474 .recorder475 .weight_calls_budget(<StructureWeight<T>>::find_parent());476477 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)478 .map_err(dispatch_to_evm::<T>)?;479 Ok(())480 }481482 /// @notice Set or reaffirm the approved address for an NFT483 /// @dev The zero address indicates there is no approved address.484 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized485 /// operator of the current owner.486 /// @param approved The new approved NFT controller487 /// @param tokenId The NFT to approve488 #[weight(<SelfWeightOf<T>>::approve())]489 fn approve(&mut self, caller: Caller, approved: Address, token_id: U256) -> Result<()> {490 let caller = T::CrossAccountId::from_eth(caller);491 let approved = T::CrossAccountId::from_eth(approved);492 let token = token_id.try_into()?;493494 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))495 .map_err(dispatch_to_evm::<T>)?;496 Ok(())497 }498499 /// @notice Sets or unsets the approval of a given operator.500 /// The `operator` is allowed to transfer all tokens of the `caller` on their behalf.501 /// @param operator Operator502 /// @param approved Should operator status be granted or revoked?503 #[weight(<SelfWeightOf<T>>::set_allowance_for_all())]504 fn set_approval_for_all(505 &mut self,506 caller: Caller,507 operator: Address,508 approved: bool,509 ) -> Result<()> {510 let caller = T::CrossAccountId::from_eth(caller);511 let operator = T::CrossAccountId::from_eth(operator);512513 <Pallet<T>>::set_allowance_for_all(self, &caller, &operator, approved)514 .map_err(dispatch_to_evm::<T>)?;515 Ok(())516 }517518 /// @dev Not implemented519 fn get_approved(&self, _token_id: U256) -> Result<Address> {520 // TODO: Not implemetable521 Err("not implemented".into())522 }523524 /// @notice Tells whether the given `owner` approves the `operator`.525 #[weight(<SelfWeightOf<T>>::allowance_for_all())]526 fn is_approved_for_all(&self, owner: Address, operator: Address) -> Result<bool> {527 let owner = T::CrossAccountId::from_eth(owner);528 let operator = T::CrossAccountId::from_eth(operator);529530 Ok(<Pallet<T>>::allowance_for_all(self, &owner, &operator))531 }532}533534/// @title ERC721 Token that can be irreversibly burned (destroyed).535#[solidity_interface(name = ERC721Burnable, enum(derive(PreDispatch)), enum_attr(weight))]536impl<T: Config> NonfungibleHandle<T> {537 /// @notice Burns a specific ERC721 token.538 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized539 /// operator of the current owner.540 /// @param tokenId The NFT to approve541 #[weight(<SelfWeightOf<T>>::burn_item())]542 fn burn(&mut self, caller: Caller, token_id: U256) -> Result<()> {543 let caller = T::CrossAccountId::from_eth(caller);544 let token = token_id.try_into()?;545546 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;547 Ok(())548 }549}550551/// @title ERC721 minting logic.552#[solidity_interface(name = ERC721UniqueMintable, enum(derive(PreDispatch)), enum_attr(weight))]553impl<T: Config> NonfungibleHandle<T> {554 /// @notice Function to mint a token.555 /// @param to The new owner556 /// @return uint256 The id of the newly minted token557 #[weight(<SelfWeightOf<T>>::create_item())]558 fn mint(&mut self, caller: Caller, to: Address) -> Result<U256> {559 let token_id: U256 = <TokensMinted<T>>::get(self.id)560 .checked_add(1)561 .ok_or("item id overflow")?562 .into();563 self.mint_check_id(caller, to, token_id)?;564 Ok(token_id)565 }566567 /// @notice Function to mint a token.568 /// @dev `tokenId` should be obtained with `nextTokenId` method,569 /// unlike standard, you can't specify it manually570 /// @param to The new owner571 /// @param tokenId ID of the minted NFT572 #[solidity(hide, rename_selector = "mint")]573 #[weight(<SelfWeightOf<T>>::create_item())]574 fn mint_check_id(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<bool> {575 let caller = T::CrossAccountId::from_eth(caller);576 let to = T::CrossAccountId::from_eth(to);577 let token_id: u32 = token_id.try_into()?;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 <Pallet<T>>::create_item(591 self,592 &caller,593 CreateItemData::<T> {594 properties: BoundedVec::default(),595 owner: to,596 },597 &budget,598 )599 .map_err(dispatch_to_evm::<T>)?;600601 Ok(true)602 }603604 /// @notice Function to mint token with the given tokenUri.605 /// @param to The new owner606 /// @param tokenUri Token URI that would be stored in the NFT properties607 /// @return uint256 The id of the newly minted token608 #[solidity(rename_selector = "mintWithTokenURI")]609 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]610 fn mint_with_token_uri(611 &mut self,612 caller: Caller,613 to: Address,614 token_uri: String,615 ) -> Result<U256> {616 let token_id: U256 = <TokensMinted<T>>::get(self.id)617 .checked_add(1)618 .ok_or("item id overflow")?619 .into();620 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;621 Ok(token_id)622 }623624 /// @notice Function to mint token with the given tokenUri.625 /// @dev `tokenId` should be obtained with `nextTokenId` method,626 /// unlike standard, you can't specify it manually627 /// @param to The new owner628 /// @param tokenId ID of the minted NFT629 /// @param tokenUri Token URI that would be stored in the NFT properties630 #[solidity(hide, rename_selector = "mintWithTokenURI")]631 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(1))]632 fn mint_with_token_uri_check_id(633 &mut self,634 caller: Caller,635 to: Address,636 token_id: U256,637 token_uri: String,638 ) -> Result<bool> {639 let key = key::url();640 let permission = get_token_permission::<T>(self.id, &key)?;641 if !permission.collection_admin {642 return Err("Operation is not allowed".into());643 }644645 let caller = T::CrossAccountId::from_eth(caller);646 let to = T::CrossAccountId::from_eth(to);647 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;648 let budget = self649 .recorder650 .weight_calls_budget(<StructureWeight<T>>::find_parent());651652 if <TokensMinted<T>>::get(self.id)653 .checked_add(1)654 .ok_or("item id overflow")?655 != token_id656 {657 return Err("item id should be next".into());658 }659660 let mut properties = CollectionPropertiesVec::default();661 properties662 .try_push(Property {663 key,664 value: token_uri665 .into_bytes()666 .try_into()667 .map_err(|_| "token uri is too long")?,668 })669 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;670671 <Pallet<T>>::create_item(672 self,673 &caller,674 CreateItemData::<T> {675 properties,676 owner: to,677 },678 &budget,679 )680 .map_err(dispatch_to_evm::<T>)?;681 Ok(true)682 }683}684685fn get_token_property<T: Config>(686 collection: &CollectionHandle<T>,687 token_id: u32,688 key: &up_data_structs::PropertyKey,689) -> Result<String> {690 collection.consume_store_reads(1)?;691 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))692 .map_err(|_| Error::Revert("Token properties not found".into()))?;693 if let Some(property) = properties.get(key) {694 return Ok(String::from_utf8_lossy(property).into());695 }696697 Err("Property tokenURI not found".into())698}699700fn get_token_permission<T: Config>(701 collection_id: CollectionId,702 key: &PropertyKey,703) -> Result<PropertyPermission> {704 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)705 .map_err(|_| Error::Revert("No permissions for collection".into()))?;706 let a = token_property_permissions707 .get(key)708 .map(Clone::clone)709 .ok_or_else(|| {710 let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();711 Error::Revert(alloc::format!("No permission for key {}", key))712 })?;713 Ok(a)714}715716/// @title Unique extensions for ERC721.717#[solidity_interface(name = ERC721UniqueExtensions, enum(derive(PreDispatch)), enum_attr(weight))]718impl<T: Config> NonfungibleHandle<T>719where720 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,721{722 /// @notice A descriptive name for a collection of NFTs in this contract723 fn name(&self) -> String {724 decode_utf16(self.name.iter().copied())725 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))726 .collect::<String>()727 }728729 /// @notice An abbreviated name for NFTs in this contract730 fn symbol(&self) -> String {731 String::from_utf8_lossy(&self.token_prefix).into()732 }733734 /// @notice A description for the collection.735 fn description(&self) -> String {736 decode_utf16(self.description.iter().copied())737 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))738 .collect::<String>()739 }740741 /// Returns the owner (in cross format) of the token.742 ///743 /// @param tokenId Id for the token.744 fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {745 Self::token_owner(&self, token_id.try_into()?)746 .map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))747 .map_err(|_| Error::Revert("token not found".into()))748 }749750 /// Returns the token properties.751 ///752 /// @param tokenId Id for the token.753 /// @param keys Properties keys. Empty keys for all propertyes.754 /// @return Vector of properties key/value pairs.755 fn properties(&self, token_id: U256, keys: Vec<String>) -> Result<Vec<eth::Property>> {756 let keys = keys757 .into_iter()758 .map(|key| {759 <Vec<u8>>::from(key)760 .try_into()761 .map_err(|_| Error::Revert("key too large".into()))762 })763 .collect::<Result<Vec<_>>>()?;764765 <Self as CommonCollectionOperations<T>>::token_properties(766 &self,767 token_id.try_into()?,768 if keys.is_empty() { None } else { Some(keys) },769 )770 .into_iter()771 .map(eth::Property::try_from)772 .collect::<Result<Vec<_>>>()773 }774775 /// @notice Set or reaffirm the approved address for an NFT776 /// @dev The zero address indicates there is no approved address.777 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized778 /// operator of the current owner.779 /// @param approved The new substrate address approved NFT controller780 /// @param tokenId The NFT to approve781 #[weight(<SelfWeightOf<T>>::approve())]782 fn approve_cross(783 &mut self,784 caller: Caller,785 approved: eth::CrossAddress,786 token_id: U256,787 ) -> Result<()> {788 let caller = T::CrossAccountId::from_eth(caller);789 let approved = approved.into_sub_cross_account::<T>()?;790 let token = token_id.try_into()?;791792 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))793 .map_err(dispatch_to_evm::<T>)?;794 Ok(())795 }796797 /// @notice Transfer ownership of an NFT798 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`799 /// is the zero address. Throws if `tokenId` is not a valid NFT.800 /// @param to The new owner801 /// @param tokenId The NFT to transfer802 #[weight(<SelfWeightOf<T>>::transfer())]803 fn transfer(&mut self, caller: Caller, to: Address, token_id: U256) -> Result<()> {804 let caller = T::CrossAccountId::from_eth(caller);805 let to = T::CrossAccountId::from_eth(to);806 let token = token_id.try_into()?;807 let budget = self808 .recorder809 .weight_calls_budget(<StructureWeight<T>>::find_parent());810811 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;812 Ok(())813 }814815 /// @notice Transfer ownership of an NFT816 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`817 /// is the zero address. Throws if `tokenId` is not a valid NFT.818 /// @param to The new owner819 /// @param tokenId The NFT to transfer820 #[weight(<SelfWeightOf<T>>::transfer())]821 fn transfer_cross(822 &mut self,823 caller: Caller,824 to: eth::CrossAddress,825 token_id: U256,826 ) -> Result<()> {827 let caller = T::CrossAccountId::from_eth(caller);828 let to = to.into_sub_cross_account::<T>()?;829 let token = token_id.try_into()?;830 let budget = self831 .recorder832 .weight_calls_budget(<StructureWeight<T>>::find_parent());833834 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;835 Ok(())836 }837838 /// @notice Transfer ownership of an NFT from cross account address to cross account address839 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`840 /// is the zero address. Throws if `tokenId` is not a valid NFT.841 /// @param from Cross acccount address of current owner842 /// @param to Cross acccount address of new owner843 /// @param tokenId The NFT to transfer844 #[weight(<SelfWeightOf<T>>::transfer())]845 fn transfer_from_cross(846 &mut self,847 caller: Caller,848 from: eth::CrossAddress,849 to: eth::CrossAddress,850 token_id: U256,851 ) -> Result<()> {852 let caller = T::CrossAccountId::from_eth(caller);853 let from = from.into_sub_cross_account::<T>()?;854 let to = to.into_sub_cross_account::<T>()?;855 let token_id = token_id.try_into()?;856 let budget = self857 .recorder858 .weight_calls_budget(<StructureWeight<T>>::find_parent());859 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)860 .map_err(dispatch_to_evm::<T>)?;861 Ok(())862 }863864 /// @notice Burns a specific ERC721 token.865 /// @dev Throws unless `msg.sender` is the current owner or an authorized866 /// operator for this NFT. Throws if `from` is not the current owner. Throws867 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.868 /// @param from The current owner of the NFT869 /// @param tokenId The NFT to transfer870 #[solidity(hide)]871 #[weight(<SelfWeightOf<T>>::burn_from())]872 fn burn_from(&mut self, caller: Caller, from: Address, token_id: U256) -> Result<()> {873 let caller = T::CrossAccountId::from_eth(caller);874 let from = T::CrossAccountId::from_eth(from);875 let token = token_id.try_into()?;876 let budget = self877 .recorder878 .weight_calls_budget(<StructureWeight<T>>::find_parent());879880 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)881 .map_err(dispatch_to_evm::<T>)?;882 Ok(())883 }884885 /// @notice Burns a specific ERC721 token.886 /// @dev Throws unless `msg.sender` is the current owner or an authorized887 /// operator for this NFT. Throws if `from` is not the current owner. Throws888 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.889 /// @param from The current owner of the NFT890 /// @param tokenId The NFT to transfer891 #[weight(<SelfWeightOf<T>>::burn_from())]892 fn burn_from_cross(893 &mut self,894 caller: Caller,895 from: eth::CrossAddress,896 token_id: U256,897 ) -> Result<()> {898 let caller = T::CrossAccountId::from_eth(caller);899 let from = from.into_sub_cross_account::<T>()?;900 let token = token_id.try_into()?;901 let budget = self902 .recorder903 .weight_calls_budget(<StructureWeight<T>>::find_parent());904905 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)906 .map_err(dispatch_to_evm::<T>)?;907 Ok(())908 }909910 /// @notice Returns next free NFT ID.911 fn next_token_id(&self) -> Result<U256> {912 self.consume_store_reads(1)?;913 Ok(<TokensMinted<T>>::get(self.id)914 .checked_add(1)915 .ok_or("item id overflow")?916 .into())917 }918919 /// @notice Function to mint multiple tokens.920 /// @dev `tokenIds` should be an array of consecutive numbers and first number921 /// should be obtained with `nextTokenId` method922 /// @param to The new owner923 /// @param tokenIds IDs of the minted NFTs924 #[solidity(hide)]925 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]926 fn mint_bulk(&mut self, caller: Caller, to: Address, token_ids: Vec<U256>) -> Result<bool> {927 let caller = T::CrossAccountId::from_eth(caller);928 let to = T::CrossAccountId::from_eth(to);929 let mut expected_index = <TokensMinted<T>>::get(self.id)930 .checked_add(1)931 .ok_or("item id overflow")?;932 let budget = self933 .recorder934 .weight_calls_budget(<StructureWeight<T>>::find_parent());935936 let total_tokens = token_ids.len();937 for id in token_ids.into_iter() {938 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;939 if id != expected_index {940 return Err("item id should be next".into());941 }942 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;943 }944 let data = (0..total_tokens)945 .map(|_| CreateItemData::<T> {946 properties: BoundedVec::default(),947 owner: to.clone(),948 })949 .collect();950951 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)952 .map_err(dispatch_to_evm::<T>)?;953 Ok(true)954 }955956 /// @notice Function to mint multiple tokens with the given tokenUris.957 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive958 /// numbers and first number should be obtained with `nextTokenId` method959 /// @param to The new owner960 /// @param tokens array of pairs of token ID and token URI for minted tokens961 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]962 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32) + <SelfWeightOf<T>>::set_token_properties(tokens.len() as u32))]963 fn mint_bulk_with_token_uri(964 &mut self,965 caller: Caller,966 to: Address,967 tokens: Vec<TokenUri>,968 ) -> Result<bool> {969 let key = key::url();970 let caller = T::CrossAccountId::from_eth(caller);971 let to = T::CrossAccountId::from_eth(to);972 let mut expected_index = <TokensMinted<T>>::get(self.id)973 .checked_add(1)974 .ok_or("item id overflow")?;975 let budget = self976 .recorder977 .weight_calls_budget(<StructureWeight<T>>::find_parent());978979 let mut data = Vec::with_capacity(tokens.len());980 for TokenUri { id, uri } in tokens {981 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;982 if id != expected_index {983 return Err("item id should be next".into());984 }985 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;986987 let mut properties = CollectionPropertiesVec::default();988 properties989 .try_push(Property {990 key: key.clone(),991 value: uri992 .into_bytes()993 .try_into()994 .map_err(|_| "token uri is too long")?,995 })996 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;997998 data.push(CreateItemData::<T> {999 properties,1000 owner: to.clone(),1001 });1002 }10031004 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)1005 .map_err(dispatch_to_evm::<T>)?;1006 Ok(true)1007 }10081009 /// @notice Function to mint a token.1010 /// @param to The new owner crossAccountId1011 /// @param properties Properties of minted token1012 /// @return uint256 The id of the newly minted token1013 #[weight(<SelfWeightOf<T>>::create_item() + <SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]1014 fn mint_cross(1015 &mut self,1016 caller: Caller,1017 to: eth::CrossAddress,1018 properties: Vec<eth::Property>,1019 ) -> Result<U256> {1020 let token_id = <TokensMinted<T>>::get(self.id)1021 .checked_add(1)1022 .ok_or("item id overflow")?;10231024 let to = to.into_sub_cross_account::<T>()?;10251026 let properties = properties1027 .into_iter()1028 .map(eth::Property::try_into)1029 .collect::<Result<Vec<_>>>()?1030 .try_into()1031 .map_err(|_| Error::Revert(alloc::format!("too many properties")))?;10321033 let caller = T::CrossAccountId::from_eth(caller);10341035 let budget = self1036 .recorder1037 .weight_calls_budget(<StructureWeight<T>>::find_parent());10381039 <Pallet<T>>::create_item(1040 self,1041 &caller,1042 CreateItemData::<T> {1043 properties,1044 owner: to,1045 },1046 &budget,1047 )1048 .map_err(dispatch_to_evm::<T>)?;10491050 Ok(token_id.into())1051 }10521053 /// @notice Returns collection helper contract address1054 fn collection_helper_address(&self) -> Address {1055 T::ContractAddress::get()1056 }1057}10581059#[solidity_interface(1060 name = UniqueNFT,1061 is(1062 ERC721,1063 ERC721Enumerable,1064 ERC721UniqueExtensions,1065 ERC721UniqueMintable,1066 ERC721Burnable,1067 ERC721Metadata(if(this.flags.erc721metadata)),1068 Collection(via(common_mut returns CollectionHandle<T>)),1069 TokenProperties,1070 ),1071 enum(derive(PreDispatch)),1072)]1073impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}10741075// Not a tests, but code generators1076generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1077generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10781079impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1080where1081 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1082{1083 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10841085 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1086 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1087 }1088}pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -19,7 +19,7 @@
/// @dev inlined interface
contract ERC721TokenEvent {
- event TokenChanged(address indexed collectionId, uint256 tokenId);
+ event TokenChanged(uint256 indexed tokenId);
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -64,10 +64,8 @@
pub enum ERC721TokenEvent {
/// The token has been changed.
TokenChanged {
- /// Collection ID.
+ /// Token ID.
#[indexed]
- collection_id: Address,
- /// Token ID.
token_id: U256,
},
}
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -19,7 +19,7 @@
/// @dev inlined interface
contract ERC721TokenEvent {
- event TokenChanged(address indexed collectionId, uint256 tokenId);
+ event TokenChanged(uint256 indexed tokenId);
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
tests/src/eth/abi/nonFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -54,12 +54,6 @@
"inputs": [
{
"indexed": true,
- "internalType": "address",
- "name": "collectionId",
- "type": "address"
- },
- {
- "indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
tests/src/eth/abi/reFungible.jsondiffbeforeafterboth--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -54,12 +54,6 @@
"inputs": [
{
"indexed": true,
- "internalType": "address",
- "name": "collectionId",
- "type": "address"
- },
- {
- "indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -14,7 +14,7 @@
/// @dev inlined interface
interface ERC721TokenEvent {
- event TokenChanged(address indexed collectionId, uint256 tokenId);
+ event TokenChanged(uint256 indexed tokenId);
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -14,7 +14,7 @@
/// @dev inlined interface
interface ERC721TokenEvent {
- event TokenChanged(address indexed collectionId, uint256 tokenId);
+ event TokenChanged(uint256 indexed tokenId);
}
/// @title A contract that allows to set and delete token properties and change token property permissions.
tests/src/eth/events.test.tsdiffbeforeafterboth--- a/tests/src/eth/events.test.ts
+++ b/tests/src/eth/events.test.ts
@@ -393,7 +393,6 @@
expect(result.events.TokenChanged).to.be.like({
event: 'TokenChanged',
returnValues: {
- collectionId: collectionAddress,
tokenId: tokenId,
},
});
@@ -406,7 +405,6 @@
expect(result.events.TokenChanged).to.be.like({
event: 'TokenChanged',
returnValues: {
- collectionId: collectionAddress,
tokenId: tokenId,
},
});