1#![cfg_attr(not(feature = "std"), no_std)]
12#![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 type Balance: Parameter
40 + Member
41 + AtLeast32BitUnsigned
42 + Default
43 + Copy
44 + MaybeSerializeDeserialize
45 + MaxEncodedLen;
46
47 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 type Handler: AuctionHandler<Self::AccountId, Self::Balance, BlockNumberFor<Self>, Self::AuctionId>;
61
62 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 Bid {
80 auction_id: T::AuctionId,
81 bidder: T::AccountId,
82 amount: T::Balance,
83 },
84 }
85
86 #[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 #[pallet::storage]
99 #[pallet::getter(fn auctions_index)]
100 pub type AuctionsIndex<T: Config> = StorageValue<_, T::AuctionId, ValueQuery>;
101
102 #[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 #[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 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}