Skip to content

Commit 9dad17f

Browse files
jsidorenkoParity Bot
andauthored
Buy&Sell methods for Uniques (paritytech#11398)
* 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 <[email protected]> Co-authored-by: command-bot <>
1 parent 4204405 commit 9dad17f

File tree

6 files changed

+485
-72
lines changed

6 files changed

+485
-72
lines changed

frame/uniques/src/benchmarking.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,5 +408,41 @@ benchmarks_instance_pallet! {
408408
}.into());
409409
}
410410

411+
set_price {
412+
let (collection, caller, _) = create_collection::<T, I>();
413+
let (item, ..) = mint_item::<T, I>(0);
414+
let delegate: T::AccountId = account("delegate", 0, SEED);
415+
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
416+
let price = ItemPrice::<T, I>::from(100u32);
417+
}: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup))
418+
verify {
419+
assert_last_event::<T, I>(Event::ItemPriceSet {
420+
collection,
421+
item,
422+
price,
423+
whitelisted_buyer: Some(delegate),
424+
}.into());
425+
}
426+
427+
buy_item {
428+
let (collection, seller, _) = create_collection::<T, I>();
429+
let (item, ..) = mint_item::<T, I>(0);
430+
let buyer: T::AccountId = account("buyer", 0, SEED);
431+
let buyer_lookup = T::Lookup::unlookup(buyer.clone());
432+
let price = ItemPrice::<T, I>::from(0u32);
433+
let origin = SystemOrigin::Signed(seller.clone()).into();
434+
Uniques::<T, I>::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?;
435+
T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::<T, I>::max_value());
436+
}: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone())
437+
verify {
438+
assert_last_event::<T, I>(Event::ItemBought {
439+
collection,
440+
item,
441+
price,
442+
seller,
443+
buyer,
444+
}.into());
445+
}
446+
411447
impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test);
412448
}

frame/uniques/src/functions.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
//! Various pieces of common functionality.
1919
2020
use super::*;
21-
use frame_support::{ensure, traits::Get};
21+
use frame_support::{
22+
ensure,
23+
traits::{ExistenceRequirement, Get},
24+
};
2225
use sp_runtime::{DispatchError, DispatchResult};
2326

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

5054
Self::deposit_event(Event::Transferred {
5155
collection,
@@ -112,6 +116,8 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
112116
}
113117
#[allow(deprecated)]
114118
ItemMetadataOf::<T, I>::remove_prefix(&collection, None);
119+
#[allow(deprecated)]
120+
ItemPriceOf::<T, I>::remove_prefix(&collection, None);
115121
CollectionMetadataOf::<T, I>::remove(&collection);
116122
#[allow(deprecated)]
117123
Attribute::<T, I>::remove_prefix((&collection,), None);
@@ -196,8 +202,79 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
196202

197203
Item::<T, I>::remove(&collection, &item);
198204
Account::<T, I>::remove((&owner, &collection, &item));
205+
ItemPriceOf::<T, I>::remove(&collection, &item);
199206

200207
Self::deposit_event(Event::Burned { collection, item, owner });
201208
Ok(())
202209
}
210+
211+
pub fn do_set_price(
212+
collection: T::CollectionId,
213+
item: T::ItemId,
214+
sender: T::AccountId,
215+
price: Option<ItemPrice<T, I>>,
216+
whitelisted_buyer: Option<T::AccountId>,
217+
) -> DispatchResult {
218+
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
219+
ensure!(details.owner == sender, Error::<T, I>::NoPermission);
220+
221+
if let Some(ref price) = price {
222+
ItemPriceOf::<T, I>::insert(
223+
&collection,
224+
&item,
225+
(price.clone(), whitelisted_buyer.clone()),
226+
);
227+
Self::deposit_event(Event::ItemPriceSet {
228+
collection,
229+
item,
230+
price: price.clone(),
231+
whitelisted_buyer,
232+
});
233+
} else {
234+
ItemPriceOf::<T, I>::remove(&collection, &item);
235+
Self::deposit_event(Event::ItemPriceRemoved { collection, item });
236+
}
237+
238+
Ok(())
239+
}
240+
241+
pub fn do_buy_item(
242+
collection: T::CollectionId,
243+
item: T::ItemId,
244+
buyer: T::AccountId,
245+
bid_price: ItemPrice<T, I>,
246+
) -> DispatchResult {
247+
let details = Item::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::UnknownItem)?;
248+
ensure!(details.owner != buyer, Error::<T, I>::NoPermission);
249+
250+
let price_info =
251+
ItemPriceOf::<T, I>::get(&collection, &item).ok_or(Error::<T, I>::NotForSale)?;
252+
253+
ensure!(bid_price >= price_info.0, Error::<T, I>::BidTooLow);
254+
255+
if let Some(only_buyer) = price_info.1 {
256+
ensure!(only_buyer == buyer, Error::<T, I>::NoPermission);
257+
}
258+
259+
T::Currency::transfer(
260+
&buyer,
261+
&details.owner,
262+
price_info.0,
263+
ExistenceRequirement::KeepAlive,
264+
)?;
265+
266+
let old_owner = details.owner.clone();
267+
268+
Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?;
269+
270+
Self::deposit_event(Event::ItemBought {
271+
collection,
272+
item,
273+
price: price_info.0,
274+
seller: old_owner,
275+
buyer,
276+
});
277+
278+
Ok(())
279+
}
203280
}

