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.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -21,19 +21,15 @@
extern crate alloc;
-use alloc::string::ToString;
use core::{
char::{REPLACEMENT_CHARACTER, decode_utf16},
convert::TryInto,
};
use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*, weight};
-use frame_support::BoundedBTreeMap;
+use frame_support::{BoundedBTreeMap, BoundedVec};
use pallet_common::{
CollectionHandle, CollectionPropertyPermissions,
- erc::{
- CommonEvmHandler, CollectionCall,
- static_property::{key, value as property_value},
- },
+ erc::{CommonEvmHandler, CollectionCall, static_property::key, static_property::value},
};
use pallet_evm::{account::CrossAccountId, PrecompileHandle};
use pallet_evm_coder_substrate::{call, dispatch_to_evm};
@@ -222,37 +218,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,
+ },
+ )
}
}
@@ -765,17 +768,29 @@
}
}
+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
+ }
+ }
+}
+
#[solidity_interface(
name = UniqueRefungible,
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> RefungibleHandle<T> where T::AccountId: From<[u8; 32]> + AsRef<[u8; 32]> {}
pallets/refungible/src/lib.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 Pallet18//!19//! The Refungible pallet provides functionality for handling refungible collections and tokens.20//!21//! - [`Config`]22//! - [`RefungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`](common::CommonWeights)25//!26//! ## Overview27//!28//! The Refungible pallet provides functions for:29//!30//! - RFT collection creation and removal31//! - Minting and burning of RFT tokens32//! - Partition and repartition of RFT tokens33//! - Retrieving number of pieces of RFT token34//! - Retrieving account balances35//! - Transfering RFT token pieces36//! - Burning RFT token pieces37//! - Setting and checking allowance for RFT tokens38//!39//! ### Terminology40//!41//! - **RFT token:** Non fungible token that was partitioned to pieces. If an account owns all42//! of the RFT token pieces than it owns the RFT token and can repartition it.43//!44//! - **RFT Collection:** A collection of RFT tokens. All RFT tokens are part of a collection.45//! Each collection has its own settings and set of permissions.46//!47//! - **RFT token piece:** A fungible part of an RFT token.48//!49//! - **Balance:** RFT token pieces owned by an account50//!51//! - **Allowance:** Maximum number of RFT token pieces that one account is allowed to52//! transfer from the balance of another account53//!54//! - **Burning:** The process of “deleting” a token from a collection or removing token pieces from55//! an account balance.56//!57//! ### Implementations58//!59//! The Refungible pallet provides implementations for the following traits. If these traits provide60//! the functionality that you need, then you can avoid coupling with the Refungible pallet.61//!62//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight63//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing64//! with collections65//! - [`RefungibleExtensions`](pallet_common::RefungibleExtensions): Functions specific for refungible66//! collection67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create RFT collection. RFT collection can be configured to allow or deny access for73//! some accounts.74//! - `destroy_collection` - Destroy exising RFT collection. There should be no tokens in the collection.75//! - `burn` - Burn some amount of RFT token pieces owned by account. Burns the RFT token if no pieces left.76//! - `transfer` - Transfer some amount of RFT token pieces. Transfers should be enabled for RFT collection.77//! Nests the RFT token if RFT token pieces are sent to another token.78//! - `create_item` - Mint RFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account to transfer balance from sender's account.80//! - `repartition` - Repartition token to selected number of pieces. Sender should own all existing pieces.81//!82//! ## Assumptions83//!84//! * Total number of pieces for one token shouldn't exceed `up_data_structs::MAX_REFUNGIBLE_PIECES`.85//! * Total number of tokens of all types shouldn't be greater than `up_data_structs::MAX_TOKEN_PREFIX_LENGTH`.86//! * Sender should be in collection's allow list to perform operations on tokens.8788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use codec::{Encode, Decode, MaxEncodedLen};94use core::ops::Deref;95use derivative::Derivative;96use evm_coder::ToLog;97use frame_support::{98 BoundedBTreeMap, BoundedVec, ensure, fail, storage::with_transaction, transactional,99 pallet_prelude::ConstU32,100};101use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};102use pallet_evm_coder_substrate::WithRecorder;103use pallet_common::{104 CommonCollectionOperations,105 erc::static_property::{key, value},106 Error as CommonError,107 eth::collection_id_to_address,108 Event as CommonEvent, Pallet as PalletCommon,109};110use pallet_structure::Pallet as PalletStructure;111use scale_info::TypeInfo;112use sp_core::H160;113use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};114use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};115use up_data_structs::{116 AccessMode, budget::Budget, CollectionId, CollectionFlags, CollectionPropertiesVec,117 CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH,118 MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,119 PropertyScope, PropertyValue, TokenId, TrySetProperty,120};121122pub use pallet::*;123#[cfg(feature = "runtime-benchmarks")]124pub mod benchmarking;125pub mod common;126pub mod erc;127pub mod erc_token;128pub mod weights;129130#[derive(Derivative, Clone)]131pub struct CreateItemData<CrossAccountId> {132 #[derivative(Debug(format_with = "bounded::map_debug"))]133 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,134 #[derivative(Debug(format_with = "bounded::vec_debug"))]135 pub properties: CollectionPropertiesVec,136}137pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;138139/// Token data, stored independently from other data used to describe it140/// for the convenience of database access. Notably contains the token metadata.141#[struct_versioning::versioned(version = 2, upper)]142#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]143#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]144pub struct ItemData {145 pub const_data: BoundedVec<u8, CustomDataLimit>,146147 #[version(..2)]148 pub variable_data: BoundedVec<u8, CustomDataLimit>,149}150151#[frame_support::pallet]152pub mod pallet {153 use super::*;154 use frame_support::{155 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,156 traits::StorageVersion,157 };158 use frame_system::pallet_prelude::*;159 use up_data_structs::{CollectionId, TokenId};160 use super::weights::WeightInfo;161162 #[pallet::error]163 pub enum Error<T> {164 /// Not Refungible item data used to mint in Refungible collection.165 NotRefungibleDataUsedToMintFungibleCollectionToken,166 /// Maximum refungibility exceeded.167 WrongRefungiblePieces,168 /// Refungible token can't be repartitioned by user who isn't owns all pieces.169 RepartitionWhileNotOwningAllPieces,170 /// Refungible token can't nest other tokens.171 RefungibleDisallowsNesting,172 /// Setting item properties is not allowed.173 SettingPropertiesNotAllowed,174 }175176 #[pallet::config]177 pub trait Config:178 frame_system::Config + pallet_common::Config + pallet_structure::Config179 {180 type WeightInfo: WeightInfo;181 }182183 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);184185 #[pallet::pallet]186 #[pallet::storage_version(STORAGE_VERSION)]187 #[pallet::generate_store(pub(super) trait Store)]188 pub struct Pallet<T>(_);189190 /// Total amount of minted tokens in a collection.191 #[pallet::storage]192 pub type TokensMinted<T: Config> =193 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;194195 /// Amount of tokens burnt in a collection.196 #[pallet::storage]197 pub type TokensBurnt<T: Config> =198 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;199200 /// Token data, used to partially describe a token.201 // TODO: remove202 #[pallet::storage]203 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]204 pub type TokenData<T: Config> = StorageNMap<205 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),206 Value = ItemData,207 QueryKind = ValueQuery,208 >;209210 /// Amount of pieces a refungible token is split into.211 #[pallet::storage]212 #[pallet::getter(fn token_properties)]213 pub type TokenProperties<T: Config> = StorageNMap<214 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),215 Value = up_data_structs::Properties,216 QueryKind = ValueQuery,217 OnEmpty = up_data_structs::TokenProperties,218 >;219220 /// Total amount of pieces for token221 #[pallet::storage]222 pub type TotalSupply<T: Config> = StorageNMap<223 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),224 Value = u128,225 QueryKind = ValueQuery,226 >;227228 /// Used to enumerate tokens owned by account.229 #[pallet::storage]230 pub type Owned<T: Config> = StorageNMap<231 Key = (232 Key<Twox64Concat, CollectionId>,233 Key<Blake2_128Concat, T::CrossAccountId>,234 Key<Twox64Concat, TokenId>,235 ),236 Value = bool,237 QueryKind = ValueQuery,238 >;239240 /// Amount of tokens (not pieces) partially owned by an account within a collection.241 #[pallet::storage]242 pub type AccountBalance<T: Config> = StorageNMap<243 Key = (244 Key<Twox64Concat, CollectionId>,245 // Owner246 Key<Blake2_128Concat, T::CrossAccountId>,247 ),248 Value = u32,249 QueryKind = ValueQuery,250 >;251252 /// Amount of token pieces owned by account.253 #[pallet::storage]254 pub type Balance<T: Config> = StorageNMap<255 Key = (256 Key<Twox64Concat, CollectionId>,257 Key<Twox64Concat, TokenId>,258 // Owner259 Key<Blake2_128Concat, T::CrossAccountId>,260 ),261 Value = u128,262 QueryKind = ValueQuery,263 >;264265 /// Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.266 #[pallet::storage]267 pub type Allowance<T: Config> = StorageNMap<268 Key = (269 Key<Twox64Concat, CollectionId>,270 Key<Twox64Concat, TokenId>,271 // Owner272 Key<Blake2_128, T::CrossAccountId>,273 // Spender274 Key<Blake2_128Concat, T::CrossAccountId>,275 ),276 Value = u128,277 QueryKind = ValueQuery,278 >;279280 #[pallet::hooks]281 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {282 fn on_runtime_upgrade() -> Weight {283 let storage_version = StorageVersion::get::<Pallet<T>>();284 if storage_version < StorageVersion::new(2) {285 <TokenData<T>>::remove_all(None);286 }287 StorageVersion::new(2).put::<Pallet<T>>();288289 Weight::zero()290 }291 }292}293294pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);295impl<T: Config> RefungibleHandle<T> {296 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {297 Self(inner)298 }299 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {300 self.0301 }302 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {303 &mut self.0304 }305}306307impl<T: Config> RefungibleHandle<T> {308 pub fn supports_metadata(&self) -> bool {309 if let Some(erc721_metadata) =310 pallet_common::Pallet::<T>::get_collection_property(self.id, &key::erc721_metadata())311 {312 *erc721_metadata.into_inner() == *value::ERC721_METADATA_SUPPORTED313 } else {314 false315 }316 }317}318319impl<T: Config> Deref for RefungibleHandle<T> {320 type Target = pallet_common::CollectionHandle<T>;321322 fn deref(&self) -> &Self::Target {323 &self.0324 }325}326327impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {328 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {329 self.0.recorder()330 }331 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {332 self.0.into_recorder()333 }334}335336impl<T: Config> Pallet<T> {337 /// Get number of RFT tokens in collection338 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {339 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)340 }341342 /// Check that RFT token exists343 ///344 /// - `token`: Token ID.345 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {346 <TotalSupply<T>>::contains_key((collection.id, token))347 }348349 pub fn set_scoped_token_property(350 collection_id: CollectionId,351 token_id: TokenId,352 scope: PropertyScope,353 property: Property,354 ) -> DispatchResult {355 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {356 properties.try_scoped_set(scope, property.key, property.value)357 })358 .map_err(<CommonError<T>>::from)?;359360 Ok(())361 }362363 pub fn set_scoped_token_properties(364 collection_id: CollectionId,365 token_id: TokenId,366 scope: PropertyScope,367 properties: impl Iterator<Item = Property>,368 ) -> DispatchResult {369 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {370 stored_properties.try_scoped_set_from_iter(scope, properties)371 })372 .map_err(<CommonError<T>>::from)?;373374 Ok(())375 }376}377378// unchecked calls skips any permission checks379impl<T: Config> Pallet<T> {380 /// Create RFT collection381 ///382 /// `init_collection` will take non-refundable deposit for collection creation.383 ///384 /// - `data`: Contains settings for collection limits and permissions.385 pub fn init_collection(386 owner: T::CrossAccountId,387 payer: T::CrossAccountId,388 data: CreateCollectionData<T::AccountId>,389 ) -> Result<CollectionId, DispatchError> {390 <PalletCommon<T>>::init_collection(owner, payer, data, CollectionFlags::default())391 }392393 /// Destroy RFT collection394 ///395 /// `destroy_collection` will throw error if collection contains any tokens.396 /// Only owner can destroy collection.397 pub fn destroy_collection(398 collection: RefungibleHandle<T>,399 sender: &T::CrossAccountId,400 ) -> DispatchResult {401 let id = collection.id;402403 if Self::collection_has_tokens(id) {404 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());405 }406407 // =========408409 PalletCommon::destroy_collection(collection.0, sender)?;410411 <TokensMinted<T>>::remove(id);412 <TokensBurnt<T>>::remove(id);413 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);414 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);415 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);416 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);417 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);418 Ok(())419 }420421 fn collection_has_tokens(collection_id: CollectionId) -> bool {422 <TotalSupply<T>>::iter_prefix((collection_id,))423 .next()424 .is_some()425 }426427 pub fn burn_token_unchecked(428 collection: &RefungibleHandle<T>,429 owner: &T::CrossAccountId,430 token_id: TokenId,431 ) -> DispatchResult {432 let burnt = <TokensBurnt<T>>::get(collection.id)433 .checked_add(1)434 .ok_or(ArithmeticError::Overflow)?;435436 <TokensBurnt<T>>::insert(collection.id, burnt);437 <TokenProperties<T>>::remove((collection.id, token_id));438 <TotalSupply<T>>::remove((collection.id, token_id));439 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);440 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);441 <PalletEvm<T>>::deposit_log(442 ERC721Events::Transfer {443 from: *owner.as_eth(),444 to: H160::default(),445 token_id: token_id.into(),446 }447 .to_log(collection_id_to_address(collection.id)),448 );449 Ok(())450 }451452 /// Burn RFT token pieces453 ///454 /// `burn` will decrease total amount of token pieces and amount owned by sender.455 /// `burn` can be called even if there are multiple owners of the RFT token.456 /// If sender wouldn't have any pieces left after `burn` than she will stop being457 /// one of the owners of the token. If there is no account that owns any pieces of458 /// the token than token will be burned too.459 ///460 /// - `amount`: Amount of token pieces to burn.461 /// - `token`: Token who's pieces should be burned462 /// - `collection`: Collection that contains the token463 pub fn burn(464 collection: &RefungibleHandle<T>,465 owner: &T::CrossAccountId,466 token: TokenId,467 amount: u128,468 ) -> DispatchResult {469 let total_supply = <TotalSupply<T>>::get((collection.id, token))470 .checked_sub(amount)471 .ok_or(<CommonError<T>>::TokenValueTooLow)?;472473 // This was probally last owner of this token?474 if total_supply == 0 {475 // Ensure user actually owns this amount476 ensure!(477 <Balance<T>>::get((collection.id, token, owner)) == amount,478 <CommonError<T>>::TokenValueTooLow479 );480 let account_balance = <AccountBalance<T>>::get((collection.id, owner))481 .checked_sub(1)482 // Should not occur483 .ok_or(ArithmeticError::Underflow)?;484485 // =========486487 <Owned<T>>::remove((collection.id, owner, token));488 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);489 <AccountBalance<T>>::insert((collection.id, owner), account_balance);490 Self::burn_token_unchecked(collection, owner, token)?;491 <PalletEvm<T>>::deposit_log(492 ERC20Events::Transfer {493 from: *owner.as_eth(),494 to: H160::default(),495 value: amount.into(),496 }497 .to_log(collection_id_to_address(collection.id)),498 );499 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(500 collection.id,501 token,502 owner.clone(),503 amount,504 ));505 return Ok(());506 }507508 let balance = <Balance<T>>::get((collection.id, token, owner))509 .checked_sub(amount)510 .ok_or(<CommonError<T>>::TokenValueTooLow)?;511 let account_balance = if balance == 0 {512 <AccountBalance<T>>::get((collection.id, owner))513 .checked_sub(1)514 // Should not occur515 .ok_or(ArithmeticError::Underflow)?516 } else {517 0518 };519520 // =========521522 if balance == 0 {523 <Owned<T>>::remove((collection.id, owner, token));524 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);525 <Balance<T>>::remove((collection.id, token, owner));526 <AccountBalance<T>>::insert((collection.id, owner), account_balance);527528 if let Some(user) = Self::token_owner(collection.id, token) {529 <PalletEvm<T>>::deposit_log(530 ERC721Events::Transfer {531 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,532 to: *user.as_eth(),533 token_id: token.into(),534 }535 .to_log(collection_id_to_address(collection.id)),536 );537 }538 } else {539 <Balance<T>>::insert((collection.id, token, owner), balance);540 }541 <TotalSupply<T>>::insert((collection.id, token), total_supply);542543 <PalletEvm<T>>::deposit_log(544 ERC20Events::Transfer {545 from: *owner.as_eth(),546 to: H160::default(),547 value: amount.into(),548 }549 .to_log(T::EvmTokenAddressMapping::token_to_address(550 collection.id,551 token,552 )),553 );554 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(555 collection.id,556 token,557 owner.clone(),558 amount,559 ));560 Ok(())561 }562563 #[transactional]564 fn modify_token_properties(565 collection: &RefungibleHandle<T>,566 sender: &T::CrossAccountId,567 token_id: TokenId,568 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,569 is_token_create: bool,570 nesting_budget: &dyn Budget,571 ) -> DispatchResult {572 let is_collection_admin = || collection.is_owner_or_admin(sender);573 let is_token_owner = || -> Result<bool, DispatchError> {574 let balance = collection.balance(sender.clone(), token_id);575 let total_pieces: u128 =576 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);577 if balance != total_pieces {578 return Ok(false);579 }580581 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(582 sender.clone(),583 collection.id,584 token_id,585 None,586 nesting_budget,587 )?;588589 Ok(is_bundle_owner)590 };591592 for (key, value) in properties {593 let permission = <PalletCommon<T>>::property_permissions(collection.id)594 .get(&key)595 .cloned()596 .unwrap_or_else(PropertyPermission::none);597598 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))599 .get(&key)600 .is_some();601602 match permission {603 PropertyPermission { mutable: false, .. } if is_property_exists => {604 return Err(<CommonError<T>>::NoPermission.into());605 }606607 PropertyPermission {608 collection_admin,609 token_owner,610 ..611 } => {612 //TODO: investigate threats during public minting.613 let is_token_create =614 is_token_create && (collection_admin || token_owner) && value.is_some();615 if !(is_token_create616 || (collection_admin && is_collection_admin())617 || (token_owner && is_token_owner()?))618 {619 fail!(<CommonError<T>>::NoPermission);620 }621 }622 }623624 match value {625 Some(value) => {626 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {627 properties.try_set(key.clone(), value)628 })629 .map_err(<CommonError<T>>::from)?;630631 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(632 collection.id,633 token_id,634 key,635 ));636 }637 None => {638 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {639 properties.remove(&key)640 })641 .map_err(<CommonError<T>>::from)?;642643 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(644 collection.id,645 token_id,646 key,647 ));648 }649 }650 }651652 Ok(())653 }654655 pub fn set_token_properties(656 collection: &RefungibleHandle<T>,657 sender: &T::CrossAccountId,658 token_id: TokenId,659 properties: impl Iterator<Item = Property>,660 is_token_create: bool,661 nesting_budget: &dyn Budget,662 ) -> DispatchResult {663 Self::modify_token_properties(664 collection,665 sender,666 token_id,667 properties.map(|p| (p.key, Some(p.value))),668 is_token_create,669 nesting_budget,670 )671 }672673 pub fn set_token_property(674 collection: &RefungibleHandle<T>,675 sender: &T::CrossAccountId,676 token_id: TokenId,677 property: Property,678 nesting_budget: &dyn Budget,679 ) -> DispatchResult {680 let is_token_create = false;681682 Self::set_token_properties(683 collection,684 sender,685 token_id,686 [property].into_iter(),687 is_token_create,688 nesting_budget,689 )690 }691692 pub fn delete_token_properties(693 collection: &RefungibleHandle<T>,694 sender: &T::CrossAccountId,695 token_id: TokenId,696 property_keys: impl Iterator<Item = PropertyKey>,697 nesting_budget: &dyn Budget,698 ) -> DispatchResult {699 let is_token_create = false;700701 Self::modify_token_properties(702 collection,703 sender,704 token_id,705 property_keys.into_iter().map(|key| (key, None)),706 is_token_create,707 nesting_budget,708 )709 }710711 pub fn delete_token_property(712 collection: &RefungibleHandle<T>,713 sender: &T::CrossAccountId,714 token_id: TokenId,715 property_key: PropertyKey,716 nesting_budget: &dyn Budget,717 ) -> DispatchResult {718 Self::delete_token_properties(719 collection,720 sender,721 token_id,722 [property_key].into_iter(),723 nesting_budget,724 )725 }726727 /// Transfer RFT token pieces from one account to another.728 ///729 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.730 ///731 /// - `from`: Owner of token pieces to transfer.732 /// - `to`: Recepient of transfered token pieces.733 /// - `amount`: Amount of token pieces to transfer.734 /// - `token`: Token whos pieces should be transfered735 /// - `collection`: Collection that contains the token736 pub fn transfer(737 collection: &RefungibleHandle<T>,738 from: &T::CrossAccountId,739 to: &T::CrossAccountId,740 token: TokenId,741 amount: u128,742 nesting_budget: &dyn Budget,743 ) -> DispatchResult {744 ensure!(745 collection.limits.transfers_enabled(),746 <CommonError<T>>::TransferNotAllowed747 );748749 if collection.permissions.access() == AccessMode::AllowList {750 collection.check_allowlist(from)?;751 collection.check_allowlist(to)?;752 }753 <PalletCommon<T>>::ensure_correct_receiver(to)?;754755 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));756 let updated_balance_from = initial_balance_from757 .checked_sub(amount)758 .ok_or(<CommonError<T>>::TokenValueTooLow)?;759 let mut create_target = false;760 let from_to_differ = from != to;761 let updated_balance_to = if from != to {762 let old_balance = <Balance<T>>::get((collection.id, token, to));763 if old_balance == 0 {764 create_target = true;765 }766 Some(767 old_balance768 .checked_add(amount)769 .ok_or(ArithmeticError::Overflow)?,770 )771 } else {772 None773 };774775 let account_balance_from = if updated_balance_from == 0 {776 Some(777 <AccountBalance<T>>::get((collection.id, from))778 .checked_sub(1)779 // Should not occur780 .ok_or(ArithmeticError::Underflow)?,781 )782 } else {783 None784 };785 // Account data is created in token, AccountBalance should be increased786 // But only if from != to as we shouldn't check overflow in this case787 let account_balance_to = if create_target && from_to_differ {788 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))789 .checked_add(1)790 .ok_or(ArithmeticError::Overflow)?;791 ensure!(792 account_balance_to < collection.limits.account_token_ownership_limit(),793 <CommonError<T>>::AccountTokenLimitExceeded,794 );795796 Some(account_balance_to)797 } else {798 None799 };800801 // =========802803 <PalletStructure<T>>::nest_if_sent_to_token(804 from.clone(),805 to,806 collection.id,807 token,808 nesting_budget,809 )?;810811 if let Some(updated_balance_to) = updated_balance_to {812 // from != to813 if updated_balance_from == 0 {814 <Balance<T>>::remove((collection.id, token, from));815 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);816 } else {817 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);818 }819 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);820 if let Some(account_balance_from) = account_balance_from {821 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);822 <Owned<T>>::remove((collection.id, from, token));823 }824 if let Some(account_balance_to) = account_balance_to {825 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);826 <Owned<T>>::insert((collection.id, to, token), true);827 }828 }829830 <PalletEvm<T>>::deposit_log(831 ERC20Events::Transfer {832 from: *from.as_eth(),833 to: *to.as_eth(),834 value: amount.into(),835 }836 .to_log(T::EvmTokenAddressMapping::token_to_address(837 collection.id,838 token,839 )),840 );841842 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(843 collection.id,844 token,845 from.clone(),846 to.clone(),847 amount,848 ));849850 let total_supply = <TotalSupply<T>>::get((collection.id, token));851852 if amount == total_supply {853 // if token was fully owned by `from` and will be fully owned by `to` after transfer854 <PalletEvm<T>>::deposit_log(855 ERC721Events::Transfer {856 from: *from.as_eth(),857 to: *to.as_eth(),858 token_id: token.into(),859 }860 .to_log(collection_id_to_address(collection.id)),861 );862 } else if let Some(updated_balance_to) = updated_balance_to {863 // if `from` not equals `to`. This condition is needed to avoid sending event864 // when `from` fully owns token and sends part of token pieces to itself.865 if initial_balance_from == total_supply {866 // if token was fully owned by `from` and will be only partially owned by `to`867 // and `from` after transfer868 <PalletEvm<T>>::deposit_log(869 ERC721Events::Transfer {870 from: *from.as_eth(),871 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,872 token_id: token.into(),873 }874 .to_log(collection_id_to_address(collection.id)),875 );876 } else if updated_balance_to == total_supply {877 // if token was partially owned by `from` and will be fully owned by `to` after transfer878 <PalletEvm<T>>::deposit_log(879 ERC721Events::Transfer {880 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,881 to: *to.as_eth(),882 token_id: token.into(),883 }884 .to_log(collection_id_to_address(collection.id)),885 );886 }887 }888889 Ok(())890 }891892 /// Batched operation to create multiple RFT tokens.893 ///894 /// Same as `create_item` but creates multiple tokens.895 ///896 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.897 pub fn create_multiple_items(898 collection: &RefungibleHandle<T>,899 sender: &T::CrossAccountId,900 data: Vec<CreateItemData<T::CrossAccountId>>,901 nesting_budget: &dyn Budget,902 ) -> DispatchResult {903 if !collection.is_owner_or_admin(sender) {904 ensure!(905 collection.permissions.mint_mode(),906 <CommonError<T>>::PublicMintingNotAllowed907 );908 collection.check_allowlist(sender)?;909910 for item in data.iter() {911 for user in item.users.keys() {912 collection.check_allowlist(user)?;913 }914 }915 }916917 for item in data.iter() {918 for (owner, _) in item.users.iter() {919 <PalletCommon<T>>::ensure_correct_receiver(owner)?;920 }921 }922923 // Total pieces per tokens924 let totals = data925 .iter()926 .map(|data| {927 Ok(data928 .users929 .iter()930 .map(|u| u.1)931 .try_fold(0u128, |acc, v| acc.checked_add(*v))932 .ok_or(ArithmeticError::Overflow)?)933 })934 .collect::<Result<Vec<_>, DispatchError>>()?;935 for total in &totals {936 ensure!(937 *total <= MAX_REFUNGIBLE_PIECES,938 <Error<T>>::WrongRefungiblePieces939 );940 }941942 let first_token_id = <TokensMinted<T>>::get(collection.id);943 let tokens_minted = first_token_id944 .checked_add(data.len() as u32)945 .ok_or(ArithmeticError::Overflow)?;946 ensure!(947 tokens_minted < collection.limits.token_limit(),948 <CommonError<T>>::CollectionTokenLimitExceeded949 );950951 let mut balances = BTreeMap::new();952 for data in &data {953 for owner in data.users.keys() {954 let balance = balances955 .entry(owner)956 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));957 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;958959 ensure!(960 *balance <= collection.limits.account_token_ownership_limit(),961 <CommonError<T>>::AccountTokenLimitExceeded,962 );963 }964 }965966 for (i, token) in data.iter().enumerate() {967 let token_id = TokenId(first_token_id + i as u32 + 1);968 for (to, _) in token.users.iter() {969 <PalletStructure<T>>::check_nesting(970 sender.clone(),971 to,972 collection.id,973 token_id,974 nesting_budget,975 )?;976 }977 }978979 // =========980981 with_transaction(|| {982 for (i, data) in data.iter().enumerate() {983 let token_id = first_token_id + i as u32 + 1;984 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);985986 for (user, amount) in data.users.iter() {987 if *amount == 0 {988 continue;989 }990 <Balance<T>>::insert((collection.id, token_id, &user), amount);991 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);992 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(993 user,994 collection.id,995 TokenId(token_id),996 );997 }998999 if let Err(e) = Self::set_token_properties(1000 collection,1001 sender,1002 TokenId(token_id),1003 data.properties.clone().into_iter(),1004 true,1005 nesting_budget,1006 ) {1007 return TransactionOutcome::Rollback(Err(e));1008 }1009 }1010 TransactionOutcome::Commit(Ok(()))1011 })?;10121013 <TokensMinted<T>>::insert(collection.id, tokens_minted);10141015 for (account, balance) in balances {1016 <AccountBalance<T>>::insert((collection.id, account), balance);1017 }10181019 for (i, token) in data.into_iter().enumerate() {1020 let token_id = first_token_id + i as u32 + 1;10211022 let receivers = token1023 .users1024 .into_iter()1025 .filter(|(_, amount)| *amount > 0)1026 .collect::<Vec<_>>();10271028 if let [(user, _)] = receivers.as_slice() {1029 // if there is exactly one receiver1030 <PalletEvm<T>>::deposit_log(1031 ERC721Events::Transfer {1032 from: H160::default(),1033 to: *user.as_eth(),1034 token_id: token_id.into(),1035 }1036 .to_log(collection_id_to_address(collection.id)),1037 );1038 } else if let [_, ..] = receivers.as_slice() {1039 // if there is more than one receiver1040 <PalletEvm<T>>::deposit_log(1041 ERC721Events::Transfer {1042 from: H160::default(),1043 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1044 token_id: token_id.into(),1045 }1046 .to_log(collection_id_to_address(collection.id)),1047 );1048 }10491050 for (user, amount) in receivers.into_iter() {1051 <PalletEvm<T>>::deposit_log(1052 ERC20Events::Transfer {1053 from: H160::default(),1054 to: *user.as_eth(),1055 value: amount.into(),1056 }1057 .to_log(T::EvmTokenAddressMapping::token_to_address(1058 collection.id,1059 TokenId(token_id),1060 )),1061 );1062 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1063 collection.id,1064 TokenId(token_id),1065 user,1066 amount,1067 ));1068 }1069 }1070 Ok(())1071 }10721073 pub fn set_allowance_unchecked(1074 collection: &RefungibleHandle<T>,1075 sender: &T::CrossAccountId,1076 spender: &T::CrossAccountId,1077 token: TokenId,1078 amount: u128,1079 ) {1080 if amount == 0 {1081 <Allowance<T>>::remove((collection.id, token, sender, spender));1082 } else {1083 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1084 }10851086 <PalletEvm<T>>::deposit_log(1087 ERC20Events::Approval {1088 owner: *sender.as_eth(),1089 spender: *spender.as_eth(),1090 value: amount.into(),1091 }1092 .to_log(T::EvmTokenAddressMapping::token_to_address(1093 collection.id,1094 token,1095 )),1096 );1097 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1098 collection.id,1099 token,1100 sender.clone(),1101 spender.clone(),1102 amount,1103 ))1104 }11051106 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1107 ///1108 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1109 pub fn set_allowance(1110 collection: &RefungibleHandle<T>,1111 sender: &T::CrossAccountId,1112 spender: &T::CrossAccountId,1113 token: TokenId,1114 amount: u128,1115 ) -> DispatchResult {1116 if collection.permissions.access() == AccessMode::AllowList {1117 collection.check_allowlist(sender)?;1118 collection.check_allowlist(spender)?;1119 }11201121 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11221123 if <Balance<T>>::get((collection.id, token, sender)) < amount {1124 ensure!(1125 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1126 <CommonError<T>>::CantApproveMoreThanOwned1127 );1128 }11291130 // =========11311132 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1133 Ok(())1134 }11351136 /// Returns allowance, which should be set after transaction1137 fn check_allowed(1138 collection: &RefungibleHandle<T>,1139 spender: &T::CrossAccountId,1140 from: &T::CrossAccountId,1141 token: TokenId,1142 amount: u128,1143 nesting_budget: &dyn Budget,1144 ) -> Result<Option<u128>, DispatchError> {1145 if spender.conv_eq(from) {1146 return Ok(None);1147 }1148 if collection.permissions.access() == AccessMode::AllowList {1149 // `from`, `to` checked in [`transfer`]1150 collection.check_allowlist(spender)?;1151 }1152 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1153 // TODO: should collection owner be allowed to perform this transfer?1154 ensure!(1155 <PalletStructure<T>>::check_indirectly_owned(1156 spender.clone(),1157 source.0,1158 source.1,1159 None,1160 nesting_budget1161 )?,1162 <CommonError<T>>::ApprovedValueTooLow,1163 );1164 return Ok(None);1165 }1166 let allowance =1167 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1168 if allowance.is_none() {1169 ensure!(1170 collection.ignores_allowance(spender),1171 <CommonError<T>>::ApprovedValueTooLow1172 );1173 }1174 Ok(allowance)1175 }11761177 /// Transfer RFT token pieces from one account to another.1178 ///1179 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1180 /// The owner should set allowance for the spender to transfer pieces.1181 ///1182 /// [`transfer`]: struct.Pallet.html#method.transfer1183 pub fn transfer_from(1184 collection: &RefungibleHandle<T>,1185 spender: &T::CrossAccountId,1186 from: &T::CrossAccountId,1187 to: &T::CrossAccountId,1188 token: TokenId,1189 amount: u128,1190 nesting_budget: &dyn Budget,1191 ) -> DispatchResult {1192 let allowance =1193 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11941195 // =========11961197 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1198 if let Some(allowance) = allowance {1199 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1200 }1201 Ok(())1202 }12031204 /// Burn RFT token pieces from the account.1205 ///1206 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1207 /// set allowance for the spender to burn pieces1208 ///1209 /// [`burn`]: struct.Pallet.html#method.burn1210 pub fn burn_from(1211 collection: &RefungibleHandle<T>,1212 spender: &T::CrossAccountId,1213 from: &T::CrossAccountId,1214 token: TokenId,1215 amount: u128,1216 nesting_budget: &dyn Budget,1217 ) -> DispatchResult {1218 let allowance =1219 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12201221 // =========12221223 Self::burn(collection, from, token, amount)?;1224 if let Some(allowance) = allowance {1225 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1226 }1227 Ok(())1228 }12291230 /// Create RFT token.1231 ///1232 /// The sender should be the owner/admin of the collection or collection should be configured1233 /// to allow public minting.1234 ///1235 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1236 /// of token pieces they will receive.1237 pub fn create_item(1238 collection: &RefungibleHandle<T>,1239 sender: &T::CrossAccountId,1240 data: CreateItemData<T::CrossAccountId>,1241 nesting_budget: &dyn Budget,1242 ) -> DispatchResult {1243 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1244 }12451246 /// Repartition RFT token.1247 ///1248 /// `repartition` will set token balance of the sender and total amount of token pieces.1249 /// Sender should own all of the token pieces. `repartition' could be done even if some1250 /// token pieces were burned before.1251 ///1252 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1253 pub fn repartition(1254 collection: &RefungibleHandle<T>,1255 owner: &T::CrossAccountId,1256 token: TokenId,1257 amount: u128,1258 ) -> DispatchResult {1259 ensure!(1260 amount <= MAX_REFUNGIBLE_PIECES,1261 <Error<T>>::WrongRefungiblePieces1262 );1263 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1264 // Ensure user owns all pieces1265 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1266 let balance = <Balance<T>>::get((collection.id, token, owner));1267 ensure!(1268 total_pieces == balance,1269 <Error<T>>::RepartitionWhileNotOwningAllPieces1270 );12711272 <Balance<T>>::insert((collection.id, token, owner), amount);1273 <TotalSupply<T>>::insert((collection.id, token), amount);12741275 if amount > total_pieces {1276 let mint_amount = amount - total_pieces;1277 <PalletEvm<T>>::deposit_log(1278 ERC20Events::Transfer {1279 from: H160::default(),1280 to: *owner.as_eth(),1281 value: mint_amount.into(),1282 }1283 .to_log(T::EvmTokenAddressMapping::token_to_address(1284 collection.id,1285 token,1286 )),1287 );1288 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1289 collection.id,1290 token,1291 owner.clone(),1292 mint_amount,1293 ));1294 } else if total_pieces > amount {1295 let burn_amount = total_pieces - amount;1296 <PalletEvm<T>>::deposit_log(1297 ERC20Events::Transfer {1298 from: *owner.as_eth(),1299 to: H160::default(),1300 value: burn_amount.into(),1301 }1302 .to_log(T::EvmTokenAddressMapping::token_to_address(1303 collection.id,1304 token,1305 )),1306 );1307 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1308 collection.id,1309 token,1310 owner.clone(),1311 burn_amount,1312 ));1313 }13141315 Ok(())1316 }13171318 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1319 let mut owner = None;1320 let mut count = 0;1321 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1322 count += 1;1323 if count > 1 {1324 return None;1325 }1326 owner = Some(key);1327 }1328 owner1329 }13301331 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1332 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1333 }13341335 pub fn set_collection_properties(1336 collection: &RefungibleHandle<T>,1337 sender: &T::CrossAccountId,1338 properties: Vec<Property>,1339 ) -> DispatchResult {1340 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1341 }13421343 pub fn delete_collection_properties(1344 collection: &RefungibleHandle<T>,1345 sender: &T::CrossAccountId,1346 property_keys: Vec<PropertyKey>,1347 ) -> DispatchResult {1348 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1349 }13501351 pub fn set_token_property_permissions(1352 collection: &RefungibleHandle<T>,1353 sender: &T::CrossAccountId,1354 property_permissions: Vec<PropertyKeyPermission>,1355 ) -> DispatchResult {1356 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1357 }13581359 pub fn set_scoped_token_property_permissions(1360 collection: &RefungibleHandle<T>,1361 sender: &T::CrossAccountId,1362 scope: PropertyScope,1363 property_permissions: Vec<PropertyKeyPermission>,1364 ) -> DispatchResult {1365 <PalletCommon<T>>::set_scoped_token_property_permissions(1366 collection,1367 sender,1368 scope,1369 property_permissions,1370 )1371 }13721373 /// Returns 10 token in no particular order.1374 ///1375 /// There is no direct way to get token holders in ascending order,1376 /// since `iter_prefix` returns values in no particular order.1377 /// Therefore, getting the 10 largest holders with a large value of holders1378 /// can lead to impact memory allocation + sorting with `n * log (n)`.1379 pub fn token_owners(1380 collection_id: CollectionId,1381 token: TokenId,1382 ) -> Option<Vec<T::CrossAccountId>> {1383 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1384 .map(|(owner, _amount)| owner)1385 .take(10)1386 .collect();13871388 if res.is_empty() {1389 None1390 } else {1391 Some(res)1392 }1393 }1394}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 Pallet18//!19//! The Refungible pallet provides functionality for handling refungible collections and tokens.20//!21//! - [`Config`]22//! - [`RefungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`](common::CommonWeights)25//!26//! ## Overview27//!28//! The Refungible pallet provides functions for:29//!30//! - RFT collection creation and removal31//! - Minting and burning of RFT tokens32//! - Partition and repartition of RFT tokens33//! - Retrieving number of pieces of RFT token34//! - Retrieving account balances35//! - Transfering RFT token pieces36//! - Burning RFT token pieces37//! - Setting and checking allowance for RFT tokens38//!39//! ### Terminology40//!41//! - **RFT token:** Non fungible token that was partitioned to pieces. If an account owns all42//! of the RFT token pieces than it owns the RFT token and can repartition it.43//!44//! - **RFT Collection:** A collection of RFT tokens. All RFT tokens are part of a collection.45//! Each collection has its own settings and set of permissions.46//!47//! - **RFT token piece:** A fungible part of an RFT token.48//!49//! - **Balance:** RFT token pieces owned by an account50//!51//! - **Allowance:** Maximum number of RFT token pieces that one account is allowed to52//! transfer from the balance of another account53//!54//! - **Burning:** The process of “deleting” a token from a collection or removing token pieces from55//! an account balance.56//!57//! ### Implementations58//!59//! The Refungible pallet provides implementations for the following traits. If these traits provide60//! the functionality that you need, then you can avoid coupling with the Refungible pallet.61//!62//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight63//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing64//! with collections65//! - [`RefungibleExtensions`](pallet_common::RefungibleExtensions): Functions specific for refungible66//! collection67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create RFT collection. RFT collection can be configured to allow or deny access for73//! some accounts.74//! - `destroy_collection` - Destroy exising RFT collection. There should be no tokens in the collection.75//! - `burn` - Burn some amount of RFT token pieces owned by account. Burns the RFT token if no pieces left.76//! - `transfer` - Transfer some amount of RFT token pieces. Transfers should be enabled for RFT collection.77//! Nests the RFT token if RFT token pieces are sent to another token.78//! - `create_item` - Mint RFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account to transfer balance from sender's account.80//! - `repartition` - Repartition token to selected number of pieces. Sender should own all existing pieces.81//!82//! ## Assumptions83//!84//! * Total number of pieces for one token shouldn't exceed `up_data_structs::MAX_REFUNGIBLE_PIECES`.85//! * Total number of tokens of all types shouldn't be greater than `up_data_structs::MAX_TOKEN_PREFIX_LENGTH`.86//! * Sender should be in collection's allow list to perform operations on tokens.8788#![cfg_attr(not(feature = "std"), no_std)]8990use crate::erc_token::ERC20Events;91use crate::erc::ERC721Events;9293use codec::{Encode, Decode, MaxEncodedLen};94use core::ops::Deref;95use derivative::Derivative;96use evm_coder::ToLog;97use frame_support::{98 BoundedBTreeMap, BoundedVec, ensure, fail, storage::with_transaction, transactional,99 pallet_prelude::ConstU32,100};101use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};102use pallet_evm_coder_substrate::WithRecorder;103use pallet_common::{104 CommonCollectionOperations,105 erc::static_property::{key, value},106 Error as CommonError,107 eth::collection_id_to_address,108 Event as CommonEvent, Pallet as PalletCommon,109};110use pallet_structure::Pallet as PalletStructure;111use scale_info::TypeInfo;112use sp_core::H160;113use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};114use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};115use up_data_structs::{116 AccessMode, budget::Budget, CollectionId, CollectionFlags, CollectionPropertiesVec,117 CreateCollectionData, CustomDataLimit, mapping::TokenAddressMapping, MAX_ITEMS_PER_BATCH,118 MAX_REFUNGIBLE_PIECES, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,119 PropertyScope, PropertyValue, TokenId, TrySetProperty,120};121122pub use pallet::*;123#[cfg(feature = "runtime-benchmarks")]124pub mod benchmarking;125pub mod common;126pub mod erc;127pub mod erc_token;128pub mod weights;129130#[derive(Derivative, Clone)]131pub struct CreateItemData<CrossAccountId> {132 #[derivative(Debug(format_with = "bounded::map_debug"))]133 pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,134 #[derivative(Debug(format_with = "bounded::vec_debug"))]135 pub properties: CollectionPropertiesVec,136}137pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;138139/// Token data, stored independently from other data used to describe it140/// for the convenience of database access. Notably contains the token metadata.141#[struct_versioning::versioned(version = 2, upper)]142#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]143#[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]144pub struct ItemData {145 pub const_data: BoundedVec<u8, CustomDataLimit>,146147 #[version(..2)]148 pub variable_data: BoundedVec<u8, CustomDataLimit>,149}150151#[frame_support::pallet]152pub mod pallet {153 use super::*;154 use frame_support::{155 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,156 traits::StorageVersion,157 };158 use frame_system::pallet_prelude::*;159 use up_data_structs::{CollectionId, TokenId};160 use super::weights::WeightInfo;161162 #[pallet::error]163 pub enum Error<T> {164 /// Not Refungible item data used to mint in Refungible collection.165 NotRefungibleDataUsedToMintFungibleCollectionToken,166 /// Maximum refungibility exceeded.167 WrongRefungiblePieces,168 /// Refungible token can't be repartitioned by user who isn't owns all pieces.169 RepartitionWhileNotOwningAllPieces,170 /// Refungible token can't nest other tokens.171 RefungibleDisallowsNesting,172 /// Setting item properties is not allowed.173 SettingPropertiesNotAllowed,174 }175176 #[pallet::config]177 pub trait Config:178 frame_system::Config + pallet_common::Config + pallet_structure::Config179 {180 type WeightInfo: WeightInfo;181 }182183 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);184185 #[pallet::pallet]186 #[pallet::storage_version(STORAGE_VERSION)]187 #[pallet::generate_store(pub(super) trait Store)]188 pub struct Pallet<T>(_);189190 /// Total amount of minted tokens in a collection.191 #[pallet::storage]192 pub type TokensMinted<T: Config> =193 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;194195 /// Amount of tokens burnt in a collection.196 #[pallet::storage]197 pub type TokensBurnt<T: Config> =198 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;199200 /// Token data, used to partially describe a token.201 // TODO: remove202 #[pallet::storage]203 #[deprecated(since = "0.2.0", note = "ItemData is no more contains usefull data")]204 pub type TokenData<T: Config> = StorageNMap<205 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),206 Value = ItemData,207 QueryKind = ValueQuery,208 >;209210 /// Amount of pieces a refungible token is split into.211 #[pallet::storage]212 #[pallet::getter(fn token_properties)]213 pub type TokenProperties<T: Config> = StorageNMap<214 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),215 Value = up_data_structs::Properties,216 QueryKind = ValueQuery,217 OnEmpty = up_data_structs::TokenProperties,218 >;219220 /// Total amount of pieces for token221 #[pallet::storage]222 pub type TotalSupply<T: Config> = StorageNMap<223 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),224 Value = u128,225 QueryKind = ValueQuery,226 >;227228 /// Used to enumerate tokens owned by account.229 #[pallet::storage]230 pub type Owned<T: Config> = StorageNMap<231 Key = (232 Key<Twox64Concat, CollectionId>,233 Key<Blake2_128Concat, T::CrossAccountId>,234 Key<Twox64Concat, TokenId>,235 ),236 Value = bool,237 QueryKind = ValueQuery,238 >;239240 /// Amount of tokens (not pieces) partially owned by an account within a collection.241 #[pallet::storage]242 pub type AccountBalance<T: Config> = StorageNMap<243 Key = (244 Key<Twox64Concat, CollectionId>,245 // Owner246 Key<Blake2_128Concat, T::CrossAccountId>,247 ),248 Value = u32,249 QueryKind = ValueQuery,250 >;251252 /// Amount of token pieces owned by account.253 #[pallet::storage]254 pub type Balance<T: Config> = StorageNMap<255 Key = (256 Key<Twox64Concat, CollectionId>,257 Key<Twox64Concat, TokenId>,258 // Owner259 Key<Blake2_128Concat, T::CrossAccountId>,260 ),261 Value = u128,262 QueryKind = ValueQuery,263 >;264265 /// Allowance set by a token owner for another user to perform one of certain transactions on a number of pieces of a token.266 #[pallet::storage]267 pub type Allowance<T: Config> = StorageNMap<268 Key = (269 Key<Twox64Concat, CollectionId>,270 Key<Twox64Concat, TokenId>,271 // Owner272 Key<Blake2_128, T::CrossAccountId>,273 // Spender274 Key<Blake2_128Concat, T::CrossAccountId>,275 ),276 Value = u128,277 QueryKind = ValueQuery,278 >;279280 #[pallet::hooks]281 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {282 fn on_runtime_upgrade() -> Weight {283 let storage_version = StorageVersion::get::<Pallet<T>>();284 if storage_version < StorageVersion::new(2) {285 <TokenData<T>>::remove_all(None);286 }287 StorageVersion::new(2).put::<Pallet<T>>();288289 Weight::zero()290 }291 }292}293294pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);295impl<T: Config> RefungibleHandle<T> {296 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {297 Self(inner)298 }299 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {300 self.0301 }302 pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {303 &mut self.0304 }305}306307impl<T: Config> Deref for RefungibleHandle<T> {308 type Target = pallet_common::CollectionHandle<T>;309310 fn deref(&self) -> &Self::Target {311 &self.0312 }313}314315impl<T: Config> WithRecorder<T> for RefungibleHandle<T> {316 fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {317 self.0.recorder()318 }319 fn into_recorder(self) -> pallet_evm_coder_substrate::SubstrateRecorder<T> {320 self.0.into_recorder()321 }322}323324impl<T: Config> Pallet<T> {325 /// Get number of RFT tokens in collection326 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {327 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)328 }329330 /// Check that RFT token exists331 ///332 /// - `token`: Token ID.333 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {334 <TotalSupply<T>>::contains_key((collection.id, token))335 }336337 pub fn set_scoped_token_property(338 collection_id: CollectionId,339 token_id: TokenId,340 scope: PropertyScope,341 property: Property,342 ) -> DispatchResult {343 TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {344 properties.try_scoped_set(scope, property.key, property.value)345 })346 .map_err(<CommonError<T>>::from)?;347348 Ok(())349 }350351 pub fn set_scoped_token_properties(352 collection_id: CollectionId,353 token_id: TokenId,354 scope: PropertyScope,355 properties: impl Iterator<Item = Property>,356 ) -> DispatchResult {357 TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {358 stored_properties.try_scoped_set_from_iter(scope, properties)359 })360 .map_err(<CommonError<T>>::from)?;361362 Ok(())363 }364}365366// unchecked calls skips any permission checks367impl<T: Config> Pallet<T> {368 /// Create RFT collection369 ///370 /// `init_collection` will take non-refundable deposit for collection creation.371 ///372 /// - `data`: Contains settings for collection limits and permissions.373 pub fn init_collection(374 owner: T::CrossAccountId,375 payer: T::CrossAccountId,376 data: CreateCollectionData<T::AccountId>,377 ) -> Result<CollectionId, DispatchError> {378 <PalletCommon<T>>::init_collection(owner, payer, data, CollectionFlags::default())379 }380381 /// Destroy RFT collection382 ///383 /// `destroy_collection` will throw error if collection contains any tokens.384 /// Only owner can destroy collection.385 pub fn destroy_collection(386 collection: RefungibleHandle<T>,387 sender: &T::CrossAccountId,388 ) -> DispatchResult {389 let id = collection.id;390391 if Self::collection_has_tokens(id) {392 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());393 }394395 // =========396397 PalletCommon::destroy_collection(collection.0, sender)?;398399 <TokensMinted<T>>::remove(id);400 <TokensBurnt<T>>::remove(id);401 let _ = <TotalSupply<T>>::clear_prefix((id,), u32::MAX, None);402 let _ = <Balance<T>>::clear_prefix((id,), u32::MAX, None);403 let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);404 let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);405 let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);406 Ok(())407 }408409 fn collection_has_tokens(collection_id: CollectionId) -> bool {410 <TotalSupply<T>>::iter_prefix((collection_id,))411 .next()412 .is_some()413 }414415 pub fn burn_token_unchecked(416 collection: &RefungibleHandle<T>,417 owner: &T::CrossAccountId,418 token_id: TokenId,419 ) -> DispatchResult {420 let burnt = <TokensBurnt<T>>::get(collection.id)421 .checked_add(1)422 .ok_or(ArithmeticError::Overflow)?;423424 <TokensBurnt<T>>::insert(collection.id, burnt);425 <TokenProperties<T>>::remove((collection.id, token_id));426 <TotalSupply<T>>::remove((collection.id, token_id));427 let _ = <Balance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);428 let _ = <Allowance<T>>::clear_prefix((collection.id, token_id), u32::MAX, None);429 <PalletEvm<T>>::deposit_log(430 ERC721Events::Transfer {431 from: *owner.as_eth(),432 to: H160::default(),433 token_id: token_id.into(),434 }435 .to_log(collection_id_to_address(collection.id)),436 );437 Ok(())438 }439440 /// Burn RFT token pieces441 ///442 /// `burn` will decrease total amount of token pieces and amount owned by sender.443 /// `burn` can be called even if there are multiple owners of the RFT token.444 /// If sender wouldn't have any pieces left after `burn` than she will stop being445 /// one of the owners of the token. If there is no account that owns any pieces of446 /// the token than token will be burned too.447 ///448 /// - `amount`: Amount of token pieces to burn.449 /// - `token`: Token who's pieces should be burned450 /// - `collection`: Collection that contains the token451 pub fn burn(452 collection: &RefungibleHandle<T>,453 owner: &T::CrossAccountId,454 token: TokenId,455 amount: u128,456 ) -> DispatchResult {457 let total_supply = <TotalSupply<T>>::get((collection.id, token))458 .checked_sub(amount)459 .ok_or(<CommonError<T>>::TokenValueTooLow)?;460461 // This was probally last owner of this token?462 if total_supply == 0 {463 // Ensure user actually owns this amount464 ensure!(465 <Balance<T>>::get((collection.id, token, owner)) == amount,466 <CommonError<T>>::TokenValueTooLow467 );468 let account_balance = <AccountBalance<T>>::get((collection.id, owner))469 .checked_sub(1)470 // Should not occur471 .ok_or(ArithmeticError::Underflow)?;472473 // =========474475 <Owned<T>>::remove((collection.id, owner, token));476 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);477 <AccountBalance<T>>::insert((collection.id, owner), account_balance);478 Self::burn_token_unchecked(collection, owner, token)?;479 <PalletEvm<T>>::deposit_log(480 ERC20Events::Transfer {481 from: *owner.as_eth(),482 to: H160::default(),483 value: amount.into(),484 }485 .to_log(collection_id_to_address(collection.id)),486 );487 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(488 collection.id,489 token,490 owner.clone(),491 amount,492 ));493 return Ok(());494 }495496 let balance = <Balance<T>>::get((collection.id, token, owner))497 .checked_sub(amount)498 .ok_or(<CommonError<T>>::TokenValueTooLow)?;499 let account_balance = if balance == 0 {500 <AccountBalance<T>>::get((collection.id, owner))501 .checked_sub(1)502 // Should not occur503 .ok_or(ArithmeticError::Underflow)?504 } else {505 0506 };507508 // =========509510 if balance == 0 {511 <Owned<T>>::remove((collection.id, owner, token));512 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);513 <Balance<T>>::remove((collection.id, token, owner));514 <AccountBalance<T>>::insert((collection.id, owner), account_balance);515516 if let Some(user) = Self::token_owner(collection.id, token) {517 <PalletEvm<T>>::deposit_log(518 ERC721Events::Transfer {519 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,520 to: *user.as_eth(),521 token_id: token.into(),522 }523 .to_log(collection_id_to_address(collection.id)),524 );525 }526 } else {527 <Balance<T>>::insert((collection.id, token, owner), balance);528 }529 <TotalSupply<T>>::insert((collection.id, token), total_supply);530531 <PalletEvm<T>>::deposit_log(532 ERC20Events::Transfer {533 from: *owner.as_eth(),534 to: H160::default(),535 value: amount.into(),536 }537 .to_log(T::EvmTokenAddressMapping::token_to_address(538 collection.id,539 token,540 )),541 );542 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(543 collection.id,544 token,545 owner.clone(),546 amount,547 ));548 Ok(())549 }550551 #[transactional]552 fn modify_token_properties(553 collection: &RefungibleHandle<T>,554 sender: &T::CrossAccountId,555 token_id: TokenId,556 properties: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,557 is_token_create: bool,558 nesting_budget: &dyn Budget,559 ) -> DispatchResult {560 let is_collection_admin = || collection.is_owner_or_admin(sender);561 let is_token_owner = || -> Result<bool, DispatchError> {562 let balance = collection.balance(sender.clone(), token_id);563 let total_pieces: u128 =564 Self::total_pieces(collection.id, token_id).unwrap_or(u128::MAX);565 if balance != total_pieces {566 return Ok(false);567 }568569 let is_bundle_owner = <PalletStructure<T>>::check_indirectly_owned(570 sender.clone(),571 collection.id,572 token_id,573 None,574 nesting_budget,575 )?;576577 Ok(is_bundle_owner)578 };579580 for (key, value) in properties {581 let permission = <PalletCommon<T>>::property_permissions(collection.id)582 .get(&key)583 .cloned()584 .unwrap_or_else(PropertyPermission::none);585586 let is_property_exists = TokenProperties::<T>::get((collection.id, token_id))587 .get(&key)588 .is_some();589590 match permission {591 PropertyPermission { mutable: false, .. } if is_property_exists => {592 return Err(<CommonError<T>>::NoPermission.into());593 }594595 PropertyPermission {596 collection_admin,597 token_owner,598 ..599 } => {600 //TODO: investigate threats during public minting.601 let is_token_create =602 is_token_create && (collection_admin || token_owner) && value.is_some();603 if !(is_token_create604 || (collection_admin && is_collection_admin())605 || (token_owner && is_token_owner()?))606 {607 fail!(<CommonError<T>>::NoPermission);608 }609 }610 }611612 match value {613 Some(value) => {614 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {615 properties.try_set(key.clone(), value)616 })617 .map_err(<CommonError<T>>::from)?;618619 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertySet(620 collection.id,621 token_id,622 key,623 ));624 }625 None => {626 <TokenProperties<T>>::try_mutate((collection.id, token_id), |properties| {627 properties.remove(&key)628 })629 .map_err(<CommonError<T>>::from)?;630631 <PalletCommon<T>>::deposit_event(CommonEvent::TokenPropertyDeleted(632 collection.id,633 token_id,634 key,635 ));636 }637 }638 }639640 Ok(())641 }642643 pub fn set_token_properties(644 collection: &RefungibleHandle<T>,645 sender: &T::CrossAccountId,646 token_id: TokenId,647 properties: impl Iterator<Item = Property>,648 is_token_create: bool,649 nesting_budget: &dyn Budget,650 ) -> DispatchResult {651 Self::modify_token_properties(652 collection,653 sender,654 token_id,655 properties.map(|p| (p.key, Some(p.value))),656 is_token_create,657 nesting_budget,658 )659 }660661 pub fn set_token_property(662 collection: &RefungibleHandle<T>,663 sender: &T::CrossAccountId,664 token_id: TokenId,665 property: Property,666 nesting_budget: &dyn Budget,667 ) -> DispatchResult {668 let is_token_create = false;669670 Self::set_token_properties(671 collection,672 sender,673 token_id,674 [property].into_iter(),675 is_token_create,676 nesting_budget,677 )678 }679680 pub fn delete_token_properties(681 collection: &RefungibleHandle<T>,682 sender: &T::CrossAccountId,683 token_id: TokenId,684 property_keys: impl Iterator<Item = PropertyKey>,685 nesting_budget: &dyn Budget,686 ) -> DispatchResult {687 let is_token_create = false;688689 Self::modify_token_properties(690 collection,691 sender,692 token_id,693 property_keys.into_iter().map(|key| (key, None)),694 is_token_create,695 nesting_budget,696 )697 }698699 pub fn delete_token_property(700 collection: &RefungibleHandle<T>,701 sender: &T::CrossAccountId,702 token_id: TokenId,703 property_key: PropertyKey,704 nesting_budget: &dyn Budget,705 ) -> DispatchResult {706 Self::delete_token_properties(707 collection,708 sender,709 token_id,710 [property_key].into_iter(),711 nesting_budget,712 )713 }714715 /// Transfer RFT token pieces from one account to another.716 ///717 /// If the sender is no longer owns any pieces after the `transfer` than she stops being an owner of the token.718 ///719 /// - `from`: Owner of token pieces to transfer.720 /// - `to`: Recepient of transfered token pieces.721 /// - `amount`: Amount of token pieces to transfer.722 /// - `token`: Token whos pieces should be transfered723 /// - `collection`: Collection that contains the token724 pub fn transfer(725 collection: &RefungibleHandle<T>,726 from: &T::CrossAccountId,727 to: &T::CrossAccountId,728 token: TokenId,729 amount: u128,730 nesting_budget: &dyn Budget,731 ) -> DispatchResult {732 ensure!(733 collection.limits.transfers_enabled(),734 <CommonError<T>>::TransferNotAllowed735 );736737 if collection.permissions.access() == AccessMode::AllowList {738 collection.check_allowlist(from)?;739 collection.check_allowlist(to)?;740 }741 <PalletCommon<T>>::ensure_correct_receiver(to)?;742743 let initial_balance_from = <Balance<T>>::get((collection.id, token, from));744 let updated_balance_from = initial_balance_from745 .checked_sub(amount)746 .ok_or(<CommonError<T>>::TokenValueTooLow)?;747 let mut create_target = false;748 let from_to_differ = from != to;749 let updated_balance_to = if from != to {750 let old_balance = <Balance<T>>::get((collection.id, token, to));751 if old_balance == 0 {752 create_target = true;753 }754 Some(755 old_balance756 .checked_add(amount)757 .ok_or(ArithmeticError::Overflow)?,758 )759 } else {760 None761 };762763 let account_balance_from = if updated_balance_from == 0 {764 Some(765 <AccountBalance<T>>::get((collection.id, from))766 .checked_sub(1)767 // Should not occur768 .ok_or(ArithmeticError::Underflow)?,769 )770 } else {771 None772 };773 // Account data is created in token, AccountBalance should be increased774 // But only if from != to as we shouldn't check overflow in this case775 let account_balance_to = if create_target && from_to_differ {776 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))777 .checked_add(1)778 .ok_or(ArithmeticError::Overflow)?;779 ensure!(780 account_balance_to < collection.limits.account_token_ownership_limit(),781 <CommonError<T>>::AccountTokenLimitExceeded,782 );783784 Some(account_balance_to)785 } else {786 None787 };788789 // =========790791 <PalletStructure<T>>::nest_if_sent_to_token(792 from.clone(),793 to,794 collection.id,795 token,796 nesting_budget,797 )?;798799 if let Some(updated_balance_to) = updated_balance_to {800 // from != to801 if updated_balance_from == 0 {802 <Balance<T>>::remove((collection.id, token, from));803 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);804 } else {805 <Balance<T>>::insert((collection.id, token, from), updated_balance_from);806 }807 <Balance<T>>::insert((collection.id, token, to), updated_balance_to);808 if let Some(account_balance_from) = account_balance_from {809 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);810 <Owned<T>>::remove((collection.id, from, token));811 }812 if let Some(account_balance_to) = account_balance_to {813 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);814 <Owned<T>>::insert((collection.id, to, token), true);815 }816 }817818 <PalletEvm<T>>::deposit_log(819 ERC20Events::Transfer {820 from: *from.as_eth(),821 to: *to.as_eth(),822 value: amount.into(),823 }824 .to_log(T::EvmTokenAddressMapping::token_to_address(825 collection.id,826 token,827 )),828 );829830 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(831 collection.id,832 token,833 from.clone(),834 to.clone(),835 amount,836 ));837838 let total_supply = <TotalSupply<T>>::get((collection.id, token));839840 if amount == total_supply {841 // if token was fully owned by `from` and will be fully owned by `to` after transfer842 <PalletEvm<T>>::deposit_log(843 ERC721Events::Transfer {844 from: *from.as_eth(),845 to: *to.as_eth(),846 token_id: token.into(),847 }848 .to_log(collection_id_to_address(collection.id)),849 );850 } else if let Some(updated_balance_to) = updated_balance_to {851 // if `from` not equals `to`. This condition is needed to avoid sending event852 // when `from` fully owns token and sends part of token pieces to itself.853 if initial_balance_from == total_supply {854 // if token was fully owned by `from` and will be only partially owned by `to`855 // and `from` after transfer856 <PalletEvm<T>>::deposit_log(857 ERC721Events::Transfer {858 from: *from.as_eth(),859 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,860 token_id: token.into(),861 }862 .to_log(collection_id_to_address(collection.id)),863 );864 } else if updated_balance_to == total_supply {865 // if token was partially owned by `from` and will be fully owned by `to` after transfer866 <PalletEvm<T>>::deposit_log(867 ERC721Events::Transfer {868 from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,869 to: *to.as_eth(),870 token_id: token.into(),871 }872 .to_log(collection_id_to_address(collection.id)),873 );874 }875 }876877 Ok(())878 }879880 /// Batched operation to create multiple RFT tokens.881 ///882 /// Same as `create_item` but creates multiple tokens.883 ///884 /// - `data`: Same as 'data` in `create_item` but contains data for multiple tokens.885 pub fn create_multiple_items(886 collection: &RefungibleHandle<T>,887 sender: &T::CrossAccountId,888 data: Vec<CreateItemData<T::CrossAccountId>>,889 nesting_budget: &dyn Budget,890 ) -> DispatchResult {891 if !collection.is_owner_or_admin(sender) {892 ensure!(893 collection.permissions.mint_mode(),894 <CommonError<T>>::PublicMintingNotAllowed895 );896 collection.check_allowlist(sender)?;897898 for item in data.iter() {899 for user in item.users.keys() {900 collection.check_allowlist(user)?;901 }902 }903 }904905 for item in data.iter() {906 for (owner, _) in item.users.iter() {907 <PalletCommon<T>>::ensure_correct_receiver(owner)?;908 }909 }910911 // Total pieces per tokens912 let totals = data913 .iter()914 .map(|data| {915 Ok(data916 .users917 .iter()918 .map(|u| u.1)919 .try_fold(0u128, |acc, v| acc.checked_add(*v))920 .ok_or(ArithmeticError::Overflow)?)921 })922 .collect::<Result<Vec<_>, DispatchError>>()?;923 for total in &totals {924 ensure!(925 *total <= MAX_REFUNGIBLE_PIECES,926 <Error<T>>::WrongRefungiblePieces927 );928 }929930 let first_token_id = <TokensMinted<T>>::get(collection.id);931 let tokens_minted = first_token_id932 .checked_add(data.len() as u32)933 .ok_or(ArithmeticError::Overflow)?;934 ensure!(935 tokens_minted < collection.limits.token_limit(),936 <CommonError<T>>::CollectionTokenLimitExceeded937 );938939 let mut balances = BTreeMap::new();940 for data in &data {941 for owner in data.users.keys() {942 let balance = balances943 .entry(owner)944 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));945 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;946947 ensure!(948 *balance <= collection.limits.account_token_ownership_limit(),949 <CommonError<T>>::AccountTokenLimitExceeded,950 );951 }952 }953954 for (i, token) in data.iter().enumerate() {955 let token_id = TokenId(first_token_id + i as u32 + 1);956 for (to, _) in token.users.iter() {957 <PalletStructure<T>>::check_nesting(958 sender.clone(),959 to,960 collection.id,961 token_id,962 nesting_budget,963 )?;964 }965 }966967 // =========968969 with_transaction(|| {970 for (i, data) in data.iter().enumerate() {971 let token_id = first_token_id + i as u32 + 1;972 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);973974 for (user, amount) in data.users.iter() {975 if *amount == 0 {976 continue;977 }978 <Balance<T>>::insert((collection.id, token_id, &user), amount);979 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);980 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(981 user,982 collection.id,983 TokenId(token_id),984 );985 }986987 if let Err(e) = Self::set_token_properties(988 collection,989 sender,990 TokenId(token_id),991 data.properties.clone().into_iter(),992 true,993 nesting_budget,994 ) {995 return TransactionOutcome::Rollback(Err(e));996 }997 }998 TransactionOutcome::Commit(Ok(()))999 })?;10001001 <TokensMinted<T>>::insert(collection.id, tokens_minted);10021003 for (account, balance) in balances {1004 <AccountBalance<T>>::insert((collection.id, account), balance);1005 }10061007 for (i, token) in data.into_iter().enumerate() {1008 let token_id = first_token_id + i as u32 + 1;10091010 let receivers = token1011 .users1012 .into_iter()1013 .filter(|(_, amount)| *amount > 0)1014 .collect::<Vec<_>>();10151016 if let [(user, _)] = receivers.as_slice() {1017 // if there is exactly one receiver1018 <PalletEvm<T>>::deposit_log(1019 ERC721Events::Transfer {1020 from: H160::default(),1021 to: *user.as_eth(),1022 token_id: token_id.into(),1023 }1024 .to_log(collection_id_to_address(collection.id)),1025 );1026 } else if let [_, ..] = receivers.as_slice() {1027 // if there is more than one receiver1028 <PalletEvm<T>>::deposit_log(1029 ERC721Events::Transfer {1030 from: H160::default(),1031 to: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,1032 token_id: token_id.into(),1033 }1034 .to_log(collection_id_to_address(collection.id)),1035 );1036 }10371038 for (user, amount) in receivers.into_iter() {1039 <PalletEvm<T>>::deposit_log(1040 ERC20Events::Transfer {1041 from: H160::default(),1042 to: *user.as_eth(),1043 value: amount.into(),1044 }1045 .to_log(T::EvmTokenAddressMapping::token_to_address(1046 collection.id,1047 TokenId(token_id),1048 )),1049 );1050 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1051 collection.id,1052 TokenId(token_id),1053 user,1054 amount,1055 ));1056 }1057 }1058 Ok(())1059 }10601061 pub fn set_allowance_unchecked(1062 collection: &RefungibleHandle<T>,1063 sender: &T::CrossAccountId,1064 spender: &T::CrossAccountId,1065 token: TokenId,1066 amount: u128,1067 ) {1068 if amount == 0 {1069 <Allowance<T>>::remove((collection.id, token, sender, spender));1070 } else {1071 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);1072 }10731074 <PalletEvm<T>>::deposit_log(1075 ERC20Events::Approval {1076 owner: *sender.as_eth(),1077 spender: *spender.as_eth(),1078 value: amount.into(),1079 }1080 .to_log(T::EvmTokenAddressMapping::token_to_address(1081 collection.id,1082 token,1083 )),1084 );1085 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(1086 collection.id,1087 token,1088 sender.clone(),1089 spender.clone(),1090 amount,1091 ))1092 }10931094 /// Set allowance for the spender to `transfer` or `burn` sender's token pieces.1095 ///1096 /// - `amount`: Amount of token pieces the spender is allowed to `transfer` or `burn.1097 pub fn set_allowance(1098 collection: &RefungibleHandle<T>,1099 sender: &T::CrossAccountId,1100 spender: &T::CrossAccountId,1101 token: TokenId,1102 amount: u128,1103 ) -> DispatchResult {1104 if collection.permissions.access() == AccessMode::AllowList {1105 collection.check_allowlist(sender)?;1106 collection.check_allowlist(spender)?;1107 }11081109 <PalletCommon<T>>::ensure_correct_receiver(spender)?;11101111 if <Balance<T>>::get((collection.id, token, sender)) < amount {1112 ensure!(1113 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),1114 <CommonError<T>>::CantApproveMoreThanOwned1115 );1116 }11171118 // =========11191120 Self::set_allowance_unchecked(collection, sender, spender, token, amount);1121 Ok(())1122 }11231124 /// Returns allowance, which should be set after transaction1125 fn check_allowed(1126 collection: &RefungibleHandle<T>,1127 spender: &T::CrossAccountId,1128 from: &T::CrossAccountId,1129 token: TokenId,1130 amount: u128,1131 nesting_budget: &dyn Budget,1132 ) -> Result<Option<u128>, DispatchError> {1133 if spender.conv_eq(from) {1134 return Ok(None);1135 }1136 if collection.permissions.access() == AccessMode::AllowList {1137 // `from`, `to` checked in [`transfer`]1138 collection.check_allowlist(spender)?;1139 }1140 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1141 // TODO: should collection owner be allowed to perform this transfer?1142 ensure!(1143 <PalletStructure<T>>::check_indirectly_owned(1144 spender.clone(),1145 source.0,1146 source.1,1147 None,1148 nesting_budget1149 )?,1150 <CommonError<T>>::ApprovedValueTooLow,1151 );1152 return Ok(None);1153 }1154 let allowance =1155 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);1156 if allowance.is_none() {1157 ensure!(1158 collection.ignores_allowance(spender),1159 <CommonError<T>>::ApprovedValueTooLow1160 );1161 }1162 Ok(allowance)1163 }11641165 /// Transfer RFT token pieces from one account to another.1166 ///1167 /// Same as the [`transfer`] but spender doesn't needs to be an owner of the token pieces.1168 /// The owner should set allowance for the spender to transfer pieces.1169 ///1170 /// [`transfer`]: struct.Pallet.html#method.transfer1171 pub fn transfer_from(1172 collection: &RefungibleHandle<T>,1173 spender: &T::CrossAccountId,1174 from: &T::CrossAccountId,1175 to: &T::CrossAccountId,1176 token: TokenId,1177 amount: u128,1178 nesting_budget: &dyn Budget,1179 ) -> DispatchResult {1180 let allowance =1181 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;11821183 // =========11841185 Self::transfer(collection, from, to, token, amount, nesting_budget)?;1186 if let Some(allowance) = allowance {1187 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1188 }1189 Ok(())1190 }11911192 /// Burn RFT token pieces from the account.1193 ///1194 /// Same as the [`burn`] but spender doesn't need to be an owner of the token pieces. The owner should1195 /// set allowance for the spender to burn pieces1196 ///1197 /// [`burn`]: struct.Pallet.html#method.burn1198 pub fn burn_from(1199 collection: &RefungibleHandle<T>,1200 spender: &T::CrossAccountId,1201 from: &T::CrossAccountId,1202 token: TokenId,1203 amount: u128,1204 nesting_budget: &dyn Budget,1205 ) -> DispatchResult {1206 let allowance =1207 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;12081209 // =========12101211 Self::burn(collection, from, token, amount)?;1212 if let Some(allowance) = allowance {1213 Self::set_allowance_unchecked(collection, from, spender, token, allowance);1214 }1215 Ok(())1216 }12171218 /// Create RFT token.1219 ///1220 /// The sender should be the owner/admin of the collection or collection should be configured1221 /// to allow public minting.1222 ///1223 /// - `data`: Contains list of users who will become the owners of the token pieces and amount1224 /// of token pieces they will receive.1225 pub fn create_item(1226 collection: &RefungibleHandle<T>,1227 sender: &T::CrossAccountId,1228 data: CreateItemData<T::CrossAccountId>,1229 nesting_budget: &dyn Budget,1230 ) -> DispatchResult {1231 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1232 }12331234 /// Repartition RFT token.1235 ///1236 /// `repartition` will set token balance of the sender and total amount of token pieces.1237 /// Sender should own all of the token pieces. `repartition' could be done even if some1238 /// token pieces were burned before.1239 ///1240 /// - `amount`: Total amount of token pieces that the token will have after `repartition`.1241 pub fn repartition(1242 collection: &RefungibleHandle<T>,1243 owner: &T::CrossAccountId,1244 token: TokenId,1245 amount: u128,1246 ) -> DispatchResult {1247 ensure!(1248 amount <= MAX_REFUNGIBLE_PIECES,1249 <Error<T>>::WrongRefungiblePieces1250 );1251 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);1252 // Ensure user owns all pieces1253 let total_pieces = Self::total_pieces(collection.id, token).unwrap_or(u128::MAX);1254 let balance = <Balance<T>>::get((collection.id, token, owner));1255 ensure!(1256 total_pieces == balance,1257 <Error<T>>::RepartitionWhileNotOwningAllPieces1258 );12591260 <Balance<T>>::insert((collection.id, token, owner), amount);1261 <TotalSupply<T>>::insert((collection.id, token), amount);12621263 if amount > total_pieces {1264 let mint_amount = amount - total_pieces;1265 <PalletEvm<T>>::deposit_log(1266 ERC20Events::Transfer {1267 from: H160::default(),1268 to: *owner.as_eth(),1269 value: mint_amount.into(),1270 }1271 .to_log(T::EvmTokenAddressMapping::token_to_address(1272 collection.id,1273 token,1274 )),1275 );1276 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1277 collection.id,1278 token,1279 owner.clone(),1280 mint_amount,1281 ));1282 } else if total_pieces > amount {1283 let burn_amount = total_pieces - amount;1284 <PalletEvm<T>>::deposit_log(1285 ERC20Events::Transfer {1286 from: *owner.as_eth(),1287 to: H160::default(),1288 value: burn_amount.into(),1289 }1290 .to_log(T::EvmTokenAddressMapping::token_to_address(1291 collection.id,1292 token,1293 )),1294 );1295 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(1296 collection.id,1297 token,1298 owner.clone(),1299 burn_amount,1300 ));1301 }13021303 Ok(())1304 }13051306 fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {1307 let mut owner = None;1308 let mut count = 0;1309 for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {1310 count += 1;1311 if count > 1 {1312 return None;1313 }1314 owner = Some(key);1315 }1316 owner1317 }13181319 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {1320 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()1321 }13221323 pub fn set_collection_properties(1324 collection: &RefungibleHandle<T>,1325 sender: &T::CrossAccountId,1326 properties: Vec<Property>,1327 ) -> DispatchResult {1328 <PalletCommon<T>>::set_collection_properties(collection, sender, properties)1329 }13301331 pub fn delete_collection_properties(1332 collection: &RefungibleHandle<T>,1333 sender: &T::CrossAccountId,1334 property_keys: Vec<PropertyKey>,1335 ) -> DispatchResult {1336 <PalletCommon<T>>::delete_collection_properties(collection, sender, property_keys)1337 }13381339 pub fn set_token_property_permissions(1340 collection: &RefungibleHandle<T>,1341 sender: &T::CrossAccountId,1342 property_permissions: Vec<PropertyKeyPermission>,1343 ) -> DispatchResult {1344 <PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)1345 }13461347 pub fn set_scoped_token_property_permissions(1348 collection: &RefungibleHandle<T>,1349 sender: &T::CrossAccountId,1350 scope: PropertyScope,1351 property_permissions: Vec<PropertyKeyPermission>,1352 ) -> DispatchResult {1353 <PalletCommon<T>>::set_scoped_token_property_permissions(1354 collection,1355 sender,1356 scope,1357 property_permissions,1358 )1359 }13601361 /// Returns 10 token in no particular order.1362 ///1363 /// There is no direct way to get token holders in ascending order,1364 /// since `iter_prefix` returns values in no particular order.1365 /// Therefore, getting the 10 largest holders with a large value of holders1366 /// can lead to impact memory allocation + sorting with `n * log (n)`.1367 pub fn token_owners(1368 collection_id: CollectionId,1369 token: TokenId,1370 ) -> Option<Vec<T::CrossAccountId>> {1371 let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))1372 .map(|(owner, _amount)| owner)1373 .take(10)1374 .collect();13751376 if res.is_empty() {1377 None1378 } else {1379 Some(res)1380 }1381 }1382}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();