Skip to content
Snippets Groups Projects
Commit 90b89dfc authored by wangjj9219's avatar wangjj9219 Committed by Xiliang Chen
Browse files

fix fmt error and bug and add tests (#20)

* add auction module

* remove redundant traits definition

* add tests

* get reference instead of ownership

* optimized code
parent ab868aee
No related branches found
No related tags found
No related merge requests found
......@@ -4,4 +4,5 @@ members = [
"tokens",
"traits",
"utilities",
"auction",
]
[package]
name = "orml-auction"
version = "0.0.1"
authors = ["Acala Developers"]
edition = "2018"
[dependencies]
serde = { version = "1.0", optional = true }
codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false }
sr-primitives = { git = "https://github.com/paritytech/substrate.git", default-features = false }
srml-support = { package = "srml-support", git = "https://github.com/paritytech/substrate.git", default-features = false }
srml-system = { package = "srml-system", git = "https://github.com/paritytech/substrate.git", default-features = false }
runtime_io = { package = "sr-io", git = "https://github.com/paritytech/substrate.git", default-features = false }
rstd = { package = "sr-std", git = "https://github.com/paritytech/substrate.git", default-features = false }
traits = { package = "orml-traits", path = "../traits", default-features = false }
utilities = { package = "orml-utilities", path = "../utilities", default-features = false }
[dev-dependencies]
primitives = { package = "substrate-primitives", git = "https://github.com/paritytech/substrate.git", default-features = false }
[features]
default = ["std"]
std = [
"serde",
"codec/std",
"sr-primitives/std",
"srml-support/std",
"srml-system/std",
"runtime_io/std",
"rstd/std",
"traits/std",
"utilities/std",
]
\ No newline at end of file
#![cfg_attr(not(feature = "std"), no_std)]
use rstd::result;
use sr_primitives::traits::{MaybeSerializeDeserialize, Member, SimpleArithmetic};
use srml_support::{
decl_error, decl_event, decl_module, decl_storage, dispatch::Result, ensure, Parameter, StorageMap, StorageValue,
};
use srml_system::{self as system, ensure_signed};
use utilities::{LinkedItem, LinkedList};
use traits::{Auction, AuctionHandler, AuctionInfo};
mod mock;
mod tests;
pub trait Trait: srml_system::Trait {
type Event: From<Event<Self>> + Into<<Self as srml_system::Trait>::Event>;
type Balance: Parameter + Member + SimpleArithmetic + Default + Copy + MaybeSerializeDeserialize;
type AuctionId: Parameter + Member + SimpleArithmetic + Default + Copy + MaybeSerializeDeserialize;
type Handler: AuctionHandler<Self::AccountId, Self::Balance, Self::BlockNumber, Self::AuctionId>;
}
type AuctionEndTimeList<T> =
LinkedList<AuctionEndTime<T>, <T as srml_system::Trait>::BlockNumber, <T as Trait>::AuctionId>;
decl_event!(
pub enum Event<T> where
<T as srml_system::Trait>::AccountId,
<T as Trait>::Balance,
<T as Trait>::AuctionId,
{
Bid(AuctionId, AccountId, Balance),
}
);
decl_storage! {
trait Store for Module<T: Trait> as Auction {
pub Auctions get(fn auctions): map T::AuctionId => Option<AuctionInfo<T::AccountId, T::Balance, T::BlockNumber>>;
pub AuctionsCount get(fn auctions_count): T::AuctionId;
pub AuctionEndTime get(fn auction_end_time): map(T::BlockNumber, Option<T::AuctionId>) => Option<LinkedItem<T::AuctionId>>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
fn deposit_event() = default;
pub fn bid(origin, id: T::AuctionId, value: T::Balance) -> Result {
let from = ensure_signed(origin)?;
let mut auction = <Auctions<T>>::get(id).ok_or(Error::AuctionNotExist)?;
if let Some(ref current_bid) = auction.bid {
ensure!(value > current_bid.1, Error::InvalidBidPrice.into());
} else {
ensure!(value > 0.into(), Error::InvalidBidPrice.into());
}
let bid_result = T::Handler::on_new_bid(
<srml_system::Module<T>>::block_number(),
id,
(from.clone(), value),
auction.bid.clone(),
);
ensure!(bid_result.accept_bid, Error::BidNotAccepted.into());
if let Some(new_end) = bid_result.auction_end {
if let Some(old_end_block) = auction.end {
<AuctionEndTimeList<T>>::remove(&old_end_block, id);
}
if let Some(new_end_block) = new_end {
<AuctionEndTimeList<T>>::append(&new_end_block, id);
}
auction.end = new_end;
}
auction.bid = Some((from.clone(), value));
<Auctions<T>>::insert(id, auction);
Self::deposit_event(RawEvent::Bid(id, from, value));
Ok(())
}
fn on_finalize(now: T::BlockNumber) {
let head_key: Option<T::AuctionId> = None;
if let Some(mut head_item) = <AuctionEndTime<T>>::get((now, head_key)) {
while let Some(auction_id) = head_item.next {
if let Some(auction) = Self::auctions(auction_id) {
T::Handler::on_auction_ended(auction_id, auction.bid);
<Auctions<T>>::remove(auction_id);
}
head_item = <AuctionEndTime<T>>::get((now, Some(auction_id))).unwrap_or_else(|| LinkedItem {
prev: None,
next: None,
});
<AuctionEndTime<T>>::remove((now, Some(auction_id)));
}
<AuctionEndTime<T>>::remove((now, head_key));
}
}
}
}
decl_error! {
/// Error for auction module.
pub enum Error {
AuctionNotExist,
BidNotAccepted,
InvalidBidPrice,
}
}
impl<T: Trait> Module<T> {}
impl<T: Trait> Auction<T::AccountId, T::BlockNumber> for Module<T> {
type AuctionId = T::AuctionId;
type Balance = T::Balance;
type Error = Error;
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>,
) -> result::Result<(), Self::Error> {
let auction = <Auctions<T>>::get(id).ok_or(Error::AuctionNotExist)?;
if let Some(old_end) = auction.end {
<AuctionEndTimeList<T>>::remove(&old_end, id);
}
if let Some(new_end) = info.end {
<AuctionEndTimeList<T>>::append(&new_end, id);
}
<Auctions<T>>::insert(id, info);
Ok(())
}
fn new_auction(_start: T::BlockNumber, end: Option<T::BlockNumber>) -> Self::AuctionId {
let auction = AuctionInfo { bid: None, end: end };
let auction_id = Self::auctions_count();
<AuctionsCount<T>>::mutate(|n| *n += Self::AuctionId::from(1));
<Auctions<T>>::insert(auction_id, auction);
if let Some(end_block) = end {
<AuctionEndTimeList<T>>::append(&end_block, auction_id);
}
auction_id
}
}
//! Mocks for the auction module.
#![cfg(test)]
use primitives::H256;
use sr_primitives::{testing::Header, traits::IdentityLookup, Perbill};
use srml_support::{impl_outer_event, impl_outer_origin, parameter_types};
use traits::OnNewBidResult;
use super::*;
impl_outer_origin! {
pub enum Origin for Runtime {}
}
mod auction {
pub use crate::Event;
}
impl_outer_event! {
pub enum TestEvent for Runtime {
auction<T>,
}
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Runtime;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: u32 = 1024;
pub const MaximumBlockLength: u32 = 2 * 1024;
pub const AvailableBlockRatio: Perbill = Perbill::one();
}
pub type AccountId = u64;
pub type Balance = u64;
pub type BlockNumber = u64;
pub type AuctionId = u64;
impl system::Trait for Runtime {
type Origin = Origin;
type Index = u64;
type BlockNumber = BlockNumber;
type Call = ();
type Hash = H256;
type Hashing = ::sr_primitives::traits::BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = TestEvent;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type MaximumBlockLength = MaximumBlockLength;
type AvailableBlockRatio = AvailableBlockRatio;
type Version = ();
}
pub type System = system::Module<Runtime>;
pub struct Handler;
impl AuctionHandler<AccountId, Balance, BlockNumber, AuctionId> for Handler {
fn on_new_bid(
_now: BlockNumber,
_id: AuctionId,
_new_bid: (AccountId, Balance),
_last_bid: Option<(AccountId, Balance)>,
) -> OnNewBidResult<BlockNumber> {
OnNewBidResult {
accept_bid: true,
auction_end: None,
}
}
fn on_auction_ended(_id: AuctionId, _winner: Option<(AccountId, Balance)>) {}
}
impl Trait for Runtime {
type Event = TestEvent;
type Balance = Balance;
type AuctionId = AccountId;
type Handler = Handler;
}
pub type AuctionModule = Module<Runtime>;
pub const ALICE: AccountId = 1;
pub struct ExtBuilder;
impl Default for ExtBuilder {
fn default() -> Self {
ExtBuilder
}
}
impl ExtBuilder {
pub fn build(self) -> runtime_io::TestExternalities {
let t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
t.into()
}
}
//! Unit tests for the tokens module.
#![cfg(test)]
use super::*;
use mock::{AuctionModule, ExtBuilder, ALICE};
use srml_support::assert_ok;
#[test]
fn new_auction_should_work() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(AuctionModule::new_auction(10, Some(100)), 0);
});
}
#[test]
fn update_auction_should_work() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(AuctionModule::new_auction(10, Some(100)), 0);
assert_ok!(AuctionModule::update_auction(
0,
AuctionInfo {
bid: Some((ALICE, 100)),
end: Some(100)
}
));
});
}
#[test]
fn auction_info_should_work() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(AuctionModule::new_auction(10, Some(100)), 0);
assert_eq!(
AuctionModule::auction_info(0),
Some(AuctionInfo {
bid: None,
end: Some(100)
})
);
});
}
#[test]
fn bid_should_work() {
ExtBuilder::default().build().execute_with(|| {
assert_eq!(AuctionModule::new_auction(10, Some(100)), 0);
assert_ok!(AuctionModule::bid(Some(ALICE).into(), 0, 20));
assert_eq!(
AuctionModule::auction_info(0),
Some(AuctionInfo {
bid: Some((ALICE, 20)),
end: Some(100)
})
);
});
}
use codec::FullCodec;
use rstd::fmt::Debug;
use sr_primitives::traits::MaybeSerializeDeserialize;
use codec::{Decode, Encode};
use rstd::{fmt::Debug, result};
use sr_primitives::{
traits::{MaybeSerializeDeserialize, SimpleArithmetic},
RuntimeDebug,
};
/// Auction info.
#[cfg_attr(feature = "std", derive(PartialEq, Eq))]
#[derive(Encode, Decode, RuntimeDebug)]
pub struct AuctionInfo<AccountId, Balance, BlockNumber> {
/// Current bidder and bid price.
pub bid: Option<(AccountId, Balance)>,
/// Define which block this auction will be ended.
pub end: Option<BlockNumber>,
}
pub trait Auction<AccountId, Balance, BlockNumber> {
type AuctionId: FullCodec + Copy + MaybeSerializeDeserialize + Debug;
/// Abstraction over a simple auction system.
pub trait Auction<AccountId, BlockNumber> {
/// The id of an AuctionInfo
type AuctionId: FullCodec + Default + Copy + MaybeSerializeDeserialize + Debug;
/// The price to bid.
type Balance: SimpleArithmetic + FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default;
/// The error type.
type Error: Into<&'static str>;
fn auction_info(id: Self::AuctionId) -> Option<AuctionInfo<AccountId, Balance, BlockNumber>>;
fn update_auction(id: Self::AuctionId, info: AuctionInfo<AccountId, Balance, BlockNumber>);
/// The auction info of `id`
fn auction_info(id: Self::AuctionId) -> Option<AuctionInfo<AccountId, Self::Balance, BlockNumber>>;
/// Update the auction info of `id` with `info`
fn update_auction(
id: Self::AuctionId,
info: AuctionInfo<AccountId, Self::Balance, BlockNumber>,
) -> result::Result<(), Self::Error>;
/// Create new auction with specific startblock and endblock, return the id of the auction
fn new_auction(start: BlockNumber, end: Option<BlockNumber>) -> Self::AuctionId;
}
/// The result of bid handling.
pub struct OnNewBidResult<BlockNumber> {
/// Indicates if the bid was accepted
pub accept_bid: bool,
/// `None` means don't change, `Some(None)` means no more auction end time, `Some(Some(number))` means set auction end time to this block
pub auction_end: Option<Option<BlockNumber>>,
}
/// Hooks for auction to handle bids.
pub trait AuctionHandler<AccountId, Balance, BlockNumber, AuctionId> {
/// Called when new bid is received.
/// The return value deteermine if the bid should be accepted and update auction end time.
......@@ -31,5 +55,6 @@ pub trait AuctionHandler<AccountId, Balance, BlockNumber, AuctionId> {
new_bid: (AccountId, Balance),
last_bid: Option<(AccountId, Balance)>,
) -> OnNewBidResult<BlockNumber>;
/// End an auction with `winner`
fn on_auction_ended(id: AuctionId, winner: Option<(AccountId, Balance)>);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment