#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::string_lit_as_bytes)]
#![allow(clippy::unused_unit)]
use frame_support::pallet_prelude::*;
use frame_system::{ensure_signed, pallet_prelude::*};
use orml_traits::{Auction, AuctionHandler, AuctionInfo, Change};
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Bounded, MaybeSerializeDeserialize, Member, One, Zero},
DispatchError, DispatchResult,
};
mod default_weight;
mod mock;
mod tests;
pub use module::*;
#[frame_support::pallet]
pub mod module {
use super::*;
pub trait WeightInfo {
fn bid_collateral_auction() -> Weight;
fn bid_surplus_auction() -> Weight;
fn bid_debit_auction() -> Weight;
fn on_finalize(c: u32) -> Weight;
}
#[pallet::config]
pub trait Config: frame_system::Config {
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
type Balance: Parameter + Member + AtLeast32BitUnsigned + Default + Copy + MaybeSerializeDeserialize;
type AuctionId: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Bounded
+ codec::FullCodec;
type Handler: AuctionHandler<Self::AccountId, Self::Balance, Self::BlockNumber, Self::AuctionId>;
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T> {
AuctionNotExist,
AuctionNotStarted,
BidNotAccepted,
InvalidBidPrice,
NoAvailableAuctionId,
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
Bid(T::AuctionId, T::AccountId, T::Balance),
}
#[pallet::storage]
#[pallet::getter(fn auctions)]
pub type Auctions<T: Config> =
StorageMap<_, Twox64Concat, T::AuctionId, AuctionInfo<T::AccountId, T::Balance, T::BlockNumber>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn auctions_index)]
pub type AuctionsIndex<T: Config> = StorageValue<_, T::AuctionId, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn auction_end_time)]
pub type AuctionEndTime<T: Config> =
StorageDoubleMap<_, Twox64Concat, T::BlockNumber, Blake2_128Concat, T::AuctionId, (), OptionQuery>;
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
fn on_initialize(now: T::BlockNumber) -> Weight {
T::WeightInfo::on_finalize(AuctionEndTime::<T>::iter_prefix(&now).count() as u32)
}
fn on_finalize(now: T::BlockNumber) {
for (auction_id, _) in AuctionEndTime::<T>::drain_prefix(&now) {
if let Some(auction) = Auctions::<T>::take(&auction_id) {
T::Handler::on_auction_ended(auction_id, auction.bid);
}
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::WeightInfo::bid_collateral_auction())]
pub fn bid(
origin: OriginFor<T>,
id: T::AuctionId,
#[pallet::compact] value: T::Balance,
) -> DispatchResultWithPostInfo {
let from = ensure_signed(origin)?;
Auctions::<T>::try_mutate_exists(id, |auction| -> DispatchResult {
let mut auction = auction.as_mut().ok_or(Error::<T>::AuctionNotExist)?;
let block_number = <frame_system::Module<T>>::block_number();
ensure!(block_number >= auction.start, Error::<T>::AuctionNotStarted);
if let Some(ref current_bid) = auction.bid {
ensure!(value > current_bid.1, Error::<T>::InvalidBidPrice);
} else {
ensure!(!value.is_zero(), Error::<T>::InvalidBidPrice);
}
let bid_result = T::Handler::on_new_bid(block_number, id, (from.clone(), value), auction.bid.clone());
ensure!(bid_result.accept_bid, Error::<T>::BidNotAccepted);
match bid_result.auction_end_change {
Change::NewValue(new_end) => {
if let Some(old_end_block) = auction.end {
AuctionEndTime::<T>::remove(&old_end_block, id);
}
if let Some(new_end_block) = new_end {
AuctionEndTime::<T>::insert(&new_end_block, id, ());
}
auction.end = new_end;
}
Change::NoChange => {}
}
auction.bid = Some((from.clone(), value));
Ok(())
})?;
Self::deposit_event(Event::Bid(id, from, value));
Ok(().into())
}
}
}
impl<T: Config> Auction<T::AccountId, T::BlockNumber> for Pallet<T> {
type AuctionId = T::AuctionId;
type Balance = T::Balance;
fn auction_info(id: Self::AuctionId) -> Option<AuctionInfo<T::AccountId, Self::Balance, T::BlockNumber>> {
Self::auctions(id)
}
fn update_auction(
id: Self::AuctionId,
info: AuctionInfo<T::AccountId, Self::Balance, T::BlockNumber>,
) -> DispatchResult {
let auction = Auctions::<T>::get(id).ok_or(Error::<T>::AuctionNotExist)?;
if let Some(old_end) = auction.end {
AuctionEndTime::<T>::remove(&old_end, id);
}
if let Some(new_end) = info.end {
AuctionEndTime::<T>::insert(&new_end, id, ());
}
Auctions::<T>::insert(id, info);
Ok(())
}
fn new_auction(
start: T::BlockNumber,
end: Option<T::BlockNumber>,
) -> sp_std::result::Result<Self::AuctionId, DispatchError> {
let auction = AuctionInfo { bid: None, start, end };
let auction_id =
<AuctionsIndex<T>>::try_mutate(|n| -> sp_std::result::Result<Self::AuctionId, DispatchError> {
let id = *n;
ensure!(id != Self::AuctionId::max_value(), Error::<T>::NoAvailableAuctionId);
*n += One::one();
Ok(id)
})?;
Auctions::<T>::insert(auction_id, auction);
if let Some(end_block) = end {
AuctionEndTime::<T>::insert(&end_block, auction_id, ());
}
Ok(auction_id)
}
fn remove_auction(id: Self::AuctionId) {
if let Some(auction) = Auctions::<T>::take(&id) {
if let Some(end_block) = auction.end {
AuctionEndTime::<T>::remove(end_block, id);
}
}
}
}