Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Buy&Sell methods for Uniques (#11398)
Browse files Browse the repository at this point in the history
* Allow to set item's price

* Clean the state when we transfer/burn an item or destroy a collection

* Allow to buy an item

* Remove redundant checks

* Improve events

* Cover with tests

* Add comments

* Apply suggestions

* Fmt

* Improvements for price validation

* Improve validation

* Update to use the new terminology

* Remove multi-assets support

* Chore

* Weights + benchmarking

* Shield against human error

* Test when we pass the higher item's price

* fmt fix

* Chore

* cargo run --quiet --profile=production  --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Remove is_frozen check when setting the price

* Try to fix benchmarking

* Fix benchmarking

* cargo run --quiet --profile=production  --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Add transactional

* Add 'allow deprecated' flag for transactional

* Remove #[allow(deprecated)]

* ".git/.scripts/bench-bot.sh" pallet dev pallet_uniques

Co-authored-by: Parity Bot <admin@parity.io>
Co-authored-by: command-bot <>
  • Loading branch information
jsidorenko and Parity Bot committed Jul 13, 2022
1 parent 4204405 commit 9dad17f
Show file tree
Hide file tree
Showing 6 changed files with 485 additions and 72 deletions.
36 changes: 36 additions & 0 deletions frame/uniques/src/benchmarking.rs
Expand Up @@ -408,5 +408,41 @@ benchmarks_instance_pallet! {
}.into());
}

set_price {
let (collection, caller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let delegate: T::AccountId = account("delegate", 0, SEED);
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
let price = ItemPrice::<T, I>::from(100u32);
}: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup))
verify {
assert_last_event::<T, I>(Event::ItemPriceSet {
collection,
item,
price,
whitelisted_buyer: Some(delegate),
}.into());
}

buy_item {
let (collection, seller, _) = create_collection::<T, I>();
let (item, ..) = mint_item::<T, I>(0);
let buyer: T::AccountId = account("buyer", 0, SEED);
let buyer_lookup = T::Lookup::unlookup(buyer.clone());
let price = ItemPrice::<T, I>::from(0u32);
let origin = SystemOrigin::Signed(seller.clone()).into();
Uniques::<T, I>::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?;
T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::<T, I>::max_value());
}: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone())
verify {
assert_last_event::<T, I>(Event::ItemBought {
collection,
item,
price,
seller,
buyer,
}.into());
}

impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test);
}
79 changes: 78 additions & 1 deletion frame/uniques/src/functions.rs
Expand Up @@ -18,7 +18,10 @@
//! Various pieces of common functionality.

use super::*;
use frame_support::{ensure, traits::Get};
use frame_support::{
ensure,
traits::{ExistenceRequirement, Get},
};
use sp_runtime::{DispatchError, DispatchResult};

impl<T: Config<I>, I: 'static> Pallet<T, I> {
Expand Down Expand Up @@ -46,6 +49,7 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
let origin = details.owner;
details.owner = dest;
Item::<T, I>::insert(&collection, &item, &details);
ItemPriceOf::<T, I>::remove(&collection, &item);

Self::deposit_event(Event::Transferred {
collection,
Expand Down Expand Up @@ -112,6 +116,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
}
#[allow(deprecated)]
ItemMetadataOf::<T, I>::remove_prefix(&collection, None);
#[allow(deprecated)]
ItemPriceOf::<T, I>::remove_prefix(&collection, None);
CollectionMetadataOf::<T, I>::remove(&collection);
#[allow(deprecated)]
Attribute::<T, I>::remove_prefix((&collection,), None);
Expand Down Expand Up @@ -196,8 +202,79 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {

Item::<T, I>::remove(&collection, &item);
Account::<T, I>::remove((&owner, &collection, &item));
ItemPriceOf::<T, I>::remove(&collection, &item);

Self::deposit_event(Event::Burned { collection, item, owner });
Ok(())
}

pub fn do_set_price(
collection: T::CollectionId,
item: T::ItemId,
sender: T::AccountId,
price: Option<ItemPrice<T, I>>,
whitelisted_buyer: Option<T::AccountId>,
) -> DispatchResult {
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(details.owner == sender, Error::<T, I>::NoPermission);

if let Some(ref price) = price {
ItemPriceOf::<T, I>::insert(
&collection,
&item,
(price.clone(), whitelisted_buyer.clone()),
);
Self::deposit_event(Event::ItemPriceSet {
collection,
item,
price: price.clone(),
whitelisted_buyer,
});
} else {
ItemPriceOf::<T, I>::remove(&collection, &item);
Self::deposit_event(Event::ItemPriceRemoved { collection, item });
}

Ok(())
}

pub fn do_buy_item(
collection: T::CollectionId,
item: T::ItemId,
buyer: T::AccountId,
bid_price: ItemPrice<T, I>,
) -> DispatchResult {
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
ensure!(details.owner != buyer, Error::<T, I>::NoPermission);

let price_info =
ItemPriceOf::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::NotForSale)?;

ensure!(bid_price >= price_info.0, Error::<T, I>::BidTooLow);

if let Some(only_buyer) = price_info.1 {
ensure!(only_buyer == buyer, Error::<T, I>::NoPermission);
}

T::Currency::transfer(
&buyer,
&details.owner,
price_info.0,
ExistenceRequirement::KeepAlive,
)?;

let old_owner = details.owner.clone();

Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?;

Self::deposit_event(Event::ItemBought {
collection,
item,
price: price_info.0,
seller: old_owner,
buyer,
});

Ok(())
}
}
88 changes: 86 additions & 2 deletions frame/uniques/src/lib.rs
Expand Up @@ -24,6 +24,7 @@
//! * [`System`](../frame_system/index.html)
//! * [`Support`](../frame_support/index.html)

