Skip to content

Commit 4b817ca

Browse files
committed
Merge remote-tracking branch 'origin/feature/offers' into feature/role-base-access-control
2 parents 626f4f0 + 2291d78 commit 4b817ca

File tree

6 files changed

+798
-45
lines changed

6 files changed

+798
-45
lines changed

pallets/gated-marketplace/README.md

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ This module allows to:
4949
- Enroll or reject applicants to your marketplace.
5050
- Add or remove users as supported authorities to your marketplace, like administrators and/or asset appraisers
5151
- WIP: Assign a rating to assets as an Appraiser.
52+
- Create sell or buy orders. Users can bid on the item if they don't like the sale price.
5253

5354
### Terminology
5455
- **Authority**: Refers to any user that has special faculties within the marketplace, like enroll new users or grade assets.
@@ -60,6 +61,8 @@ This module allows to:
6061
- **Enroll**: When enrolled, the user's application becomes approved, gaining the ability to sell and buy assets.
6162
- **Reject**: If the user gets rejected, its application becomes rejected and won't have access to the marketplace.
6263
- **Custodian**: When submitting an application, the user can optionally specify a third account that will have access to the confidential documents. A custodian doesn't need to be an authority nor being part of the marketplace.
64+
- **Sell order**: The owner of the item creates sales offer fot the item.
65+
- **Buy order**: Users from the marketplace can bid for the item.
6366

6467
## Interface
6568

@@ -72,14 +75,28 @@ This module allows to:
7275
- `remove_authority` is only callable by the marketplace owner or administrator. Removes the authority enforcer from the marketplace. The marketplace owner cannot be removed and the administrator cannot remove itself.
7376
- `update_label_marketplace` is only callable by the marketplace owner or administrator. Changes the marketplace label. If the new label already exists, the old name won't be changed.
7477
- `remove_marketplace` is only callable by the marketplace owner or administrator. This action allows the user to remove a marketplace as well as all the information related to this marketplace.
78+
- `enlist_sell_offer` is only callable by the owner of the item. It allows the user to sell an item in the selected marketplace.
79+
- `take_sell_offer` any user interested to buy the item can call this extrinsic. User must have enough balance to buy it. When the transaction is completed, the item ownership is transferred to the buyer.
80+
- `duplicate_offer` allows the owner of the item to duplicate an sell order in any marketplace.
81+
- `remove_offer` is only callable by the creator of the offer, it deletes any offer type from all the storages.
82+
- `enlist_buy_offer` is callable by any market participant, the owner of the item can't create buy orders for their own items. User must have the enough balance to call it.
83+
- `take_buy_offer` is only callable by the owner of the item. If the owner accepts the offer, the buyer must have enough balance to finalize the transaction.
7584

7685

7786
### Getters
78-
- `marketplaces`
79-
- `applications`
80-
- `applications_by_account` (double storage map)
81-
- `applicants_by_marketplace` (double storage map)
82-
- `custodians` (double storage map)
87+
|Name| Type |
88+
|--|--|
89+
|`marketplaces`| storage map|
90+
|`marketplaces_by_authority`|double storage map|
91+
|`authorities_by_marketplace`|double storage map|
92+
|`applications`| storage map|
93+
|`applications_by_account`|double storage map|
94+
|`applicants_by_marketplace`|double storage map|
95+
|`custodians`|double storage map|
96+
|`offers_info` |storage map|
97+
|`offers_by_item`|double storage map|
98+
|`offers_by_account`|storage map|
99+
|`offers_by_marketplace`|storage map|
83100

84101

