difftreelog
doc: minor fixes + typos
in: master
7 files changed
client/rpc/src/lib.rsdiffbeforeafterboth--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -50,6 +50,7 @@
account: CrossAccountId,
at: Option<BlockHash>,
) -> Result<Vec<TokenId>>;
+
/// Get tokens contained in collection
#[method(name = "unique_collectionTokens")]
fn collection_tokens(
@@ -57,6 +58,7 @@
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<TokenId>>;
+
/// Check if token exists
#[method(name = "unique_tokenExists")]
fn token_exists(
@@ -65,6 +67,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<bool>;
+
/// Get token owner
#[method(name = "unique_tokenOwner")]
fn token_owner(
@@ -73,6 +76,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
+
/// Get token owner, in case of nested token - find the parent recursively
#[method(name = "unique_topmostTokenOwner")]
fn topmost_token_owner(
@@ -81,6 +85,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<CrossAccountId>>;
+
/// Get tokens nested directly into the token
#[method(name = "unique_tokenChildren")]
fn token_children(
@@ -89,6 +94,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Vec<TokenChild>>;
+
/// Get collection properties
#[method(name = "unique_collectionProperties")]
fn collection_properties(
@@ -97,6 +103,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<Property>>;
+
/// Get token properties
#[method(name = "unique_tokenProperties")]
fn token_properties(
@@ -106,6 +113,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<Property>>;
+
/// Get property permissions
#[method(name = "unique_propertyPermissions")]
fn property_permissions(
@@ -114,6 +122,7 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<Vec<PropertyKeyPermission>>;
+
/// Get token data
#[method(name = "unique_tokenData")]
fn token_data(
@@ -123,9 +132,11 @@
keys: Option<Vec<String>>,
at: Option<BlockHash>,
) -> Result<TokenData<CrossAccountId>>;
+
/// Get amount of unique collection tokens
#[method(name = "unique_totalSupply")]
fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;
+
/// Get owned amount of any user tokens
#[method(name = "unique_accountBalance")]
fn account_balance(
@@ -134,6 +145,7 @@
account: CrossAccountId,
at: Option<BlockHash>,
) -> Result<u32>;
+
/// Get owned amount of specific account token
#[method(name = "unique_balance")]
fn balance(
@@ -143,6 +155,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<String>;
+
/// Get allowed amount
#[method(name = "unique_allowance")]
fn allowance(
@@ -153,6 +166,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<String>;
+
/// Get admin list
#[method(name = "unique_adminlist")]
fn adminlist(
@@ -160,6 +174,7 @@
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<CrossAccountId>>;
+
/// Get allowlist
#[method(name = "unique_allowlist")]
fn allowlist(
@@ -167,6 +182,7 @@
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Vec<CrossAccountId>>;
+
/// Check if user is allowed to use collection
#[method(name = "unique_allowed")]
fn allowed(
@@ -175,9 +191,11 @@
user: CrossAccountId,
at: Option<BlockHash>,
) -> Result<bool>;
+
/// Get last token ID created in a collection
#[method(name = "unique_lastTokenId")]
fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;
+
/// Get collection by specified ID
#[method(name = "unique_collectionById")]
fn collection_by_id(
@@ -185,9 +203,11 @@
collection: CollectionId,
at: Option<BlockHash>,
) -> Result<Option<RpcCollection<AccountId>>>;
+
/// Get collection stats
#[method(name = "unique_collectionStats")]
fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;
+
/// Get number of blocks when sponsored transaction is available
#[method(name = "unique_nextSponsored")]
fn next_sponsored(
@@ -197,6 +217,7 @@
token: TokenId,
at: Option<BlockHash>,
) -> Result<Option<u64>>;
+
/// Get effective collection limits
#[method(name = "unique_effectiveCollectionLimits")]
fn effective_collection_limits(
@@ -204,6 +225,7 @@
collection_id: CollectionId,
at: Option<BlockHash>,
) -> Result<Option<CollectionLimits>>;
+
/// Get total pieces of token
#[method(name = "unique_totalPieces")]
fn total_pieces(
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -334,7 +334,7 @@
///
/// * collection_id: ID of the collection to which the item belongs.
///
- /// * item_id: ID of the item trasnferred.
+ /// * item_id: ID of the item transferred.
///
/// * sender: Original owner of the item.
///
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -58,13 +58,13 @@
pub enum Error<T> {
/// Not Fungible item data used to mint in Fungible collection.
NotFungibleDataUsedToMintFungibleCollectionToken,
- /// Not default id passed as TokenId argument
+ /// Not default id passed as TokenId argument.
FungibleItemsHaveNoId,
- /// Tried to set data for fungible item
+ /// Tried to set data for fungible item.
FungibleItemsDontHaveData,
- /// Fungible token does not support nested
+ /// Fungible token does not support nesting.
FungibleDisallowsNesting,
- /// Setting item properties is not allowed
+ /// Setting item properties is not allowed.
SettingPropertiesNotAllowed,
}
pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -171,7 +171,7 @@
QueryKind = ValueQuery,
>;
- /// Amount of tokens owned in a collection.s
+ /// Amount of tokens owned in a collection.
#[pallet::storage]
pub type AccountBalance<T: Config> = StorageNMap<
Key = (
@@ -182,7 +182,7 @@
QueryKind = ValueQuery,
>;
- /// todo doc
+ /// todo:doc
#[pallet::storage]
pub type Allowance<T: Config> = StorageNMap<
Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{ensure, BoundedVec};20use up_data_structs::{21 AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,22 CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping, budget::Budget,23};24use pallet_evm::account::CrossAccountId;25use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};26use pallet_structure::Pallet as PalletStructure;27use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};28use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};29use core::ops::Deref;30use codec::{Encode, Decode, MaxEncodedLen};31use scale_info::TypeInfo;3233pub use pallet::*;34#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod common;37pub mod erc;38pub mod weights;39pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4041/// Token data, stored independently from other data used to describe it.42/// Notably contains the token metadata.43#[struct_versioning::versioned(version = 2, upper)]44#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]45pub struct ItemData {46 pub const_data: BoundedVec<u8, CustomDataLimit>,4748 #[version(..2)]49 pub variable_data: BoundedVec<u8, CustomDataLimit>,50}5152#[frame_support::pallet]53pub mod pallet {54 use super::*;55 use frame_support::{56 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,57 traits::StorageVersion,58 };59 use frame_system::pallet_prelude::*;60 use up_data_structs::{CollectionId, TokenId};61 use super::weights::WeightInfo;6263 #[pallet::error]64 pub enum Error<T> {65 /// Not Refungible item data used to mint in Refungible collection.66 NotRefungibleDataUsedToMintFungibleCollectionToken,67 /// Maximum refungibility exceeded68 WrongRefungiblePieces,69 /// Refungible token can't be repartitioned by user who isn't owns all pieces70 RepartitionWhileNotOwningAllPieces,71 /// Refungible token can't nest other tokens72 RefungibleDisallowsNesting,73 /// Setting item properties is not allowed74 SettingPropertiesNotAllowed,75 }7677 #[pallet::config]78 pub trait Config:79 frame_system::Config + pallet_common::Config + pallet_structure::Config80 {81 type WeightInfo: WeightInfo;82 }8384 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);8586 #[pallet::pallet]87 #[pallet::storage_version(STORAGE_VERSION)]88 #[pallet::generate_store(pub(super) trait Store)]89 pub struct Pallet<T>(_);9091 /// Total amount of minted tokens in a collection.92 #[pallet::storage]93 pub type TokensMinted<T: Config> =94 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;9596 /// Amount of tokens burnt in a collection.97 #[pallet::storage]98 pub type TokensBurnt<T: Config> =99 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;100101 /// Token data, used to partially describe a token.102 #[pallet::storage]103 pub type TokenData<T: Config> = StorageNMap<104 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),105 Value = ItemData,106 QueryKind = ValueQuery,107 >;108109 /// Amount of pieces a refungible token is split into.110 #[pallet::storage]111 pub type TotalSupply<T: Config> = StorageNMap<112 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),113 Value = u128,114 QueryKind = ValueQuery,115 >;116117 /// Used to enumerate tokens owned by account.118 #[pallet::storage]119 pub type Owned<T: Config> = StorageNMap<120 Key = (121 Key<Twox64Concat, CollectionId>,122 Key<Blake2_128Concat, T::CrossAccountId>,123 Key<Twox64Concat, TokenId>,124 ),125 Value = bool,126 QueryKind = ValueQuery,127 >;128129 /// Amount of tokens (not pieces) partially owned by an account within a collection.130 #[pallet::storage]131 pub type AccountBalance<T: Config> = StorageNMap<132 Key = (133 Key<Twox64Concat, CollectionId>,134 // Owner135 Key<Blake2_128Concat, T::CrossAccountId>,136 ),137 Value = u32,138 QueryKind = ValueQuery,139 >;140141 /// Amount of pieces of a token owned by an account.142 #[pallet::storage]143 pub type Balance<T: Config> = StorageNMap<144 Key = (145 Key<Twox64Concat, CollectionId>,146 Key<Twox64Concat, TokenId>,147 // Owner148 Key<Blake2_128Concat, T::CrossAccountId>,149 ),150 Value = u128,151 QueryKind = ValueQuery,152 >;153154 /// todo:doc155 #[pallet::storage]156 pub type Allowance<T: Config> = StorageNMap<157 Key = (158 Key<Twox64Concat, CollectionId>,159 Key<Twox64Concat, TokenId>,160 // Owner161 Key<Blake2_128, T::CrossAccountId>,162 // Spender163 Key<Blake2_128Concat, T::CrossAccountId>,164 ),165 Value = u128,166 QueryKind = ValueQuery,167 >;168169 #[pallet::hooks]170 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {171 fn on_runtime_upgrade() -> Weight {172 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {173 <TokenData<T>>::translate_values::<ItemDataVersion1, _>(|v| {174 Some(<ItemDataVersion2>::from(v))175 })176 }177178 0179 }180 }181}182183pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);184impl<T: Config> RefungibleHandle<T> {185 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {186 Self(inner)187 }188 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {189 self.0190 }191}192impl<T: Config> Deref for RefungibleHandle<T> {193 type Target = pallet_common::CollectionHandle<T>;194195 fn deref(&self) -> &Self::Target {196 &self.0197 }198}199200impl<T: Config> Pallet<T> {201 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {202 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)203 }204 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {205 <TotalSupply<T>>::contains_key((collection.id, token))206 }207}208209// unchecked calls skips any permission checks210impl<T: Config> Pallet<T> {211 pub fn init_collection(212 owner: T::CrossAccountId,213 data: CreateCollectionData<T::AccountId>,214 ) -> Result<CollectionId, DispatchError> {215 <PalletCommon<T>>::init_collection(owner, data, false)216 }217 pub fn destroy_collection(218 collection: RefungibleHandle<T>,219 sender: &T::CrossAccountId,220 ) -> DispatchResult {221 let id = collection.id;222223 if Self::collection_has_tokens(id) {224 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());225 }226227 // =========228229 PalletCommon::destroy_collection(collection.0, sender)?;230231 <TokensMinted<T>>::remove(id);232 <TokensBurnt<T>>::remove(id);233 <TokenData<T>>::remove_prefix((id,), None);234 <TotalSupply<T>>::remove_prefix((id,), None);235 <Balance<T>>::remove_prefix((id,), None);236 <Allowance<T>>::remove_prefix((id,), None);237 <Owned<T>>::remove_prefix((id,), None);238 <AccountBalance<T>>::remove_prefix((id,), None);239 Ok(())240 }241242 fn collection_has_tokens(collection_id: CollectionId) -> bool {243 <TokenData<T>>::iter_prefix((collection_id,))244 .next()245 .is_some()246 }247248 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {249 let burnt = <TokensBurnt<T>>::get(collection.id)250 .checked_add(1)251 .ok_or(ArithmeticError::Overflow)?;252253 <TokensBurnt<T>>::insert(collection.id, burnt);254 <TokenData<T>>::remove((collection.id, token_id));255 <TotalSupply<T>>::remove((collection.id, token_id));256 <Balance<T>>::remove_prefix((collection.id, token_id), None);257 <Allowance<T>>::remove_prefix((collection.id, token_id), None);258 // TODO: ERC721 transfer event259 Ok(())260 }261262 pub fn burn(263 collection: &RefungibleHandle<T>,264 owner: &T::CrossAccountId,265 token: TokenId,266 amount: u128,267 ) -> DispatchResult {268 let total_supply = <TotalSupply<T>>::get((collection.id, token))269 .checked_sub(amount)270 .ok_or(<CommonError<T>>::TokenValueTooLow)?;271272 // This was probally last owner of this token?273 if total_supply == 0 {274 // Ensure user actually owns this amount275 ensure!(276 <Balance<T>>::get((collection.id, token, owner)) == amount,277 <CommonError<T>>::TokenValueTooLow278 );279 let account_balance = <AccountBalance<T>>::get((collection.id, owner))280 .checked_sub(1)281 // Should not occur282 .ok_or(ArithmeticError::Underflow)?;283284 // =========285286 <Owned<T>>::remove((collection.id, owner, token));287 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);288 <AccountBalance<T>>::insert((collection.id, owner), account_balance);289 Self::burn_token(collection, token)?;290 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(291 collection.id,292 token,293 owner.clone(),294 amount,295 ));296 return Ok(());297 }298299 let balance = <Balance<T>>::get((collection.id, token, owner))300 .checked_sub(amount)301 .ok_or(<CommonError<T>>::TokenValueTooLow)?;302 let account_balance = if balance == 0 {303 <AccountBalance<T>>::get((collection.id, owner))304 .checked_sub(1)305 // Should not occur306 .ok_or(ArithmeticError::Underflow)?307 } else {308 0309 };310311 // =========312313 if balance == 0 {314 <Owned<T>>::remove((collection.id, owner, token));315 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);316 <Balance<T>>::remove((collection.id, token, owner));317 <AccountBalance<T>>::insert((collection.id, owner), account_balance);318 } else {319 <Balance<T>>::insert((collection.id, token, owner), balance);320 }321 <TotalSupply<T>>::insert((collection.id, token), total_supply);322 // TODO: ERC20 transfer event323 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(324 collection.id,325 token,326 owner.clone(),327 amount,328 ));329 Ok(())330 }331332 pub fn transfer(333 collection: &RefungibleHandle<T>,334 from: &T::CrossAccountId,335 to: &T::CrossAccountId,336 token: TokenId,337 amount: u128,338 nesting_budget: &dyn Budget,339 ) -> DispatchResult {340 ensure!(341 collection.limits.transfers_enabled(),342 <CommonError<T>>::TransferNotAllowed343 );344345 if collection.permissions.access() == AccessMode::AllowList {346 collection.check_allowlist(from)?;347 collection.check_allowlist(to)?;348 }349 <PalletCommon<T>>::ensure_correct_receiver(to)?;350351 let balance_from = <Balance<T>>::get((collection.id, token, from))352 .checked_sub(amount)353 .ok_or(<CommonError<T>>::TokenValueTooLow)?;354 let mut create_target = false;355 let from_to_differ = from != to;356 let balance_to = if from != to {357 let old_balance = <Balance<T>>::get((collection.id, token, to));358 if old_balance == 0 {359 create_target = true;360 }361 Some(362 old_balance363 .checked_add(amount)364 .ok_or(ArithmeticError::Overflow)?,365 )366 } else {367 None368 };369370 let account_balance_from = if balance_from == 0 {371 Some(372 <AccountBalance<T>>::get((collection.id, from))373 .checked_sub(1)374 // Should not occur375 .ok_or(ArithmeticError::Underflow)?,376 )377 } else {378 None379 };380 // Account data is created in token, AccountBalance should be increased381 // But only if from != to as we shouldn't check overflow in this case382 let account_balance_to = if create_target && from_to_differ {383 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))384 .checked_add(1)385 .ok_or(ArithmeticError::Overflow)?;386 ensure!(387 account_balance_to < collection.limits.account_token_ownership_limit(),388 <CommonError<T>>::AccountTokenLimitExceeded,389 );390391 Some(account_balance_to)392 } else {393 None394 };395396 // =========397398 <PalletStructure<T>>::nest_if_sent_to_token(399 from.clone(),400 to,401 collection.id,402 token,403 nesting_budget,404 )?;405406 if let Some(balance_to) = balance_to {407 // from != to408 if balance_from == 0 {409 <Balance<T>>::remove((collection.id, token, from));410 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);411 } else {412 <Balance<T>>::insert((collection.id, token, from), balance_from);413 }414 <Balance<T>>::insert((collection.id, token, to), balance_to);415 if let Some(account_balance_from) = account_balance_from {416 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);417 <Owned<T>>::remove((collection.id, from, token));418 }419 if let Some(account_balance_to) = account_balance_to {420 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);421 <Owned<T>>::insert((collection.id, to, token), true);422 }423 }424425 // TODO: ERC20 transfer event426 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(427 collection.id,428 token,429 from.clone(),430 to.clone(),431 amount,432 ));433 Ok(())434 }435436 pub fn create_multiple_items(437 collection: &RefungibleHandle<T>,438 sender: &T::CrossAccountId,439 data: Vec<CreateRefungibleExData<T::CrossAccountId>>,440 nesting_budget: &dyn Budget,441 ) -> DispatchResult {442 if !collection.is_owner_or_admin(sender) {443 ensure!(444 collection.permissions.mint_mode(),445 <CommonError<T>>::PublicMintingNotAllowed446 );447 collection.check_allowlist(sender)?;448449 for item in data.iter() {450 for user in item.users.keys() {451 collection.check_allowlist(user)?;452 }453 }454 }455456 for item in data.iter() {457 for (owner, _) in item.users.iter() {458 <PalletCommon<T>>::ensure_correct_receiver(owner)?;459 }460 }461462 // Total pieces per tokens463 let totals = data464 .iter()465 .map(|data| {466 Ok(data467 .users468 .iter()469 .map(|u| u.1)470 .try_fold(0u128, |acc, v| acc.checked_add(*v))471 .ok_or(ArithmeticError::Overflow)?)472 })473 .collect::<Result<Vec<_>, DispatchError>>()?;474 for total in &totals {475 ensure!(476 *total <= MAX_REFUNGIBLE_PIECES,477 <Error<T>>::WrongRefungiblePieces478 );479 }480481 let first_token_id = <TokensMinted<T>>::get(collection.id);482 let tokens_minted = first_token_id483 .checked_add(data.len() as u32)484 .ok_or(ArithmeticError::Overflow)?;485 ensure!(486 tokens_minted < collection.limits.token_limit(),487 <CommonError<T>>::CollectionTokenLimitExceeded488 );489490 let mut balances = BTreeMap::new();491 for data in &data {492 for owner in data.users.keys() {493 let balance = balances494 .entry(owner)495 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));496 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;497498 ensure!(499 *balance <= collection.limits.account_token_ownership_limit(),500 <CommonError<T>>::AccountTokenLimitExceeded,501 );502 }503 }504505 for (i, token) in data.iter().enumerate() {506 let token_id = TokenId(first_token_id + i as u32 + 1);507 for (to, _) in token.users.iter() {508 <PalletStructure<T>>::check_nesting(509 sender.clone(),510 to,511 collection.id,512 token_id,513 nesting_budget,514 )?;515 }516 }517518 // =========519520 <TokensMinted<T>>::insert(collection.id, tokens_minted);521 for (account, balance) in balances {522 <AccountBalance<T>>::insert((collection.id, account), balance);523 }524 for (i, token) in data.into_iter().enumerate() {525 let token_id = first_token_id + i as u32 + 1;526 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);527528 <TokenData<T>>::insert(529 (collection.id, token_id),530 ItemData {531 const_data: token.const_data,532 },533 );534535 for (user, amount) in token.users.into_iter() {536 if amount == 0 {537 continue;538 }539 <Balance<T>>::insert((collection.id, token_id, &user), amount);540 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);541 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(542 &user,543 collection.id,544 TokenId(token_id),545 );546547 // TODO: ERC20 transfer event548 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(549 collection.id,550 TokenId(token_id),551 user,552 amount,553 ));554 }555 }556 Ok(())557 }558559 pub fn set_allowance_unchecked(560 collection: &RefungibleHandle<T>,561 sender: &T::CrossAccountId,562 spender: &T::CrossAccountId,563 token: TokenId,564 amount: u128,565 ) {566 if amount == 0 {567 <Allowance<T>>::remove((collection.id, token, sender, spender));568 } else {569 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);570 }571 // TODO: ERC20 approval event572 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(573 collection.id,574 token,575 sender.clone(),576 spender.clone(),577 amount,578 ))579 }580581 pub fn set_allowance(582 collection: &RefungibleHandle<T>,583 sender: &T::CrossAccountId,584 spender: &T::CrossAccountId,585 token: TokenId,586 amount: u128,587 ) -> DispatchResult {588 if collection.permissions.access() == AccessMode::AllowList {589 collection.check_allowlist(sender)?;590 collection.check_allowlist(spender)?;591 }592593 <PalletCommon<T>>::ensure_correct_receiver(spender)?;594595 if <Balance<T>>::get((collection.id, token, sender)) < amount {596 ensure!(597 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),598 <CommonError<T>>::CantApproveMoreThanOwned599 );600 }601602 // =========603604 Self::set_allowance_unchecked(collection, sender, spender, token, amount);605 Ok(())606 }607608 /// todo:doc oh look, a precedent. not pub, too. but it has an unclear use-case.609 /// Returns allowance, which should be set after transaction610 fn check_allowed(611 collection: &RefungibleHandle<T>,612 spender: &T::CrossAccountId,613 from: &T::CrossAccountId,614 token: TokenId,615 amount: u128,616 nesting_budget: &dyn Budget,617 ) -> Result<Option<u128>, DispatchError> {618 if spender.conv_eq(from) {619 return Ok(None);620 }621 if collection.permissions.access() == AccessMode::AllowList {622 // `from`, `to` checked in [`transfer`]623 collection.check_allowlist(spender)?;624 }625 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {626 // TODO: should collection owner be allowed to perform this transfer?627 ensure!(628 <PalletStructure<T>>::check_indirectly_owned(629 spender.clone(),630 source.0,631 source.1,632 None,633 nesting_budget634 )?,635 <CommonError<T>>::ApprovedValueTooLow,636 );637 return Ok(None);638 }639 let allowance =640 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);641 if allowance.is_none() {642 ensure!(643 collection.ignores_allowance(spender),644 <CommonError<T>>::ApprovedValueTooLow645 );646 }647 Ok(allowance)648 }649650 pub fn transfer_from(651 collection: &RefungibleHandle<T>,652 spender: &T::CrossAccountId,653 from: &T::CrossAccountId,654 to: &T::CrossAccountId,655 token: TokenId,656 amount: u128,657 nesting_budget: &dyn Budget,658 ) -> DispatchResult {659 let allowance =660 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;661662 // =========663664 Self::transfer(collection, from, to, token, amount, nesting_budget)?;665 if let Some(allowance) = allowance {666 Self::set_allowance_unchecked(collection, from, spender, token, allowance);667 }668 Ok(())669 }670671 pub fn burn_from(672 collection: &RefungibleHandle<T>,673 spender: &T::CrossAccountId,674 from: &T::CrossAccountId,675 token: TokenId,676 amount: u128,677 nesting_budget: &dyn Budget,678 ) -> DispatchResult {679 let allowance =680 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;681682 // =========683684 Self::burn(collection, from, token, amount)?;685 if let Some(allowance) = allowance {686 Self::set_allowance_unchecked(collection, from, spender, token, allowance);687 }688 Ok(())689 }690691 /// Delegated to `create_multiple_items`692 pub fn create_item(693 collection: &RefungibleHandle<T>,694 sender: &T::CrossAccountId,695 data: CreateRefungibleExData<T::CrossAccountId>,696 nesting_budget: &dyn Budget,697 ) -> DispatchResult {698 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)699 }700701 pub fn repartition(702 collection: &RefungibleHandle<T>,703 owner: &T::CrossAccountId,704 token: TokenId,705 amount: u128,706 ) -> DispatchResult {707 ensure!(708 amount <= MAX_REFUNGIBLE_PIECES,709 <Error<T>>::WrongRefungiblePieces710 );711 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);712 // Ensure user owns all pieces713 let total_supply = <TotalSupply<T>>::get((collection.id, token));714 let balance = <Balance<T>>::get((collection.id, token, owner));715 ensure!(716 total_supply == balance,717 <Error<T>>::RepartitionWhileNotOwningAllPieces718 );719720 <Balance<T>>::insert((collection.id, token, owner), amount);721 <TotalSupply<T>>::insert((collection.id, token), amount);722 Ok(())723 }724725 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {726 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()727 }728}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#![cfg_attr(not(feature = "std"), no_std)]1819use frame_support::{ensure, BoundedVec};20use up_data_structs::{21 AccessMode, CollectionId, CustomDataLimit, MAX_REFUNGIBLE_PIECES, TokenId,22 CreateCollectionData, CreateRefungibleExData, mapping::TokenAddressMapping, budget::Budget,23};24use pallet_evm::account::CrossAccountId;25use pallet_common::{Error as CommonError, Event as CommonEvent, Pallet as PalletCommon};26use pallet_structure::Pallet as PalletStructure;27use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};28use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};29use core::ops::Deref;30use codec::{Encode, Decode, MaxEncodedLen};31use scale_info::TypeInfo;3233pub use pallet::*;34#[cfg(feature = "runtime-benchmarks")]35pub mod benchmarking;36pub mod common;37pub mod erc;38pub mod weights;39pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;4041/// Token data, stored independently from other data used to describe it.42/// Notably contains the token metadata.43#[struct_versioning::versioned(version = 2, upper)]44#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)]45pub struct ItemData {46 pub const_data: BoundedVec<u8, CustomDataLimit>,4748 #[version(..2)]49 pub variable_data: BoundedVec<u8, CustomDataLimit>,50}5152#[frame_support::pallet]53pub mod pallet {54 use super::*;55 use frame_support::{56 Blake2_128, Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key,57 traits::StorageVersion,58 };59 use frame_system::pallet_prelude::*;60 use up_data_structs::{CollectionId, TokenId};61 use super::weights::WeightInfo;6263 #[pallet::error]64 pub enum Error<T> {65 /// Not Refungible item data used to mint in Refungible collection.66 NotRefungibleDataUsedToMintFungibleCollectionToken,67 /// Maximum refungibility exceeded.68 WrongRefungiblePieces,69 /// Refungible token can't be repartitioned by user who isn't owns all pieces.70 RepartitionWhileNotOwningAllPieces,71 /// Refungible token can't nest other tokens.72 RefungibleDisallowsNesting,73 /// Setting item properties is not allowed.74 SettingPropertiesNotAllowed,75 }7677 #[pallet::config]78 pub trait Config:79 frame_system::Config + pallet_common::Config + pallet_structure::Config80 {81 type WeightInfo: WeightInfo;82 }8384 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);8586 #[pallet::pallet]87 #[pallet::storage_version(STORAGE_VERSION)]88 #[pallet::generate_store(pub(super) trait Store)]89 pub struct Pallet<T>(_);9091 /// Total amount of minted tokens in a collection.92 #[pallet::storage]93 pub type TokensMinted<T: Config> =94 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;9596 /// Amount of tokens burnt in a collection.97 #[pallet::storage]98 pub type TokensBurnt<T: Config> =99 StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;100101 /// Token data, used to partially describe a token.102 #[pallet::storage]103 pub type TokenData<T: Config> = StorageNMap<104 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),105 Value = ItemData,106 QueryKind = ValueQuery,107 >;108109 /// Amount of pieces a refungible token is split into.110 #[pallet::storage]111 pub type TotalSupply<T: Config> = StorageNMap<112 Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),113 Value = u128,114 QueryKind = ValueQuery,115 >;116117 /// Used to enumerate tokens owned by account.118 #[pallet::storage]119 pub type Owned<T: Config> = StorageNMap<120 Key = (121 Key<Twox64Concat, CollectionId>,122 Key<Blake2_128Concat, T::CrossAccountId>,123 Key<Twox64Concat, TokenId>,124 ),125 Value = bool,126 QueryKind = ValueQuery,127 >;128129 /// Amount of tokens (not pieces) partially owned by an account within a collection.130 #[pallet::storage]131 pub type AccountBalance<T: Config> = StorageNMap<132 Key = (133 Key<Twox64Concat, CollectionId>,134 // Owner135 Key<Blake2_128Concat, T::CrossAccountId>,136 ),137 Value = u32,138 QueryKind = ValueQuery,139 >;140141 /// Amount of pieces of a token owned by an account.142 #[pallet::storage]143 pub type Balance<T: Config> = StorageNMap<144 Key = (145 Key<Twox64Concat, CollectionId>,146 Key<Twox64Concat, TokenId>,147 // Owner148 Key<Blake2_128Concat, T::CrossAccountId>,149 ),150 Value = u128,151 QueryKind = ValueQuery,152 >;153154 /// todo:doc155 #[pallet::storage]156 pub type Allowance<T: Config> = StorageNMap<157 Key = (158 Key<Twox64Concat, CollectionId>,159 Key<Twox64Concat, TokenId>,160 // Owner161 Key<Blake2_128, T::CrossAccountId>,162 // Spender163 Key<Blake2_128Concat, T::CrossAccountId>,164 ),165 Value = u128,166 QueryKind = ValueQuery,167 >;168169 #[pallet::hooks]170 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {171 fn on_runtime_upgrade() -> Weight {172 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {173 <TokenData<T>>::translate_values::<ItemDataVersion1, _>(|v| {174 Some(<ItemDataVersion2>::from(v))175 })176 }177178 0179 }180 }181}182183pub struct RefungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);184impl<T: Config> RefungibleHandle<T> {185 pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {186 Self(inner)187 }188 pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {189 self.0190 }191}192impl<T: Config> Deref for RefungibleHandle<T> {193 type Target = pallet_common::CollectionHandle<T>;194195 fn deref(&self) -> &Self::Target {196 &self.0197 }198}199200impl<T: Config> Pallet<T> {201 pub fn total_supply(collection: &RefungibleHandle<T>) -> u32 {202 <TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)203 }204 pub fn token_exists(collection: &RefungibleHandle<T>, token: TokenId) -> bool {205 <TotalSupply<T>>::contains_key((collection.id, token))206 }207}208209// unchecked calls skips any permission checks210impl<T: Config> Pallet<T> {211 pub fn init_collection(212 owner: T::CrossAccountId,213 data: CreateCollectionData<T::AccountId>,214 ) -> Result<CollectionId, DispatchError> {215 <PalletCommon<T>>::init_collection(owner, data, false)216 }217 pub fn destroy_collection(218 collection: RefungibleHandle<T>,219 sender: &T::CrossAccountId,220 ) -> DispatchResult {221 let id = collection.id;222223 if Self::collection_has_tokens(id) {224 return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());225 }226227 // =========228229 PalletCommon::destroy_collection(collection.0, sender)?;230231 <TokensMinted<T>>::remove(id);232 <TokensBurnt<T>>::remove(id);233 <TokenData<T>>::remove_prefix((id,), None);234 <TotalSupply<T>>::remove_prefix((id,), None);235 <Balance<T>>::remove_prefix((id,), None);236 <Allowance<T>>::remove_prefix((id,), None);237 <Owned<T>>::remove_prefix((id,), None);238 <AccountBalance<T>>::remove_prefix((id,), None);239 Ok(())240 }241242 fn collection_has_tokens(collection_id: CollectionId) -> bool {243 <TokenData<T>>::iter_prefix((collection_id,))244 .next()245 .is_some()246 }247248 pub fn burn_token(collection: &RefungibleHandle<T>, token_id: TokenId) -> DispatchResult {249 let burnt = <TokensBurnt<T>>::get(collection.id)250 .checked_add(1)251 .ok_or(ArithmeticError::Overflow)?;252253 <TokensBurnt<T>>::insert(collection.id, burnt);254 <TokenData<T>>::remove((collection.id, token_id));255 <TotalSupply<T>>::remove((collection.id, token_id));256 <Balance<T>>::remove_prefix((collection.id, token_id), None);257 <Allowance<T>>::remove_prefix((collection.id, token_id), None);258 // TODO: ERC721 transfer event259 Ok(())260 }261262 pub fn burn(263 collection: &RefungibleHandle<T>,264 owner: &T::CrossAccountId,265 token: TokenId,266 amount: u128,267 ) -> DispatchResult {268 let total_supply = <TotalSupply<T>>::get((collection.id, token))269 .checked_sub(amount)270 .ok_or(<CommonError<T>>::TokenValueTooLow)?;271272 // This was probally last owner of this token?273 if total_supply == 0 {274 // Ensure user actually owns this amount275 ensure!(276 <Balance<T>>::get((collection.id, token, owner)) == amount,277 <CommonError<T>>::TokenValueTooLow278 );279 let account_balance = <AccountBalance<T>>::get((collection.id, owner))280 .checked_sub(1)281 // Should not occur282 .ok_or(ArithmeticError::Underflow)?;283284 // =========285286 <Owned<T>>::remove((collection.id, owner, token));287 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);288 <AccountBalance<T>>::insert((collection.id, owner), account_balance);289 Self::burn_token(collection, token)?;290 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(291 collection.id,292 token,293 owner.clone(),294 amount,295 ));296 return Ok(());297 }298299 let balance = <Balance<T>>::get((collection.id, token, owner))300 .checked_sub(amount)301 .ok_or(<CommonError<T>>::TokenValueTooLow)?;302 let account_balance = if balance == 0 {303 <AccountBalance<T>>::get((collection.id, owner))304 .checked_sub(1)305 // Should not occur306 .ok_or(ArithmeticError::Underflow)?307 } else {308 0309 };310311 // =========312313 if balance == 0 {314 <Owned<T>>::remove((collection.id, owner, token));315 <PalletStructure<T>>::unnest_if_nested(owner, collection.id, token);316 <Balance<T>>::remove((collection.id, token, owner));317 <AccountBalance<T>>::insert((collection.id, owner), account_balance);318 } else {319 <Balance<T>>::insert((collection.id, token, owner), balance);320 }321 <TotalSupply<T>>::insert((collection.id, token), total_supply);322 // TODO: ERC20 transfer event323 <PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(324 collection.id,325 token,326 owner.clone(),327 amount,328 ));329 Ok(())330 }331332 pub fn transfer(333 collection: &RefungibleHandle<T>,334 from: &T::CrossAccountId,335 to: &T::CrossAccountId,336 token: TokenId,337 amount: u128,338 nesting_budget: &dyn Budget,339 ) -> DispatchResult {340 ensure!(341 collection.limits.transfers_enabled(),342 <CommonError<T>>::TransferNotAllowed343 );344345 if collection.permissions.access() == AccessMode::AllowList {346 collection.check_allowlist(from)?;347 collection.check_allowlist(to)?;348 }349 <PalletCommon<T>>::ensure_correct_receiver(to)?;350351 let balance_from = <Balance<T>>::get((collection.id, token, from))352 .checked_sub(amount)353 .ok_or(<CommonError<T>>::TokenValueTooLow)?;354 let mut create_target = false;355 let from_to_differ = from != to;356 let balance_to = if from != to {357 let old_balance = <Balance<T>>::get((collection.id, token, to));358 if old_balance == 0 {359 create_target = true;360 }361 Some(362 old_balance363 .checked_add(amount)364 .ok_or(ArithmeticError::Overflow)?,365 )366 } else {367 None368 };369370 let account_balance_from = if balance_from == 0 {371 Some(372 <AccountBalance<T>>::get((collection.id, from))373 .checked_sub(1)374 // Should not occur375 .ok_or(ArithmeticError::Underflow)?,376 )377 } else {378 None379 };380 // Account data is created in token, AccountBalance should be increased381 // But only if from != to as we shouldn't check overflow in this case382 let account_balance_to = if create_target && from_to_differ {383 let account_balance_to = <AccountBalance<T>>::get((collection.id, to))384 .checked_add(1)385 .ok_or(ArithmeticError::Overflow)?;386 ensure!(387 account_balance_to < collection.limits.account_token_ownership_limit(),388 <CommonError<T>>::AccountTokenLimitExceeded,389 );390391 Some(account_balance_to)392 } else {393 None394 };395396 // =========397398 <PalletStructure<T>>::nest_if_sent_to_token(399 from.clone(),400 to,401 collection.id,402 token,403 nesting_budget,404 )?;405406 if let Some(balance_to) = balance_to {407 // from != to408 if balance_from == 0 {409 <Balance<T>>::remove((collection.id, token, from));410 <PalletStructure<T>>::unnest_if_nested(from, collection.id, token);411 } else {412 <Balance<T>>::insert((collection.id, token, from), balance_from);413 }414 <Balance<T>>::insert((collection.id, token, to), balance_to);415 if let Some(account_balance_from) = account_balance_from {416 <AccountBalance<T>>::insert((collection.id, from), account_balance_from);417 <Owned<T>>::remove((collection.id, from, token));418 }419 if let Some(account_balance_to) = account_balance_to {420 <AccountBalance<T>>::insert((collection.id, to), account_balance_to);421 <Owned<T>>::insert((collection.id, to, token), true);422 }423 }424425 // TODO: ERC20 transfer event426 <PalletCommon<T>>::deposit_event(CommonEvent::Transfer(427 collection.id,428 token,429 from.clone(),430 to.clone(),431 amount,432 ));433 Ok(())434 }435436 pub fn create_multiple_items(437 collection: &RefungibleHandle<T>,438 sender: &T::CrossAccountId,439 data: Vec<CreateRefungibleExData<T::CrossAccountId>>,440 nesting_budget: &dyn Budget,441 ) -> DispatchResult {442 if !collection.is_owner_or_admin(sender) {443 ensure!(444 collection.permissions.mint_mode(),445 <CommonError<T>>::PublicMintingNotAllowed446 );447 collection.check_allowlist(sender)?;448449 for item in data.iter() {450 for user in item.users.keys() {451 collection.check_allowlist(user)?;452 }453 }454 }455456 for item in data.iter() {457 for (owner, _) in item.users.iter() {458 <PalletCommon<T>>::ensure_correct_receiver(owner)?;459 }460 }461462 // Total pieces per tokens463 let totals = data464 .iter()465 .map(|data| {466 Ok(data467 .users468 .iter()469 .map(|u| u.1)470 .try_fold(0u128, |acc, v| acc.checked_add(*v))471 .ok_or(ArithmeticError::Overflow)?)472 })473 .collect::<Result<Vec<_>, DispatchError>>()?;474 for total in &totals {475 ensure!(476 *total <= MAX_REFUNGIBLE_PIECES,477 <Error<T>>::WrongRefungiblePieces478 );479 }480481 let first_token_id = <TokensMinted<T>>::get(collection.id);482 let tokens_minted = first_token_id483 .checked_add(data.len() as u32)484 .ok_or(ArithmeticError::Overflow)?;485 ensure!(486 tokens_minted < collection.limits.token_limit(),487 <CommonError<T>>::CollectionTokenLimitExceeded488 );489490 let mut balances = BTreeMap::new();491 for data in &data {492 for owner in data.users.keys() {493 let balance = balances494 .entry(owner)495 .or_insert_with(|| <AccountBalance<T>>::get((collection.id, owner)));496 *balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;497498 ensure!(499 *balance <= collection.limits.account_token_ownership_limit(),500 <CommonError<T>>::AccountTokenLimitExceeded,501 );502 }503 }504505 for (i, token) in data.iter().enumerate() {506 let token_id = TokenId(first_token_id + i as u32 + 1);507 for (to, _) in token.users.iter() {508 <PalletStructure<T>>::check_nesting(509 sender.clone(),510 to,511 collection.id,512 token_id,513 nesting_budget,514 )?;515 }516 }517518 // =========519520 <TokensMinted<T>>::insert(collection.id, tokens_minted);521 for (account, balance) in balances {522 <AccountBalance<T>>::insert((collection.id, account), balance);523 }524 for (i, token) in data.into_iter().enumerate() {525 let token_id = first_token_id + i as u32 + 1;526 <TotalSupply<T>>::insert((collection.id, token_id), totals[i]);527528 <TokenData<T>>::insert(529 (collection.id, token_id),530 ItemData {531 const_data: token.const_data,532 },533 );534535 for (user, amount) in token.users.into_iter() {536 if amount == 0 {537 continue;538 }539 <Balance<T>>::insert((collection.id, token_id, &user), amount);540 <Owned<T>>::insert((collection.id, &user, TokenId(token_id)), true);541 <PalletStructure<T>>::nest_if_sent_to_token_unchecked(542 &user,543 collection.id,544 TokenId(token_id),545 );546547 // TODO: ERC20 transfer event548 <PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(549 collection.id,550 TokenId(token_id),551 user,552 amount,553 ));554 }555 }556 Ok(())557 }558559 pub fn set_allowance_unchecked(560 collection: &RefungibleHandle<T>,561 sender: &T::CrossAccountId,562 spender: &T::CrossAccountId,563 token: TokenId,564 amount: u128,565 ) {566 if amount == 0 {567 <Allowance<T>>::remove((collection.id, token, sender, spender));568 } else {569 <Allowance<T>>::insert((collection.id, token, sender, spender), amount);570 }571 // TODO: ERC20 approval event572 <PalletCommon<T>>::deposit_event(CommonEvent::Approved(573 collection.id,574 token,575 sender.clone(),576 spender.clone(),577 amount,578 ))579 }580581 pub fn set_allowance(582 collection: &RefungibleHandle<T>,583 sender: &T::CrossAccountId,584 spender: &T::CrossAccountId,585 token: TokenId,586 amount: u128,587 ) -> DispatchResult {588 if collection.permissions.access() == AccessMode::AllowList {589 collection.check_allowlist(sender)?;590 collection.check_allowlist(spender)?;591 }592593 <PalletCommon<T>>::ensure_correct_receiver(spender)?;594595 if <Balance<T>>::get((collection.id, token, sender)) < amount {596 ensure!(597 collection.ignores_owned_amount(sender) && Self::token_exists(collection, token),598 <CommonError<T>>::CantApproveMoreThanOwned599 );600 }601602 // =========603604 Self::set_allowance_unchecked(collection, sender, spender, token, amount);605 Ok(())606 }607608 /// Returns allowance, which should be set after transaction609 fn check_allowed(610 collection: &RefungibleHandle<T>,611 spender: &T::CrossAccountId,612 from: &T::CrossAccountId,613 token: TokenId,614 amount: u128,615 nesting_budget: &dyn Budget,616 ) -> Result<Option<u128>, DispatchError> {617 if spender.conv_eq(from) {618 return Ok(None);619 }620 if collection.permissions.access() == AccessMode::AllowList {621 // `from`, `to` checked in [`transfer`]622 collection.check_allowlist(spender)?;623 }624 if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {625 // TODO: should collection owner be allowed to perform this transfer?626 ensure!(627 <PalletStructure<T>>::check_indirectly_owned(628 spender.clone(),629 source.0,630 source.1,631 None,632 nesting_budget633 )?,634 <CommonError<T>>::ApprovedValueTooLow,635 );636 return Ok(None);637 }638 let allowance =639 <Allowance<T>>::get((collection.id, token, from, &spender)).checked_sub(amount);640 if allowance.is_none() {641 ensure!(642 collection.ignores_allowance(spender),643 <CommonError<T>>::ApprovedValueTooLow644 );645 }646 Ok(allowance)647 }648649 pub fn transfer_from(650 collection: &RefungibleHandle<T>,651 spender: &T::CrossAccountId,652 from: &T::CrossAccountId,653 to: &T::CrossAccountId,654 token: TokenId,655 amount: u128,656 nesting_budget: &dyn Budget,657 ) -> DispatchResult {658 let allowance =659 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;660661 // =========662663 Self::transfer(collection, from, to, token, amount, nesting_budget)?;664 if let Some(allowance) = allowance {665 Self::set_allowance_unchecked(collection, from, spender, token, allowance);666 }667 Ok(())668 }669670 pub fn burn_from(671 collection: &RefungibleHandle<T>,672 spender: &T::CrossAccountId,673 from: &T::CrossAccountId,674 token: TokenId,675 amount: u128,676 nesting_budget: &dyn Budget,677 ) -> DispatchResult {678 let allowance =679 Self::check_allowed(collection, spender, from, token, amount, nesting_budget)?;680681 // =========682683 Self::burn(collection, from, token, amount)?;684 if let Some(allowance) = allowance {685 Self::set_allowance_unchecked(collection, from, spender, token, allowance);686 }687 Ok(())688 }689690 /// Delegated to `create_multiple_items`691 pub fn create_item(692 collection: &RefungibleHandle<T>,693 sender: &T::CrossAccountId,694 data: CreateRefungibleExData<T::CrossAccountId>,695 nesting_budget: &dyn Budget,696 ) -> DispatchResult {697 Self::create_multiple_items(collection, sender, vec![data], nesting_budget)698 }699700 pub fn repartition(701 collection: &RefungibleHandle<T>,702 owner: &T::CrossAccountId,703 token: TokenId,704 amount: u128,705 ) -> DispatchResult {706 ensure!(707 amount <= MAX_REFUNGIBLE_PIECES,708 <Error<T>>::WrongRefungiblePieces709 );710 ensure!(amount > 0, <CommonError<T>>::TokenValueTooLow);711 // Ensure user owns all pieces712 let total_supply = <TotalSupply<T>>::get((collection.id, token));713 let balance = <Balance<T>>::get((collection.id, token, owner));714 ensure!(715 total_supply == balance,716 <Error<T>>::RepartitionWhileNotOwningAllPieces717 );718719 <Balance<T>>::insert((collection.id, token, owner), amount);720 <TotalSupply<T>>::insert((collection.id, token), amount);721 Ok(())722 }723724 fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {725 <TotalSupply<T>>::try_get((collection_id, token_id)).ok()726 }727}pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -31,7 +31,7 @@
DepthLimit,
/// While iterating over children, reached the breadth limit.
BreadthLimit,
- /// Couldn't find the token owner that is a token. Perhaps, it does not yet exist. todo:doc? rephrase?
+ /// Couldn't find the token owner that is itself a token.
TokenNotFound,
}
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -286,7 +286,7 @@
}
}
-/// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version)
+/// Collection parameters, used in storage (see [`RpcCollection`] for the RPC version).
#[struct_versioning::versioned(version = 2, upper)]
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
pub struct Collection<AccountId> {
@@ -327,7 +327,7 @@
pub meta_update_permission: MetaUpdatePermission,
}
-/// Collection parameters, used in RPC calls (see [`Collection`] for the storage version)
+/// Collection parameters, used in RPC calls (see [`Collection`] for the storage version).
#[derive(Encode, Decode, Clone, PartialEq, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub struct RpcCollection<AccountId> {
@@ -568,7 +568,7 @@
ReFungible(CreateReFungibleData),
}
-/// Explicit NFT creation data with meta parameters
+/// Explicit NFT creation data with meta parameters.
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug)]
pub struct CreateNftExData<CrossAccountId> {
@@ -577,7 +577,7 @@
pub owner: CrossAccountId,
}
-/// Explicit RFT creation data with meta parameters
+/// Explicit RFT creation data with meta parameters.
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
pub struct CreateRefungibleExData<CrossAccountId> {
@@ -587,7 +587,7 @@
pub users: BoundedBTreeMap<CrossAccountId, u128, ConstU32<MAX_ITEMS_PER_BATCH>>,
}
-/// Explicit item creation data with meta parameters, namely the owner
+/// Explicit item creation data with meta parameters, namely the owner.
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, TypeInfo, Derivative)]
#[derivative(Debug(bound = "CrossAccountId: fmt::Debug + Ord"))]
pub enum CreateItemExData<CrossAccountId> {
@@ -635,7 +635,7 @@
}
}
-/// Token's address, dictated by its collection and token IDs
+/// Token's address, dictated by its collection and token IDs.
#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
// todo possibly rename to be used generally as an address pair