Skip to content

Commit

Permalink
Merge pull request #178 from rmrk-team/bug/173-enable-mint-to-nft
Browse files Browse the repository at this point in the history
allow minting directly to nft
  • Loading branch information
bmacer authored Jul 7, 2022
2 parents 2098179 + ecd5997 commit 4b04e9f
Show file tree
Hide file tree
Showing 7 changed files with 385 additions and 35 deletions.
83 changes: 76 additions & 7 deletions pallets/rmrk-core/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ where
collection_id: CollectionId,
nft_id: NftId,
resource: ResourceTypes<BoundedVec<u8, T::StringLimit>, BoundedVec<PartId, T::PartsLimit>>,
adding_on_mint: bool,
) -> Result<ResourceId, DispatchError> {
let collection = Self::collections(collection_id).ok_or(Error::<T>::CollectionUnknown)?;
let resource_id = Self::get_next_resource_id(collection_id, nft_id)?;
Expand All @@ -102,10 +103,19 @@ where
},
}

// Resource should be in a pending state if the rootowner of the resource is not the sender
// of the transaction, unless the resource is being added on mint. This prevents the
// situation where an NFT being minted *directly to* a non-owned NFT *with resources* will
// have those resources be *pending*. While the minted NFT itself will be pending, it is
// inefficent and unnecessary to have the resources also be pending. Otherwise, in such a
// case, the owner would have to accept not only the NFT but also all originally-added
// resources.
let pending = (root_owner != sender) && !adding_on_mint;

let res: ResourceInfo<BoundedVec<u8, T::StringLimit>, BoundedVec<PartId, T::PartsLimit>> =
ResourceInfo::<BoundedVec<u8, T::StringLimit>, BoundedVec<PartId, T::PartsLimit>> {
id: resource_id,
pending: root_owner != sender,
pending,
pending_removal: false,
resource,
};
Expand Down Expand Up @@ -267,7 +277,7 @@ where
type MaxRecursions = T::MaxRecursions;

fn nft_mint(
_sender: T::AccountId,
sender: T::AccountId,
owner: T::AccountId,
collection_id: CollectionId,
royalty_recipient: Option<T::AccountId>,
Expand All @@ -283,6 +293,9 @@ where
ensure!(nft_id < max, Error::<T>::CollectionFullOrLocked);
}

// NFT should be pending if minting to another account
let pending = owner != sender;

let mut royalty: Option<RoyaltyInfo<T::AccountId>> = None;

if let Some(amount) = royalty_amount {
Expand All @@ -291,20 +304,76 @@ where
royalty = Some(RoyaltyInfo::<T::AccountId> { recipient, amount });
},
None => {
royalty =
Some(RoyaltyInfo::<T::AccountId> { recipient: owner.clone(), amount });
// If a royalty amount is passed but no recipient, defaults to the sender
royalty = Some(RoyaltyInfo::<T::AccountId> { recipient: sender, amount });
},
}
};

let owner_as_maybe_account = AccountIdOrCollectionNftTuple::AccountId(owner);
let nft = NftInfo {
owner: AccountIdOrCollectionNftTuple::AccountId(owner),
royalty,
metadata,
equipped: false,
pending,
transferable,
};

Nfts::<T>::insert(collection_id, nft_id, nft);

// increment nfts counter
let nfts_count = collection.nfts_count.checked_add(1).ok_or(ArithmeticError::Overflow)?;
Collections::<T>::try_mutate(collection_id, |collection| -> DispatchResult {
let collection = collection.as_mut().ok_or(Error::<T>::CollectionUnknown)?;
collection.nfts_count = nfts_count;
Ok(())
})?;

Ok((collection_id, nft_id))
}

