From 3c083fe1053980e63551457aaf1d72636d3137ee Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 06 May 2021 13:35:01 +0000 Subject: [PATCH] feat: approval chain extensions --- --- a/pallets/nft/src/lib.rs +++ b/pallets/nft/src/lib.rs @@ -1259,43 +1259,10 @@ pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult { let sender = ensure_signed(origin)?; - let target_collection = Self::get_collection(collection_id)?; - - Self::token_exists(&target_collection, item_id)?; + let collection = Self::get_collection(collection_id)?; - // Transfer permissions check - let bypasses_limits = target_collection.limits.owner_can_transfer && - Self::is_owner_or_admin_permissions( - &target_collection, - sender.clone(), - ); - - let allowance_limit = if bypasses_limits { - None - } else if let Some(amount) = Self::owned_amount( - sender.clone(), - &target_collection, - item_id, - ) { - Some(amount) - } else { - fail!(Error::::NoPermission); - }; - - if target_collection.access == AccessMode::WhiteList { - Self::check_white_list(&target_collection, &sender)?; - Self::check_white_list(&target_collection, &spender)?; - } - - let allowance: u128 = amount - .checked_add(>::get(collection_id, (item_id, &sender, &spender))) - .ok_or(Error::::NumOverflow)?; - if let Some(limit) = allowance_limit { - ensure!(limit >= allowance, Error::::TokenValueTooLow); - } - >::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance); + Self::approve_internal(sender, spender, &collection, item_id, amount)?; - Self::deposit_event(RawEvent::Approved(target_collection.id, item_id, sender, spender, allowance)); Ok(()) } @@ -1323,46 +1290,10 @@ pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult { let sender = ensure_signed(origin)?; - let target_collection = Self::get_collection(collection_id)?; - - // Check approval - let approval: u128 = >::get(collection_id, (item_id, &from, &sender)); + let collection = Self::get_collection(collection_id)?; - // Limits check - Self::is_correct_transfer(&target_collection, &recipient)?; - - // Transfer permissions check - ensure!( - approval >= value || - ( - target_collection.limits.owner_can_transfer && - Self::is_owner_or_admin_permissions(&target_collection, sender.clone()) - ), - Error::::NoPermission - ); - - if target_collection.access == AccessMode::WhiteList { - Self::check_white_list(&target_collection, &sender)?; - Self::check_white_list(&target_collection, &recipient)?; - } - - // Reduce approval by transferred amount or remove if remaining approval drops to 0 - if approval.saturating_sub(value) > 0 { - >::insert(collection_id, (item_id, &from, &sender), approval - value); - } - else { - >::remove(collection_id, (item_id, &from, &sender)); - } + Self::transfer_from_internal(sender, from, recipient, &collection, item_id, value)?; - match target_collection.mode - { - CollectionMode::NFT => Self::transfer_nft(&target_collection, item_id, from.clone(), recipient.clone())?, - CollectionMode::Fungible(_) => Self::transfer_fungible(&target_collection, value, &from, &recipient)?, - CollectionMode::ReFungible => Self::transfer_refungible(&target_collection, item_id, value, from.clone(), recipient.clone())?, - _ => () - }; - - Self::deposit_event(RawEvent::Transfer(target_collection.id, item_id, from, recipient, value)); Ok(()) } @@ -1792,6 +1723,104 @@ Ok(()) } + pub fn approve_internal( + sender: T::AccountId, + spender: T::AccountId, + collection: &CollectionHandle, + item_id: TokenId, + amount: u128, + ) -> DispatchResult { + Self::token_exists(&collection, item_id)?; + + // Transfer permissions check + let bypasses_limits = collection.limits.owner_can_transfer && + Self::is_owner_or_admin_permissions( + &collection, + sender.clone(), + ); + + let allowance_limit = if bypasses_limits { + None + } else if let Some(amount) = Self::owned_amount( + sender.clone(), + &collection, + item_id, + ) { + Some(amount) + } else { + fail!(Error::::NoPermission); + }; + + if collection.access == AccessMode::WhiteList { + Self::check_white_list(&collection, &sender)?; + Self::check_white_list(&collection, &spender)?; + } + + let allowance: u128 = amount + .checked_add(>::get(collection.id, (item_id, &sender, &spender))) + .ok_or(Error::::NumOverflow)?; + if let Some(limit) = allowance_limit { + ensure!(limit >= allowance, Error::::TokenValueTooLow); + } + >::insert(collection.id, (item_id, &sender, &spender), allowance); + + Self::deposit_event(RawEvent::Approved(collection.id, item_id, sender, spender, allowance)); + Ok(()) + } + + pub fn transfer_from_internal( + sender: T::AccountId, + from: T::AccountId, + recipient: T::AccountId, + collection: &CollectionHandle, + item_id: TokenId, + amount: u128, + ) -> DispatchResult { + // Check approval + let approval: u128 = >::get(collection.id, (item_id, &from, &sender)); + + // Limits check + Self::is_correct_transfer(&collection, &recipient)?; + + // Transfer permissions check + ensure!( + approval >= amount || + ( + collection.limits.owner_can_transfer && + Self::is_owner_or_admin_permissions(&collection, sender.clone()) + ), + Error::::NoPermission + ); + + if collection.access == AccessMode::WhiteList { + Self::check_white_list(&collection, &sender)?; + Self::check_white_list(&collection, &recipient)?; + } + + // Reduce approval by transferred amount or remove if remaining approval drops to 0 + let allowance = approval.saturating_sub(amount); + if allowance > 0 { + >::insert(collection.id, (item_id, &from, &sender), allowance); + } else { + >::remove(collection.id, (item_id, &from, &sender)); + } + + match collection.mode { + CollectionMode::NFT => { + Self::transfer_nft(&collection, item_id, from.clone(), recipient.clone())? + } + CollectionMode::Fungible(_) => { + Self::transfer_fungible(&collection, amount, &from, &recipient)? + } + CollectionMode::ReFungible => { + Self::transfer_refungible(&collection, item_id, amount, from.clone(), recipient.clone())? + } + _ => () + }; + + Ok(()) + } + pub fn create_multiple_items_internal( sender: T::AccountId, collection: &CollectionHandle, --- a/runtime/src/chain_extension.rs +++ b/runtime/src/chain_extension.rs @@ -43,6 +43,23 @@ pub data: Vec, } +#[derive(Debug, PartialEq, Encode, Decode)] +pub struct NFTExtApprove { + pub spender: ::AccountId, + pub collection_id: u32, + pub item_id: u32, + pub amount: u128, +} + +#[derive(Debug, PartialEq, Encode, Decode)] +pub struct NFTExtTransferFrom { + pub owner: ::AccountId, + pub recipient: ::AccountId, + pub collection_id: u32, + pub item_id: u32, + pub amount: u128, +} + /// The chain Extension of NFT pallet pub struct NFTExtension; @@ -103,6 +120,39 @@ _ => Err(DispatchError::Other("CreateMultipleItems error")) } }, + 3 => { + // Approve + let mut env = env.buf_in_buf_out(); + let input: NFTExtApprove = env.read_as()?; + + let collection = pallet_nft::Module::::get_collection(input.collection_id)?; + + pallet_nft::Module::::approve_internal( + env.ext().address().clone(), + input.spender, + &collection, + input.item_id, + input.amount, + )?; + Ok(RetVal::Converging(func_id)) + }, + 4 => { + // Transfer from + let mut env = env.buf_in_buf_out(); + let input: NFTExtTransferFrom = env.read_as()?; + + let collection = pallet_nft::Module::::get_collection(input.collection_id)?; + + pallet_nft::Module::::transfer_from_internal( + env.ext().address().clone(), + input.owner, + input.recipient, + &collection, + input.item_id, + input.amount + )?; + Ok(RetVal::Converging(func_id)) + }, _ => { panic!("Passed unknown func_id to test chain extension: {}", func_id); } -- gitstuff