difftreelog
add `collectionComtractAddress` funtion to `CollectionHelpers` interface
in: master
13 files changed
pallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/erc.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29 types::Property as PropertyStruct, weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34 CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},40 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};45use sp_core::Get;4647use crate::{48 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,49 SelfWeightOf, weights::WeightInfo, TokenProperties,50};5152/// @title A contract that allows to set and delete token properties and change token property permissions.53#[solidity_interface(name = TokenProperties)]54impl<T: Config> NonfungibleHandle<T> {55 /// @notice Set permissions for token property.56 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.57 /// @param key Property key.58 /// @param isMutable Permission to mutate property.59 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.60 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.61 fn set_token_property_permission(62 &mut self,63 caller: caller,64 key: string,65 is_mutable: bool,66 collection_admin: bool,67 token_owner: bool,68 ) -> Result<()> {69 let caller = T::CrossAccountId::from_eth(caller);70 <Pallet<T>>::set_property_permission(71 self,72 &caller,73 PropertyKeyPermission {74 key: <Vec<u8>>::from(key)75 .try_into()76 .map_err(|_| "too long key")?,77 permission: PropertyPermission {78 mutable: is_mutable,79 collection_admin,80 token_owner,81 },82 },83 )84 .map_err(dispatch_to_evm::<T>)85 }8687 /// @notice Set token property value.88 /// @dev Throws error if `msg.sender` has no permission to edit the property.89 /// @param tokenId ID of the token.90 /// @param key Property key.91 /// @param value Property value.92 #[solidity(hide)]93 fn set_property(94 &mut self,95 caller: caller,96 token_id: uint256,97 key: string,98 value: bytes,99 ) -> Result<()> {100 let caller = T::CrossAccountId::from_eth(caller);101 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;102 let key = <Vec<u8>>::from(key)103 .try_into()104 .map_err(|_| "key too long")?;105 let value = value.0.try_into().map_err(|_| "value too long")?;106107 let nesting_budget = self108 .recorder109 .weight_calls_budget(<StructureWeight<T>>::find_parent());110111 <Pallet<T>>::set_token_property(112 self,113 &caller,114 TokenId(token_id),115 Property { key, value },116 &nesting_budget,117 )118 .map_err(dispatch_to_evm::<T>)119 }120121 /// @notice Set token properties value.122 /// @dev Throws error if `msg.sender` has no permission to edit the property.123 /// @param tokenId ID of the token.124 /// @param properties settable properties125 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]126 fn set_properties(127 &mut self,128 caller: caller,129 token_id: uint256,130 properties: Vec<PropertyStruct>,131 ) -> Result<()> {132 let caller = T::CrossAccountId::from_eth(caller);133 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;134135 let nesting_budget = self136 .recorder137 .weight_calls_budget(<StructureWeight<T>>::find_parent());138139 let properties = properties140 .into_iter()141 .map(|PropertyStruct { key, value }| {142 let key = <Vec<u8>>::from(key)143 .try_into()144 .map_err(|_| "key too large")?;145146 let value = value.0.try_into().map_err(|_| "value too large")?;147148 Ok(Property { key, value })149 })150 .collect::<Result<Vec<_>>>()?;151152 <Pallet<T>>::set_token_properties(153 self,154 &caller,155 TokenId(token_id),156 properties.into_iter(),157 <Pallet<T>>::token_exists(&self, TokenId(token_id)),158 &nesting_budget,159 )160 .map_err(dispatch_to_evm::<T>)161 }162163 /// @notice Delete token property value.164 /// @dev Throws error if `msg.sender` has no permission to edit the property.165 /// @param tokenId ID of the token.166 /// @param key Property key.167 #[solidity(hide)]168 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {169 let caller = T::CrossAccountId::from_eth(caller);170 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;171 let key = <Vec<u8>>::from(key)172 .try_into()173 .map_err(|_| "key too long")?;174175 let nesting_budget = self176 .recorder177 .weight_calls_budget(<StructureWeight<T>>::find_parent());178179 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)180 .map_err(dispatch_to_evm::<T>)181 }182183 /// @notice Delete token properties value.184 /// @dev Throws error if `msg.sender` has no permission to edit the property.185 /// @param tokenId ID of the token.186 /// @param keys Properties key.187 fn delete_properties(188 &mut self,189 token_id: uint256,190 caller: caller,191 keys: Vec<string>,192 ) -> Result<()> {193 let caller = T::CrossAccountId::from_eth(caller);194 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;195 let keys = keys196 .into_iter()197 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))198 .collect::<Result<Vec<_>>>()?;199200 let nesting_budget = self201 .recorder202 .weight_calls_budget(<StructureWeight<T>>::find_parent());203204 <Pallet<T>>::delete_token_properties(205 self,206 &caller,207 TokenId(token_id),208 keys.into_iter(),209 &nesting_budget,210 )211 .map_err(dispatch_to_evm::<T>)212 }213214 /// @notice Get token property value.215 /// @dev Throws error if key not found216 /// @param tokenId ID of the token.217 /// @param key Property key.218 /// @return Property value bytes219 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {220 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;221 let key = <Vec<u8>>::from(key)222 .try_into()223 .map_err(|_| "key too long")?;224225 let props = <TokenProperties<T>>::get((self.id, token_id));226 let prop = props.get(&key).ok_or("key not found")?;227228 Ok(prop.to_vec().into())229 }230}231232#[derive(ToLog)]233pub enum ERC721Events {234 /// @dev This emits when ownership of any NFT changes by any mechanism.235 /// This event emits when NFTs are created (`from` == 0) and destroyed236 /// (`to` == 0). Exception: during contract creation, any number of NFTs237 /// may be created and assigned without emitting Transfer. At the time of238 /// any transfer, the approved address for that NFT (if any) is reset to none.239 Transfer {240 #[indexed]241 from: address,242 #[indexed]243 to: address,244 #[indexed]245 token_id: uint256,246 },247 /// @dev This emits when the approved address for an NFT is changed or248 /// reaffirmed. The zero address indicates there is no approved address.249 /// When a Transfer event emits, this also indicates that the approved250 /// address for that NFT (if any) is reset to none.251 Approval {252 #[indexed]253 owner: address,254 #[indexed]255 approved: address,256 #[indexed]257 token_id: uint256,258 },259 /// @dev This emits when an operator is enabled or disabled for an owner.260 /// The operator can manage all NFTs of the owner.261 #[allow(dead_code)]262 ApprovalForAll {263 #[indexed]264 owner: address,265 #[indexed]266 operator: address,267 approved: bool,268 },269}270271#[derive(ToLog)]272pub enum ERC721UniqueMintableEvents {273 #[allow(dead_code)]274 MintingFinished {},275}276277/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension278/// @dev See https://eips.ethereum.org/EIPS/eip-721279#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]280impl<T: Config> NonfungibleHandle<T>281where282 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,283{284 /// @notice A descriptive name for a collection of NFTs in this contract285 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`286 #[solidity(hide, rename_selector = "name")]287 fn name_proxy(&self) -> Result<string> {288 self.name()289 }290291 /// @notice An abbreviated name for NFTs in this contract292 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`293 #[solidity(hide, rename_selector = "symbol")]294 fn symbol_proxy(&self) -> Result<string> {295 self.symbol()296 }297298 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.299 ///300 /// @dev If the token has a `url` property and it is not empty, it is returned.301 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.302 /// If the collection property `baseURI` is empty or absent, return "" (empty string)303 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix304 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).305 ///306 /// @return token's const_metadata307 #[solidity(rename_selector = "tokenURI")]308 fn token_uri(&self, token_id: uint256) -> Result<string> {309 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;310311 match get_token_property(self, token_id_u32, &key::url()).as_deref() {312 Err(_) | Ok("") => (),313 Ok(url) => {314 return Ok(url.into());315 }316 };317318 let base_uri =319 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())320 .map(BoundedVec::into_inner)321 .map(string::from_utf8)322 .transpose()323 .map_err(|e| {324 Error::Revert(alloc::format!(325 "Can not convert value \"baseURI\" to string with error \"{}\"",326 e327 ))328 })?;329330 let base_uri = match base_uri.as_deref() {331 None | Some("") => {332 return Ok("".into());333 }334 Some(base_uri) => base_uri.into(),335 };336337 Ok(338 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {339 Err(_) | Ok("") => base_uri,340 Ok(suffix) => base_uri + suffix,341 },342 )343 }344}345346/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension347/// @dev See https://eips.ethereum.org/EIPS/eip-721348#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]349impl<T: Config> NonfungibleHandle<T> {350 /// @notice Enumerate valid NFTs351 /// @param index A counter less than `totalSupply()`352 /// @return The token identifier for the `index`th NFT,353 /// (sort order not specified)354 fn token_by_index(&self, index: uint256) -> Result<uint256> {355 Ok(index)356 }357358 /// @dev Not implemented359 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {360 // TODO: Not implemetable361 Err("not implemented".into())362 }363364 /// @notice Count NFTs tracked by this contract365 /// @return A count of valid NFTs tracked by this contract, where each one of366 /// them has an assigned and queryable owner not equal to the zero address367 fn total_supply(&self) -> Result<uint256> {368 self.consume_store_reads(1)?;369 Ok(<Pallet<T>>::total_supply(self).into())370 }371}372373/// @title ERC-721 Non-Fungible Token Standard374/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md375#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]376impl<T: Config> NonfungibleHandle<T> {377 /// @notice Count all NFTs assigned to an owner378 /// @dev NFTs assigned to the zero address are considered invalid, and this379 /// function throws for queries about the zero address.380 /// @param owner An address for whom to query the balance381 /// @return The number of NFTs owned by `owner`, possibly zero382 fn balance_of(&self, owner: address) -> Result<uint256> {383 self.consume_store_reads(1)?;384 let owner = T::CrossAccountId::from_eth(owner);385 let balance = <AccountBalance<T>>::get((self.id, owner));386 Ok(balance.into())387 }388 /// @notice Find the owner of an NFT389 /// @dev NFTs assigned to zero address are considered invalid, and queries390 /// about them do throw.391 /// @param tokenId The identifier for an NFT392 /// @return The address of the owner of the NFT393 fn owner_of(&self, token_id: uint256) -> Result<address> {394 self.consume_store_reads(1)?;395 let token: TokenId = token_id.try_into()?;396 Ok(*<TokenData<T>>::get((self.id, token))397 .ok_or("token not found")?398 .owner399 .as_eth())400 }401 /// @dev Not implemented402 #[solidity(rename_selector = "safeTransferFrom")]403 fn safe_transfer_from_with_data(404 &mut self,405 _from: address,406 _to: address,407 _token_id: uint256,408 _data: bytes,409 ) -> Result<void> {410 // TODO: Not implemetable411 Err("not implemented".into())412 }413 /// @dev Not implemented414 fn safe_transfer_from(415 &mut self,416 _from: address,417 _to: address,418 _token_id: uint256,419 ) -> Result<void> {420 // TODO: Not implemetable421 Err("not implemented".into())422 }423424 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE425 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE426 /// THEY MAY BE PERMANENTLY LOST427 /// @dev Throws unless `msg.sender` is the current owner or an authorized428 /// operator for this NFT. Throws if `from` is not the current owner. Throws429 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.430 /// @param from The current owner of the NFT431 /// @param to The new owner432 /// @param tokenId The NFT to transfer433 #[weight(<SelfWeightOf<T>>::transfer_from())]434 fn transfer_from(435 &mut self,436 caller: caller,437 from: address,438 to: address,439 token_id: uint256,440 ) -> Result<void> {441 let caller = T::CrossAccountId::from_eth(caller);442 let from = T::CrossAccountId::from_eth(from);443 let to = T::CrossAccountId::from_eth(to);444 let token = token_id.try_into()?;445 let budget = self446 .recorder447 .weight_calls_budget(<StructureWeight<T>>::find_parent());448449 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)450 .map_err(dispatch_to_evm::<T>)?;451 Ok(())452 }453454 /// @notice Set or reaffirm the approved address for an NFT455 /// @dev The zero address indicates there is no approved address.456 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized457 /// operator of the current owner.458 /// @param approved The new approved NFT controller459 /// @param tokenId The NFT to approve460 #[weight(<SelfWeightOf<T>>::approve())]461 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {462 let caller = T::CrossAccountId::from_eth(caller);463 let approved = T::CrossAccountId::from_eth(approved);464 let token = token_id.try_into()?;465466 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))467 .map_err(dispatch_to_evm::<T>)?;468 Ok(())469 }470471 /// @dev Not implemented472 fn set_approval_for_all(473 &mut self,474 _caller: caller,475 _operator: address,476 _approved: bool,477 ) -> Result<void> {478 // TODO: Not implemetable479 Err("not implemented".into())480 }481482 /// @dev Not implemented483 fn get_approved(&self, _token_id: uint256) -> Result<address> {484 // TODO: Not implemetable485 Err("not implemented".into())486 }487488 /// @dev Not implemented489 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {490 // TODO: Not implemetable491 Err("not implemented".into())492 }493 494 /// @notice Returns collection helper contract address 495 fn collection_helper_address(&self) -> Result<address> {496 Ok(T::ContractAddress::get())497 }498}499500/// @title ERC721 Token that can be irreversibly burned (destroyed).501#[solidity_interface(name = ERC721Burnable)]502impl<T: Config> NonfungibleHandle<T> {503 /// @notice Burns a specific ERC721 token.504 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized505 /// operator of the current owner.506 /// @param tokenId The NFT to approve507 #[weight(<SelfWeightOf<T>>::burn_item())]508 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {509 let caller = T::CrossAccountId::from_eth(caller);510 let token = token_id.try_into()?;511512 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;513 Ok(())514 }515}516517/// @title ERC721 minting logic.518#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]519impl<T: Config> NonfungibleHandle<T> {520 fn minting_finished(&self) -> Result<bool> {521 Ok(false)522 }523524 /// @notice Function to mint token.525 /// @param to The new owner526 /// @return uint256 The id of the newly minted token527 #[weight(<SelfWeightOf<T>>::create_item())]528 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {529 let token_id: uint256 = <TokensMinted<T>>::get(self.id)530 .checked_add(1)531 .ok_or("item id overflow")?532 .into();533 self.mint_check_id(caller, to, token_id)?;534 Ok(token_id)535 }536537 /// @notice Function to mint token.538 /// @dev `tokenId` should be obtained with `nextTokenId` method,539 /// unlike standard, you can't specify it manually540 /// @param to The new owner541 /// @param tokenId ID of the minted NFT542 #[solidity(hide, rename_selector = "mint")]543 #[weight(<SelfWeightOf<T>>::create_item())]544 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {545 let caller = T::CrossAccountId::from_eth(caller);546 let to = T::CrossAccountId::from_eth(to);547 let token_id: u32 = token_id.try_into()?;548 let budget = self549 .recorder550 .weight_calls_budget(<StructureWeight<T>>::find_parent());551552 if <TokensMinted<T>>::get(self.id)553 .checked_add(1)554 .ok_or("item id overflow")?555 != token_id556 {557 return Err("item id should be next".into());558 }559560 <Pallet<T>>::create_item(561 self,562 &caller,563 CreateItemData::<T> {564 properties: BoundedVec::default(),565 owner: to,566 },567 &budget,568 )569 .map_err(dispatch_to_evm::<T>)?;570571 Ok(true)572 }573574 /// @notice Function to mint token with the given tokenUri.575 /// @param to The new owner576 /// @param tokenUri Token URI that would be stored in the NFT properties577 /// @return uint256 The id of the newly minted token578 #[solidity(rename_selector = "mintWithTokenURI")]579 #[weight(<SelfWeightOf<T>>::create_item())]580 fn mint_with_token_uri(581 &mut self,582 caller: caller,583 to: address,584 token_uri: string,585 ) -> Result<uint256> {586 let token_id: uint256 = <TokensMinted<T>>::get(self.id)587 .checked_add(1)588 .ok_or("item id overflow")?589 .into();590 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;591 Ok(token_id)592 }593594 /// @notice Function to mint token with the given tokenUri.595 /// @dev `tokenId` should be obtained with `nextTokenId` method,596 /// unlike standard, you can't specify it manually597 /// @param to The new owner598 /// @param tokenId ID of the minted NFT599 /// @param tokenUri Token URI that would be stored in the NFT properties600 #[solidity(hide, rename_selector = "mintWithTokenURI")]601 #[weight(<SelfWeightOf<T>>::create_item())]602 fn mint_with_token_uri_check_id(603 &mut self,604 caller: caller,605 to: address,606 token_id: uint256,607 token_uri: string,608 ) -> Result<bool> {609 let key = key::url();610 let permission = get_token_permission::<T>(self.id, &key)?;611 if !permission.collection_admin {612 return Err("Operation is not allowed".into());613 }614615 let caller = T::CrossAccountId::from_eth(caller);616 let to = T::CrossAccountId::from_eth(to);617 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;618 let budget = self619 .recorder620 .weight_calls_budget(<StructureWeight<T>>::find_parent());621622 if <TokensMinted<T>>::get(self.id)623 .checked_add(1)624 .ok_or("item id overflow")?625 != token_id626 {627 return Err("item id should be next".into());628 }629630 let mut properties = CollectionPropertiesVec::default();631 properties632 .try_push(Property {633 key,634 value: token_uri635 .into_bytes()636 .try_into()637 .map_err(|_| "token uri is too long")?,638 })639 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;640641 <Pallet<T>>::create_item(642 self,643 &caller,644 CreateItemData::<T> {645 properties,646 owner: to,647 },648 &budget,649 )650 .map_err(dispatch_to_evm::<T>)?;651 Ok(true)652 }653654 /// @dev Not implemented655 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {656 Err("not implementable".into())657 }658}659660fn get_token_property<T: Config>(661 collection: &CollectionHandle<T>,662 token_id: u32,663 key: &up_data_structs::PropertyKey,664) -> Result<string> {665 collection.consume_store_reads(1)?;666 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))667 .map_err(|_| Error::Revert("Token properties not found".into()))?;668 if let Some(property) = properties.get(key) {669 return Ok(string::from_utf8_lossy(property).into());670 }671672 Err("Property tokenURI not found".into())673}674675fn get_token_permission<T: Config>(676 collection_id: CollectionId,677 key: &PropertyKey,678) -> Result<PropertyPermission> {679 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)680 .map_err(|_| Error::Revert("No permissions for collection".into()))?;681 let a = token_property_permissions682 .get(key)683 .map(Clone::clone)684 .ok_or_else(|| {685 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();686 Error::Revert(alloc::format!("No permission for key {}", key))687 })?;688 Ok(a)689}690691/// @title Unique extensions for ERC721.692#[solidity_interface(name = ERC721UniqueExtensions)]693impl<T: Config> NonfungibleHandle<T>694where695 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,696{697 /// @notice A descriptive name for a collection of NFTs in this contract698 fn name(&self) -> Result<string> {699 Ok(decode_utf16(self.name.iter().copied())700 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))701 .collect::<string>())702 }703704 /// @notice An abbreviated name for NFTs in this contract705 fn symbol(&self) -> Result<string> {706 Ok(string::from_utf8_lossy(&self.token_prefix).into())707 }708709 /// @notice A description for the collection.710 fn description(&self) -> Result<string> {711 Ok(decode_utf16(self.description.iter().copied())712 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))713 .collect::<string>())714 }715716 /// Returns the owner (in cross format) of the token.717 ///718 /// @param tokenId Id for the token.719 fn cross_owner_of(&self, token_id: uint256) -> Result<EthCrossAccount> {720 Self::token_owner(&self, token_id.try_into()?)721 .map(|o| EthCrossAccount::from_sub_cross_account::<T>(&o))722 .ok_or(Error::Revert("key too large".into()))723 }724725 /// Returns the token properties.726 ///727 /// @param tokenId Id for the token.728 /// @param keys Properties keys. Empty keys for all propertyes.729 /// @return Vector of properties key/value pairs.730 fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {731 let keys = keys732 .into_iter()733 .map(|key| {734 <Vec<u8>>::from(key)735 .try_into()736 .map_err(|_| Error::Revert("key too large".into()))737 })738 .collect::<Result<Vec<_>>>()?;739740 <Self as CommonCollectionOperations<T>>::token_properties(741 &self,742 token_id.try_into()?,743 if keys.is_empty() { None } else { Some(keys) },744 )745 .into_iter()746 .map(|p| {747 let key = string::from_utf8(p.key.to_vec())748 .map_err(|e| Error::Revert(alloc::format!("{}", e)))?;749 let value = bytes(p.value.to_vec());750 Ok(PropertyStruct { key, value })751 })752 .collect::<Result<Vec<_>>>()753 }754755 /// @notice Set or reaffirm the approved address for an NFT756 /// @dev The zero address indicates there is no approved address.757 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized758 /// operator of the current owner.759 /// @param approved The new substrate address approved NFT controller760 /// @param tokenId The NFT to approve761 #[weight(<SelfWeightOf<T>>::approve())]762 fn approve_cross(763 &mut self,764 caller: caller,765 approved: EthCrossAccount,766 token_id: uint256,767 ) -> Result<void> {768 let caller = T::CrossAccountId::from_eth(caller);769 let approved = approved.into_sub_cross_account::<T>()?;770 let token = token_id.try_into()?;771772 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))773 .map_err(dispatch_to_evm::<T>)?;774 Ok(())775 }776777 /// @notice Transfer ownership of an NFT778 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`779 /// is the zero address. Throws if `tokenId` is not a valid NFT.780 /// @param to The new owner781 /// @param tokenId The NFT to transfer782 #[weight(<SelfWeightOf<T>>::transfer())]783 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {784 let caller = T::CrossAccountId::from_eth(caller);785 let to = T::CrossAccountId::from_eth(to);786 let token = token_id.try_into()?;787 let budget = self788 .recorder789 .weight_calls_budget(<StructureWeight<T>>::find_parent());790791 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;792 Ok(())793 }794795 /// @notice Transfer ownership of an NFT796 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`797 /// is the zero address. Throws if `tokenId` is not a valid NFT.798 /// @param to The new owner799 /// @param tokenId The NFT to transfer800 #[weight(<SelfWeightOf<T>>::transfer())]801 fn transfer_cross(802 &mut self,803 caller: caller,804 to: EthCrossAccount,805 token_id: uint256,806 ) -> Result<void> {807 let caller = T::CrossAccountId::from_eth(caller);808 let to = to.into_sub_cross_account::<T>()?;809 let token = token_id.try_into()?;810 let budget = self811 .recorder812 .weight_calls_budget(<StructureWeight<T>>::find_parent());813814 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;815 Ok(())816 }817818 /// @notice Transfer ownership of an NFT from cross account address to cross account address819 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`820 /// is the zero address. Throws if `tokenId` is not a valid NFT.821 /// @param from Cross acccount address of current owner822 /// @param to Cross acccount address of new owner823 /// @param tokenId The NFT to transfer824 #[weight(<SelfWeightOf<T>>::transfer())]825 fn transfer_from_cross(826 &mut self,827 caller: caller,828 from: EthCrossAccount,829 to: EthCrossAccount,830 token_id: uint256,831 ) -> Result<void> {832 let caller = T::CrossAccountId::from_eth(caller);833 let from = from.into_sub_cross_account::<T>()?;834 let to = to.into_sub_cross_account::<T>()?;835 let token_id = token_id.try_into()?;836 let budget = self837 .recorder838 .weight_calls_budget(<StructureWeight<T>>::find_parent());839 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)840 .map_err(dispatch_to_evm::<T>)?;841 Ok(())842 }843844 /// @notice Burns a specific ERC721 token.845 /// @dev Throws unless `msg.sender` is the current owner or an authorized846 /// operator for this NFT. Throws if `from` is not the current owner. Throws847 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.848 /// @param from The current owner of the NFT849 /// @param tokenId The NFT to transfer850 #[solidity(hide)]851 #[weight(<SelfWeightOf<T>>::burn_from())]852 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {853 let caller = T::CrossAccountId::from_eth(caller);854 let from = T::CrossAccountId::from_eth(from);855 let token = token_id.try_into()?;856 let budget = self857 .recorder858 .weight_calls_budget(<StructureWeight<T>>::find_parent());859860 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)861 .map_err(dispatch_to_evm::<T>)?;862 Ok(())863 }864865 /// @notice Burns a specific ERC721 token.866 /// @dev Throws unless `msg.sender` is the current owner or an authorized867 /// operator for this NFT. Throws if `from` is not the current owner. Throws868 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.869 /// @param from The current owner of the NFT870 /// @param tokenId The NFT to transfer871 #[weight(<SelfWeightOf<T>>::burn_from())]872 fn burn_from_cross(873 &mut self,874 caller: caller,875 from: EthCrossAccount,876 token_id: uint256,877 ) -> Result<void> {878 let caller = T::CrossAccountId::from_eth(caller);879 let from = from.into_sub_cross_account::<T>()?;880 let token = token_id.try_into()?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884885 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)886 .map_err(dispatch_to_evm::<T>)?;887 Ok(())888 }889890 /// @notice Returns next free NFT ID.891 fn next_token_id(&self) -> Result<uint256> {892 self.consume_store_reads(1)?;893 Ok(<TokensMinted<T>>::get(self.id)894 .checked_add(1)895 .ok_or("item id overflow")?896 .into())897 }898899 /// @notice Function to mint multiple tokens.900 /// @dev `tokenIds` should be an array of consecutive numbers and first number901 /// should be obtained with `nextTokenId` method902 /// @param to The new owner903 /// @param tokenIds IDs of the minted NFTs904 #[solidity(hide)]905 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]906 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {907 let caller = T::CrossAccountId::from_eth(caller);908 let to = T::CrossAccountId::from_eth(to);909 let mut expected_index = <TokensMinted<T>>::get(self.id)910 .checked_add(1)911 .ok_or("item id overflow")?;912 let budget = self913 .recorder914 .weight_calls_budget(<StructureWeight<T>>::find_parent());915916 let total_tokens = token_ids.len();917 for id in token_ids.into_iter() {918 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;919 if id != expected_index {920 return Err("item id should be next".into());921 }922 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;923 }924 let data = (0..total_tokens)925 .map(|_| CreateItemData::<T> {926 properties: BoundedVec::default(),927 owner: to.clone(),928 })929 .collect();930931 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)932 .map_err(dispatch_to_evm::<T>)?;933 Ok(true)934 }935936 /// @notice Function to mint multiple tokens with the given tokenUris.937 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive938 /// numbers and first number should be obtained with `nextTokenId` method939 /// @param to The new owner940 /// @param tokens array of pairs of token ID and token URI for minted tokens941 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]942 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]943 fn mint_bulk_with_token_uri(944 &mut self,945 caller: caller,946 to: address,947 tokens: Vec<(uint256, string)>,948 ) -> Result<bool> {949 let key = key::url();950 let caller = T::CrossAccountId::from_eth(caller);951 let to = T::CrossAccountId::from_eth(to);952 let mut expected_index = <TokensMinted<T>>::get(self.id)953 .checked_add(1)954 .ok_or("item id overflow")?;955 let budget = self956 .recorder957 .weight_calls_budget(<StructureWeight<T>>::find_parent());958959 let mut data = Vec::with_capacity(tokens.len());960 for (id, token_uri) in tokens {961 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;962 if id != expected_index {963 return Err("item id should be next".into());964 }965 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;966967 let mut properties = CollectionPropertiesVec::default();968 properties969 .try_push(Property {970 key: key.clone(),971 value: token_uri972 .into_bytes()973 .try_into()974 .map_err(|_| "token uri is too long")?,975 })976 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;977978 data.push(CreateItemData::<T> {979 properties,980 owner: to.clone(),981 });982 }983984 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)985 .map_err(dispatch_to_evm::<T>)?;986 Ok(true)987 }988}989990#[solidity_interface(991 name = UniqueNFT,992 is(993 ERC721,994 ERC721Enumerable,995 ERC721UniqueExtensions,996 ERC721UniqueMintable,997 ERC721Burnable,998 ERC721Metadata(if(this.flags.erc721metadata)),999 Collection(via(common_mut returns CollectionHandle<T>)),1000 TokenProperties,1001 )1002)]1003impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}10041005// Not a tests, but code generators1006generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1007generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10081009impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1010where1011 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1012{1013 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10141015 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1016 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1017 }1018}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet EVM API18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Nonfungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.2122extern crate alloc;23use core::{24 char::{REPLACEMENT_CHARACTER, decode_utf16},25 convert::TryInto,26};27use evm_coder::{28 abi::AbiType, ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*,29 types::Property as PropertyStruct, weight,30};31use frame_support::BoundedVec;32use up_data_structs::{33 TokenId, PropertyPermission, PropertyKeyPermission, Property, CollectionId, PropertyKey,34 CollectionPropertiesVec,35};36use pallet_evm_coder_substrate::dispatch_to_evm;37use sp_std::vec::Vec;38use pallet_common::{39 erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},40 CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,41};42use pallet_evm::{account::CrossAccountId, PrecompileHandle};43use pallet_evm_coder_substrate::call;44use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};45use sp_core::Get;4647use crate::{48 AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,49 SelfWeightOf, weights::WeightInfo, TokenProperties,50};5152/// @title A contract that allows to set and delete token properties and change token property permissions.53#[solidity_interface(name = TokenProperties)]54impl<T: Config> NonfungibleHandle<T> {55 /// @notice Set permissions for token property.56 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.57 /// @param key Property key.58 /// @param isMutable Permission to mutate property.59 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.60 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.61 fn set_token_property_permission(62 &mut self,63 caller: caller,64 key: string,65 is_mutable: bool,66 collection_admin: bool,67 token_owner: bool,68 ) -> Result<()> {69 let caller = T::CrossAccountId::from_eth(caller);70 <Pallet<T>>::set_property_permission(71 self,72 &caller,73 PropertyKeyPermission {74 key: <Vec<u8>>::from(key)75 .try_into()76 .map_err(|_| "too long key")?,77 permission: PropertyPermission {78 mutable: is_mutable,79 collection_admin,80 token_owner,81 },82 },83 )84 .map_err(dispatch_to_evm::<T>)85 }8687 /// @notice Set token property value.88 /// @dev Throws error if `msg.sender` has no permission to edit the property.89 /// @param tokenId ID of the token.90 /// @param key Property key.91 /// @param value Property value.92 #[solidity(hide)]93 fn set_property(94 &mut self,95 caller: caller,96 token_id: uint256,97 key: string,98 value: bytes,99 ) -> Result<()> {100 let caller = T::CrossAccountId::from_eth(caller);101 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;102 let key = <Vec<u8>>::from(key)103 .try_into()104 .map_err(|_| "key too long")?;105 let value = value.0.try_into().map_err(|_| "value too long")?;106107 let nesting_budget = self108 .recorder109 .weight_calls_budget(<StructureWeight<T>>::find_parent());110111 <Pallet<T>>::set_token_property(112 self,113 &caller,114 TokenId(token_id),115 Property { key, value },116 &nesting_budget,117 )118 .map_err(dispatch_to_evm::<T>)119 }120121 /// @notice Set token properties value.122 /// @dev Throws error if `msg.sender` has no permission to edit the property.123 /// @param tokenId ID of the token.124 /// @param properties settable properties125 #[weight(<SelfWeightOf<T>>::set_token_properties(properties.len() as u32))]126 fn set_properties(127 &mut self,128 caller: caller,129 token_id: uint256,130 properties: Vec<PropertyStruct>,131 ) -> Result<()> {132 let caller = T::CrossAccountId::from_eth(caller);133 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;134135 let nesting_budget = self136 .recorder137 .weight_calls_budget(<StructureWeight<T>>::find_parent());138139 let properties = properties140 .into_iter()141 .map(|PropertyStruct { key, value }| {142 let key = <Vec<u8>>::from(key)143 .try_into()144 .map_err(|_| "key too large")?;145146 let value = value.0.try_into().map_err(|_| "value too large")?;147148 Ok(Property { key, value })149 })150 .collect::<Result<Vec<_>>>()?;151152 <Pallet<T>>::set_token_properties(153 self,154 &caller,155 TokenId(token_id),156 properties.into_iter(),157 <Pallet<T>>::token_exists(&self, TokenId(token_id)),158 &nesting_budget,159 )160 .map_err(dispatch_to_evm::<T>)161 }162163 /// @notice Delete token property value.164 /// @dev Throws error if `msg.sender` has no permission to edit the property.165 /// @param tokenId ID of the token.166 /// @param key Property key.167 #[solidity(hide)]168 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {169 let caller = T::CrossAccountId::from_eth(caller);170 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;171 let key = <Vec<u8>>::from(key)172 .try_into()173 .map_err(|_| "key too long")?;174175 let nesting_budget = self176 .recorder177 .weight_calls_budget(<StructureWeight<T>>::find_parent());178179 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)180 .map_err(dispatch_to_evm::<T>)181 }182183 /// @notice Delete token properties value.184 /// @dev Throws error if `msg.sender` has no permission to edit the property.185 /// @param tokenId ID of the token.186 /// @param keys Properties key.187 fn delete_properties(188 &mut self,189 token_id: uint256,190 caller: caller,191 keys: Vec<string>,192 ) -> Result<()> {193 let caller = T::CrossAccountId::from_eth(caller);194 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;195 let keys = keys196 .into_iter()197 .map(|k| Ok(<Vec<u8>>::from(k).try_into().map_err(|_| "key too long")?))198 .collect::<Result<Vec<_>>>()?;199200 let nesting_budget = self201 .recorder202 .weight_calls_budget(<StructureWeight<T>>::find_parent());203204 <Pallet<T>>::delete_token_properties(205 self,206 &caller,207 TokenId(token_id),208 keys.into_iter(),209 &nesting_budget,210 )211 .map_err(dispatch_to_evm::<T>)212 }213214 /// @notice Get token property value.215 /// @dev Throws error if key not found216 /// @param tokenId ID of the token.217 /// @param key Property key.218 /// @return Property value bytes219 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {220 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;221 let key = <Vec<u8>>::from(key)222 .try_into()223 .map_err(|_| "key too long")?;224225 let props = <TokenProperties<T>>::get((self.id, token_id));226 let prop = props.get(&key).ok_or("key not found")?;227228 Ok(prop.to_vec().into())229 }230}231232#[derive(ToLog)]233pub enum ERC721Events {234 /// @dev This emits when ownership of any NFT changes by any mechanism.235 /// This event emits when NFTs are created (`from` == 0) and destroyed236 /// (`to` == 0). Exception: during contract creation, any number of NFTs237 /// may be created and assigned without emitting Transfer. At the time of238 /// any transfer, the approved address for that NFT (if any) is reset to none.239 Transfer {240 #[indexed]241 from: address,242 #[indexed]243 to: address,244 #[indexed]245 token_id: uint256,246 },247 /// @dev This emits when the approved address for an NFT is changed or248 /// reaffirmed. The zero address indicates there is no approved address.249 /// When a Transfer event emits, this also indicates that the approved250 /// address for that NFT (if any) is reset to none.251 Approval {252 #[indexed]253 owner: address,254 #[indexed]255 approved: address,256 #[indexed]257 token_id: uint256,258 },259 /// @dev This emits when an operator is enabled or disabled for an owner.260 /// The operator can manage all NFTs of the owner.261 #[allow(dead_code)]262 ApprovalForAll {263 #[indexed]264 owner: address,265 #[indexed]266 operator: address,267 approved: bool,268 },269}270271#[derive(ToLog)]272pub enum ERC721UniqueMintableEvents {273 #[allow(dead_code)]274 MintingFinished {},275}276277/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension278/// @dev See https://eips.ethereum.org/EIPS/eip-721279#[solidity_interface(name = ERC721Metadata, expect_selector = 0x5b5e139f)]280impl<T: Config> NonfungibleHandle<T>281where282 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,283{284 /// @notice A descriptive name for a collection of NFTs in this contract285 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`286 #[solidity(hide, rename_selector = "name")]287 fn name_proxy(&self) -> Result<string> {288 self.name()289 }290291 /// @notice An abbreviated name for NFTs in this contract292 /// @dev real implementation of this function lies in `ERC721UniqueExtensions`293 #[solidity(hide, rename_selector = "symbol")]294 fn symbol_proxy(&self) -> Result<string> {295 self.symbol()296 }297298 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.299 ///300 /// @dev If the token has a `url` property and it is not empty, it is returned.301 /// Else If the collection does not have a property with key `schemaName` or its value is not equal to `ERC721Metadata`, it return an error `tokenURI not set`.302 /// If the collection property `baseURI` is empty or absent, return "" (empty string)303 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix304 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).305 ///306 /// @return token's const_metadata307 #[solidity(rename_selector = "tokenURI")]308 fn token_uri(&self, token_id: uint256) -> Result<string> {309 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;310311 match get_token_property(self, token_id_u32, &key::url()).as_deref() {312 Err(_) | Ok("") => (),313 Ok(url) => {314 return Ok(url.into());315 }316 };317318 let base_uri =319 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())320 .map(BoundedVec::into_inner)321 .map(string::from_utf8)322 .transpose()323 .map_err(|e| {324 Error::Revert(alloc::format!(325 "Can not convert value \"baseURI\" to string with error \"{}\"",326 e327 ))328 })?;329330 let base_uri = match base_uri.as_deref() {331 None | Some("") => {332 return Ok("".into());333 }334 Some(base_uri) => base_uri.into(),335 };336337 Ok(338 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {339 Err(_) | Ok("") => base_uri,340 Ok(suffix) => base_uri + suffix,341 },342 )343 }344}345346/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension347/// @dev See https://eips.ethereum.org/EIPS/eip-721348#[solidity_interface(name = ERC721Enumerable, expect_selector = 0x780e9d63)]349impl<T: Config> NonfungibleHandle<T> {350 /// @notice Enumerate valid NFTs351 /// @param index A counter less than `totalSupply()`352 /// @return The token identifier for the `index`th NFT,353 /// (sort order not specified)354 fn token_by_index(&self, index: uint256) -> Result<uint256> {355 Ok(index)356 }357358 /// @dev Not implemented359 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {360 // TODO: Not implemetable361 Err("not implemented".into())362 }363364 /// @notice Count NFTs tracked by this contract365 /// @return A count of valid NFTs tracked by this contract, where each one of366 /// them has an assigned and queryable owner not equal to the zero address367 fn total_supply(&self) -> Result<uint256> {368 self.consume_store_reads(1)?;369 Ok(<Pallet<T>>::total_supply(self).into())370 }371}372373/// @title ERC-721 Non-Fungible Token Standard374/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md375#[solidity_interface(name = ERC721, events(ERC721Events), expect_selector = 0x80ac58cd)]376impl<T: Config> NonfungibleHandle<T> {377 /// @notice Count all NFTs assigned to an owner378 /// @dev NFTs assigned to the zero address are considered invalid, and this379 /// function throws for queries about the zero address.380 /// @param owner An address for whom to query the balance381 /// @return The number of NFTs owned by `owner`, possibly zero382 fn balance_of(&self, owner: address) -> Result<uint256> {383 self.consume_store_reads(1)?;384 let owner = T::CrossAccountId::from_eth(owner);385 let balance = <AccountBalance<T>>::get((self.id, owner));386 Ok(balance.into())387 }388 /// @notice Find the owner of an NFT389 /// @dev NFTs assigned to zero address are considered invalid, and queries390 /// about them do throw.391 /// @param tokenId The identifier for an NFT392 /// @return The address of the owner of the NFT393 fn owner_of(&self, token_id: uint256) -> Result<address> {394 self.consume_store_reads(1)?;395 let token: TokenId = token_id.try_into()?;396 Ok(*<TokenData<T>>::get((self.id, token))397 .ok_or("token not found")?398 .owner399 .as_eth())400 }401 /// @dev Not implemented402 #[solidity(rename_selector = "safeTransferFrom")]403 fn safe_transfer_from_with_data(404 &mut self,405 _from: address,406 _to: address,407 _token_id: uint256,408 _data: bytes,409 ) -> Result<void> {410 // TODO: Not implemetable411 Err("not implemented".into())412 }413 /// @dev Not implemented414 fn safe_transfer_from(415 &mut self,416 _from: address,417 _to: address,418 _token_id: uint256,419 ) -> Result<void> {420 // TODO: Not implemetable421 Err("not implemented".into())422 }423424 /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE425 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE426 /// THEY MAY BE PERMANENTLY LOST427 /// @dev Throws unless `msg.sender` is the current owner or an authorized428 /// operator for this NFT. Throws if `from` is not the current owner. Throws429 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.430 /// @param from The current owner of the NFT431 /// @param to The new owner432 /// @param tokenId The NFT to transfer433 #[weight(<SelfWeightOf<T>>::transfer_from())]434 fn transfer_from(435 &mut self,436 caller: caller,437 from: address,438 to: address,439 token_id: uint256,440 ) -> Result<void> {441 let caller = T::CrossAccountId::from_eth(caller);442 let from = T::CrossAccountId::from_eth(from);443 let to = T::CrossAccountId::from_eth(to);444 let token = token_id.try_into()?;445 let budget = self446 .recorder447 .weight_calls_budget(<StructureWeight<T>>::find_parent());448449 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, &budget)450 .map_err(dispatch_to_evm::<T>)?;451 Ok(())452 }453454 /// @notice Set or reaffirm the approved address for an NFT455 /// @dev The zero address indicates there is no approved address.456 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized457 /// operator of the current owner.458 /// @param approved The new approved NFT controller459 /// @param tokenId The NFT to approve460 #[weight(<SelfWeightOf<T>>::approve())]461 fn approve(&mut self, caller: caller, approved: address, token_id: uint256) -> Result<void> {462 let caller = T::CrossAccountId::from_eth(caller);463 let approved = T::CrossAccountId::from_eth(approved);464 let token = token_id.try_into()?;465466 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))467 .map_err(dispatch_to_evm::<T>)?;468 Ok(())469 }470471 /// @dev Not implemented472 fn set_approval_for_all(473 &mut self,474 _caller: caller,475 _operator: address,476 _approved: bool,477 ) -> Result<void> {478 // TODO: Not implemetable479 Err("not implemented".into())480 }481482 /// @dev Not implemented483 fn get_approved(&self, _token_id: uint256) -> Result<address> {484 // TODO: Not implemetable485 Err("not implemented".into())486 }487488 /// @dev Not implemented489 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {490 // TODO: Not implemetable491 Err("not implemented".into())492 }493494 /// @notice Returns collection helper contract address495 fn collection_helper_address(&self) -> Result<address> {496 Ok(T::ContractAddress::get())497 }498}499500/// @title ERC721 Token that can be irreversibly burned (destroyed).501#[solidity_interface(name = ERC721Burnable)]502impl<T: Config> NonfungibleHandle<T> {503 /// @notice Burns a specific ERC721 token.504 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized505 /// operator of the current owner.506 /// @param tokenId The NFT to approve507 #[weight(<SelfWeightOf<T>>::burn_item())]508 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {509 let caller = T::CrossAccountId::from_eth(caller);510 let token = token_id.try_into()?;511512 <Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;513 Ok(())514 }515}516517/// @title ERC721 minting logic.518#[solidity_interface(name = ERC721UniqueMintable, events(ERC721UniqueMintableEvents))]519impl<T: Config> NonfungibleHandle<T> {520 fn minting_finished(&self) -> Result<bool> {521 Ok(false)522 }523524 /// @notice Function to mint token.525 /// @param to The new owner526 /// @return uint256 The id of the newly minted token527 #[weight(<SelfWeightOf<T>>::create_item())]528 fn mint(&mut self, caller: caller, to: address) -> Result<uint256> {529 let token_id: uint256 = <TokensMinted<T>>::get(self.id)530 .checked_add(1)531 .ok_or("item id overflow")?532 .into();533 self.mint_check_id(caller, to, token_id)?;534 Ok(token_id)535 }536537 /// @notice Function to mint token.538 /// @dev `tokenId` should be obtained with `nextTokenId` method,539 /// unlike standard, you can't specify it manually540 /// @param to The new owner541 /// @param tokenId ID of the minted NFT542 #[solidity(hide, rename_selector = "mint")]543 #[weight(<SelfWeightOf<T>>::create_item())]544 fn mint_check_id(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {545 let caller = T::CrossAccountId::from_eth(caller);546 let to = T::CrossAccountId::from_eth(to);547 let token_id: u32 = token_id.try_into()?;548 let budget = self549 .recorder550 .weight_calls_budget(<StructureWeight<T>>::find_parent());551552 if <TokensMinted<T>>::get(self.id)553 .checked_add(1)554 .ok_or("item id overflow")?555 != token_id556 {557 return Err("item id should be next".into());558 }559560 <Pallet<T>>::create_item(561 self,562 &caller,563 CreateItemData::<T> {564 properties: BoundedVec::default(),565 owner: to,566 },567 &budget,568 )569 .map_err(dispatch_to_evm::<T>)?;570571 Ok(true)572 }573574 /// @notice Function to mint token with the given tokenUri.575 /// @param to The new owner576 /// @param tokenUri Token URI that would be stored in the NFT properties577 /// @return uint256 The id of the newly minted token578 #[solidity(rename_selector = "mintWithTokenURI")]579 #[weight(<SelfWeightOf<T>>::create_item())]580 fn mint_with_token_uri(581 &mut self,582 caller: caller,583 to: address,584 token_uri: string,585 ) -> Result<uint256> {586 let token_id: uint256 = <TokensMinted<T>>::get(self.id)587 .checked_add(1)588 .ok_or("item id overflow")?589 .into();590 self.mint_with_token_uri_check_id(caller, to, token_id, token_uri)?;591 Ok(token_id)592 }593594 /// @notice Function to mint token with the given tokenUri.595 /// @dev `tokenId` should be obtained with `nextTokenId` method,596 /// unlike standard, you can't specify it manually597 /// @param to The new owner598 /// @param tokenId ID of the minted NFT599 /// @param tokenUri Token URI that would be stored in the NFT properties600 #[solidity(hide, rename_selector = "mintWithTokenURI")]601 #[weight(<SelfWeightOf<T>>::create_item())]602 fn mint_with_token_uri_check_id(603 &mut self,604 caller: caller,605 to: address,606 token_id: uint256,607 token_uri: string,608 ) -> Result<bool> {609 let key = key::url();610 let permission = get_token_permission::<T>(self.id, &key)?;611 if !permission.collection_admin {612 return Err("Operation is not allowed".into());613 }614615 let caller = T::CrossAccountId::from_eth(caller);616 let to = T::CrossAccountId::from_eth(to);617 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;618 let budget = self619 .recorder620 .weight_calls_budget(<StructureWeight<T>>::find_parent());621622 if <TokensMinted<T>>::get(self.id)623 .checked_add(1)624 .ok_or("item id overflow")?625 != token_id626 {627 return Err("item id should be next".into());628 }629630 let mut properties = CollectionPropertiesVec::default();631 properties632 .try_push(Property {633 key,634 value: token_uri635 .into_bytes()636 .try_into()637 .map_err(|_| "token uri is too long")?,638 })639 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;640641 <Pallet<T>>::create_item(642 self,643 &caller,644 CreateItemData::<T> {645 properties,646 owner: to,647 },648 &budget,649 )650 .map_err(dispatch_to_evm::<T>)?;651 Ok(true)652 }653654 /// @dev Not implemented655 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {656 Err("not implementable".into())657 }658}659660fn get_token_property<T: Config>(661 collection: &CollectionHandle<T>,662 token_id: u32,663 key: &up_data_structs::PropertyKey,664) -> Result<string> {665 collection.consume_store_reads(1)?;666 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))667 .map_err(|_| Error::Revert("Token properties not found".into()))?;668 if let Some(property) = properties.get(key) {669 return Ok(string::from_utf8_lossy(property).into());670 }671672 Err("Property tokenURI not found".into())673}674675fn get_token_permission<T: Config>(676 collection_id: CollectionId,677 key: &PropertyKey,678) -> Result<PropertyPermission> {679 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)680 .map_err(|_| Error::Revert("No permissions for collection".into()))?;681 let a = token_property_permissions682 .get(key)683 .map(Clone::clone)684 .ok_or_else(|| {685 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();686 Error::Revert(alloc::format!("No permission for key {}", key))687 })?;688 Ok(a)689}690691/// @title Unique extensions for ERC721.692#[solidity_interface(name = ERC721UniqueExtensions)]693impl<T: Config> NonfungibleHandle<T>694where695 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,696{697 /// @notice A descriptive name for a collection of NFTs in this contract698 fn name(&self) -> Result<string> {699 Ok(decode_utf16(self.name.iter().copied())700 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))701 .collect::<string>())702 }703704 /// @notice An abbreviated name for NFTs in this contract705 fn symbol(&self) -> Result<string> {706 Ok(string::from_utf8_lossy(&self.token_prefix).into())707 }708709 /// @notice A description for the collection.710 fn description(&self) -> Result<string> {711 Ok(decode_utf16(self.description.iter().copied())712 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))713 .collect::<string>())714 }715716 /// Returns the owner (in cross format) of the token.717 ///718 /// @param tokenId Id for the token.719 fn cross_owner_of(&self, token_id: uint256) -> Result<EthCrossAccount> {720 Self::token_owner(&self, token_id.try_into()?)721 .map(|o| EthCrossAccount::from_sub_cross_account::<T>(&o))722 .ok_or(Error::Revert("key too large".into()))723 }724725 /// Returns the token properties.726 ///727 /// @param tokenId Id for the token.728 /// @param keys Properties keys. Empty keys for all propertyes.729 /// @return Vector of properties key/value pairs.730 fn properties(&self, token_id: uint256, keys: Vec<string>) -> Result<Vec<PropertyStruct>> {731 let keys = keys732 .into_iter()733 .map(|key| {734 <Vec<u8>>::from(key)735 .try_into()736 .map_err(|_| Error::Revert("key too large".into()))737 })738 .collect::<Result<Vec<_>>>()?;739740 <Self as CommonCollectionOperations<T>>::token_properties(741 &self,742 token_id.try_into()?,743 if keys.is_empty() { None } else { Some(keys) },744 )745 .into_iter()746 .map(|p| {747 let key = string::from_utf8(p.key.to_vec())748 .map_err(|e| Error::Revert(alloc::format!("{}", e)))?;749 let value = bytes(p.value.to_vec());750 Ok(PropertyStruct { key, value })751 })752 .collect::<Result<Vec<_>>>()753 }754755 /// @notice Set or reaffirm the approved address for an NFT756 /// @dev The zero address indicates there is no approved address.757 /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized758 /// operator of the current owner.759 /// @param approved The new substrate address approved NFT controller760 /// @param tokenId The NFT to approve761 #[weight(<SelfWeightOf<T>>::approve())]762 fn approve_cross(763 &mut self,764 caller: caller,765 approved: EthCrossAccount,766 token_id: uint256,767 ) -> Result<void> {768 let caller = T::CrossAccountId::from_eth(caller);769 let approved = approved.into_sub_cross_account::<T>()?;770 let token = token_id.try_into()?;771772 <Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))773 .map_err(dispatch_to_evm::<T>)?;774 Ok(())775 }776777 /// @notice Transfer ownership of an NFT778 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`779 /// is the zero address. Throws if `tokenId` is not a valid NFT.780 /// @param to The new owner781 /// @param tokenId The NFT to transfer782 #[weight(<SelfWeightOf<T>>::transfer())]783 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {784 let caller = T::CrossAccountId::from_eth(caller);785 let to = T::CrossAccountId::from_eth(to);786 let token = token_id.try_into()?;787 let budget = self788 .recorder789 .weight_calls_budget(<StructureWeight<T>>::find_parent());790791 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;792 Ok(())793 }794795 /// @notice Transfer ownership of an NFT796 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`797 /// is the zero address. Throws if `tokenId` is not a valid NFT.798 /// @param to The new owner799 /// @param tokenId The NFT to transfer800 #[weight(<SelfWeightOf<T>>::transfer())]801 fn transfer_cross(802 &mut self,803 caller: caller,804 to: EthCrossAccount,805 token_id: uint256,806 ) -> Result<void> {807 let caller = T::CrossAccountId::from_eth(caller);808 let to = to.into_sub_cross_account::<T>()?;809 let token = token_id.try_into()?;810 let budget = self811 .recorder812 .weight_calls_budget(<StructureWeight<T>>::find_parent());813814 <Pallet<T>>::transfer(self, &caller, &to, token, &budget).map_err(dispatch_to_evm::<T>)?;815 Ok(())816 }817818 /// @notice Transfer ownership of an NFT from cross account address to cross account address819 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`820 /// is the zero address. Throws if `tokenId` is not a valid NFT.821 /// @param from Cross acccount address of current owner822 /// @param to Cross acccount address of new owner823 /// @param tokenId The NFT to transfer824 #[weight(<SelfWeightOf<T>>::transfer())]825 fn transfer_from_cross(826 &mut self,827 caller: caller,828 from: EthCrossAccount,829 to: EthCrossAccount,830 token_id: uint256,831 ) -> Result<void> {832 let caller = T::CrossAccountId::from_eth(caller);833 let from = from.into_sub_cross_account::<T>()?;834 let to = to.into_sub_cross_account::<T>()?;835 let token_id = token_id.try_into()?;836 let budget = self837 .recorder838 .weight_calls_budget(<StructureWeight<T>>::find_parent());839 Pallet::<T>::transfer_from(self, &caller, &from, &to, token_id, &budget)840 .map_err(dispatch_to_evm::<T>)?;841 Ok(())842 }843844 /// @notice Burns a specific ERC721 token.845 /// @dev Throws unless `msg.sender` is the current owner or an authorized846 /// operator for this NFT. Throws if `from` is not the current owner. Throws847 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.848 /// @param from The current owner of the NFT849 /// @param tokenId The NFT to transfer850 #[solidity(hide)]851 #[weight(<SelfWeightOf<T>>::burn_from())]852 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {853 let caller = T::CrossAccountId::from_eth(caller);854 let from = T::CrossAccountId::from_eth(from);855 let token = token_id.try_into()?;856 let budget = self857 .recorder858 .weight_calls_budget(<StructureWeight<T>>::find_parent());859860 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)861 .map_err(dispatch_to_evm::<T>)?;862 Ok(())863 }864865 /// @notice Burns a specific ERC721 token.866 /// @dev Throws unless `msg.sender` is the current owner or an authorized867 /// operator for this NFT. Throws if `from` is not the current owner. Throws868 /// if `to` is the zero address. Throws if `tokenId` is not a valid NFT.869 /// @param from The current owner of the NFT870 /// @param tokenId The NFT to transfer871 #[weight(<SelfWeightOf<T>>::burn_from())]872 fn burn_from_cross(873 &mut self,874 caller: caller,875 from: EthCrossAccount,876 token_id: uint256,877 ) -> Result<void> {878 let caller = T::CrossAccountId::from_eth(caller);879 let from = from.into_sub_cross_account::<T>()?;880 let token = token_id.try_into()?;881 let budget = self882 .recorder883 .weight_calls_budget(<StructureWeight<T>>::find_parent());884885 <Pallet<T>>::burn_from(self, &caller, &from, token, &budget)886 .map_err(dispatch_to_evm::<T>)?;887 Ok(())888 }889890 /// @notice Returns next free NFT ID.891 fn next_token_id(&self) -> Result<uint256> {892 self.consume_store_reads(1)?;893 Ok(<TokensMinted<T>>::get(self.id)894 .checked_add(1)895 .ok_or("item id overflow")?896 .into())897 }898899 /// @notice Function to mint multiple tokens.900 /// @dev `tokenIds` should be an array of consecutive numbers and first number901 /// should be obtained with `nextTokenId` method902 /// @param to The new owner903 /// @param tokenIds IDs of the minted NFTs904 #[solidity(hide)]905 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]906 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {907 let caller = T::CrossAccountId::from_eth(caller);908 let to = T::CrossAccountId::from_eth(to);909 let mut expected_index = <TokensMinted<T>>::get(self.id)910 .checked_add(1)911 .ok_or("item id overflow")?;912 let budget = self913 .recorder914 .weight_calls_budget(<StructureWeight<T>>::find_parent());915916 let total_tokens = token_ids.len();917 for id in token_ids.into_iter() {918 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;919 if id != expected_index {920 return Err("item id should be next".into());921 }922 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;923 }924 let data = (0..total_tokens)925 .map(|_| CreateItemData::<T> {926 properties: BoundedVec::default(),927 owner: to.clone(),928 })929 .collect();930931 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)932 .map_err(dispatch_to_evm::<T>)?;933 Ok(true)934 }935936 /// @notice Function to mint multiple tokens with the given tokenUris.937 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive938 /// numbers and first number should be obtained with `nextTokenId` method939 /// @param to The new owner940 /// @param tokens array of pairs of token ID and token URI for minted tokens941 #[solidity(hide, rename_selector = "mintBulkWithTokenURI")]942 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]943 fn mint_bulk_with_token_uri(944 &mut self,945 caller: caller,946 to: address,947 tokens: Vec<(uint256, string)>,948 ) -> Result<bool> {949 let key = key::url();950 let caller = T::CrossAccountId::from_eth(caller);951 let to = T::CrossAccountId::from_eth(to);952 let mut expected_index = <TokensMinted<T>>::get(self.id)953 .checked_add(1)954 .ok_or("item id overflow")?;955 let budget = self956 .recorder957 .weight_calls_budget(<StructureWeight<T>>::find_parent());958959 let mut data = Vec::with_capacity(tokens.len());960 for (id, token_uri) in tokens {961 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;962 if id != expected_index {963 return Err("item id should be next".into());964 }965 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;966967 let mut properties = CollectionPropertiesVec::default();968 properties969 .try_push(Property {970 key: key.clone(),971 value: token_uri972 .into_bytes()973 .try_into()974 .map_err(|_| "token uri is too long")?,975 })976 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;977978 data.push(CreateItemData::<T> {979 properties,980 owner: to.clone(),981 });982 }983984 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)985 .map_err(dispatch_to_evm::<T>)?;986 Ok(true)987 }988}989990#[solidity_interface(991 name = UniqueNFT,992 is(993 ERC721,994 ERC721Enumerable,995 ERC721UniqueExtensions,996 ERC721UniqueMintable,997 ERC721Burnable,998 ERC721Metadata(if(this.flags.erc721metadata)),999 Collection(via(common_mut returns CollectionHandle<T>)),1000 TokenProperties,1001 )1002)]1003impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}10041005// Not a tests, but code generators1006generate_stubgen!(gen_impl, UniqueNFTCall<()>, true);1007generate_stubgen!(gen_iface, UniqueNFTCall<()>, false);10081009impl<T: Config> CommonEvmHandler for NonfungibleHandle<T>1010where1011 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,1012{1013 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");10141015 fn call(self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {1016 call::<T, UniqueNFTCall<T>, _, _>(handle, self)1017 }1018}pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -482,7 +482,7 @@
// TODO: Not implemetable
Err("not implemented".into())
}
-
+
/// @notice Returns collection helper contract address
fn collection_helper_address(&self) -> Result<address> {
Ok(T::ContractAddress::get())
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -28,6 +28,7 @@
CollectionById,
dispatch::CollectionDispatch,
erc::{CollectionHelpersEvents, static_property::key},
+ eth::{map_eth_to_id, collection_id_to_address},
Pallet as PalletCommon,
};
use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};
@@ -35,7 +36,7 @@
use sp_std::vec;
use up_data_structs::{
CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,
- CreateCollectionData,
+ CreateCollectionData, CollectionId,
};
use crate::{weights::WeightInfo, Config, SelfWeightOf};
@@ -361,6 +362,25 @@
.expect("Collection creation price should be convertible to u128");
Ok(price.into())
}
+
+ /// Returns address of a collection.
+ /// @param collectionId - CollectionId of the collection
+ /// @return eth mirror address of the collection
+ fn collection_address(&self, collection_id: uint32) -> Result<address> {
+ Ok(collection_id_to_address(collection_id.into()))
+ }
+
+ /// Returns collectionId of a collection.
+ /// @param collectionAddress - Eth address of the collection
+ /// @return collectionId of the collection
+ fn collection_id(&self, collection_address: address) -> Result<uint32> {
+ map_eth_to_id(&collection_address)
+ .map(|id| id.0)
+ .ok_or(Error::Revert(format!(
+ "failed to convert address {} into collectionId.",
+ collection_address
+ )))
+ }
}
/// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`]
pallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterbothbinary blob — no preview
pallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -24,7 +24,7 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x7dea03b1
+/// @dev the ERC-165 identifier for this interface is 0xe65011aa
contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
@@ -130,4 +130,28 @@
dummy;
return 0;
}
+
+ /// Returns address of a collection.
+ /// @param collectionId - CollectionId of the collection
+ /// @return eth mirror address of the collection
+ /// @dev EVM selector for this function is: 0x2e716683,
+ /// or in textual repr: collectionAddress(uint32)
+ function collectionAddress(uint32 collectionId) public view returns (address) {
+ require(false, stub_error);
+ collectionId;
+ dummy;
+ return 0x0000000000000000000000000000000000000000;
+ }
+
+ /// Returns collectionId of a collection.
+ /// @param collectionAddress - Eth address of the collection
+ /// @return collectionId of the collection
+ /// @dev EVM selector for this function is: 0xb5cb7498,
+ /// or in textual repr: collectionId(address)
+ function collectionId(address collectionAddress) public view returns (uint32) {
+ require(false, stub_error);
+ collectionAddress;
+ dummy;
+ return 0;
+ }
}
tests/src/eth/abi/collectionHelpers.jsondiffbeforeafterboth--- a/tests/src/eth/abi/collectionHelpers.json
+++ b/tests/src/eth/abi/collectionHelpers.json
@@ -32,6 +32,15 @@
"type": "event"
},
{
+ "inputs": [
+ { "internalType": "uint32", "name": "collectionId", "type": "uint32" }
+ ],
+ "name": "collectionAddress",
+ "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
"inputs": [],
"name": "collectionCreationFee",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
@@ -40,6 +49,19 @@
},
{
"inputs": [
+ {
+ "internalType": "address",
+ "name": "collectionAddress",
+ "type": "address"
+ }
+ ],
+ "name": "collectionId",
+ "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
{ "internalType": "string", "name": "name", "type": "string" },
{ "internalType": "uint8", "name": "decimals", "type": "uint8" },
{ "internalType": "string", "name": "description", "type": "string" },
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -19,7 +19,7 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x7dea03b1
+/// @dev the ERC-165 identifier for this interface is 0xe65011aa
interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
@@ -78,4 +78,18 @@
/// @dev EVM selector for this function is: 0xd23a7ab1,
/// or in textual repr: collectionCreationFee()
function collectionCreationFee() external view returns (uint256);
+
+ /// Returns address of a collection.
+ /// @param collectionId - CollectionId of the collection
+ /// @return eth mirror address of the collection
+ /// @dev EVM selector for this function is: 0x2e716683,
+ /// or in textual repr: collectionAddress(uint32)
+ function collectionAddress(uint32 collectionId) external view returns (address);
+
+ /// Returns collectionId of a collection.
+ /// @param collectionAddress - Eth address of the collection
+ /// @return collectionId of the collection
+ /// @dev EVM selector for this function is: 0xb5cb7498,
+ /// or in textual repr: collectionId(address)
+ function collectionId(address collectionAddress) external view returns (uint32);
}
tests/src/eth/collectionHelperAddress.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionHelperAddress.test.ts
+++ b/tests/src/eth/collectionHelperAddress.test.ts
@@ -16,10 +16,11 @@
import {itEth, usingEthPlaygrounds, expect} from './util';
import {IKeyringPair} from '@polkadot/types/types';
+import {Pallets} from '../util';
const EVM_COLLECTION_HELPERS_ADDRESS = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';
-describe('[eth]CollectionHelerpAddress test: ERC721 ', () => {
+describe('[eth]CollectionHelperAddress test: ERC20/ERC721 ', () => {
let donor: IKeyringPair;
before(async function() {
@@ -28,18 +29,22 @@
});
});
- itEth('NFT\\RFT', async ({helper}) => {
+ itEth('NFT', async ({helper}) => {
const owner = await helper.eth.createAccountWithBalance(donor);
const {collectionAddress: nftCollectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
const nftCollection = helper.ethNativeContract.collection(nftCollectionAddress, 'nft', owner);
-
- const {collectionAddress: rftCollectionAddress} = await helper.eth.createRFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
- const rftCollection = helper.ethNativeContract.collection(rftCollectionAddress, 'rft', owner);
expect((await nftCollection.methods.collectionHelperAddress().call())
.toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
-
+ });
+
+ itEth.ifWithPallets('RFT ', [Pallets.ReFungible], async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+
+ const {collectionAddress: rftCollectionAddress} = await helper.eth.createRFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
+
+ const rftCollection = helper.ethNativeContract.collection(rftCollectionAddress, 'rft', owner);
expect((await rftCollection.methods.collectionHelperAddress().call())
.toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
});
@@ -53,5 +58,15 @@
expect((await collection.methods.collectionHelperAddress().call())
.toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
});
+
+ itEth('[collectionHelpers] convert collectionId into address', async ({helper}) => {
+ const owner = await helper.eth.createAccountWithBalance(donor);
+ const collectionId = 7;
+ const collectionAddress = helper.ethAddress.fromCollectionId(collectionId);
+ const helperContract = helper.ethNativeContract.collectionHelpers(owner);
+
+ expect(await helperContract.methods.collectionAddress(collectionId).call()).to.be.equal(collectionAddress);
+ expect(parseInt(await helperContract.methods.collectionId(collectionAddress).call())).to.be.equal(collectionId);
+ });
});
tests/src/interfaces/lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -2936,13 +2936,13 @@
}
},
/**
- * Lookup338: pallet_maintenance::pallet::Call<T>
+ * Lookup340: pallet_maintenance::pallet::Call<T>
**/
PalletMaintenanceCall: {
_enum: ['enable', 'disable']
},
/**
- * Lookup339: pallet_test_utils::pallet::Call<T>
+ * Lookup341: pallet_test_utils::pallet::Call<T>
**/
PalletTestUtilsCall: {
_enum: {
tests/src/interfaces/types-lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -3175,14 +3175,14 @@
readonly type: 'Begin' | 'SetData' | 'Finish' | 'InsertEthLogs' | 'InsertEvents';
}
- /** @name PalletMaintenanceCall (338) */
+ /** @name PalletMaintenanceCall (340) */
interface PalletMaintenanceCall extends Enum {
readonly isEnable: boolean;
readonly isDisable: boolean;
readonly type: 'Enable' | 'Disable';
}
- /** @name PalletTestUtilsCall (339) */
+ /** @name PalletTestUtilsCall (341) */
interface PalletTestUtilsCall extends Enum {
readonly isEnable: boolean;
readonly isSetTestValue: boolean;