orml_auction/
lib.rs

1//! # Auction
2//!
3//! ## Overview
4//!
5//! This module provides a basic abstraction to implement on-chain auctioning
6//! feature.
7//!
8//! The auction logic can be customized by implement and supplying
9//! `AuctionHandler` trait.
10
11#![cfg_attr(not(feature = "std"), no_std)]
12// Disable the following two lints since they originate from an external macro (namely decl_storage)
13#![allow(clippy::string_lit_as_bytes)]
14#![allow(clippy::unused_unit)]
15
16use frame_support::pallet_prelude::*;
17use frame_system::{ensure_signed, pallet_prelude::*};
18use orml_traits::{Auction, AuctionHandler, AuctionInfo, Change};
19use parity_scale_codec::MaxEncodedLen;
20use sp_runtime::{
21	traits::{AtLeast32BitUnsigned, Bounded, CheckedAdd, MaybeSerializeDeserialize, Member, One, Zero},
22	DispatchError, DispatchResult,
23};
24
25mod mock;
26mod tests;
27mod weights;
28
29pub use module::*;
30pub use weights::WeightInfo;
31
32#[frame_support::pallet]
33pub mod module {
34	use super::*;
35
36	#[pallet::config]
37	pub trait Config: frame_system::Config {
38		/// The balance type for bidding.
39		type Balance: Parameter
40			+ Member
41			+ AtLeast32BitUnsigned
42			+ Default
43			+ Copy
44			+ MaybeSerializeDeserialize
45			+ MaxEncodedLen;
46
47		/// The auction ID type.
48		type AuctionId: Parameter
49			+ Member
50			+ AtLeast32BitUnsigned
51			+ Default
52			+ Copy
53			+ MaybeSerializeDeserialize
54			+ Bounded
55			+ parity_scale_codec::FullCodec
56			+ parity_scale_codec::MaxEncodedLen;
57
58		/// The `AuctionHandler` that allow custom bidding logic and handles
59		/// auction result.
60		type Handler: AuctionHandler<Self::AccountId, Self::Balance, BlockNumberFor<Self>, Self::AuctionId>;
61
62		/// Weight information for extrinsics in this module.
63		type WeightInfo: WeightInfo;
64	}
65
66	#[pallet::error]
67	pub enum Error<T> {
68		AuctionNotExist,
69		AuctionNotStarted,
70		BidNotAccepted,
71		InvalidBidPrice,
72		NoAvailableAuctionId,
73	}
74
75	#[pallet::event]
76	#[pallet::generate_deposit(fn deposit_event)]
77	pub enum Event<T: Config> {
78		/// A bid is placed
79		Bid {
80			auction_id: T::AuctionId,
81			bidder: T::AccountId,
82			amount: T::Balance,
83		},
84	}
85
86	/// Stores on-going and future auctions. Closed auction are removed.
87	#[pallet::storage]
88	#[pallet::getter(fn auctions)]
89	pub type Auctions<T: Config> = StorageMap<
90		_,
91		Twox64Concat,
92		T::AuctionId,
93		AuctionInfo<T::AccountId, T::Balance, BlockNumberFor<T>>,
94		OptionQuery,
95	>;
96
97	/// Track the next auction ID.
98	#[pallet::storage]
99	#[pallet::getter(fn auctions_index)]
100	pub type AuctionsIndex<T: Config> = StorageValue<_, T::AuctionId, ValueQuery>;
101
102	/// Index auctions by end time.
103	#[pallet::storage]
104	#[pallet::getter(fn auction_end_time)]
105	pub type AuctionEndTime<T: Config> =
106		StorageDoubleMap<_, Twox64Concat, BlockNumberFor<T>, Blake2_128Concat, T::AuctionId, (), OptionQuery>;
107
108	#[pallet::pallet]
109	pub struct Pallet<T>(_);
110
111	#[pallet::hooks]
112	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
113		fn on_initialize(now: BlockNumberFor<T>) -> Weight {
114			T::WeightInfo::on_finalize(AuctionEndTime::<T>::iter_prefix(now).count() as u32)
115		}
116
117		fn on_finalize(now: BlockNumberFor<T>) {
118			for (auction_id, _) in AuctionEndTime::<T>::drain_prefix(now) {
119				if let Some(auction) = Auctions::<T>::take(auction_id) {
120					T::Handler::on_auction_ended(auction_id, auction.bid);
121				}
122			}
123		}
124	}
125
126	#[pallet::call]
127	impl<T: Config> Pallet<T> {
128		/// Bid an auction.
129		///
130		/// The dispatch origin for this call must be `Signed` by the
131		/// transactor.
132		#[pallet::call_index(0)]
133		#[pallet::weight(T::WeightInfo::bid_collateral_auction())]
134		pub fn bid(origin: OriginFor<T>, id: T::AuctionId, #[pallet::compact] value: T::Balance) -> DispatchResult {
135			let from = ensure_signed(origin)?;
136
137			Auctions::<T>::try_mutate_exists(id, |auction| -> DispatchResult {
138				let auction = auction.as_mut().ok_or(Error::<T>::AuctionNotExist)?;
139
140				let block_number = <frame_system::Pallet<T>>::block_number();
141
142				// make sure auction is started
143				ensure!(block_number >= auction.start, Error::<T>::AuctionNotStarted);
144
145				if let Some(ref current_bid) = auction.bid {
146					ensure!(value > current_bid.1, Error::<T>::InvalidBidPrice);
147				} else {
148					ensure!(!value.is_zero(), Error::<T>::InvalidBidPrice);
149				}
150				let bid_result = T::Handler::on_new_bid(block_number, id, (from.clone(), value), auction.bid.clone());
151
152				ensure!(bid_result.accept_bid, Error::<T>::BidNotAccepted);
153				match bid_result.auction_end_change {
154					Change::NewValue(new_end) => {
155						if let Some(old_end_block) = auction.end {
156							AuctionEndTime::<T>::remove(old_end_block, id);
157						}
158						if let Some(new_end_block) = new_end {
159							AuctionEndTime::<T>::insert(new_end_block, id, ());
160						}
161						auction.end = new_end;
162					}
163					Change::NoChange => {}
164				}
165				auction.bid = Some((from.clone(), value));
166
167				Ok(())
168			})?;
169
170			Self::deposit_event(Event::Bid {
171				auction_id: id,
172				bidder: from,
173				amount: value,
174			});
175			Ok(())
176		}
177	}
178}
179
180impl<T: Config> Auction<T::AccountId, BlockNumberFor<T>> for Pallet<T> {
181	type AuctionId = T::AuctionId;
182	type Balance = T::Balance;
183
184	fn auction_info(id: Self::AuctionId) -> Option<AuctionInfo<T::AccountId, Self::Balance, BlockNumberFor<T>>> {
185		Self::auctions(id)
186	}
187
188	fn update_auction(
189		id: Self::AuctionId,
190		info: AuctionInfo<T::AccountId, Self::Balance, BlockNumberFor<T>>,
191	) -> DispatchResult {
192		let auction = Auctions::<T>::get(id).ok_or(Error::<T>::AuctionNotExist)?;
193		if let Some(old_end) = auction.end {
194			AuctionEndTime::<T>::remove(old_end, id);
195		}
196		if let Some(new_end) = info.end {
197			AuctionEndTime::<T>::insert(new_end, id, ());
198		}
199		Auctions::<T>::insert(id, info);
200		Ok(())
201	}
202
203	fn new_auction(
204		start: BlockNumberFor<T>,
205		end: Option<BlockNumberFor<T>>,
206	) -> sp_std::result::Result<Self::AuctionId, DispatchError> {
207		let auction = AuctionInfo { bid: None, start, end };
208		let auction_id =
209			<AuctionsIndex<T>>::try_mutate(|n| -> sp_std::result::Result<Self::AuctionId, DispatchError> {
210				let id = *n;
211				*n = n.checked_add(&One::one()).ok_or(Error::<T>::NoAvailableAuctionId)?;
212				Ok(id)
213			})?;
214		Auctions::<T>::insert(auction_id, auction);
215		if let Some(end_block) = end {
216			AuctionEndTime::<T>::insert(end_block, auction_id, ());
217		}
218
219		Ok(auction_id)
220	}
221
222	fn remove_auction(id: Self::AuctionId) {
223		if let Some(auction) = Auctions::<T>::take(id) {
224			if let Some(end_block) = auction.end {
225				AuctionEndTime::<T>::remove(end_block, id);
226			}
227		}
228	}
229}