1use std::fmt::Display;
4
5use crate::{
6 price::PriceBaseInQuote,
7 storage::{
8 Collateral, DirectionToBase, LeverageToBase, LpToken, MarketId, NonZero, RawAddr,
9 TakeProfitTrader,
10 },
11};
12use cosmwasm_std::{Addr, Binary, Decimal256, Uint128};
13
14use super::market::{
15 deferred_execution::DeferredExecId,
16 position::{PositionId, PositionQueryResponse},
17};
18
19#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
21#[serde(rename_all = "snake_case")]
22pub struct InstantiateMsg {
23 pub market: RawAddr,
25 pub admin: RawAddr,
27 pub config: ConfigUpdate,
29}
30
31#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
33#[serde(rename_all = "snake_case")]
34pub struct Config {
36 pub admin: Addr,
38 pub pending_admin: Option<Addr>,
40 pub market: Addr,
42 pub min_funding: Decimal256,
44 pub target_funding: Decimal256,
46 pub max_funding: Decimal256,
48 pub iterations: u8,
50 pub take_profit_factor: Decimal256,
52 pub stop_loss_factor: Decimal256,
54 pub max_leverage: LeverageToBase,
58}
59
60impl Config {
61 pub fn check(&self) -> anyhow::Result<()> {
63 if self.min_funding >= self.target_funding {
64 Err(anyhow::anyhow!(
65 "Minimum funding must be strictly less than target"
66 ))
67 } else if self.target_funding >= self.max_funding {
68 Err(anyhow::anyhow!(
69 "Target funding must be strictly less than max"
70 ))
71 } else if self.max_leverage.into_decimal256() < Decimal256::from_ratio(2u32, 1u32) {
72 Err(anyhow::anyhow!("Max leverage must be at least 2"))
73 } else {
74 Ok(())
75 }
76 }
77}
78
79#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Default)]
80#[serde(rename_all = "snake_case")]
81#[allow(missing_docs)]
82pub struct ConfigUpdate {
86 pub min_funding: Option<Decimal256>,
87 pub target_funding: Option<Decimal256>,
88 pub max_funding: Option<Decimal256>,
89 pub max_leverage: Option<LeverageToBase>,
90 pub iterations: Option<u8>,
91 pub take_profit_factor: Option<Decimal256>,
92 pub stop_loss_factor: Option<Decimal256>,
93}
94
95#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
97#[serde(rename_all = "snake_case")]
98pub enum ExecuteMsg {
99 Receive {
101 sender: RawAddr,
103 amount: Uint128,
105 msg: Binary,
107 },
108 Deposit {},
110 Withdraw {
112 amount: NonZero<LpToken>,
114 },
115 DoWork {},
117 AppointAdmin {
119 admin: RawAddr,
121 },
122 AcceptAdmin {},
124 UpdateConfig(ConfigUpdate),
126}
127
128#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
130#[serde(rename_all = "snake_case")]
131pub enum QueryMsg {
132 Config {},
136 Balance {
140 address: RawAddr,
142 },
143 Status {},
147 HasWork {},
151}
152
153#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
155#[serde(rename_all = "snake_case")]
156pub struct MarketBalance {
157 pub market: MarketId,
159 pub token: crate::token::Token,
161 pub shares: LpToken,
163 pub collateral: Collateral,
165 pub pool_size: LpToken,
167}
168
169#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
171#[serde(rename_all = "snake_case")]
172pub enum Token {
173 Native(String),
175 Cw20(Addr),
177}
178impl Token {
179 pub fn ensure_matches(&self, token: &crate::token::Token) -> anyhow::Result<()> {
181 match (self, token) {
182 (Token::Native(_), crate::token::Token::Cw20 { addr, .. }) => {
183 anyhow::bail!("Provided native funds, but market requires a CW20 (contract {addr})")
184 }
185 (
186 Token::Native(denom1),
187 crate::token::Token::Native {
188 denom: denom2,
189 decimal_places: _,
190 },
191 ) => {
192 if denom1 == denom2 {
193 Ok(())
194 } else {
195 Err(anyhow::anyhow!("Wrong denom provided. You sent {denom1}, but the contract expects {denom2}"))
196 }
197 }
198 (
199 Token::Cw20(addr1),
200 crate::token::Token::Cw20 {
201 addr: addr2,
202 decimal_places: _,
203 },
204 ) => {
205 if addr1.as_str() == addr2.as_str() {
206 Ok(())
207 } else {
208 Err(anyhow::anyhow!(
209 "Wrong CW20 used. You used {addr1}, but the contract expects {addr2}"
210 ))
211 }
212 }
213 (Token::Cw20(_), crate::token::Token::Native { denom, .. }) => {
214 anyhow::bail!(
215 "Provided CW20 funds, but market requires native funds with denom {denom}"
216 )
217 }
218 }
219 }
220}
221
222impl Display for Token {
223 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
224 match self {
225 Token::Native(denom) => f.write_str(denom),
226 Token::Cw20(addr) => f.write_str(addr.as_str()),
227 }
228 }
229}
230
231#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
233#[serde(rename_all = "snake_case")]
234pub struct MarketStatus {
235 pub id: MarketId,
237 pub collateral: Collateral,
241 pub shares: LpToken,
243 pub position: Option<PositionQueryResponse>,
245 pub too_many_positions: bool,
247}
248
249#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
251#[serde(rename_all = "snake_case")]
252pub enum HasWorkResp {
253 NoWork {},
255 Work {
257 desc: WorkDescription,
259 },
260}
261
262#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
264#[serde(rename_all = "snake_case")]
265pub enum WorkDescription {
266 OpenPosition {
268 direction: DirectionToBase,
270 leverage: LeverageToBase,
272 collateral: NonZero<Collateral>,
274 take_profit: TakeProfitTrader,
276 stop_loss_override: Option<PriceBaseInQuote>,
278 },
279 ClosePosition {
281 pos_id: PositionId,
283 },
284 ResetShares,
286 ClearDeferredExec {
288 id: DeferredExecId,
290 },
291 UpdatePositionAddCollateralImpactSize {
293 pos_id: PositionId,
295 amount: NonZero<Collateral>,
297 },
298 UpdatePositionRemoveCollateralImpactSize {
300 pos_id: PositionId,
302 amount: NonZero<Collateral>,
304 crank_fee: Collateral,
306 },
307}
308
309impl WorkDescription {
310 pub fn is_close_position(&self) -> bool {
312 match self {
313 WorkDescription::OpenPosition { .. } => false,
314 WorkDescription::ClosePosition { .. } => true,
315 WorkDescription::ResetShares => false,
316 WorkDescription::ClearDeferredExec { .. } => false,
317 WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => false,
318 WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => false,
319 }
320 }
321
322 pub fn is_collect_closed_position(&self) -> bool {
324 match self {
325 WorkDescription::OpenPosition { .. } => false,
326 WorkDescription::ClosePosition { .. } => false,
327 WorkDescription::ResetShares => false,
328 WorkDescription::ClearDeferredExec { .. } => false,
329 WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => false,
330 WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => false,
331 }
332 }
333
334 pub fn is_update_position(&self) -> bool {
336 match self {
337 WorkDescription::OpenPosition { .. } => false,
338 WorkDescription::ClosePosition { .. } => false,
339 WorkDescription::ResetShares => false,
340 WorkDescription::ClearDeferredExec { .. } => false,
341 WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => true,
342 WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => true,
343 }
344 }
345
346 pub fn is_open_position(&self) -> bool {
348 match self {
349 WorkDescription::OpenPosition { .. } => true,
350 WorkDescription::ClosePosition { .. } => false,
351 WorkDescription::ResetShares => false,
352 WorkDescription::ClearDeferredExec { .. } => false,
353 WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => false,
354 WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => false,
355 }
356 }
357
358 pub fn is_handle_deferred_exec(&self) -> bool {
360 match self {
361 WorkDescription::OpenPosition { .. } => false,
362 WorkDescription::ClosePosition { .. } => false,
363 WorkDescription::ResetShares => false,
364 WorkDescription::ClearDeferredExec { .. } => true,
365 WorkDescription::UpdatePositionAddCollateralImpactSize { .. } => false,
366 WorkDescription::UpdatePositionRemoveCollateralImpactSize { .. } => false,
367 }
368 }
369}
370
371impl std::fmt::Display for WorkDescription {
372 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373 match self {
374 WorkDescription::OpenPosition {
375 direction,
376 leverage,
377 collateral,
378 ..
379 } => write!(
380 f,
381 "Open {direction:?} position with leverage {leverage} and collateral {collateral}"
382 ),
383 WorkDescription::ClosePosition { pos_id } => write!(f, "Close Position {pos_id}"),
384 WorkDescription::ResetShares => write!(f, "Reset Shares"),
385 WorkDescription::ClearDeferredExec { id, .. } => {
386 write!(f, "Handle Deferred Exec Id of {id}")
387 }
388 WorkDescription::UpdatePositionAddCollateralImpactSize { pos_id, amount } => {
389 write!(
390 f,
391 "Add {amount} Collateral to Position Id of {pos_id} impacting size"
392 )
393 }
394 WorkDescription::UpdatePositionRemoveCollateralImpactSize {
395 pos_id, amount, ..
396 } => write!(
397 f,
398 "Remove {amount} Collateral to Position Id of {pos_id} impacting size"
399 ),
400 }
401 }
402}
403
404#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
406#[serde(rename_all = "snake_case")]
407pub struct MigrateMsg {}