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