#![recursion_limit = "256"]
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]

Expand All @@ -42,8 +43,11 @@ pub mod migration;
pub mod weights;

use codec::{Decode, Encode};
use frame_support::traits::{
tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency,
use frame_support::{
traits::{
tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency,
},
transactional,
};
use frame_system::Config as SystemConfig;
use sp_runtime::{
Expand Down Expand Up @@ -245,6 +249,18 @@ pub mod pallet {
OptionQuery,
>;

#[pallet::storage]
/// Price of an asset instance.
pub(super) type ItemPriceOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
_,
Blake2_128Concat,
T::CollectionId,
Blake2_128Concat,
T::ItemId,
(ItemPrice<T, I>, Option<T::AccountId>),
OptionQuery,
>;

#[pallet::storage]
/// Keeps track of the number of items a collection might have.
pub(super) type CollectionMaxSupply<T: Config<I>, I: 'static = ()> =
Expand Down Expand Up @@ -341,6 +357,23 @@ pub mod pallet {
OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option<T::CollectionId> },
/// Max supply has been set for a collection.
CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 },
/// The price was set for the instance.
ItemPriceSet {
collection: T::CollectionId,
item: T::ItemId,
price: ItemPrice<T, I>,
whitelisted_buyer: Option<T::AccountId>,
},
/// The price for the instance was removed.
ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId },
/// An item was bought.
ItemBought {
collection: T::CollectionId,
item: T::ItemId,
price: ItemPrice<T, I>,
seller: T::AccountId,
buyer: T::AccountId,
},
}

#[pallet::error]
Expand Down Expand Up @@ -375,6 +408,12 @@ pub mod pallet {
MaxSupplyAlreadySet,
/// The provided max supply is less to the amount of items a collection already has.
MaxSupplyTooSmall,
/// The given item ID is unknown.
UnknownItem,
/// Item is not for sale.
NotForSale,
/// The provided bid is too low.
BidTooLow,
}

impl<T: Config<I>, I: 'static> Pallet<T, I> {
Expand Down Expand Up @@ -1408,5 +1447,50 @@ pub mod pallet {
Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
Ok(())
}

/// Set (or reset) the price for an item.
///
/// Origin must be Signed and must be the owner of the asset `item`.
///
/// - `collection`: The collection of the item.
/// - `item`: The item to set the price for.
/// - `price`: The price for the item. Pass `None`, to reset the price.
/// - `buyer`: Restricts the buy operation to a specific account.
///
/// Emits `ItemPriceSet` on success if the price is not `None`.
/// Emits `ItemPriceRemoved` on success if the price is `None`.
#[pallet::weight(T::WeightInfo::set_price())]
pub fn set_price(
origin: OriginFor<T>,
collection: T::CollectionId,
item: T::ItemId,
price: Option<ItemPrice<T, I>>,
whitelisted_buyer: Option<<T::Lookup as StaticLookup>::Source>,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?;
Self::do_set_price(collection, item, origin, price, whitelisted_buyer)
}

/// Allows to buy an item if it's up for sale.
///
/// Origin must be Signed and must not be the owner of the `item`.
///
/// - `collection`: The collection of the item.
/// - `item`: The item the sender wants to buy.
/// - `bid_price`: The price the sender is willing to pay.
///
/// Emits `ItemBought` on success.
#[pallet::weight(T::WeightInfo::buy_item())]
#[transactional]
pub fn buy_item(
origin: OriginFor<T>,
collection: T::CollectionId,
item: T::ItemId,
bid_price: ItemPrice<T, I>,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
Self::do_buy_item(collection, item, origin, bid_price)
}
}
}

0 comments on commit 9dad17f

Please sign in to comment.