frame/uniques/src/lib.rs

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
//! * [`System`](../frame_system/index.html)
2525
//! * [`Support`](../frame_support/index.html)
2626
27+
#![recursion_limit = "256"]
2728
// Ensure we're `no_std` when compiling for Wasm.
2829
#![cfg_attr(not(feature = "std"), no_std)]
2930

@@ -42,8 +43,11 @@ pub mod migration;
4243
pub mod weights;
4344

4445
use codec::{Decode, Encode};
45-
use frame_support::traits::{
46-
tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency,
46+
use frame_support::{
47+
traits::{
48+
tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency,
49+
},
50+
transactional,
4751
};
4852
use frame_system::Config as SystemConfig;
4953
use sp_runtime::{
@@ -245,6 +249,18 @@ pub mod pallet {
245249
OptionQuery,
246250
>;
247251

252+
#[pallet::storage]
253+
/// Price of an asset instance.
254+
pub(super) type ItemPriceOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
255+
_,
256+
Blake2_128Concat,
257+
T::CollectionId,
258+
Blake2_128Concat,
259+
T::ItemId,
260+
(ItemPrice<T, I>, Option<T::AccountId>),
261+
OptionQuery,
262+
>;
263+
248264
#[pallet::storage]
249265
/// Keeps track of the number of items a collection might have.
250266
pub(super) type CollectionMaxSupply<T: Config<I>, I: 'static = ()> =
@@ -341,6 +357,23 @@ pub mod pallet {
341357
OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option<T::CollectionId> },
342358
/// Max supply has been set for a collection.
343359
CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 },
360+
/// The price was set for the instance.
361+
ItemPriceSet {
362+
collection: T::CollectionId,
363+
item: T::ItemId,
364+
price: ItemPrice<T, I>,
365+
whitelisted_buyer: Option<T::AccountId>,
366+
},
367+
/// The price for the instance was removed.
368+
ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId },
369+
/// An item was bought.
370+
ItemBought {
371+
collection: T::CollectionId,
372+
item: T::ItemId,
373+
price: ItemPrice<T, I>,
374+
seller: T::AccountId,
375+
buyer: T::AccountId,
376+
},
344377
}
345378

346379
#[pallet::error]
@@ -375,6 +408,12 @@ pub mod pallet {
375408
MaxSupplyAlreadySet,
376409
/// The provided max supply is less to the amount of items a collection already has.
377410
MaxSupplyTooSmall,
411+
/// The given item ID is unknown.
412+
UnknownItem,
413+
/// Item is not for sale.
414+
NotForSale,
415+
/// The provided bid is too low.
416+
BidTooLow,
378417
}
379418

380419
impl<T: Config<I>, I: 'static> Pallet<T, I> {
@@ -1408,5 +1447,50 @@ pub mod pallet {
14081447
Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply });
14091448
Ok(())
14101449
}
1450+
1451+
/// Set (or reset) the price for an item.
1452+
///
1453+
/// Origin must be Signed and must be the owner of the asset `item`.
1454+
///
1455+
/// - `collection`: The collection of the item.
1456+
/// - `item`: The item to set the price for.
1457+
/// - `price`: The price for the item. Pass `None`, to reset the price.
1458+
/// - `buyer`: Restricts the buy operation to a specific account.
1459+
///
1460+
/// Emits `ItemPriceSet` on success if the price is not `None`.
1461+
/// Emits `ItemPriceRemoved` on success if the price is `None`.
1462+
#[pallet::weight(T::WeightInfo::set_price())]
1463+
pub fn set_price(
1464+
origin: OriginFor<T>,
1465+
collection: T::CollectionId,
1466+
item: T::ItemId,
1467+
price: Option<ItemPrice<T, I>>,
1468+
whitelisted_buyer: Option<<T::Lookup as StaticLookup>::Source>,
1469+
) -> DispatchResult {
1470+
let origin = ensure_signed(origin)?;
1471+
let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?;
1472+
Self::do_set_price(collection, item, origin, price, whitelisted_buyer)
1473+
}
1474+
1475+
/// Allows to buy an item if it's up for sale.
1476+
///
1477+
/// Origin must be Signed and must not be the owner of the `item`.
1478+
///
1479+
/// - `collection`: The collection of the item.
1480+
/// - `item`: The item the sender wants to buy.
1481+
/// - `bid_price`: The price the sender is willing to pay.
1482+
///
1483+
/// Emits `ItemBought` on success.
1484+
#[pallet::weight(T::WeightInfo::buy_item())]
1485+
#[transactional]
1486+
pub fn buy_item(
1487+
origin: OriginFor<T>,
1488+
collection: T::CollectionId,
1489+
item: T::ItemId,
1490+
bid_price: ItemPrice<T, I>,
1491+
) -> DispatchResult {
1492+
let origin = ensure_signed(origin)?;
1493+
Self::do_buy_item(collection, item, origin, bid_price)
1494+
}
14111495
}
14121496
}

0 commit comments

Comments
 (0)