use cosmwasm_std::{Addr, Order, StdError, StdResult, Storage, Uint128, Decimal256, Uint256};
use cosmwasm_storage::{singleton, singleton_read, Bucket, ReadonlyBucket};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
static KEY_CONFIG: &[u8] = b"config";
static KEY_BID_IDX: &[u8] = b"bid_idx";
static PREFIX_BID: &[u8] = b"bid";
static PREFIX_BID_BY_USER: &[u8] = b"bid_by_user";
static PREFIX_BID_POOL_BY_COLLATERAL: &[u8] = b"bid_pool_by_col";
static PREFIX_TOTAL_BIDS_BY_COLLATERAL: &[u8] = b"total_bids_by_col";
static PREFIX_COLLATERAL_INFO: &[u8] = b"col_info";
static PREFIX_EPOCH_SCALE_SUM: &[u8] = b"epoch_scale_sum";
const MAX_LIMIT: u8 = 31;
const DEFAULT_LIMIT: u8 = 10;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct Config {
pub owner: Addr,
pub oracle_contract: Addr,
pub stable_contract: Addr,
pub safe_ratio: Decimal256,
pub bid_fee: Decimal256,
pub liquidator_fee: Decimal256,
pub liquidation_threshold: Uint256,
pub price_timeframe: u64,
pub waiting_period: u64,
pub overseer: Addr,
}
pub fn store_config(storage: &mut dyn Storage, config: &Config) -> StdResult<()> {
singleton(storage, KEY_CONFIG).save(config)
}
pub fn read_config(storage: &dyn Storage) -> StdResult<Config> {
singleton_read(storage, KEY_CONFIG).load()
}
pub fn pop_bid_idx(storage: &mut dyn Storage) -> StdResult<Uint128> {
let mut idx_store = singleton(storage, KEY_BID_IDX);
let last_idx: Uint128 = idx_store.load().unwrap_or_else(|_| Uint128::from(1u128));
idx_store.save(&(last_idx + Uint128::from(1u128)))?;
Ok(last_idx)
}
pub fn store_total_bids(
storage: &mut dyn Storage,
collateral_token: &Addr,
total_bids: Uint256,
) -> StdResult<()> {
let mut total_bids_bucket: Bucket<Uint256> =
Bucket::new(storage, PREFIX_TOTAL_BIDS_BY_COLLATERAL);
total_bids_bucket.save(collateral_token.as_bytes(), &total_bids)?;
Ok(())
}
pub fn read_total_bids(storage: &dyn Storage, collateral_token: &Addr) -> StdResult<Uint256> {
let total_bids_bucket: ReadonlyBucket<Uint256> =
ReadonlyBucket::new(storage, PREFIX_TOTAL_BIDS_BY_COLLATERAL);
total_bids_bucket.load(collateral_token.as_bytes())
}
pub fn store_epoch_scale_sum(
storage: &mut dyn Storage,
collateral_token: &Addr,
premium_slot: u8,
epoch: Uint128,
scale: Uint128,
sum: Decimal256,
) -> StdResult<()> {
let mut epoch_scale_sum: Bucket<Decimal256> = Bucket::multilevel(
storage,
&[
PREFIX_EPOCH_SCALE_SUM,
collateral_token.as_bytes(),
&premium_slot.to_be_bytes(),
&epoch.u128().to_be_bytes(),
],
);
epoch_scale_sum.save(&scale.u128().to_be_bytes(), &sum)
}
pub fn read_epoch_scale_sum(
storage: &dyn Storage,
collateral_token: &Addr,
premium_slot: u8,
epoch: Uint128,
scale: Uint128,
) -> StdResult<Decimal256> {
let epoch_scale_sum: ReadonlyBucket<Decimal256> = ReadonlyBucket::multilevel(
storage,
&[
PREFIX_EPOCH_SCALE_SUM,
collateral_token.as_bytes(),
&premium_slot.to_be_bytes(),
&epoch.u128().to_be_bytes(),
],
);
epoch_scale_sum.load(&scale.u128().to_be_bytes())
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct CollateralInfo {
pub collateral_token: Addr,
pub bid_threshold: Uint256,
pub max_slot: u8,
pub premium_rate_per_slot: Decimal256,
}
pub fn store_collateral_info(
storage: &mut dyn Storage,
collateral_token: &Addr,
collateral_info: &CollateralInfo,
) -> StdResult<()> {
let mut collateral_info_bucket: Bucket<CollateralInfo> =
Bucket::new(storage, PREFIX_COLLATERAL_INFO);
collateral_info_bucket.save(collateral_token.as_bytes(), collateral_info)?;
Ok(())
}
pub fn read_collateral_info(
storage: &dyn Storage,
collateral_token: &Addr,
) -> StdResult<CollateralInfo> {
let collateral_info_bucket: ReadonlyBucket<CollateralInfo> =
ReadonlyBucket::new(storage, PREFIX_COLLATERAL_INFO);
collateral_info_bucket
.load(collateral_token.as_bytes())
.map_err(|_| StdError::generic_err("Collateral is not whitelisted"))
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct BidPool {
pub sum_snapshot: Decimal256,
pub product_snapshot: Decimal256,
pub total_bid_amount: Uint256,
pub premium_rate: Decimal256,
pub current_epoch: Uint128,
pub current_scale: Uint128,
pub residue_collateral: Decimal256,
pub residue_bid: Decimal256,
}
pub fn store_bid_pool(
storage: &mut dyn Storage,
collateral_token: &Addr,
premium_slot: u8,
bid_pool: &BidPool,
) -> StdResult<()> {
let mut bid_pool_bucket: Bucket<BidPool> = Bucket::multilevel(
storage,
&[PREFIX_BID_POOL_BY_COLLATERAL, collateral_token.as_bytes()],
);
bid_pool_bucket.save(&premium_slot.to_be_bytes(), bid_pool)
}
pub fn read_bid_pool(
storage: &dyn Storage,
collateral_token: &Addr,
premium_slot: u8,
) -> StdResult<BidPool> {
let bid_pool_bucket: ReadonlyBucket<BidPool> = ReadonlyBucket::multilevel(
storage,
&[PREFIX_BID_POOL_BY_COLLATERAL, collateral_token.as_bytes()],
);
bid_pool_bucket
.load(&premium_slot.to_be_bytes())
.map_err(|_| StdError::generic_err("Bid pool not found"))
}
pub fn read_or_create_bid_pool(
storage: &mut dyn Storage,
collateral_info: &CollateralInfo,
premium_slot: u8,
) -> StdResult<BidPool> {
let bid_pool_bucket: ReadonlyBucket<BidPool> = ReadonlyBucket::multilevel(
storage,
&[
PREFIX_BID_POOL_BY_COLLATERAL,
collateral_info.collateral_token.as_bytes(),
],
);
match bid_pool_bucket.load(&premium_slot.to_be_bytes()) {
Ok(bid_pool) => Ok(bid_pool),
Err(_) => {
if (0..collateral_info.max_slot + 1).contains(&premium_slot) {
let bid_pool = BidPool {
product_snapshot: Decimal256::from_ratio(Decimal256::one().atomics(), Uint256::one()),
sum_snapshot: Decimal256::zero(),
total_bid_amount: Uint256::zero(),
premium_rate: collateral_info.premium_rate_per_slot
* Decimal256::from_ratio(Uint256::from(premium_slot as u128), Uint256::one()),
current_epoch: Uint128::zero(),
current_scale: Uint128::zero(),
residue_collateral: Decimal256::zero(),
residue_bid: Decimal256::zero(),
};
store_bid_pool(
storage,
&collateral_info.collateral_token,
premium_slot,
&bid_pool,
)?;
Ok(bid_pool)
} else {
Err(StdError::generic_err("Invalid premium slot"))
}
}
}
}
pub fn read_bid_pools(
storage: &dyn Storage,
collateral_token: &Addr,
start_after: Option<u8>,
limit: Option<u8>,
) -> StdResult<Vec<BidPool>> {
let bid_pool_bucket: ReadonlyBucket<BidPool> = ReadonlyBucket::multilevel(
storage,
&[PREFIX_BID_POOL_BY_COLLATERAL, collateral_token.as_bytes()],
);
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let start = calc_range_start(start_after);
bid_pool_bucket
.range(start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|elem| {
let (_, pool) = elem?;
Ok(pool)
})
.collect()
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct Bid {
pub idx: Uint128,
pub collateral_token: Addr,
pub premium_slot: u8,
pub bidder: Addr,
pub amount: Uint256,
pub product_snapshot: Decimal256,
pub sum_snapshot: Decimal256,
pub pending_liquidated_collateral: Uint256,
pub wait_end: Option<u64>,
pub epoch_snapshot: Uint128,
pub scale_snapshot: Uint128,
}
pub fn store_bid(storage: &mut dyn Storage, bid_idx: Uint128, bid: &Bid) -> StdResult<()> {
let mut bid_bucket: Bucket<Bid> = Bucket::new(storage, PREFIX_BID);
bid_bucket.save(&bid_idx.u128().to_be_bytes(), bid)?;
let mut bid_indexer_by_user: Bucket<bool> = Bucket::multilevel(
storage,
&[
PREFIX_BID_BY_USER,
bid.collateral_token.as_bytes(),
bid.bidder.as_bytes(),
],
);
bid_indexer_by_user.save(&bid_idx.u128().to_be_bytes(), &true)?;
Ok(())
}
pub fn remove_bid(storage: &mut dyn Storage, bid_idx: Uint128) -> StdResult<()> {
let bid: Bid = read_bid(storage, bid_idx)?;
let mut bid_bucket: Bucket<Bid> = Bucket::new(storage, PREFIX_BID);
bid_bucket.remove(&bid_idx.u128().to_be_bytes());
let mut bid_indexer_by_user: Bucket<bool> = Bucket::multilevel(
storage,
&[
PREFIX_BID_BY_USER,
bid.collateral_token.as_bytes(),
bid.bidder.as_bytes(),
],
);
bid_indexer_by_user.remove(&bid_idx.u128().to_be_bytes());
Ok(())
}
pub fn read_bid(storage: &dyn Storage, bid_idx: Uint128) -> StdResult<Bid> {
let bid_bucket: ReadonlyBucket<Bid> = ReadonlyBucket::new(storage, PREFIX_BID);
bid_bucket
.load(&bid_idx.u128().to_be_bytes())
.map_err(|_| StdError::generic_err("No bids with the specified information exist"))
}
pub fn read_bids_by_user(
storage: &dyn Storage,
collateral_token: &Addr,
bidder: &Addr,
start_after: Option<Uint128>,
limit: Option<u8>,
) -> StdResult<Vec<Bid>> {
let bid_user_index: ReadonlyBucket<bool> = ReadonlyBucket::multilevel(
storage,
&[
PREFIX_BID_BY_USER,
collateral_token.as_bytes(),
bidder.as_bytes(),
],
);
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let start = calc_range_start_idx(start_after);
bid_user_index
.range(start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|elem| {
let (k, _) = elem?;
read_bid(storage, Uint128::from(bytes_to_u128(&k)?))
})
.collect()
}
fn bytes_to_u128(data: &[u8]) -> StdResult<u128> {
match data[0..16].try_into() {
Ok(bytes) => Ok(u128::from_be_bytes(bytes)),
Err(_) => Err(StdError::generic_err(
"Corrupted data found. 16 byte expected.",
)),
}
}
fn calc_range_start_idx(start_after: Option<Uint128>) -> Option<Vec<u8>> {
start_after.map(|idx| {
let mut v = idx.u128().to_be_bytes().to_vec();
v.push(1);
v
})
}
fn calc_range_start(start_after: Option<u8>) -> Option<Vec<u8>> {
start_after.map(|id| {
let mut v = id.to_be_bytes().to_vec();
v.push(1);
v
})
}