fn nft_mint_directly_to_nft(
sender: T::AccountId,
owner: (CollectionId, NftId),
collection_id: CollectionId,
royalty_recipient: Option<T::AccountId>,
royalty_amount: Option<Permill>,
metadata: StringLimitOf<T>,
transferable: bool,
) -> sp_std::result::Result<(CollectionId, NftId), DispatchError> {
let nft_id = Self::get_next_nft_id(collection_id)?;
let collection = Self::collections(collection_id).ok_or(Error::<T>::CollectionUnknown)?;

// Prevent minting when next NFT id is greater than the collection max.
if let Some(max) = collection.max {
ensure!(nft_id < max, Error::<T>::CollectionFullOrLocked);
}

// Calculate the rootowner of the intended owner of the minted NFT
let (rootowner, _) = Self::lookup_root_owner(owner.0, owner.1)?;

// NFT should be pending if minting either to an NFT owned by another account
let pending = rootowner != sender;

let mut royalty: Option<RoyaltyInfo<T::AccountId>> = None;

if let Some(amount) = royalty_amount {
match royalty_recipient {
Some(recipient) => {
royalty = Some(RoyaltyInfo::<T::AccountId> { recipient, amount });
},
None => {
royalty = Some(RoyaltyInfo::<T::AccountId> { recipient: rootowner, amount });
},
}
};

let nft = NftInfo {
owner: owner_as_maybe_account,
owner: AccountIdOrCollectionNftTuple::CollectionAndNftTuple(owner.0, owner.1),
royalty,
metadata,
equipped: false,
pending: false,
pending,
transferable,
};

Expand Down
104 changes: 95 additions & 9 deletions pallets/rmrk-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ pub mod pallet {
},
// NftMinted(T::AccountId, CollectionId, NftId),
NftMinted {
owner: T::AccountId,
owner: AccountIdOrCollectionNftTuple<T::AccountId>,
collection_id: CollectionId,
nft_id: NftId,
},
Expand Down Expand Up @@ -373,14 +373,15 @@ pub mod pallet {
return Err(Error::<T>::CollectionUnknown.into())
}

// Default owner to minter
// Extract intended owner or default to sender
let nft_owner = match owner {
Some(owner) => owner,
None => sender.clone(),
};

// Mint NFT for RMRK storage
let (collection_id, nft_id) = Self::nft_mint(
sender,
sender.clone(),
nft_owner.clone(),
collection_id,
royalty_recipient,
Expand All @@ -396,13 +397,87 @@ pub mod pallet {
|_details| Ok(()),
)?;

// Add all at-mint resources
if let Some(resources) = resources {
for res in resources {
Self::resource_add(sender.clone(), collection_id, nft_id, res, true)?;
}
}

Self::deposit_event(Event::NftMinted {
owner: AccountIdOrCollectionNftTuple::AccountId(nft_owner),
collection_id,
nft_id,
});

Ok(())
}

/// Mints an NFT in the specified collection directly to another NFT
/// Sets metadata and the royalty attribute
///
/// Parameters:
/// - `collection_id`: The class of the asset to be minted.
/// - `nft_id`: The nft value of the asset to be minted.
/// - `recipient`: Receiver of the royalty
/// - `royalty`: Permillage reward from each trade for the Recipient
/// - `metadata`: Arbitrary data about an nft, e.g. IPFS hash
#[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1))]
#[transactional]
pub fn mint_nft_directly_to_nft(
origin: OriginFor<T>,
owner: (CollectionId, NftId),
collection_id: CollectionId,
royalty_recipient: Option<T::AccountId>,
royalty: Option<Permill>,
metadata: BoundedVec<u8, T::StringLimit>,
transferable: bool,
resources: Option<BoundedResourceTypeOf<T>>,
) -> DispatchResult {
let sender = ensure_signed(origin.clone())?;

// Collection must exist and sender must be issuer of collection
if let Some(collection_issuer) =
pallet_uniques::Pallet::<T>::collection_owner(collection_id)
{
ensure!(collection_issuer == sender, Error::<T>::NoPermission);
} else {
return Err(Error::<T>::CollectionUnknown.into())
}

// Mint NFT for RMRK storage
let (collection_id, nft_id) = Self::nft_mint_directly_to_nft(
sender.clone(),
owner,
collection_id,
royalty_recipient,
royalty,
metadata,
transferable,
)?;

// For Uniques, we need to decode the "virtual account" ID to be the owner
let uniques_owner = Self::nft_to_account_id(owner.0, owner.1);

pallet_uniques::Pallet::<T>::do_mint(
collection_id,
nft_id,
uniques_owner,
|_details| Ok(()),
)?;

// Add all at-mint resources
if let Some(resources) = resources {
for res in resources {
Self::resource_add(nft_owner.clone(), collection_id, nft_id, res)?;
Self::resource_add(sender.clone(), collection_id, nft_id, res, true)?;
}
}

Self::deposit_event(Event::NftMinted { owner: nft_owner, collection_id, nft_id });
Self::deposit_event(Event::NftMinted {
owner: AccountIdOrCollectionNftTuple::CollectionAndNftTuple(owner.0, owner.1),
collection_id,
nft_id,
});

Ok(())
}
Expand Down Expand Up @@ -660,8 +735,13 @@ pub mod pallet {
) -> DispatchResult {
let sender = ensure_signed(origin)?;

let resource_id =
Self::resource_add(sender, collection_id, nft_id, ResourceTypes::Basic(resource))?;
let resource_id = Self::resource_add(
sender,
collection_id,
nft_id,
ResourceTypes::Basic(resource),
false,
)?;

Self::deposit_event(Event::ResourceAdded { nft_id, resource_id });
Ok(())
Expand All @@ -683,6 +763,7 @@ pub mod pallet {
collection_id,
nft_id,
ResourceTypes::Composable(resource),
false,
)?;

Self::deposit_event(Event::ResourceAdded { nft_id, resource_id });
Expand All @@ -700,8 +781,13 @@ pub mod pallet {
) -> DispatchResult {
let sender = ensure_signed(origin)?;

let resource_id =
Self::resource_add(sender, collection_id, nft_id, ResourceTypes::Slot(resource))?;
let resource_id = Self::resource_add(
sender,
collection_id,
nft_id,
ResourceTypes::Slot(resource),
false,
)?;

Self::deposit_event(Event::ResourceAdded { nft_id, resource_id });
Ok(())
Expand Down
Loading

0 comments on commit 4b04e9f

Please sign in to comment.