difftreelog
chore code review requests
in: master
20 files changed
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -34,15 +34,14 @@
use sp_std::vec::Vec;
use pallet_common::{
erc::{
- CommonEvmHandler, PrecompileResult, CollectionCall,
- static_property::{key, value as property_value},
+ CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key,
+ static_property::value,
},
CollectionHandle, CollectionPropertyPermissions,
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::call;
use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
-use alloc::string::ToString;
use crate::{
AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,
@@ -226,37 +225,44 @@
/// @return token's const_metadata
#[solidity(rename_selector = "tokenURI")]
fn token_uri(&self, token_id: uint256) -> Result<string> {
+ if !self.supports_metadata() {
+ return Ok("".into());
+ }
+
let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
- if let Ok(url) = get_token_property(self, token_id_u32, &key::url()) {
- if !url.is_empty() {
- return Ok(url);
+ match get_token_property(self, token_id_u32, &key::url()).as_deref() {
+ Err(_) | Ok("") => (),
+ Ok(url) => {
+ return Ok(url.into());
}
- } else if !self.supports_metadata() {
- return Err("tokenURI not set".into());
- }
+ };
- if let Some(base_uri) =
+ let base_uri =
pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())
- {
- if !base_uri.is_empty() {
- let base_uri = string::from_utf8(base_uri.into_inner()).map_err(|e| {
+ .map(BoundedVec::into_inner)
+ .map(string::from_utf8)
+ .transpose()
+ .map_err(|e| {
Error::Revert(alloc::format!(
"Can not convert value \"baseURI\" to string with error \"{}\"",
e
))
})?;
- if let Ok(suffix) = get_token_property(self, token_id_u32, &key::suffix()) {
- if !suffix.is_empty() {
- return Ok(base_uri + suffix.as_str());
- }
- }
- return Ok(base_uri);
+ let base_uri = match base_uri.as_deref() {
+ None | Some("") => {
+ return Ok("".into());
}
- }
+ Some(base_uri) => base_uri.into(),
+ };
- Ok("".into())
+ Ok(
+ match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {
+ Err(_) | Ok("") => base_uri,
+ Ok(suffix) => base_uri + suffix,
+ },
+ )
}
}
@@ -706,17 +712,29 @@
}
}
+impl<T: Config> NonfungibleHandle<T> {
+ pub fn supports_metadata(&self) -> bool {
+ if let Some(erc721_metadata) =
+ pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())
+ {
+ *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED
+ } else {
+ false
+ }
+ }
+}
+
#[solidity_interface(
name = UniqueNFT,
is(
ERC721,
- ERC721Metadata(if(this.supports_metadata())),
ERC721Enumerable,
ERC721UniqueExtensions,
ERC721Mintable,
ERC721Burnable,
Collection(via(common_mut returns CollectionHandle<T>)),
TokenProperties,
+ ERC721Metadata(if(this.supports_metadata())),
)
)]
impl<T: Config> NonfungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -297,18 +297,6 @@
}
}
-impl<T: Config> NonfungibleHandle<T> {
- pub fn supports_metadata(&self) -> bool {
- if let Some(erc721_metadata) =
- pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())
- {
- *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED
- } else {
- false
- }
- }
-}
-
impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {
fn recorder(&self) -> &SubstrateRecorder<T> {
self.0.recorder()
pallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterbothbinary blob — no preview
pallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -17,6 +17,47 @@
}
}
+/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
+/// @dev See https://eips.ethereum.org/EIPS/eip-721
+/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
+contract ERC721Metadata is Dummy, ERC165 {
+ /// @notice A descriptive name for a collection of NFTs in this contract
+ /// @dev EVM selector for this function is: 0x06fdde03,
+ /// or in textual repr: name()
+ function name() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+
+ /// @notice An abbreviated name for NFTs in this contract
+ /// @dev EVM selector for this function is: 0x95d89b41,
+ /// or in textual repr: symbol()
+ function symbol() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+
+ /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ ///
+ /// @dev If the token has a `url` property and it is not empty, it is returned.
+ /// 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`.
+ /// If the collection property `baseURI` is empty or absent, return "" (empty string)
+ /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
+ /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
+ ///
+ /// @return token's const_metadata
+ /// @dev EVM selector for this function is: 0xc87b56dd,
+ /// or in textual repr: tokenURI(uint256)
+ function tokenURI(uint256 tokenId) public view returns (string memory) {
+ require(false, stub_error);
+ tokenId;
+ dummy;
+ return "";
+ }
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0x41369377
contract TokenProperties is Dummy, ERC165 {
@@ -177,10 +218,10 @@
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
- function collectionSponsor() public view returns (Tuple17 memory) {
+ function collectionSponsor() public view returns (Tuple15 memory) {
require(false, stub_error);
dummy;
- return Tuple17(0x0000000000000000000000000000000000000000, 0);
+ return Tuple15(0x0000000000000000000000000000000000000000, 0);
}
/// Set limits for the collection.
@@ -359,10 +400,10 @@
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
- function collectionOwner() public view returns (Tuple17 memory) {
+ function collectionOwner() public view returns (Tuple15 memory) {
require(false, stub_error);
dummy;
- return Tuple17(0x0000000000000000000000000000000000000000, 0);
+ return Tuple15(0x0000000000000000000000000000000000000000, 0);
}
/// Changes collection owner to another account
@@ -379,7 +420,7 @@
}
/// @dev anonymous struct
-struct Tuple17 {
+struct Tuple15 {
address field_0;
uint256 field_1;
}
@@ -525,7 +566,7 @@
/// @param tokens array of pairs of token ID and token URI for minted tokens
/// @dev EVM selector for this function is: 0x36543006,
/// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
- function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) public returns (bool) {
+ function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) public returns (bool) {
require(false, stub_error);
to;
tokens;
@@ -535,7 +576,7 @@
}
/// @dev anonymous struct
-struct Tuple8 {
+struct Tuple6 {
uint256 field_0;
string field_1;
}
@@ -577,48 +618,7 @@
require(false, stub_error);
dummy;
return 0;
- }
-}
-
-/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
-/// @dev See https://eips.ethereum.org/EIPS/eip-721
-/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
-contract ERC721Metadata is Dummy, ERC165 {
- /// @notice A descriptive name for a collection of NFTs in this contract
- /// @dev EVM selector for this function is: 0x06fdde03,
- /// or in textual repr: name()
- function name() public view returns (string memory) {
- require(false, stub_error);
- dummy;
- return "";
- }
-
- /// @notice An abbreviated name for NFTs in this contract
- /// @dev EVM selector for this function is: 0x95d89b41,
- /// or in textual repr: symbol()
- function symbol() public view returns (string memory) {
- require(false, stub_error);
- dummy;
- return "";
}
-
- /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
- ///
- /// @dev If the token has a `url` property and it is not empty, it is returned.
- /// 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`.
- /// If the collection property `baseURI` is empty or absent, return "" (empty string)
- /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
- /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
- ///
- /// @return token's const_metadata
- /// @dev EVM selector for this function is: 0xc87b56dd,
- /// or in textual repr: tokenURI(uint256)
- function tokenURI(uint256 tokenId) public view returns (string memory) {
- require(false, stub_error);
- tokenId;
- dummy;
- return "";
- }
}
/// @dev inlined interface
@@ -766,11 +766,11 @@
Dummy,
ERC165,
ERC721,
- ERC721Metadata,
ERC721Enumerable,
ERC721UniqueExtensions,
ERC721Mintable,
ERC721Burnable,
Collection,
- TokenProperties
+ TokenProperties,
+ ERC721Metadata
{}
pallets/refungible/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//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use alloc::string::ToString;25use core::{26 char::{REPLACEMENT_CHARACTER, decode_utf16},27 convert::TryInto,28};29use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};30use frame_support::BoundedBTreeMap;31use pallet_common::{32 CollectionHandle, CollectionPropertyPermissions,33 erc::{34 CommonEvmHandler, CollectionCall,35 static_property::{key, value as property_value},36 },37};38use pallet_evm::{account::CrossAccountId, PrecompileHandle};39use pallet_evm_coder_substrate::{call, dispatch_to_evm};40use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};41use sp_core::H160;42use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};43use up_data_structs::{44 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,45 PropertyKeyPermission, PropertyPermission, TokenId,46};4748use crate::{49 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,50 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,51};5253pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5455/// @title A contract that allows to set and delete token properties and change token property permissions.56#[solidity_interface(name = TokenProperties)]57impl<T: Config> RefungibleHandle<T> {58 /// @notice Set permissions for token property.59 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.60 /// @param key Property key.61 /// @param isMutable Permission to mutate property.62 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.63 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.64 fn set_token_property_permission(65 &mut self,66 caller: caller,67 key: string,68 is_mutable: bool,69 collection_admin: bool,70 token_owner: bool,71 ) -> Result<()> {72 let caller = T::CrossAccountId::from_eth(caller);73 <Pallet<T>>::set_token_property_permissions(74 self,75 &caller,76 vec![PropertyKeyPermission {77 key: <Vec<u8>>::from(key)78 .try_into()79 .map_err(|_| "too long key")?,80 permission: PropertyPermission {81 mutable: is_mutable,82 collection_admin,83 token_owner,84 },85 }],86 )87 .map_err(dispatch_to_evm::<T>)88 }8990 /// @notice Set token property value.91 /// @dev Throws error if `msg.sender` has no permission to edit the property.92 /// @param tokenId ID of the token.93 /// @param key Property key.94 /// @param value Property value.95 fn set_property(96 &mut self,97 caller: caller,98 token_id: uint256,99 key: string,100 value: bytes,101 ) -> Result<()> {102 let caller = T::CrossAccountId::from_eth(caller);103 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;104 let key = <Vec<u8>>::from(key)105 .try_into()106 .map_err(|_| "key too long")?;107 let value = value.try_into().map_err(|_| "value too long")?;108109 let nesting_budget = self110 .recorder111 .weight_calls_budget(<StructureWeight<T>>::find_parent());112113 <Pallet<T>>::set_token_property(114 self,115 &caller,116 TokenId(token_id),117 Property { key, value },118 &nesting_budget,119 )120 .map_err(dispatch_to_evm::<T>)121 }122123 /// @notice Delete token property value.124 /// @dev Throws error if `msg.sender` has no permission to edit the property.125 /// @param tokenId ID of the token.126 /// @param key Property key.127 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {128 let caller = T::CrossAccountId::from_eth(caller);129 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;130 let key = <Vec<u8>>::from(key)131 .try_into()132 .map_err(|_| "key too long")?;133134 let nesting_budget = self135 .recorder136 .weight_calls_budget(<StructureWeight<T>>::find_parent());137138 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)139 .map_err(dispatch_to_evm::<T>)140 }141142 /// @notice Get token property value.143 /// @dev Throws error if key not found144 /// @param tokenId ID of the token.145 /// @param key Property key.146 /// @return Property value bytes147 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {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")?;152153 let props = <TokenProperties<T>>::get((self.id, token_id));154 let prop = props.get(&key).ok_or("key not found")?;155156 Ok(prop.to_vec())157 }158}159160#[derive(ToLog)]161pub enum ERC721Events {162 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed163 /// (`to` == 0). Exception: during contract creation, any number of RFTs164 /// may be created and assigned without emitting Transfer.165 Transfer {166 #[indexed]167 from: address,168 #[indexed]169 to: address,170 #[indexed]171 token_id: uint256,172 },173 /// @dev Not supported174 Approval {175 #[indexed]176 owner: address,177 #[indexed]178 approved: address,179 #[indexed]180 token_id: uint256,181 },182 /// @dev Not supported183 #[allow(dead_code)]184 ApprovalForAll {185 #[indexed]186 owner: address,187 #[indexed]188 operator: address,189 approved: bool,190 },191}192193#[derive(ToLog)]194pub enum ERC721MintableEvents {195 /// @dev Not supported196 #[allow(dead_code)]197 MintingFinished {},198}199200#[solidity_interface(name = ERC721Metadata)]201impl<T: Config> RefungibleHandle<T> {202 /// @notice A descriptive name for a collection of RFTs in this contract203 fn name(&self) -> Result<string> {204 Ok(decode_utf16(self.name.iter().copied())205 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))206 .collect::<string>())207 }208209 /// @notice An abbreviated name for RFTs in this contract210 fn symbol(&self) -> Result<string> {211 Ok(string::from_utf8_lossy(&self.token_prefix).into())212 }213214 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.215 ///216 /// @dev If the token has a `url` property and it is not empty, it is returned.217 /// 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`.218 /// If the collection property `baseURI` is empty or absent, return "" (empty string)219 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix220 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).221 ///222 /// @return token's const_metadata223 #[solidity(rename_selector = "tokenURI")]224 fn token_uri(&self, token_id: uint256) -> Result<string> {225 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;226227 if let Ok(url) = get_token_property(self, token_id_u32, &key::url()) {228 if !url.is_empty() {229 return Ok(url);230 }231 } else if !self.supports_metadata() {232 return Err("tokenURI not set".into());233 }234235 if let Some(base_uri) =236 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())237 {238 if !base_uri.is_empty() {239 let base_uri = string::from_utf8(base_uri.into_inner()).map_err(|e| {240 Error::Revert(alloc::format!(241 "Can not convert value \"baseURI\" to string with error \"{}\"",242 e243 ))244 })?;245 if let Ok(suffix) = get_token_property(self, token_id_u32, &key::suffix()) {246 if !suffix.is_empty() {247 return Ok(base_uri + suffix.as_str());248 }249 }250251 return Ok(base_uri);252 }253 }254255 Ok("".into())256 }257}258259/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension260/// @dev See https://eips.ethereum.org/EIPS/eip-721261#[solidity_interface(name = ERC721Enumerable)]262impl<T: Config> RefungibleHandle<T> {263 /// @notice Enumerate valid RFTs264 /// @param index A counter less than `totalSupply()`265 /// @return The token identifier for the `index`th NFT,266 /// (sort order not specified)267 fn token_by_index(&self, index: uint256) -> Result<uint256> {268 Ok(index)269 }270271 /// Not implemented272 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {273 // TODO: Not implemetable274 Err("not implemented".into())275 }276277 /// @notice Count RFTs tracked by this contract278 /// @return A count of valid RFTs tracked by this contract, where each one of279 /// them has an assigned and queryable owner not equal to the zero address280 fn total_supply(&self) -> Result<uint256> {281 self.consume_store_reads(1)?;282 Ok(<Pallet<T>>::total_supply(self).into())283 }284}285286/// @title ERC-721 Non-Fungible Token Standard287/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md288#[solidity_interface(name = ERC721, events(ERC721Events))]289impl<T: Config> RefungibleHandle<T> {290 /// @notice Count all RFTs assigned to an owner291 /// @dev RFTs assigned to the zero address are considered invalid, and this292 /// function throws for queries about the zero address.293 /// @param owner An address for whom to query the balance294 /// @return The number of RFTs owned by `owner`, possibly zero295 fn balance_of(&self, owner: address) -> Result<uint256> {296 self.consume_store_reads(1)?;297 let owner = T::CrossAccountId::from_eth(owner);298 let balance = <AccountBalance<T>>::get((self.id, owner));299 Ok(balance.into())300 }301302 /// @notice Find the owner of an RFT303 /// @dev RFTs assigned to zero address are considered invalid, and queries304 /// about them do throw.305 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for306 /// the tokens that are partially owned.307 /// @param tokenId The identifier for an RFT308 /// @return The address of the owner of the RFT309 fn owner_of(&self, token_id: uint256) -> Result<address> {310 self.consume_store_reads(2)?;311 let token = token_id.try_into()?;312 let owner = <Pallet<T>>::token_owner(self.id, token);313 Ok(owner314 .map(|address| *address.as_eth())315 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))316 }317318 /// @dev Not implemented319 fn safe_transfer_from_with_data(320 &mut self,321 _from: address,322 _to: address,323 _token_id: uint256,324 _data: bytes,325 ) -> Result<void> {326 // TODO: Not implemetable327 Err("not implemented".into())328 }329330 /// @dev Not implemented331 fn safe_transfer_from(332 &mut self,333 _from: address,334 _to: address,335 _token_id: uint256,336 ) -> Result<void> {337 // TODO: Not implemetable338 Err("not implemented".into())339 }340341 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE342 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE343 /// THEY MAY BE PERMANENTLY LOST344 /// @dev Throws unless `msg.sender` is the current owner or an authorized345 /// operator for this RFT. Throws if `from` is not the current owner. Throws346 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.347 /// Throws if RFT pieces have multiple owners.348 /// @param from The current owner of the NFT349 /// @param to The new owner350 /// @param tokenId The NFT to transfer351 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]352 fn transfer_from(353 &mut self,354 caller: caller,355 from: address,356 to: address,357 token_id: uint256,358 ) -> Result<void> {359 let caller = T::CrossAccountId::from_eth(caller);360 let from = T::CrossAccountId::from_eth(from);361 let to = T::CrossAccountId::from_eth(to);362 let token = token_id.try_into()?;363 let budget = self364 .recorder365 .weight_calls_budget(<StructureWeight<T>>::find_parent());366367 let balance = balance(&self, token, &from)?;368 ensure_single_owner(&self, token, balance)?;369370 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)371 .map_err(dispatch_to_evm::<T>)?;372373 Ok(())374 }375376 /// @dev Not implemented377 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {378 Err("not implemented".into())379 }380381 /// @dev Not implemented382 fn set_approval_for_all(383 &mut self,384 _caller: caller,385 _operator: address,386 _approved: bool,387 ) -> Result<void> {388 // TODO: Not implemetable389 Err("not implemented".into())390 }391392 /// @dev Not implemented393 fn get_approved(&self, _token_id: uint256) -> Result<address> {394 // TODO: Not implemetable395 Err("not implemented".into())396 }397398 /// @dev Not implemented399 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {400 // TODO: Not implemetable401 Err("not implemented".into())402 }403}404405/// Returns amount of pieces of `token` that `owner` have406pub fn balance<T: Config>(407 collection: &RefungibleHandle<T>,408 token: TokenId,409 owner: &T::CrossAccountId,410) -> Result<u128> {411 collection.consume_store_reads(1)?;412 let balance = <Balance<T>>::get((collection.id, token, &owner));413 Ok(balance)414}415416/// Throws if `owner_balance` is lower than total amount of `token` pieces417pub fn ensure_single_owner<T: Config>(418 collection: &RefungibleHandle<T>,419 token: TokenId,420 owner_balance: u128,421) -> Result<()> {422 collection.consume_store_reads(1)?;423 let total_supply = <TotalSupply<T>>::get((collection.id, token));424 if total_supply != owner_balance {425 return Err("token has multiple owners".into());426 }427 Ok(())428}429430/// @title ERC721 Token that can be irreversibly burned (destroyed).431#[solidity_interface(name = ERC721Burnable)]432impl<T: Config> RefungibleHandle<T> {433 /// @notice Burns a specific ERC721 token.434 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized435 /// operator of the current owner.436 /// @param tokenId The RFT to approve437 #[weight(<SelfWeightOf<T>>::burn_item_fully())]438 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {439 let caller = T::CrossAccountId::from_eth(caller);440 let token = token_id.try_into()?;441442 let balance = balance(&self, token, &caller)?;443 ensure_single_owner(&self, token, balance)?;444445 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;446 Ok(())447 }448}449450/// @title ERC721 minting logic.451#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]452impl<T: Config> RefungibleHandle<T> {453 fn minting_finished(&self) -> Result<bool> {454 Ok(false)455 }456457 /// @notice Function to mint token.458 /// @dev `tokenId` should be obtained with `nextTokenId` method,459 /// unlike standard, you can't specify it manually460 /// @param to The new owner461 /// @param tokenId ID of the minted RFT462 #[weight(<SelfWeightOf<T>>::create_item())]463 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {464 let caller = T::CrossAccountId::from_eth(caller);465 let to = T::CrossAccountId::from_eth(to);466 let token_id: u32 = token_id.try_into()?;467 let budget = self468 .recorder469 .weight_calls_budget(<StructureWeight<T>>::find_parent());470471 if <TokensMinted<T>>::get(self.id)472 .checked_add(1)473 .ok_or("item id overflow")?474 != token_id475 {476 return Err("item id should be next".into());477 }478479 let users = [(to.clone(), 1)]480 .into_iter()481 .collect::<BTreeMap<_, _>>()482 .try_into()483 .unwrap();484 <Pallet<T>>::create_item(485 self,486 &caller,487 CreateItemData::<T::CrossAccountId> {488 users,489 properties: CollectionPropertiesVec::default(),490 },491 &budget,492 )493 .map_err(dispatch_to_evm::<T>)?;494495 Ok(true)496 }497498 /// @notice Function to mint token with the given tokenUri.499 /// @dev `tokenId` should be obtained with `nextTokenId` method,500 /// unlike standard, you can't specify it manually501 /// @param to The new owner502 /// @param tokenId ID of the minted RFT503 /// @param tokenUri Token URI that would be stored in the RFT properties504 #[solidity(rename_selector = "mintWithTokenURI")]505 #[weight(<SelfWeightOf<T>>::create_item())]506 fn mint_with_token_uri(507 &mut self,508 caller: caller,509 to: address,510 token_id: uint256,511 token_uri: string,512 ) -> Result<bool> {513 let key = key::url();514 let permission = get_token_permission::<T>(self.id, &key)?;515 if !permission.collection_admin {516 return Err("Operation is not allowed".into());517 }518519 let caller = T::CrossAccountId::from_eth(caller);520 let to = T::CrossAccountId::from_eth(to);521 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;522 let budget = self523 .recorder524 .weight_calls_budget(<StructureWeight<T>>::find_parent());525526 if <TokensMinted<T>>::get(self.id)527 .checked_add(1)528 .ok_or("item id overflow")?529 != token_id530 {531 return Err("item id should be next".into());532 }533534 let mut properties = CollectionPropertiesVec::default();535 properties536 .try_push(Property {537 key,538 value: token_uri539 .into_bytes()540 .try_into()541 .map_err(|_| "token uri is too long")?,542 })543 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;544545 let users = [(to.clone(), 1)]546 .into_iter()547 .collect::<BTreeMap<_, _>>()548 .try_into()549 .unwrap();550 <Pallet<T>>::create_item(551 self,552 &caller,553 CreateItemData::<T::CrossAccountId> { users, properties },554 &budget,555 )556 .map_err(dispatch_to_evm::<T>)?;557 Ok(true)558 }559560 /// @dev Not implemented561 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {562 Err("not implementable".into())563 }564}565566fn get_token_property<T: Config>(567 collection: &CollectionHandle<T>,568 token_id: u32,569 key: &up_data_structs::PropertyKey,570) -> Result<string> {571 collection.consume_store_reads(1)?;572 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))573 .map_err(|_| Error::Revert("Token properties not found".into()))?;574 if let Some(property) = properties.get(key) {575 return Ok(string::from_utf8_lossy(property).into());576 }577578 Err("Property tokenURI not found".into())579}580581fn get_token_permission<T: Config>(582 collection_id: CollectionId,583 key: &PropertyKey,584) -> Result<PropertyPermission> {585 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)586 .map_err(|_| Error::Revert("No permissions for collection".into()))?;587 let a = token_property_permissions588 .get(key)589 .map(Clone::clone)590 .ok_or_else(|| {591 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();592 Error::Revert(alloc::format!("No permission for key {}", key))593 })?;594 Ok(a)595}596597/// @title Unique extensions for ERC721.598#[solidity_interface(name = ERC721UniqueExtensions)]599impl<T: Config> RefungibleHandle<T> {600 /// @notice Transfer ownership of an RFT601 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`602 /// is the zero address. Throws if `tokenId` is not a valid RFT.603 /// Throws if RFT pieces have multiple owners.604 /// @param to The new owner605 /// @param tokenId The RFT to transfer606 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]607 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {608 let caller = T::CrossAccountId::from_eth(caller);609 let to = T::CrossAccountId::from_eth(to);610 let token = token_id.try_into()?;611 let budget = self612 .recorder613 .weight_calls_budget(<StructureWeight<T>>::find_parent());614615 let balance = balance(&self, token, &caller)?;616 ensure_single_owner(&self, token, balance)?;617618 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)619 .map_err(dispatch_to_evm::<T>)?;620 Ok(())621 }622623 /// @notice Burns a specific ERC721 token.624 /// @dev Throws unless `msg.sender` is the current owner or an authorized625 /// operator for this RFT. Throws if `from` is not the current owner. Throws626 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.627 /// Throws if RFT pieces have multiple owners.628 /// @param from The current owner of the RFT629 /// @param tokenId The RFT to transfer630 #[weight(<SelfWeightOf<T>>::burn_from())]631 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {632 let caller = T::CrossAccountId::from_eth(caller);633 let from = T::CrossAccountId::from_eth(from);634 let token = token_id.try_into()?;635 let budget = self636 .recorder637 .weight_calls_budget(<StructureWeight<T>>::find_parent());638639 let balance = balance(&self, token, &caller)?;640 ensure_single_owner(&self, token, balance)?;641642 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)643 .map_err(dispatch_to_evm::<T>)?;644 Ok(())645 }646647 /// @notice Returns next free RFT ID.648 fn next_token_id(&self) -> Result<uint256> {649 self.consume_store_reads(1)?;650 Ok(<TokensMinted<T>>::get(self.id)651 .checked_add(1)652 .ok_or("item id overflow")?653 .into())654 }655656 /// @notice Function to mint multiple tokens.657 /// @dev `tokenIds` should be an array of consecutive numbers and first number658 /// should be obtained with `nextTokenId` method659 /// @param to The new owner660 /// @param tokenIds IDs of the minted RFTs661 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]662 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {663 let caller = T::CrossAccountId::from_eth(caller);664 let to = T::CrossAccountId::from_eth(to);665 let mut expected_index = <TokensMinted<T>>::get(self.id)666 .checked_add(1)667 .ok_or("item id overflow")?;668 let budget = self669 .recorder670 .weight_calls_budget(<StructureWeight<T>>::find_parent());671672 let total_tokens = token_ids.len();673 for id in token_ids.into_iter() {674 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;675 if id != expected_index {676 return Err("item id should be next".into());677 }678 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;679 }680 let users = [(to.clone(), 1)]681 .into_iter()682 .collect::<BTreeMap<_, _>>()683 .try_into()684 .unwrap();685 let create_item_data = CreateItemData::<T::CrossAccountId> {686 users,687 properties: CollectionPropertiesVec::default(),688 };689 let data = (0..total_tokens)690 .map(|_| create_item_data.clone())691 .collect();692693 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)694 .map_err(dispatch_to_evm::<T>)?;695 Ok(true)696 }697698 /// @notice Function to mint multiple tokens with the given tokenUris.699 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive700 /// numbers and first number should be obtained with `nextTokenId` method701 /// @param to The new owner702 /// @param tokens array of pairs of token ID and token URI for minted tokens703 #[solidity(rename_selector = "mintBulkWithTokenURI")]704 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]705 fn mint_bulk_with_token_uri(706 &mut self,707 caller: caller,708 to: address,709 tokens: Vec<(uint256, string)>,710 ) -> Result<bool> {711 let key = key::url();712 let caller = T::CrossAccountId::from_eth(caller);713 let to = T::CrossAccountId::from_eth(to);714 let mut expected_index = <TokensMinted<T>>::get(self.id)715 .checked_add(1)716 .ok_or("item id overflow")?;717 let budget = self718 .recorder719 .weight_calls_budget(<StructureWeight<T>>::find_parent());720721 let mut data = Vec::with_capacity(tokens.len());722 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]723 .into_iter()724 .collect::<BTreeMap<_, _>>()725 .try_into()726 .unwrap();727 for (id, token_uri) in tokens {728 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;729 if id != expected_index {730 return Err("item id should be next".into());731 }732 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;733734 let mut properties = CollectionPropertiesVec::default();735 properties736 .try_push(Property {737 key: key.clone(),738 value: token_uri739 .into_bytes()740 .try_into()741 .map_err(|_| "token uri is too long")?,742 })743 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;744745 let create_item_data = CreateItemData::<T::CrossAccountId> {746 users: users.clone(),747 properties,748 };749 data.push(create_item_data);750 }751752 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)753 .map_err(dispatch_to_evm::<T>)?;754 Ok(true)755 }756757 /// Returns EVM address for refungible token758 ///759 /// @param token ID of the token760 fn token_contract_address(&self, token: uint256) -> Result<address> {761 Ok(T::EvmTokenAddressMapping::token_to_address(762 self.id,763 token.try_into().map_err(|_| "token id overflow")?,764 ))765 }766}767768#[solidity_interface(769 name = UniqueRefungible,770 is(771 ERC721,772 ERC721Metadata(if(this.supports_metadata())),773 ERC721Enumerable,774 ERC721UniqueExtensions,775 ERC721Mintable,776 ERC721Burnable,777 Collection(via(common_mut returns CollectionHandle<T>)),778 TokenProperties,779 )780)]781impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}782783// Not a tests, but code generators784generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);785generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);786787impl<T: Config> CommonEvmHandler for RefungibleHandle<T>788where789 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,790{791 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");792 fn call(793 self,794 handle: &mut impl PrecompileHandle,795 ) -> Option<pallet_common::erc::PrecompileResult> {796 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)797 }798}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//! # Refungible Pallet EVM API for tokens18//!19//! Provides ERC-721 standart support implementation and EVM API for unique extensions for Refungible Pallet.20//! Method implementations are mostly doing parameter conversion and calling Refungible Pallet methods.2122extern crate alloc;2324use core::{25 char::{REPLACEMENT_CHARACTER, decode_utf16},26 convert::TryInto,27};28use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};29use frame_support::{BoundedBTreeMap, BoundedVec};30use pallet_common::{31 CollectionHandle, CollectionPropertyPermissions,32 erc::{CommonEvmHandler, CollectionCall, static_property::key, static_property::value},33};34use pallet_evm::{account::CrossAccountId, PrecompileHandle};35use pallet_evm_coder_substrate::{call, dispatch_to_evm};36use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};37use sp_core::H160;38use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};39use up_data_structs::{40 CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,41 PropertyKeyPermission, PropertyPermission, TokenId,42};4344use crate::{45 AccountBalance, Balance, Config, CreateItemData, Pallet, RefungibleHandle, SelfWeightOf,46 TokenProperties, TokensMinted, TotalSupply, weights::WeightInfo,47};4849pub const ADDRESS_FOR_PARTIALLY_OWNED_TOKENS: H160 = H160::repeat_byte(0xff);5051/// @title A contract that allows to set and delete token properties and change token property permissions.52#[solidity_interface(name = TokenProperties)]53impl<T: Config> RefungibleHandle<T> {54 /// @notice Set permissions for token property.55 /// @dev Throws error if `msg.sender` is not admin or owner of the collection.56 /// @param key Property key.57 /// @param isMutable Permission to mutate property.58 /// @param collectionAdmin Permission to mutate property by collection admin if property is mutable.59 /// @param tokenOwner Permission to mutate property by token owner if property is mutable.60 fn set_token_property_permission(61 &mut self,62 caller: caller,63 key: string,64 is_mutable: bool,65 collection_admin: bool,66 token_owner: bool,67 ) -> Result<()> {68 let caller = T::CrossAccountId::from_eth(caller);69 <Pallet<T>>::set_token_property_permissions(70 self,71 &caller,72 vec![PropertyKeyPermission {73 key: <Vec<u8>>::from(key)74 .try_into()75 .map_err(|_| "too long key")?,76 permission: PropertyPermission {77 mutable: is_mutable,78 collection_admin,79 token_owner,80 },81 }],82 )83 .map_err(dispatch_to_evm::<T>)84 }8586 /// @notice Set token property value.87 /// @dev Throws error if `msg.sender` has no permission to edit the property.88 /// @param tokenId ID of the token.89 /// @param key Property key.90 /// @param value Property value.91 fn set_property(92 &mut self,93 caller: caller,94 token_id: uint256,95 key: string,96 value: bytes,97 ) -> Result<()> {98 let caller = T::CrossAccountId::from_eth(caller);99 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;100 let key = <Vec<u8>>::from(key)101 .try_into()102 .map_err(|_| "key too long")?;103 let value = value.try_into().map_err(|_| "value too long")?;104105 let nesting_budget = self106 .recorder107 .weight_calls_budget(<StructureWeight<T>>::find_parent());108109 <Pallet<T>>::set_token_property(110 self,111 &caller,112 TokenId(token_id),113 Property { key, value },114 &nesting_budget,115 )116 .map_err(dispatch_to_evm::<T>)117 }118119 /// @notice Delete token property value.120 /// @dev Throws error if `msg.sender` has no permission to edit the property.121 /// @param tokenId ID of the token.122 /// @param key Property key.123 fn delete_property(&mut self, token_id: uint256, caller: caller, key: string) -> Result<()> {124 let caller = T::CrossAccountId::from_eth(caller);125 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;126 let key = <Vec<u8>>::from(key)127 .try_into()128 .map_err(|_| "key too long")?;129130 let nesting_budget = self131 .recorder132 .weight_calls_budget(<StructureWeight<T>>::find_parent());133134 <Pallet<T>>::delete_token_property(self, &caller, TokenId(token_id), key, &nesting_budget)135 .map_err(dispatch_to_evm::<T>)136 }137138 /// @notice Get token property value.139 /// @dev Throws error if key not found140 /// @param tokenId ID of the token.141 /// @param key Property key.142 /// @return Property value bytes143 fn property(&self, token_id: uint256, key: string) -> Result<bytes> {144 let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;145 let key = <Vec<u8>>::from(key)146 .try_into()147 .map_err(|_| "key too long")?;148149 let props = <TokenProperties<T>>::get((self.id, token_id));150 let prop = props.get(&key).ok_or("key not found")?;151152 Ok(prop.to_vec())153 }154}155156#[derive(ToLog)]157pub enum ERC721Events {158 /// @dev This event emits when NFTs are created (`from` == 0) and destroyed159 /// (`to` == 0). Exception: during contract creation, any number of RFTs160 /// may be created and assigned without emitting Transfer.161 Transfer {162 #[indexed]163 from: address,164 #[indexed]165 to: address,166 #[indexed]167 token_id: uint256,168 },169 /// @dev Not supported170 Approval {171 #[indexed]172 owner: address,173 #[indexed]174 approved: address,175 #[indexed]176 token_id: uint256,177 },178 /// @dev Not supported179 #[allow(dead_code)]180 ApprovalForAll {181 #[indexed]182 owner: address,183 #[indexed]184 operator: address,185 approved: bool,186 },187}188189#[derive(ToLog)]190pub enum ERC721MintableEvents {191 /// @dev Not supported192 #[allow(dead_code)]193 MintingFinished {},194}195196#[solidity_interface(name = ERC721Metadata)]197impl<T: Config> RefungibleHandle<T> {198 /// @notice A descriptive name for a collection of RFTs in this contract199 fn name(&self) -> Result<string> {200 Ok(decode_utf16(self.name.iter().copied())201 .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))202 .collect::<string>())203 }204205 /// @notice An abbreviated name for RFTs in this contract206 fn symbol(&self) -> Result<string> {207 Ok(string::from_utf8_lossy(&self.token_prefix).into())208 }209210 /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.211 ///212 /// @dev If the token has a `url` property and it is not empty, it is returned.213 /// 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`.214 /// If the collection property `baseURI` is empty or absent, return "" (empty string)215 /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix216 /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).217 ///218 /// @return token's const_metadata219 #[solidity(rename_selector = "tokenURI")]220 fn token_uri(&self, token_id: uint256) -> Result<string> {221 if !self.supports_metadata() {222 return Ok("".into());223 }224225 let token_id_u32: u32 = token_id.try_into().map_err(|_| "token id overflow")?;226227 match get_token_property(self, token_id_u32, &key::url()).as_deref() {228 Err(_) | Ok("") => (),229 Ok(url) => {230 return Ok(url.into());231 }232 };233234 let base_uri =235 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::base_uri())236 .map(BoundedVec::into_inner)237 .map(string::from_utf8)238 .transpose()239 .map_err(|e| {240 Error::Revert(alloc::format!(241 "Can not convert value \"baseURI\" to string with error \"{}\"",242 e243 ))244 })?;245246 let base_uri = match base_uri.as_deref() {247 None | Some("") => {248 return Ok("".into());249 }250 Some(base_uri) => base_uri.into(),251 };252253 Ok(254 match get_token_property(self, token_id_u32, &key::suffix()).as_deref() {255 Err(_) | Ok("") => base_uri,256 Ok(suffix) => base_uri + suffix,257 },258 )259 }260}261262/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension263/// @dev See https://eips.ethereum.org/EIPS/eip-721264#[solidity_interface(name = ERC721Enumerable)]265impl<T: Config> RefungibleHandle<T> {266 /// @notice Enumerate valid RFTs267 /// @param index A counter less than `totalSupply()`268 /// @return The token identifier for the `index`th NFT,269 /// (sort order not specified)270 fn token_by_index(&self, index: uint256) -> Result<uint256> {271 Ok(index)272 }273274 /// Not implemented275 fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {276 // TODO: Not implemetable277 Err("not implemented".into())278 }279280 /// @notice Count RFTs tracked by this contract281 /// @return A count of valid RFTs tracked by this contract, where each one of282 /// them has an assigned and queryable owner not equal to the zero address283 fn total_supply(&self) -> Result<uint256> {284 self.consume_store_reads(1)?;285 Ok(<Pallet<T>>::total_supply(self).into())286 }287}288289/// @title ERC-721 Non-Fungible Token Standard290/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md291#[solidity_interface(name = ERC721, events(ERC721Events))]292impl<T: Config> RefungibleHandle<T> {293 /// @notice Count all RFTs assigned to an owner294 /// @dev RFTs assigned to the zero address are considered invalid, and this295 /// function throws for queries about the zero address.296 /// @param owner An address for whom to query the balance297 /// @return The number of RFTs owned by `owner`, possibly zero298 fn balance_of(&self, owner: address) -> Result<uint256> {299 self.consume_store_reads(1)?;300 let owner = T::CrossAccountId::from_eth(owner);301 let balance = <AccountBalance<T>>::get((self.id, owner));302 Ok(balance.into())303 }304305 /// @notice Find the owner of an RFT306 /// @dev RFTs assigned to zero address are considered invalid, and queries307 /// about them do throw.308 /// Returns special 0xffffffffffffffffffffffffffffffffffffffff address for309 /// the tokens that are partially owned.310 /// @param tokenId The identifier for an RFT311 /// @return The address of the owner of the RFT312 fn owner_of(&self, token_id: uint256) -> Result<address> {313 self.consume_store_reads(2)?;314 let token = token_id.try_into()?;315 let owner = <Pallet<T>>::token_owner(self.id, token);316 Ok(owner317 .map(|address| *address.as_eth())318 .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))319 }320321 /// @dev Not implemented322 fn safe_transfer_from_with_data(323 &mut self,324 _from: address,325 _to: address,326 _token_id: uint256,327 _data: bytes,328 ) -> Result<void> {329 // TODO: Not implemetable330 Err("not implemented".into())331 }332333 /// @dev Not implemented334 fn safe_transfer_from(335 &mut self,336 _from: address,337 _to: address,338 _token_id: uint256,339 ) -> Result<void> {340 // TODO: Not implemetable341 Err("not implemented".into())342 }343344 /// @notice Transfer ownership of an RFT -- THE CALLER IS RESPONSIBLE345 /// TO CONFIRM THAT `to` IS CAPABLE OF RECEIVING NFTS OR ELSE346 /// THEY MAY BE PERMANENTLY LOST347 /// @dev Throws unless `msg.sender` is the current owner or an authorized348 /// operator for this RFT. Throws if `from` is not the current owner. Throws349 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.350 /// Throws if RFT pieces have multiple owners.351 /// @param from The current owner of the NFT352 /// @param to The new owner353 /// @param tokenId The NFT to transfer354 #[weight(<SelfWeightOf<T>>::transfer_from_creating_removing())]355 fn transfer_from(356 &mut self,357 caller: caller,358 from: address,359 to: address,360 token_id: uint256,361 ) -> Result<void> {362 let caller = T::CrossAccountId::from_eth(caller);363 let from = T::CrossAccountId::from_eth(from);364 let to = T::CrossAccountId::from_eth(to);365 let token = token_id.try_into()?;366 let budget = self367 .recorder368 .weight_calls_budget(<StructureWeight<T>>::find_parent());369370 let balance = balance(&self, token, &from)?;371 ensure_single_owner(&self, token, balance)?;372373 <Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)374 .map_err(dispatch_to_evm::<T>)?;375376 Ok(())377 }378379 /// @dev Not implemented380 fn approve(&mut self, _caller: caller, _approved: address, _token_id: uint256) -> Result<void> {381 Err("not implemented".into())382 }383384 /// @dev Not implemented385 fn set_approval_for_all(386 &mut self,387 _caller: caller,388 _operator: address,389 _approved: bool,390 ) -> Result<void> {391 // TODO: Not implemetable392 Err("not implemented".into())393 }394395 /// @dev Not implemented396 fn get_approved(&self, _token_id: uint256) -> Result<address> {397 // TODO: Not implemetable398 Err("not implemented".into())399 }400401 /// @dev Not implemented402 fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {403 // TODO: Not implemetable404 Err("not implemented".into())405 }406}407408/// Returns amount of pieces of `token` that `owner` have409pub fn balance<T: Config>(410 collection: &RefungibleHandle<T>,411 token: TokenId,412 owner: &T::CrossAccountId,413) -> Result<u128> {414 collection.consume_store_reads(1)?;415 let balance = <Balance<T>>::get((collection.id, token, &owner));416 Ok(balance)417}418419/// Throws if `owner_balance` is lower than total amount of `token` pieces420pub fn ensure_single_owner<T: Config>(421 collection: &RefungibleHandle<T>,422 token: TokenId,423 owner_balance: u128,424) -> Result<()> {425 collection.consume_store_reads(1)?;426 let total_supply = <TotalSupply<T>>::get((collection.id, token));427 if total_supply != owner_balance {428 return Err("token has multiple owners".into());429 }430 Ok(())431}432433/// @title ERC721 Token that can be irreversibly burned (destroyed).434#[solidity_interface(name = ERC721Burnable)]435impl<T: Config> RefungibleHandle<T> {436 /// @notice Burns a specific ERC721 token.437 /// @dev Throws unless `msg.sender` is the current RFT owner, or an authorized438 /// operator of the current owner.439 /// @param tokenId The RFT to approve440 #[weight(<SelfWeightOf<T>>::burn_item_fully())]441 fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {442 let caller = T::CrossAccountId::from_eth(caller);443 let token = token_id.try_into()?;444445 let balance = balance(&self, token, &caller)?;446 ensure_single_owner(&self, token, balance)?;447448 <Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;449 Ok(())450 }451}452453/// @title ERC721 minting logic.454#[solidity_interface(name = ERC721Mintable, events(ERC721MintableEvents))]455impl<T: Config> RefungibleHandle<T> {456 fn minting_finished(&self) -> Result<bool> {457 Ok(false)458 }459460 /// @notice Function to mint token.461 /// @dev `tokenId` should be obtained with `nextTokenId` method,462 /// unlike standard, you can't specify it manually463 /// @param to The new owner464 /// @param tokenId ID of the minted RFT465 #[weight(<SelfWeightOf<T>>::create_item())]466 fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {467 let caller = T::CrossAccountId::from_eth(caller);468 let to = T::CrossAccountId::from_eth(to);469 let token_id: u32 = token_id.try_into()?;470 let budget = self471 .recorder472 .weight_calls_budget(<StructureWeight<T>>::find_parent());473474 if <TokensMinted<T>>::get(self.id)475 .checked_add(1)476 .ok_or("item id overflow")?477 != token_id478 {479 return Err("item id should be next".into());480 }481482 let users = [(to.clone(), 1)]483 .into_iter()484 .collect::<BTreeMap<_, _>>()485 .try_into()486 .unwrap();487 <Pallet<T>>::create_item(488 self,489 &caller,490 CreateItemData::<T::CrossAccountId> {491 users,492 properties: CollectionPropertiesVec::default(),493 },494 &budget,495 )496 .map_err(dispatch_to_evm::<T>)?;497498 Ok(true)499 }500501 /// @notice Function to mint token with the given tokenUri.502 /// @dev `tokenId` should be obtained with `nextTokenId` method,503 /// unlike standard, you can't specify it manually504 /// @param to The new owner505 /// @param tokenId ID of the minted RFT506 /// @param tokenUri Token URI that would be stored in the RFT properties507 #[solidity(rename_selector = "mintWithTokenURI")]508 #[weight(<SelfWeightOf<T>>::create_item())]509 fn mint_with_token_uri(510 &mut self,511 caller: caller,512 to: address,513 token_id: uint256,514 token_uri: string,515 ) -> Result<bool> {516 let key = key::url();517 let permission = get_token_permission::<T>(self.id, &key)?;518 if !permission.collection_admin {519 return Err("Operation is not allowed".into());520 }521522 let caller = T::CrossAccountId::from_eth(caller);523 let to = T::CrossAccountId::from_eth(to);524 let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;525 let budget = self526 .recorder527 .weight_calls_budget(<StructureWeight<T>>::find_parent());528529 if <TokensMinted<T>>::get(self.id)530 .checked_add(1)531 .ok_or("item id overflow")?532 != token_id533 {534 return Err("item id should be next".into());535 }536537 let mut properties = CollectionPropertiesVec::default();538 properties539 .try_push(Property {540 key,541 value: token_uri542 .into_bytes()543 .try_into()544 .map_err(|_| "token uri is too long")?,545 })546 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;547548 let users = [(to.clone(), 1)]549 .into_iter()550 .collect::<BTreeMap<_, _>>()551 .try_into()552 .unwrap();553 <Pallet<T>>::create_item(554 self,555 &caller,556 CreateItemData::<T::CrossAccountId> { users, properties },557 &budget,558 )559 .map_err(dispatch_to_evm::<T>)?;560 Ok(true)561 }562563 /// @dev Not implemented564 fn finish_minting(&mut self, _caller: caller) -> Result<bool> {565 Err("not implementable".into())566 }567}568569fn get_token_property<T: Config>(570 collection: &CollectionHandle<T>,571 token_id: u32,572 key: &up_data_structs::PropertyKey,573) -> Result<string> {574 collection.consume_store_reads(1)?;575 let properties = <TokenProperties<T>>::try_get((collection.id, token_id))576 .map_err(|_| Error::Revert("Token properties not found".into()))?;577 if let Some(property) = properties.get(key) {578 return Ok(string::from_utf8_lossy(property).into());579 }580581 Err("Property tokenURI not found".into())582}583584fn get_token_permission<T: Config>(585 collection_id: CollectionId,586 key: &PropertyKey,587) -> Result<PropertyPermission> {588 let token_property_permissions = CollectionPropertyPermissions::<T>::try_get(collection_id)589 .map_err(|_| Error::Revert("No permissions for collection".into()))?;590 let a = token_property_permissions591 .get(key)592 .map(Clone::clone)593 .ok_or_else(|| {594 let key = string::from_utf8(key.clone().into_inner()).unwrap_or_default();595 Error::Revert(alloc::format!("No permission for key {}", key))596 })?;597 Ok(a)598}599600/// @title Unique extensions for ERC721.601#[solidity_interface(name = ERC721UniqueExtensions)]602impl<T: Config> RefungibleHandle<T> {603 /// @notice Transfer ownership of an RFT604 /// @dev Throws unless `msg.sender` is the current owner. Throws if `to`605 /// is the zero address. Throws if `tokenId` is not a valid RFT.606 /// Throws if RFT pieces have multiple owners.607 /// @param to The new owner608 /// @param tokenId The RFT to transfer609 #[weight(<SelfWeightOf<T>>::transfer_creating_removing())]610 fn transfer(&mut self, caller: caller, to: address, token_id: uint256) -> Result<void> {611 let caller = T::CrossAccountId::from_eth(caller);612 let to = T::CrossAccountId::from_eth(to);613 let token = token_id.try_into()?;614 let budget = self615 .recorder616 .weight_calls_budget(<StructureWeight<T>>::find_parent());617618 let balance = balance(&self, token, &caller)?;619 ensure_single_owner(&self, token, balance)?;620621 <Pallet<T>>::transfer(self, &caller, &to, token, balance, &budget)622 .map_err(dispatch_to_evm::<T>)?;623 Ok(())624 }625626 /// @notice Burns a specific ERC721 token.627 /// @dev Throws unless `msg.sender` is the current owner or an authorized628 /// operator for this RFT. Throws if `from` is not the current owner. Throws629 /// if `to` is the zero address. Throws if `tokenId` is not a valid RFT.630 /// Throws if RFT pieces have multiple owners.631 /// @param from The current owner of the RFT632 /// @param tokenId The RFT to transfer633 #[weight(<SelfWeightOf<T>>::burn_from())]634 fn burn_from(&mut self, caller: caller, from: address, token_id: uint256) -> Result<void> {635 let caller = T::CrossAccountId::from_eth(caller);636 let from = T::CrossAccountId::from_eth(from);637 let token = token_id.try_into()?;638 let budget = self639 .recorder640 .weight_calls_budget(<StructureWeight<T>>::find_parent());641642 let balance = balance(&self, token, &caller)?;643 ensure_single_owner(&self, token, balance)?;644645 <Pallet<T>>::burn_from(self, &caller, &from, token, balance, &budget)646 .map_err(dispatch_to_evm::<T>)?;647 Ok(())648 }649650 /// @notice Returns next free RFT ID.651 fn next_token_id(&self) -> Result<uint256> {652 self.consume_store_reads(1)?;653 Ok(<TokensMinted<T>>::get(self.id)654 .checked_add(1)655 .ok_or("item id overflow")?656 .into())657 }658659 /// @notice Function to mint multiple tokens.660 /// @dev `tokenIds` should be an array of consecutive numbers and first number661 /// should be obtained with `nextTokenId` method662 /// @param to The new owner663 /// @param tokenIds IDs of the minted RFTs664 #[weight(<SelfWeightOf<T>>::create_multiple_items(token_ids.len() as u32))]665 fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {666 let caller = T::CrossAccountId::from_eth(caller);667 let to = T::CrossAccountId::from_eth(to);668 let mut expected_index = <TokensMinted<T>>::get(self.id)669 .checked_add(1)670 .ok_or("item id overflow")?;671 let budget = self672 .recorder673 .weight_calls_budget(<StructureWeight<T>>::find_parent());674675 let total_tokens = token_ids.len();676 for id in token_ids.into_iter() {677 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;678 if id != expected_index {679 return Err("item id should be next".into());680 }681 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;682 }683 let users = [(to.clone(), 1)]684 .into_iter()685 .collect::<BTreeMap<_, _>>()686 .try_into()687 .unwrap();688 let create_item_data = CreateItemData::<T::CrossAccountId> {689 users,690 properties: CollectionPropertiesVec::default(),691 };692 let data = (0..total_tokens)693 .map(|_| create_item_data.clone())694 .collect();695696 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)697 .map_err(dispatch_to_evm::<T>)?;698 Ok(true)699 }700701 /// @notice Function to mint multiple tokens with the given tokenUris.702 /// @dev `tokenIds` is array of pairs of token ID and token URI. Token IDs should be consecutive703 /// numbers and first number should be obtained with `nextTokenId` method704 /// @param to The new owner705 /// @param tokens array of pairs of token ID and token URI for minted tokens706 #[solidity(rename_selector = "mintBulkWithTokenURI")]707 #[weight(<SelfWeightOf<T>>::create_multiple_items(tokens.len() as u32))]708 fn mint_bulk_with_token_uri(709 &mut self,710 caller: caller,711 to: address,712 tokens: Vec<(uint256, string)>,713 ) -> Result<bool> {714 let key = key::url();715 let caller = T::CrossAccountId::from_eth(caller);716 let to = T::CrossAccountId::from_eth(to);717 let mut expected_index = <TokensMinted<T>>::get(self.id)718 .checked_add(1)719 .ok_or("item id overflow")?;720 let budget = self721 .recorder722 .weight_calls_budget(<StructureWeight<T>>::find_parent());723724 let mut data = Vec::with_capacity(tokens.len());725 let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]726 .into_iter()727 .collect::<BTreeMap<_, _>>()728 .try_into()729 .unwrap();730 for (id, token_uri) in tokens {731 let id: u32 = id.try_into().map_err(|_| "token id overflow")?;732 if id != expected_index {733 return Err("item id should be next".into());734 }735 expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;736737 let mut properties = CollectionPropertiesVec::default();738 properties739 .try_push(Property {740 key: key.clone(),741 value: token_uri742 .into_bytes()743 .try_into()744 .map_err(|_| "token uri is too long")?,745 })746 .map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;747748 let create_item_data = CreateItemData::<T::CrossAccountId> {749 users: users.clone(),750 properties,751 };752 data.push(create_item_data);753 }754755 <Pallet<T>>::create_multiple_items(self, &caller, data, &budget)756 .map_err(dispatch_to_evm::<T>)?;757 Ok(true)758 }759760 /// Returns EVM address for refungible token761 ///762 /// @param token ID of the token763 fn token_contract_address(&self, token: uint256) -> Result<address> {764 Ok(T::EvmTokenAddressMapping::token_to_address(765 self.id,766 token.try_into().map_err(|_| "token id overflow")?,767 ))768 }769}770771impl<T: Config> RefungibleHandle<T> {772 pub fn supports_metadata(&self) -> bool {773 if let Some(erc721_metadata) =774 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())775 {776 *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED777 } else {778 false779 }780 }781}782783#[solidity_interface(784 name = UniqueRefungible,785 is(786 ERC721,787 ERC721Enumerable,788 ERC721UniqueExtensions,789 ERC721Mintable,790 ERC721Burnable,791 Collection(via(common_mut returns CollectionHandle<T>)),792 TokenProperties,793 ERC721Metadata(if(this.supports_metadata())),794 )795)]796impl<T: Config> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}797798// Not a tests, but code generators799generate_stubgen!(gen_impl, UniqueRefungibleCall<()>, true);800generate_stubgen!(gen_iface, UniqueRefungibleCall<()>, false);801802impl<T: Config> CommonEvmHandler for RefungibleHandle<T>803where804 T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]>,805{806 const CODE: &'static [u8] = include_bytes!("./stubs/UniqueRefungible.raw");807 fn call(808 self,809 handle: &mut impl PrecompileHandle,810 ) -> Option<pallet_common::erc::PrecompileResult> {811 call::<T, UniqueRefungibleCall<T>, _, _>(handle, self)812 }813}pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -304,18 +304,6 @@
}
}
-impl<T: Config> RefungibleHandle<T> {
- pub fn supports_metadata(&self) -> bool {
- if let Some(erc721_metadata) =
- pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())
- {
- *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED
- } else {
- false
- }
- }
-}
-
impl<T: Config> Deref for RefungibleHandle<T> {
type Target = pallet_common::CollectionHandle<T>;
pallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterbothbinary blob — no preview
pallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -17,6 +17,45 @@
}
}
+/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
+contract ERC721Metadata is Dummy, ERC165 {
+ /// @notice A descriptive name for a collection of RFTs in this contract
+ /// @dev EVM selector for this function is: 0x06fdde03,
+ /// or in textual repr: name()
+ function name() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+
+ /// @notice An abbreviated name for RFTs in this contract
+ /// @dev EVM selector for this function is: 0x95d89b41,
+ /// or in textual repr: symbol()
+ function symbol() public view returns (string memory) {
+ require(false, stub_error);
+ dummy;
+ return "";
+ }
+
+ /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ ///
+ /// @dev If the token has a `url` property and it is not empty, it is returned.
+ /// 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`.
+ /// If the collection property `baseURI` is empty or absent, return "" (empty string)
+ /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
+ /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
+ ///
+ /// @return token's const_metadata
+ /// @dev EVM selector for this function is: 0xc87b56dd,
+ /// or in textual repr: tokenURI(uint256)
+ function tokenURI(uint256 tokenId) public view returns (string memory) {
+ require(false, stub_error);
+ tokenId;
+ dummy;
+ return "";
+ }
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0x41369377
contract TokenProperties is Dummy, ERC165 {
@@ -177,10 +216,10 @@
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
- function collectionSponsor() public view returns (Tuple17 memory) {
+ function collectionSponsor() public view returns (Tuple15 memory) {
require(false, stub_error);
dummy;
- return Tuple17(0x0000000000000000000000000000000000000000, 0);
+ return Tuple15(0x0000000000000000000000000000000000000000, 0);
}
/// Set limits for the collection.
@@ -359,10 +398,10 @@
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
- function collectionOwner() public view returns (Tuple17 memory) {
+ function collectionOwner() public view returns (Tuple15 memory) {
require(false, stub_error);
dummy;
- return Tuple17(0x0000000000000000000000000000000000000000, 0);
+ return Tuple15(0x0000000000000000000000000000000000000000, 0);
}
/// Changes collection owner to another account
@@ -379,7 +418,7 @@
}
/// @dev anonymous struct
-struct Tuple17 {
+struct Tuple15 {
address field_0;
uint256 field_1;
}
@@ -527,7 +566,7 @@
/// @param tokens array of pairs of token ID and token URI for minted tokens
/// @dev EVM selector for this function is: 0x36543006,
/// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
- function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) public returns (bool) {
+ function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) public returns (bool) {
require(false, stub_error);
to;
tokens;
@@ -549,7 +588,7 @@
}
/// @dev anonymous struct
-struct Tuple8 {
+struct Tuple6 {
uint256 field_0;
string field_1;
}
@@ -591,45 +630,6 @@
require(false, stub_error);
dummy;
return 0;
- }
-}
-
-/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
-contract ERC721Metadata is Dummy, ERC165 {
- /// @notice A descriptive name for a collection of RFTs in this contract
- /// @dev EVM selector for this function is: 0x06fdde03,
- /// or in textual repr: name()
- function name() public view returns (string memory) {
- require(false, stub_error);
- dummy;
- return "";
- }
-
- /// @notice An abbreviated name for RFTs in this contract
- /// @dev EVM selector for this function is: 0x95d89b41,
- /// or in textual repr: symbol()
- function symbol() public view returns (string memory) {
- require(false, stub_error);
- dummy;
- return "";
- }
-
- /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
- ///
- /// @dev If the token has a `url` property and it is not empty, it is returned.
- /// 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`.
- /// If the collection property `baseURI` is empty or absent, return "" (empty string)
- /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
- /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
- ///
- /// @return token's const_metadata
- /// @dev EVM selector for this function is: 0xc87b56dd,
- /// or in textual repr: tokenURI(uint256)
- function tokenURI(uint256 tokenId) public view returns (string memory) {
- require(false, stub_error);
- tokenId;
- dummy;
- return "";
}
}
@@ -776,11 +776,11 @@
Dummy,
ERC165,
ERC721,
- ERC721Metadata,
ERC721Enumerable,
ERC721UniqueExtensions,
ERC721Mintable,
ERC721Burnable,
Collection,
- TokenProperties
+ TokenProperties,
+ ERC721Metadata
{}
pallets/unique/src/eth/mod.rsdiffbeforeafterboth--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -336,27 +336,6 @@
}
#[weight(<SelfWeightOf<T>>::create_collection())]
- #[deprecated(note = "mathod was renamed to `create_rft_collection`, prefer it instead")]
- fn create_refungible_collection(
- &mut self,
- caller: caller,
- value: value,
- name: string,
- description: string,
- token_prefix: string,
- ) -> Result<address> {
- create_refungible_collection_internal::<T>(
- caller,
- value,
- name,
- description,
- token_prefix,
- Default::default(),
- false,
- )
- }
-
- #[weight(<SelfWeightOf<T>>::create_collection())]
#[solidity(rename_selector = "createERC721MetadataCompatibleRFTCollection")]
fn create_refungible_collection_with_properties(
&mut self,
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
@@ -23,7 +23,7 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x95eb98f4
+/// @dev the ERC-165 identifier for this interface is 0xd14d1221
contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
@@ -85,21 +85,6 @@
/// @dev EVM selector for this function is: 0xab173450,
/// or in textual repr: createRFTCollection(string,string,string)
function createRFTCollection(
- string memory name,
- string memory description,
- string memory tokenPrefix
- ) public payable returns (address) {
- require(false, stub_error);
- name;
- description;
- tokenPrefix;
- dummy = 0;
- return 0x0000000000000000000000000000000000000000;
- }
-
- /// @dev EVM selector for this function is: 0x44a68ad5,
- /// or in textual repr: createRefungibleCollection(string,string,string)
- function createRefungibleCollection(
string memory name,
string memory description,
string memory tokenPrefix
tests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -18,7 +18,7 @@
}
/// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x95eb98f4
+/// @dev the ERC-165 identifier for this interface is 0xd14d1221
interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
/// Create an NFT collection
/// @param name Name of the collection
@@ -58,14 +58,6 @@
/// @dev EVM selector for this function is: 0xab173450,
/// or in textual repr: createRFTCollection(string,string,string)
function createRFTCollection(
- string memory name,
- string memory description,
- string memory tokenPrefix
- ) external payable returns (address);
-
- /// @dev EVM selector for this function is: 0x44a68ad5,
- /// or in textual repr: createRefungibleCollection(string,string,string)
- function createRefungibleCollection(
string memory name,
string memory description,
string memory tokenPrefix
tests/src/eth/api/UniqueNFT.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -12,6 +12,34 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
+/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
+/// @dev See https://eips.ethereum.org/EIPS/eip-721
+/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
+interface ERC721Metadata is Dummy, ERC165 {
+ /// @notice A descriptive name for a collection of NFTs in this contract
+ /// @dev EVM selector for this function is: 0x06fdde03,
+ /// or in textual repr: name()
+ function name() external view returns (string memory);
+
+ /// @notice An abbreviated name for NFTs in this contract
+ /// @dev EVM selector for this function is: 0x95d89b41,
+ /// or in textual repr: symbol()
+ function symbol() external view returns (string memory);
+
+ /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ ///
+ /// @dev If the token has a `url` property and it is not empty, it is returned.
+ /// 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`.
+ /// If the collection property `baseURI` is empty or absent, return "" (empty string)
+ /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
+ /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
+ ///
+ /// @return token's const_metadata
+ /// @dev EVM selector for this function is: 0xc87b56dd,
+ /// or in textual repr: tokenURI(uint256)
+ function tokenURI(uint256 tokenId) external view returns (string memory);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0x41369377
interface TokenProperties is Dummy, ERC165 {
@@ -120,7 +148,7 @@
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
- function collectionSponsor() external view returns (Tuple17 memory);
+ function collectionSponsor() external view returns (Tuple15 memory);
/// Set limits for the collection.
/// @dev Throws error if limit not found.
@@ -237,7 +265,7 @@
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
- function collectionOwner() external view returns (Tuple17 memory);
+ function collectionOwner() external view returns (Tuple15 memory);
/// Changes collection owner to another account
///
@@ -249,7 +277,7 @@
}
/// @dev anonymous struct
-struct Tuple17 {
+struct Tuple15 {
address field_0;
uint256 field_1;
}
@@ -350,11 +378,11 @@
/// @param tokens array of pairs of token ID and token URI for minted tokens
/// @dev EVM selector for this function is: 0x36543006,
/// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
- function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) external returns (bool);
+ function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) external returns (bool);
}
/// @dev anonymous struct
-struct Tuple8 {
+struct Tuple6 {
uint256 field_0;
string field_1;
}
@@ -383,35 +411,7 @@
/// or in textual repr: totalSupply()
function totalSupply() external view returns (uint256);
}
-
-/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension
-/// @dev See https://eips.ethereum.org/EIPS/eip-721
-/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
-interface ERC721Metadata is Dummy, ERC165 {
- /// @notice A descriptive name for a collection of NFTs in this contract
- /// @dev EVM selector for this function is: 0x06fdde03,
- /// or in textual repr: name()
- function name() external view returns (string memory);
- /// @notice An abbreviated name for NFTs in this contract
- /// @dev EVM selector for this function is: 0x95d89b41,
- /// or in textual repr: symbol()
- function symbol() external view returns (string memory);
-
- /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
- ///
- /// @dev If the token has a `url` property and it is not empty, it is returned.
- /// 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`.
- /// If the collection property `baseURI` is empty or absent, return "" (empty string)
- /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
- /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
- ///
- /// @return token's const_metadata
- /// @dev EVM selector for this function is: 0xc87b56dd,
- /// or in textual repr: tokenURI(uint256)
- function tokenURI(uint256 tokenId) external view returns (string memory);
-}
-
/// @dev inlined interface
interface ERC721Events {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
@@ -507,11 +507,11 @@
Dummy,
ERC165,
ERC721,
- ERC721Metadata,
ERC721Enumerable,
ERC721UniqueExtensions,
ERC721Mintable,
ERC721Burnable,
Collection,
- TokenProperties
+ TokenProperties,
+ ERC721Metadata
{}
tests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -12,6 +12,32 @@
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
+/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
+interface ERC721Metadata is Dummy, ERC165 {
+ /// @notice A descriptive name for a collection of RFTs in this contract
+ /// @dev EVM selector for this function is: 0x06fdde03,
+ /// or in textual repr: name()
+ function name() external view returns (string memory);
+
+ /// @notice An abbreviated name for RFTs in this contract
+ /// @dev EVM selector for this function is: 0x95d89b41,
+ /// or in textual repr: symbol()
+ function symbol() external view returns (string memory);
+
+ /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
+ ///
+ /// @dev If the token has a `url` property and it is not empty, it is returned.
+ /// 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`.
+ /// If the collection property `baseURI` is empty or absent, return "" (empty string)
+ /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
+ /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
+ ///
+ /// @return token's const_metadata
+ /// @dev EVM selector for this function is: 0xc87b56dd,
+ /// or in textual repr: tokenURI(uint256)
+ function tokenURI(uint256 tokenId) external view returns (string memory);
+}
+
/// @title A contract that allows to set and delete token properties and change token property permissions.
/// @dev the ERC-165 identifier for this interface is 0x41369377
interface TokenProperties is Dummy, ERC165 {
@@ -120,7 +146,7 @@
/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
/// @dev EVM selector for this function is: 0x6ec0a9f1,
/// or in textual repr: collectionSponsor()
- function collectionSponsor() external view returns (Tuple17 memory);
+ function collectionSponsor() external view returns (Tuple15 memory);
/// Set limits for the collection.
/// @dev Throws error if limit not found.
@@ -237,7 +263,7 @@
/// If address is canonical then substrate mirror is zero and vice versa.
/// @dev EVM selector for this function is: 0xdf727d3b,
/// or in textual repr: collectionOwner()
- function collectionOwner() external view returns (Tuple17 memory);
+ function collectionOwner() external view returns (Tuple15 memory);
/// Changes collection owner to another account
///
@@ -249,7 +275,7 @@
}
/// @dev anonymous struct
-struct Tuple17 {
+struct Tuple15 {
address field_0;
uint256 field_1;
}
@@ -352,7 +378,7 @@
/// @param tokens array of pairs of token ID and token URI for minted tokens
/// @dev EVM selector for this function is: 0x36543006,
/// or in textual repr: mintBulkWithTokenURI(address,(uint256,string)[])
- function mintBulkWithTokenURI(address to, Tuple8[] memory tokens) external returns (bool);
+ function mintBulkWithTokenURI(address to, Tuple6[] memory tokens) external returns (bool);
/// Returns EVM address for refungible token
///
@@ -363,7 +389,7 @@
}
/// @dev anonymous struct
-struct Tuple8 {
+struct Tuple6 {
uint256 field_0;
string field_1;
}
@@ -393,32 +419,6 @@
function totalSupply() external view returns (uint256);
}
-/// @dev the ERC-165 identifier for this interface is 0x5b5e139f
-interface ERC721Metadata is Dummy, ERC165 {
- /// @notice A descriptive name for a collection of RFTs in this contract
- /// @dev EVM selector for this function is: 0x06fdde03,
- /// or in textual repr: name()
- function name() external view returns (string memory);
-
- /// @notice An abbreviated name for RFTs in this contract
- /// @dev EVM selector for this function is: 0x95d89b41,
- /// or in textual repr: symbol()
- function symbol() external view returns (string memory);
-
- /// @notice A distinct Uniform Resource Identifier (URI) for a given asset.
- ///
- /// @dev If the token has a `url` property and it is not empty, it is returned.
- /// 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`.
- /// If the collection property `baseURI` is empty or absent, return "" (empty string)
- /// otherwise, if token property `suffix` present and is non-empty, return concatenation of baseURI and suffix
- /// otherwise, return concatenation of `baseURI` and stringified token id (decimal stringifying, without paddings).
- ///
- /// @return token's const_metadata
- /// @dev EVM selector for this function is: 0xc87b56dd,
- /// or in textual repr: tokenURI(uint256)
- function tokenURI(uint256 tokenId) external view returns (string memory);
-}
-
/// @dev inlined interface
interface ERC721Events {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
@@ -512,11 +512,11 @@
Dummy,
ERC165,
ERC721,
- ERC721Metadata,
ERC721Enumerable,
ERC721UniqueExtensions,
ERC721Mintable,
ERC721Burnable,
Collection,
- TokenProperties
+ TokenProperties,
+ ERC721Metadata
{}
tests/src/eth/collectionHelpersAbi.jsondiffbeforeafterboth--- a/tests/src/eth/collectionHelpersAbi.json
+++ b/tests/src/eth/collectionHelpersAbi.json
@@ -84,17 +84,6 @@
},
{
"inputs": [
- { "internalType": "string", "name": "name", "type": "string" },
- { "internalType": "string", "name": "description", "type": "string" },
- { "internalType": "string", "name": "tokenPrefix", "type": "string" }
- ],
- "name": "createRefungibleCollection",
- "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
- "stateMutability": "payable",
- "type": "function"
- },
- {
- "inputs": [
{
"internalType": "address",
"name": "collectionAddress",
tests/src/eth/collectionProperties.test.tsdiffbeforeafterboth--- a/tests/src/eth/collectionProperties.test.ts
+++ b/tests/src/eth/collectionProperties.test.ts
@@ -1,5 +1,6 @@
import {itEth, usingEthPlaygrounds, expect} from './util/playgrounds';
import {IKeyringPair} from '@polkadot/types/types';
+import {Pallets} from '../util/playgrounds';
describe('EVM collection properties', () => {
let donor: IKeyringPair;
@@ -80,7 +81,7 @@
expect(await contract.methods.supportsInterface('0x5b5e139f').call()).to.be.false;
});
- itEth('ERC721Metadata property can be set for RFT collection', async({helper}) => {
+ itEth.ifWithPallets('ERC721Metadata property can be set for RFT collection', [Pallets.ReFungible], async({helper}) => {
const caller = await helper.eth.createAccountWithBalance(donor);
const collection = await helper.rft.mintCollection(donor, {name: 'col', description: 'descr', tokenPrefix: 'COL'});
tests/src/eth/nonFungible.test.tsdiffbeforeafterboth--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -79,11 +79,11 @@
});
});
- async function setup(helper: EthUniqueHelper, tokenPrefix: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {
+ async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {
const owner = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', tokenPrefix);
+ const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleNFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);
const contract = helper.ethNativeContract.collection(collectionAddress, 'nft', owner);
const nextTokenId = await contract.methods.nextTokenId().call();
tests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -154,7 +154,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17",
+ "internalType": "struct Tuple15",
"name": "",
"type": "tuple"
}
@@ -178,7 +178,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17",
+ "internalType": "struct Tuple15",
"name": "",
"type": "tuple"
}
@@ -287,7 +287,7 @@
{ "internalType": "uint256", "name": "field_0", "type": "uint256" },
{ "internalType": "string", "name": "field_1", "type": "string" }
],
- "internalType": "struct Tuple8[]",
+ "internalType": "struct Tuple6[]",
"name": "tokens",
"type": "tuple[]"
}
tests/src/eth/reFungibleAbi.jsondiffbeforeafterboth--- a/tests/src/eth/reFungibleAbi.json
+++ b/tests/src/eth/reFungibleAbi.json
@@ -154,7 +154,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17",
+ "internalType": "struct Tuple15",
"name": "",
"type": "tuple"
}
@@ -178,7 +178,7 @@
{ "internalType": "address", "name": "field_0", "type": "address" },
{ "internalType": "uint256", "name": "field_1", "type": "uint256" }
],
- "internalType": "struct Tuple17",
+ "internalType": "struct Tuple15",
"name": "",
"type": "tuple"
}
@@ -287,7 +287,7 @@
{ "internalType": "uint256", "name": "field_0", "type": "uint256" },
{ "internalType": "string", "name": "field_1", "type": "string" }
],
- "internalType": "struct Tuple8[]",
+ "internalType": "struct Tuple6[]",
"name": "tokens",
"type": "tuple[]"
}
tests/src/eth/reFungibleToken.test.tsdiffbeforeafterboth--- a/tests/src/eth/reFungibleToken.test.ts
+++ b/tests/src/eth/reFungibleToken.test.ts
@@ -76,11 +76,11 @@
});
});
- async function setup(helper: EthUniqueHelper, tokenPrefix: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {
+ async function setup(helper: EthUniqueHelper, baseUri: string, propertyKey?: string, propertyValue?: string): Promise<{contract: Contract, nextTokenId: string}> {
const owner = await helper.eth.createAccountWithBalance(donor);
const receiver = helper.eth.createAccount();
- const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Mint collection', 'a', 'b', tokenPrefix);
+ const {collectionAddress} = await helper.eth.createERC721MetadataCompatibleRFTCollection(owner, 'Mint collection', 'a', 'b', baseUri);
const contract = helper.ethNativeContract.collection(collectionAddress, 'rft', owner);
const nextTokenId = await contract.methods.nextTokenId().call();