|
1 | 1 | use super::*; |
2 | 2 |
|
| 3 | +impl<T: Config> Collection<StringLimitOf<T>, T::AccountId> for Pallet<T> { |
| 4 | + fn issuer(collection_id: CollectionId) -> Option<T::AccountId> { |
| 5 | + None |
| 6 | + } |
| 7 | + fn collection_create( |
| 8 | + issuer: T::AccountId, |
| 9 | + metadata: StringLimitOf<T>, |
| 10 | + max: u32, |
| 11 | + symbol: StringLimitOf<T>, |
| 12 | + ) -> Result<CollectionId, DispatchError> { |
| 13 | + let collection = CollectionInfo { issuer: issuer.clone(), metadata, max, symbol }; |
| 14 | + let collection_id = |
| 15 | + <CollectionIndex<T>>::try_mutate(|n| -> Result<CollectionId, DispatchError> { |
| 16 | + let id = *n; |
| 17 | + ensure!(id != CollectionId::max_value(), Error::<T>::NoAvailableCollectionId); |
| 18 | + *n += 1; |
| 19 | + Ok(id) |
| 20 | + })?; |
| 21 | + Collections::<T>::insert(collection_id, collection); |
| 22 | + Ok(collection_id) |
| 23 | + } |
| 24 | + |
| 25 | + fn collection_burn(issuer: T::AccountId, collection_id: CollectionId) -> DispatchResult { |
| 26 | + ensure!( |
| 27 | + NFTs::<T>::iter_prefix_values(collection_id).count() == 0, |
| 28 | + Error::<T>::CollectionNotEmpty |
| 29 | + ); |
| 30 | + Collections::<T>::remove(collection_id); |
| 31 | + Ok(()) |
| 32 | + } |
| 33 | + |
| 34 | + fn collection_change_issuer( |
| 35 | + collection_id: CollectionId, |
| 36 | + new_issuer: T::AccountId, |
| 37 | + ) -> Result<(T::AccountId, CollectionId), DispatchError> { |
| 38 | + ensure!(Collections::<T>::contains_key(collection_id), Error::<T>::NoAvailableCollectionId); |
| 39 | + |
| 40 | + Collections::<T>::try_mutate_exists(collection_id, |collection| -> DispatchResult { |
| 41 | + if let Some(col) = collection { |
| 42 | + col.issuer = new_issuer.clone(); |
| 43 | + } |
| 44 | + Ok(()) |
| 45 | + })?; |
| 46 | + |
| 47 | + Ok((new_issuer, collection_id)) |
| 48 | + } |
| 49 | + |
| 50 | + fn collection_lock(collection_id: CollectionId) -> Result<CollectionId, DispatchError> { |
| 51 | + Collections::<T>::try_mutate_exists(collection_id, |collection| -> DispatchResult { |
| 52 | + let collection = collection.as_mut().ok_or(Error::<T>::CollectionUnknown)?; |
| 53 | + let currently_minted = NFTs::<T>::iter_prefix_values(collection_id).count(); |
| 54 | + collection.max = currently_minted.try_into().unwrap(); |
| 55 | + Ok(()) |
| 56 | + })?; |
| 57 | + Ok(collection_id) |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +impl<T: Config> Nft<T::AccountId, StringLimitOf<T>> for Pallet<T> { |
| 62 | + type MaxRecursions = T::MaxRecursions; |
| 63 | + |
| 64 | + fn nft_mint( |
| 65 | + sender: T::AccountId, |
| 66 | + owner: T::AccountId, |
| 67 | + collection_id: CollectionId, |
| 68 | + recipient: Option<T::AccountId>, |
| 69 | + royalty: Option<Permill>, |
| 70 | + metadata: StringLimitOf<T>, |
| 71 | + ) -> sp_std::result::Result<(CollectionId, NftId), DispatchError> { |
| 72 | + let nft_id = Self::get_next_nft_id(collection_id)?; |
| 73 | + |
| 74 | + let collection = Self::collections(collection_id).ok_or(Error::<T>::CollectionUnknown)?; |
| 75 | + |
| 76 | + let nfts_minted = NFTs::<T>::iter_prefix_values(collection_id).count(); |
| 77 | + let max: u32 = collection.max.try_into().unwrap(); |
| 78 | + |
| 79 | + ensure!( |
| 80 | + nfts_minted < max.try_into().unwrap() || max == 0, |
| 81 | + Error::<T>::CollectionFullOrLocked |
| 82 | + ); |
| 83 | + |
| 84 | + let recipient = recipient.unwrap_or(owner.clone()); |
| 85 | + let royalty = royalty.unwrap_or(Permill::default()); |
| 86 | + |
| 87 | + let rootowner = owner.clone(); |
| 88 | + let owner_as_maybe_account = AccountIdOrCollectionNftTuple::AccountId(owner.clone()); |
| 89 | + |
| 90 | + let nft = |
| 91 | + NftInfo { owner: owner_as_maybe_account, rootowner, recipient, royalty, metadata }; |
| 92 | + |
| 93 | + NFTs::<T>::insert(collection_id, nft_id, nft); |
| 94 | + NftsByOwner::<T>::append(owner, (collection_id, nft_id)); |
| 95 | + |
| 96 | + Ok((collection_id, nft_id)) |
| 97 | + } |
| 98 | + |
| 99 | + fn nft_burn( |
| 100 | + collection_id: CollectionId, |
| 101 | + nft_id: NftId, |
| 102 | + max_recursions: u32, |
| 103 | + ) -> sp_std::result::Result<(CollectionId, NftId), DispatchError> { |
| 104 | + ensure!(max_recursions > 0, Error::<T>::TooManyRecursions); |
| 105 | + NFTs::<T>::remove(collection_id, nft_id); |
| 106 | + if let Some(kids) = Children::<T>::take(collection_id, nft_id) { |
| 107 | + for (child_collection_id, child_nft_id) in kids { |
| 108 | + Self::nft_burn( |
| 109 | + child_collection_id, |
| 110 | + child_nft_id, |
| 111 | + max_recursions - 1, |
| 112 | + )?; |
| 113 | + } |
| 114 | + } |
| 115 | + Ok((collection_id, nft_id)) |
| 116 | + } |
| 117 | + |
| 118 | + fn nft_send( |
| 119 | + sender: T::AccountId, |
| 120 | + collection_id: CollectionId, |
| 121 | + nft_id: NftId, |
| 122 | + new_owner: AccountIdOrCollectionNftTuple<T::AccountId>, |
| 123 | + max_recursions: u32, |
| 124 | + ) -> sp_std::result::Result<(CollectionId, NftId), DispatchError> { |
| 125 | + let mut sending_nft = |
| 126 | + NFTs::<T>::get(collection_id, nft_id).ok_or(Error::<T>::NoAvailableNftId)?; |
| 127 | + ensure!(&sending_nft.rootowner == &sender, Error::<T>::NoPermission); |
| 128 | + |
| 129 | + match new_owner.clone() { |
| 130 | + AccountIdOrCollectionNftTuple::AccountId(account_id) => { |
| 131 | + // Remove previous parental relationship |
| 132 | + if let AccountIdOrCollectionNftTuple::CollectionAndNftTuple(cid, nid) = |
| 133 | + sending_nft.owner |
| 134 | + { |
| 135 | + if let Some(mut kids) = Children::<T>::take(cid, nid) { |
| 136 | + kids.retain(|&kid| kid != (collection_id, nft_id)); |
| 137 | + Children::<T>::insert(cid, nid, kids); |
| 138 | + } |
| 139 | + } |
| 140 | + sending_nft.rootowner = account_id.clone(); |
| 141 | + }, |
| 142 | + AccountIdOrCollectionNftTuple::CollectionAndNftTuple(cid, nid) => { |
| 143 | + let recipient_nft = NFTs::<T>::get(cid, nid).ok_or(Error::<T>::NoAvailableNftId)?; |
| 144 | + // Check if sending NFT is already a child of recipient NFT |
| 145 | + ensure!( |
| 146 | + !Pallet::<T>::is_x_descendent_of_y(cid, nid, collection_id, nft_id), |
| 147 | + Error::<T>::CannotSendToDescendent |
| 148 | + ); |
| 149 | + |
| 150 | + // Remove parent if exists: first we only care if the owner is a non-AccountId) |
| 151 | + if let AccountIdOrCollectionNftTuple::CollectionAndNftTuple(cid, nid) = |
| 152 | + sending_nft.owner |
| 153 | + { |
| 154 | + // second we only care if the parent has children (it should) |
| 155 | + if let Some(mut kids) = Children::<T>::take(cid, nid) { |
| 156 | + // third we only "retain" the other children |
| 157 | + kids.retain(|&kid| kid != (collection_id, nft_id)); |
| 158 | + Children::<T>::insert(cid, nid, kids); |
| 159 | + } |
| 160 | + } |
| 161 | + if sending_nft.rootowner != recipient_nft.rootowner { |
| 162 | + // sending_nft.rootowner = recipient_nft.rootowner |
| 163 | + sending_nft.rootowner = recipient_nft.rootowner.clone(); |
| 164 | + |
| 165 | + Pallet::<T>::recursive_update_rootowner( |
| 166 | + collection_id, |
| 167 | + nft_id, |
| 168 | + recipient_nft.rootowner, |
| 169 | + max_recursions, |
| 170 | + )?; |
| 171 | + } |
| 172 | + match Children::<T>::take(cid, nid) { |
| 173 | + None => Children::<T>::insert(cid, nid, vec![(collection_id, nft_id)]), |
| 174 | + Some(mut kids) => { |
| 175 | + kids.push((collection_id, nft_id)); |
| 176 | + Children::<T>::insert(cid, nid, kids); |
| 177 | + }, |
| 178 | + } |
| 179 | + }, |
| 180 | + }; |
| 181 | + sending_nft.owner = new_owner.clone(); |
| 182 | + |
| 183 | + NFTs::<T>::insert(collection_id, nft_id, sending_nft); |
| 184 | + |
| 185 | + Ok((collection_id, nft_id)) |
| 186 | + } |
| 187 | +} |
| 188 | + |
3 | 189 | impl<T: Config> Pallet<T> { |
4 | 190 | pub fn is_x_descendent_of_y( |
5 | 191 | child_collection_id: CollectionId, |
@@ -61,10 +247,40 @@ impl<T: Config> Pallet<T> { |
61 | 247 | ensure!(max_recursions > 0, Error::<T>::TooManyRecursions); |
62 | 248 | NFTs::<T>::remove(collection_id, nft_id); |
63 | 249 | if let Some(kids) = Children::<T>::take(collection_id, nft_id) { |
64 | | - for child in kids { |
65 | | - Pallet::<T>::recursive_burn(child.0, child.1, max_recursions - 1)?; |
| 250 | + for (child_collection_id, child_nft_id) in kids { |
| 251 | + Pallet::<T>::recursive_burn(child_collection_id, child_nft_id, max_recursions - 1)?; |
66 | 252 | } |
67 | 253 | } |
68 | 254 | Ok(()) |
69 | 255 | } |
| 256 | + |
| 257 | + pub fn to_bounded_string(name: Vec<u8>) -> Result<BoundedVec<u8, T::StringLimit>, Error<T>> { |
| 258 | + name.try_into().map_err(|_| Error::<T>::TooLong) |
| 259 | + } |
| 260 | + |
| 261 | + pub fn to_optional_bounded_string( |
| 262 | + name: Option<Vec<u8>>, |
| 263 | + ) -> Result<Option<BoundedVec<u8, T::StringLimit>>, Error<T>> { |
| 264 | + Ok(match name { |
| 265 | + Some(n) => Some(Self::to_bounded_string(n)?), |
| 266 | + None => None, |
| 267 | + }) |
| 268 | + } |
| 269 | + |
| 270 | + pub fn get_next_nft_id(collection_id: CollectionId) -> Result<NftId, Error<T>> { |
| 271 | + NextNftId::<T>::try_mutate(collection_id, |id| { |
| 272 | + let current_id = *id; |
| 273 | + *id = id.checked_add(1).ok_or(Error::<T>::NoAvailableNftId)?; |
| 274 | + Ok(current_id) |
| 275 | + }) |
| 276 | + } |
| 277 | + |
| 278 | + pub fn get_next_resource_id() -> Result<ResourceId, Error<T>> { |
| 279 | + NextResourceId::<T>::try_mutate(|id| { |
| 280 | + let current_id = *id; |
| 281 | + *id = id.checked_add(1).ok_or(Error::<T>::NoAvailableCollectionId)?; |
| 282 | + Ok(current_id) |
| 283 | + }) |
| 284 | + } |
| 285 | + |
70 | 286 | } |
0 commit comments