use std::fmt::Display;
use crate::{
price::PriceBaseInQuote,
storage::{
Collateral, DirectionToBase, LeverageToBase, LpToken, MarketId, NonZero, RawAddr,
TakeProfitTrader,
},
};
use cosmwasm_std::{Addr, Binary, Decimal256, Uint128};
use super::market::{
deferred_execution::DeferredExecId,
position::{PositionId, PositionQueryResponse},
};
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
pub market: RawAddr,
pub admin: RawAddr,
pub config: ConfigUpdate,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct Config {
pub admin: Addr,
pub pending_admin: Option<Addr>,
pub market: Addr,
pub min_funding: Decimal256,
pub target_funding: Decimal256,
pub max_funding: Decimal256,
pub iterations: u8,
pub take_profit_factor: Decimal256,
pub stop_loss_factor: Decimal256,
pub max_leverage: LeverageToBase,
}
impl Config {
pub fn check(&self) -> anyhow::Result<()> {
if self.min_funding >= self.target_funding {
Err(anyhow::anyhow!(
"Minimum funding must be strictly less than target"
))
} else if self.target_funding >= self.max_funding {
Err(anyhow::anyhow!(
"Target funding must be strictly less than max"
))
} else if self.max_leverage.into_decimal256() < Decimal256::from_ratio(2u32, 1u32) {
Err(anyhow::anyhow!("Max leverage must be at least 2"))
} else {
Ok(())
}
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "snake_case")]
#[allow(missing_docs)]
pub struct ConfigUpdate {
pub min_funding: Option<Decimal256>,
pub target_funding: Option<Decimal256>,
pub max_funding: Option<Decimal256>,
pub max_leverage: Option<LeverageToBase>,
pub iterations: Option<u8>,
pub take_profit_factor: Option<Decimal256>,
pub stop_loss_factor: Option<Decimal256>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
Receive {
sender: RawAddr,
amount: Uint128,
msg: Binary,
},
Deposit {},
Withdraw {
amount: NonZero<LpToken>,
},
DoWork {},
AppointAdmin {
admin: RawAddr,
},
AcceptAdmin {},
UpdateConfig(ConfigUpdate),
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
Config {},
Balance {
address: RawAddr,
},
Status {},
HasWork {},
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct MarketBalance {
pub market: MarketId,
pub token: crate::token::Token,
pub shares: NonZero<LpToken>,
pub collateral: NonZero<Collateral>,
pub pool_size: NonZero<LpToken>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Token {
Native(String),
Cw20(Addr),
}
impl Token {
pub fn ensure_matches(&self, token: &crate::token::Token) -> anyhow::Result<()> {
match (self, token) {
(Token::Native(_), crate::token::Token::Cw20 { addr, .. }) => {
anyhow::bail!("Provided native funds, but market requires a CW20 (contract {addr})")
}
(
Token::Native(denom1),
crate::token::Token::Native {
denom: denom2,
decimal_places: _,
},
) => {
if denom1 == denom2 {
Ok(())
} else {
Err(anyhow::anyhow!("Wrong denom provided. You sent {denom1}, but the contract expects {denom2}"))
}
}
(
Token::Cw20(addr1),
crate::token::Token::Cw20 {
addr: addr2,
decimal_places: _,
},
) => {
if addr1.as_str() == addr2.as_str() {
Ok(())
} else {
Err(anyhow::anyhow!(
"Wrong CW20 used. You used {addr1}, but the contract expects {addr2}"
))
}
}
(Token::Cw20(_), crate::token::Token::Native { denom, .. }) => {
anyhow::bail!(
"Provided CW20 funds, but market requires native funds with denom {denom}"
)
}
}
}
}
impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Token::Native(denom) => f.write_str(denom),
Token::Cw20(addr) => f.write_str(addr.as_str()),
}
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct MarketStatus {
pub id: MarketId,
pub collateral: Collateral,
pub shares: LpToken,
pub position: Option<PositionQueryResponse>,
pub too_many_positions: bool,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HasWorkResp {
NoWork {},
Work {
desc: WorkDescription,
},
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WorkDescription {
OpenPosition {
direction: DirectionToBase,
leverage: LeverageToBase,
collateral: NonZero<Collateral>,
take_profit: TakeProfitTrader,
stop_loss_override: Option<PriceBaseInQuote>,
},
ClosePosition {
pos_id: PositionId,
},
ResetShares,
ClearDeferredExec {
id: DeferredExecId,
},
UpdatePositionAddCollateralImpactSize {
pos_id: PositionId,
amount: NonZero<Collateral>,
},
UpdatePositionRemoveCollateralImpactSize {
pos_id: PositionId,
amount: NonZero<Collateral>,
crank_fee: Collateral,
},
}
impl WorkDescription {
pub fn is_close_position(&self) -> bool {
match self {
WorkDescription::OpenPosition { .. } => false,
WorkDescription::ClosePosition { .. } => true,
WorkDescription::ResetShares => false,
WorkDescription::ClearDeferredExec { .. } => false,
WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => false,
WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => false,
}
}
pub fn is_collect_closed_position(&self) -> bool {
match self {
WorkDescription::OpenPosition { .. } => false,
WorkDescription::ClosePosition { .. } => false,
WorkDescription::ResetShares => false,
WorkDescription::ClearDeferredExec { .. } => false,
WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => false,
WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => false,
}
}
pub fn is_update_position(&self) -> bool {
match self {
WorkDescription::OpenPosition { .. } => false,
WorkDescription::ClosePosition { .. } => false,
WorkDescription::ResetShares => false,
WorkDescription::ClearDeferredExec { .. } => false,
WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => true,
WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => true,
}
}
pub fn is_open_position(&self) -> bool {
match self {
WorkDescription::OpenPosition { .. } => true,
WorkDescription::ClosePosition { .. } => false,
WorkDescription::ResetShares => false,
WorkDescription::ClearDeferredExec { .. } => false,
WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => false,
WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => false,
}
}
pub fn is_handle_deferred_exec(&self) -> bool {
match self {
WorkDescription::OpenPosition { .. } => false,
WorkDescription::ClosePosition { .. } => false,
WorkDescription::ResetShares => false,
WorkDescription::ClearDeferredExec { .. } => true,
WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => false,
WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => false,
}
}
}
impl std::fmt::Display for WorkDescription {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WorkDescription::OpenPosition {
direction,
leverage,
collateral,
..
} => write!(
f,
"Open {direction:?} position with leverage {leverage} and collateral {collateral}"
),
WorkDescription::ClosePosition { pos_id } => write!(f, "Close Position {pos_id}"),
WorkDescription::ResetShares => write!(f, "Reset Shares"),
WorkDescription::ClearDeferredExec { id, .. } => {
write!(f, "Handle Deferred Exec Id of {id}")
}
WorkDescription::UpdatePositionAddCollateralImpactSize { pos_id, amount } => {
write!(
f,
"Add {amount} Collateral to Position Id of {pos_id} impacting size"
)
}
WorkDescription::UpdatePositionRemoveCollateralImpactSize {
pos_id, amount, ..
} => write!(
f,
"Remove {amount} Collateral to Position Id of {pos_id} impacting size"
),
}
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}