85102
## Usage
@@ -542,19 +559,34 @@ const removeAuthority = await api.tx.gatedMarketplace.removeAuthority(dave.addre
542559
543560
```rust
544561
/// Marketplaces stored. [owner, admin, market_id]
545-
MarketplaceStored(T::AccountId, T::AccountId, [u8;32]),
562+
1. MarketplaceStored(T::AccountId, T::AccountId, [u8;32])
563+
546564
/// Application stored on the specified marketplace. [application_id, market_id]
547-
ApplicationStored([u8;32], [u8;32]),
565+
2. ApplicationStored([u8;32], [u8;32])
566+
548567
/// An applicant was accepted or rejected on the marketplace. [AccountOrApplication, market_id, status]
549-
ApplicationProcessed(AccountOrApplication<T>,[u8;32], ApplicationStatus),
568+
3. ApplicationProcessed(AccountOrApplication<T>,[u8;32], ApplicationStatus)
569+
550570
/// Add a new authority to the selected marketplace
551-
AuthorityAdded(T::AccountId, MarketplaceAuthority),
571+
4. AuthorityAdded(T::AccountId, MarketplaceAuthority)
572+
552573
/// Remove the selected authority from the selected marketplace
553-
AuthorityRemoved(T::AccountId, MarketplaceAuthority),
574+
5. AuthorityRemoved(T::AccountId, MarketplaceAuthority)
575+
554576
/// The label of the selected marketplace has been updated. [market_id]
555-
MarketplaceLabelUpdated([u8;32]),
577+
6. MarketplaceLabelUpdated([u8;32])
578+
556579
/// The selected marketplace has been removed. [market_id]
557-
MarketplaceRemoved([u8;32])
580+
7. MarketplaceRemoved([u8;32])
581+
582+
/// Offer stored. [collection_id, item_id]
583+
8. OfferStored(T::CollectionId, T::ItemId)
584+
585+
/// Offer was accepted [offer_id, account]
586+
9. OfferWasAccepted([u8;32], T::AccountId)
587+
588+
/// Offer was duplicated. [new_offer_id, new_marketplace_id]
589+
10. OfferDuplicated([u8;32], [u8;32])
558590
```
559591
560592
## Errors
@@ -606,4 +638,32 @@ ApplicationIdNotFound,
606638
ApplicationStatusStillPending,
607639
/// The application has already been approved, application status is approved
608640
ApplicationHasAlreadyBeenApproved,
641+
/// Collection not found
642+
CollectionNotFound,
643+
/// User who calls the function is not the owner of the collection
644+
NotOwner,
645+
/// Offer already exists
646+
OfferAlreadyExists,
647+
/// Offer not found
648+
OfferNotFound,
649+
/// Offer is not available at the moment
650+
OfferIsNotAvailable,
651+
/// Owner cannnot buy its own offer
652+
CannotTakeOffer,
653+
/// User cannot remove the offer from the marketplace
654+
CannotRemoveOffer,
655+
/// Error related to the timestamp
656+
TimestampError,
657+
/// User does not have enough balance to buy the offer
658+
NotEnoughBalance,
659+
/// User cannot delete the offer because is closed
660+
CannotDeleteOffer,
661+
/// There was a problem storing the offer
662+
OfferStorageError,
663+
/// Price must be greater than zero
664+
PriceMustBeGreaterThanZero,
665+
/// User cannot create buy offers for their own items
666+
CannotCreateOffer,
667+
/// This items is not available for sale
668+
ItemNotForSale,
609669
```

pallets/gated-marketplace/src/functions.rs

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,7 @@ impl<T: Config> Pallet<T> {
180180
}
181181

182182
pub fn do_enlist_sell_offer(authority: T::AccountId, marketplace_id: [u8;32], collection_id: T::CollectionId, item_id: T::ItemId, price: BalanceOf<T>,) -> DispatchResult {
183-
//TODO: ensure the user is a Marketparticipant
184-
183+
//This function is only called by the owner of the marketplace
185184
//ensure the marketplace exists
186185
ensure!(<Marketplaces<T>>::contains_key(marketplace_id), Error::<T>::MarketplaceNotFound);
187186

@@ -456,19 +455,21 @@ impl<T: Config> Pallet<T> {
456455
pub fn do_remove_offer(authority: T::AccountId, offer_id: [u8;32], marketplace_id: [u8;32], collection_id: T::CollectionId, item_id: T::ItemId, ) -> DispatchResult {
457456
//ensure marketplace_id exits
458457
ensure!(<Marketplaces<T>>::contains_key(marketplace_id), Error::<T>::MarketplaceNotFound);
459-
460-
//ensure the offer_id exists & get the offer data
461-
let copy_offer_data = <OffersInfo<T>>::get(offer_id).ok_or(Error::<T>::OfferNotFound)?;
458+
459+
//ensure the offer_id exists
460+
ensure!(<OffersInfo<T>>::contains_key(offer_id), Error::<T>::OfferNotFound);
461+
462+
//ensure the offer status is Open
463+
ensure!(Self::is_offer_status(offer_id, OfferStatus::Open), Error::<T>::CannotDeleteOffer);
464+
462465

463466
// ensure the owner is the same as the authority
464-
ensure!(copy_offer_data.creator == authority.clone(), Error::<T>::CannotRemoveOffer);
467+
let offer_creator = Self::get_offer_creator(offer_id).map_err(|_| Error::<T>::OfferNotFound)?;
468+
ensure!(offer_creator == authority.clone(), Error::<T>::CannotRemoveOffer);
465469

466470
//ensure the offer_id exists in OffersByItem
467471
Self::does_exist_offer_id_for_this_item(collection_id, item_id, offer_id)?;
468472

469-
//ensure the offer status is Open
470-
ensure!(Self::is_offer_status(offer_id, OfferStatus::Open), Error::<T>::CannotDeleteOffer);
471-
472473

473474
//remove the offer from OfferInfo
474475
<OffersInfo<T>>::remove(offer_id);
@@ -732,6 +733,7 @@ impl<T: Config> Pallet<T> {
732733

733734
fn get_timestamp_in_milliseconds() -> Option<(u64, u64)> {
734735
let timestamp: <T as pallet_timestamp::Config>::Moment = <pallet_timestamp::Pallet<T>>::get();
736+
735737
let timestamp2 = Self::convert_moment_to_u64_in_milliseconds(timestamp).unwrap_or(0);
736738
let timestamp3 = timestamp2 + (7 * 24 * 60 * 60 * 1000);
737739

@@ -786,26 +788,6 @@ impl<T: Config> Pallet<T> {
786788
fn update_offers_status(buyer: T::AccountId, collection_id: T::CollectionId, item_id: T::ItemId, marketplace_id: [u8;32]) -> DispatchResult{
787789
let offer_ids = <OffersByItem<T>>::try_get(collection_id, item_id).map_err(|_| Error::<T>::OfferNotFound)?;
788790

789-
//This logic is no longer needed, when an offer_id is accepted,
790-
// all the other offers for this item are automatically closed.
791-
// It doesn't matter the offertype.
792-
793-
// let mut sell_offer_ids: BoundedVec<[u8;32], T::MaxOffersPerMarket> = BoundedVec::default();
794-
795-
// for offer_id in offer_ids {
796-
// let offer_info = <OffersInfo<T>>::get(offer_id).ok_or(Error::<T>::OfferNotFound)?;
797-
// //ensure the offer_type is SellOrder, because this vector also contains offers of BuyOrder OfferType.
798-
// if offer_info.offer_type == OfferType::SellOrder {
799-
// sell_offer_ids.try_push(offer_id).map_err(|_| Error::<T>::OfferStorageError)?;
800-
// }
801-
// // //version 2
802-
// // if let Some(offer) = <OffersInfo<T>>::get(offer_id) {
803-
// // if offer.offer_type == OfferType::SellOrder {
804-
// // sell_offer_ids.try_push(offer_id).map_err(|_| Error::<T>::OfferStorageError)?;
805-
// // }
806-
// // }
807-
// }
808-
809791
for offer_id in offer_ids {
810792
<OffersInfo<T>>::try_mutate::<_,_,DispatchError,_>(offer_id, |offer|{
811793
let offer = offer.as_mut().ok_or(Error::<T>::OfferNotFound)?;

pallets/gated-marketplace/src/lib.rs

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,22 @@ pub mod pallet {
497497
Self::do_remove_marketplace(who, marketplace_id)
498498
}
499499

500+
/// Enlist a sell order.
501+
///
502+
/// This extrinsic creates a sell order in the selected marketplace.
503+
///
504+
/// ### Parameters:
505+
/// - `origin`: The user who performs the action.
506+
/// - `marketplace_id`: The id of the marketplace where we want to create the sell order.
507+
/// - `collection_id`: The id of the collection.
508+
/// - `item_id`: The id of the item inside the collection.
509+
/// - `price`: The price of the item.
510+
///
511+
/// ### Considerations:
512+
/// - You can only create a sell order in the marketplace if you are the owner of the item.
513+
/// - You can create only one sell order for each item per marketplace.
514+
/// - If the selected marketplace doesn't exist, it will throw an error.
515+
/// - If the selected collection doesn't exist, it will throw an error.
500516
#[transactional]
501517
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
502518
pub fn enlist_sell_offer(origin: OriginFor<T>, marketplace_id: [u8;32], collection_id: T::CollectionId, item_id: T::ItemId, price: BalanceOf<T>,) -> DispatchResult {
@@ -505,6 +521,20 @@ pub mod pallet {
505521
Self::do_enlist_sell_offer(who, marketplace_id, collection_id, item_id, price)
506522
}
507523

524+
/// Accepts a sell order.
525+
///
526+
/// This extrinsic accepts a sell order in the selected marketplace.
527+
///
528+
/// ### Parameters:
529+
/// - `origin`: The user who performs the action.
530+
/// - `marketplace_id`: The id of the marketplace where we want to accept the sell order.
531+
/// - `collection_id`: The id of the collection.
532+
/// - `item_id`: The id of the item inside the collection.
533+
///
534+
/// ### Considerations:
535+
/// - You don't need to be the owner of the item to accept the sell order.
536+
/// - Once the sell order is accepted, the ownership of the item is transferred to the buyer.
537+
/// - If you don't have the enough balance to accept the sell order, it will throw an error.
508538
#[transactional]
509539
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
510540
pub fn take_sell_offer(origin: OriginFor<T>, offer_id: [u8;32], marketplace_id: [u8;32], collection_id: T::CollectionId, item_id: T::ItemId,) -> DispatchResult {
@@ -513,6 +543,20 @@ pub mod pallet {
513543
Self::do_take_sell_offer(who, offer_id, marketplace_id, collection_id, item_id)
514544
}
515545

546+
/// Allows a user to duplicate a sell order.
547+
///
548+
/// This extrinsic allows a user to duplicate a sell order in any marketplace.
549+
///
550+
/// ### Parameters:
551+
/// - `origin`: The user who performs the action.
552+
/// - `marketplace_id`: The id of the marketplace where we want to duplicate the sell order.
553+
/// - `collection_id`: The id of the collection.
554+
/// - `item_id`: The id of the item inside the collection.
555+
///
556+
/// ### Considerations:
557+
/// - You can only duplicate a sell order if you are the owner of the item.
558+
/// - The expiration date of the sell order is the same as the original sell order.
559+
/// - You can update the price of the sell order.
516560
#[transactional]
517561
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
518562
pub fn duplicate_offer(origin: OriginFor<T>, offer_id: [u8;32], marketplace_id: [u8;32], collection_id: T::CollectionId, item_id: T::ItemId, modified_price: BalanceOf<T>) -> DispatchResult {
@@ -521,6 +565,23 @@ pub mod pallet {
521565
Self::do_duplicate_offer(who, offer_id, marketplace_id, collection_id, item_id, modified_price)
522566
}
523567

568+
569+
/// Delete an offer.
570+
///
571+
/// This extrinsic deletes an offer in the selected marketplace.
572+
///
573+
/// ### Parameters:
574+
/// - `origin`: The user who performs the action.
575+
/// - `marketplace_id`: The id of the marketplace where we want to delete the offer.
576+
/// - `collection_id`: The id of the collection.
577+
/// - `item_id`: The id of the item inside the collection.
578+
///
579+
/// ### Considerations:
580+
/// - You can delete sell orders or buy orders.
581+
/// - You can only delete an offer if you are the creator of the offer.
582+
/// - Only open offers can be deleted.
583+
/// - If you need to delete multiple offers for the same item, you need to
584+
/// delete them one by one.
524585
#[transactional]
525586
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
526587
pub fn remove_offer(origin: OriginFor<T>, offer_id: [u8;32], marketplace_id: [u8;32], collection_id: T::CollectionId, item_id: T::ItemId,) -> DispatchResult {
@@ -531,7 +592,21 @@ pub mod pallet {
531592
Self::do_remove_offer(who, offer_id, marketplace_id, collection_id, item_id)
532593
}
533594

534-
595+
/// Enlist a buy order.
596+
///
597+
/// This extrinsic creates a buy order in the selected marketplace.
598+
///
599+
/// ### Parameters:
600+
/// - `origin`: The user who performs the action.
601+
/// - `marketplace_id`: The id of the marketplace where we want to create the buy order.
602+
/// - `collection_id`: The id of the collection.
603+
/// - `item_id`: The id of the item inside the collection.
604+
/// - `price`: The price of the item.
605+
///
606+
/// ### Considerations:
607+
/// - Any user can create a buy order in the marketplace.
608+
/// - An item can receive multiple buy orders at a time.
609+
/// - You need to have the enough balance to create the buy order.
535610
#[transactional]
536611
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
537612
pub fn enlist_buy_offer(origin: OriginFor<T>, marketplace_id: [u8;32], collection_id: T::CollectionId, item_id: T::ItemId, price: BalanceOf<T>,) -> DispatchResult {
@@ -540,6 +615,23 @@ pub mod pallet {
540615
Self::do_enlist_buy_offer(who, marketplace_id, collection_id, item_id, price)
541616
}
542617

618+
619+
/// Accepts a buy order.
620+
///
621+
/// This extrinsic accepts a buy order in the selected marketplace.
622+
///
623+
/// ### Parameters:
624+
/// - `origin`: The user who performs the action.
625+
/// - `marketplace_id`: The id of the marketplace where we accept the buy order.
626+
/// - `collection_id`: The id of the collection.
627+
/// - `item_id`: The id of the item inside the collection.
628+
///
629+
/// ### Considerations:
630+
/// - You need to be the owner of the item to accept a buy order.
631+
/// - Owner of the item can accept only one buy order at a time.
632+
/// - When an offer is accepted, all the other offers for this item are closed.
633+
/// - The buyer needs to have the enough balance to accept the buy order.
634+
/// - Once the buy order is accepted, the ownership of the item is transferred to the buyer.
543635
#[transactional]
544636
#[pallet::weight(10_000 + T::DbWeight::get().writes(1))]
545637
pub fn take_buy_offer(origin: OriginFor<T>, offer_id: [u8;32], marketplace_id: [u8;32], collection_id: T::CollectionId, item_id: T::ItemId,) -> DispatchResult {

pallets/gated-marketplace/src/mock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ frame_support::construct_runtime!(
2323
Uniques: pallet_uniques::{Pallet, Call, Storage, Event<T>},
2424
Fruniques: pallet_fruniques::{Pallet, Call, Storage, Event<T>},
2525
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
26+
Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
2627
RBAC: pallet_rbac::{Pallet, Call, Storage, Event<T>},
27-
2828
}
2929
);
3030

0 commit comments

Comments
 (0)