1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
//! Countertrade contract

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},
};

/// Message for instantiating a new countertrade contract.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
    /// Market contract we're countertrading on
    pub market: RawAddr,
    /// Administrator of the contract
    pub admin: RawAddr,
    /// Initial configuration values
    pub config: ConfigUpdate,
}

/// Full configuration
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
/// Updates to configuration values.
pub struct Config {
    /// Administrator of the contract
    pub admin: Addr,
    /// Pending administrator, ready to be accepted, if any.
    pub pending_admin: Option<Addr>,
    /// Market address that we are allowed to open positions on
    pub market: Addr,
    /// Minimum funding rate for popular side
    pub min_funding: Decimal256,
    /// Target funding rate for popular side
    pub target_funding: Decimal256,
    /// Maximum funding rate for popular side
    pub max_funding: Decimal256,
    /// Allowed iterations to compute delta notional
    pub iterations: u8,
    /// Factor used to compute take profit price
    pub take_profit_factor: Decimal256,
    /// Factor used to compute stop loss price
    pub stop_loss_factor: Decimal256,
    /// Maximum leverage value we'll use
    ///
    /// If a market has lower max leverage, we use that instead
    pub max_leverage: LeverageToBase,
}

impl Config {
    /// Check validity of config values
    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)]
/// Updates to configuration values.
///
/// See [Config] for field meanings.
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>,
}

/// Executions available on the countertrade contract.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
    /// Cw20 interface
    Receive {
        /// Owner of funds sent to the contract
        sender: RawAddr,
        /// Amount of funds sent
        amount: Uint128,
        /// Must parse to a [ExecuteMsg]
        msg: Binary,
    },
    /// Deposit funds for a given market
    Deposit {},
    /// Withdraw funds from a given market
    Withdraw {
        /// The number of LP shares to remove
        amount: NonZero<LpToken>,
    },
    /// Perform a balancing operation on the given market
    DoWork {},
    /// Appoint a new administrator
    AppointAdmin {
        /// Address of the new administrator
        admin: RawAddr,
    },
    /// Accept appointment of admin
    AcceptAdmin {},
    /// Update configuration values
    UpdateConfig(ConfigUpdate),
}

/// Queries that can be performed on the countertrade contract.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    /// Get the current config
    ///
    /// Returns [Config]
    Config {},
    /// Check the balance of an address for all markets.
    ///
    /// Returns [MarketBalance]
    Balance {
        /// Address of the token holder
        address: RawAddr,
    },
    /// Check the status of a single market
    ///
    /// Returns [MarketStatus]
    Status {},
    /// Check if the given contract has any work to do
    ///
    /// Returns [HasWorkResp]
    HasWork {},
}

/// Individual market response from [QueryMsg::Balance]
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct MarketBalance {
    /// Market where a balance is held
    pub market: MarketId,
    /// Token for this market
    pub token: crate::token::Token,
    /// Shares of the pool held by this LP
    pub shares: NonZero<LpToken>,
    /// Collateral equivalent of these shares
    pub collateral: NonZero<Collateral>,
    /// Size of the entire pool, in LP tokens
    pub pool_size: NonZero<LpToken>,
}

/// Either a native token or CW20 contract
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Token {
    /// Native coin and its denom
    Native(String),
    /// CW20 contract and its address
    Cw20(Addr),
}
impl Token {
    /// Ensure that the two versions of the token are compatible.
    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()),
        }
    }
}

/// Status of a single market
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct MarketStatus {
    /// Which market
    pub id: MarketId,
    /// Collateral held inside the contract
    ///
    /// Does not include active collateral of a position
    pub collateral: Collateral,
    /// Number of outstanding shares
    pub shares: LpToken,
    /// Our open position, if we have exactly one
    pub position: Option<PositionQueryResponse>,
    /// Do we have too many open positions?
    pub too_many_positions: bool,
}

/// Whether or not there is work available.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HasWorkResp {
    /// No work is available
    NoWork {},
    /// There is work available to be done
    Work {
        /// A description of the work, for display and testing purposes.
        desc: WorkDescription,
    },
}

/// Work to be performed for a specific market.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum WorkDescription {
    /// Open a new position
    OpenPosition {
        /// Direction of the new position
        direction: DirectionToBase,
        /// Leverage
        leverage: LeverageToBase,
        /// Amount of deposit collateral
        collateral: NonZero<Collateral>,
        /// Take profit value
        take_profit: TakeProfitTrader,
        /// Stop loss price of new position
        stop_loss_override: Option<PriceBaseInQuote>,
    },
    /// Close an unnecessary position
    ClosePosition {
        /// Position to be closed
        pos_id: PositionId,
    },
    /// All collateral exhausted, reset shares to 0
    ResetShares,
    /// Deferred execution completed, we can continue our processing
    ClearDeferredExec {
        /// ID to be cleared
        id: DeferredExecId,
    },
    /// Add collateral to a position, causing notional size to increase
    UpdatePositionAddCollateralImpactSize {
        /// ID of position to update
        pos_id: PositionId,
        /// Amount of funds to add to the position
        amount: NonZero<Collateral>,
    },
    /// Remove collateral from a position, causing notional size to decrease
    UpdatePositionRemoveCollateralImpactSize {
        /// ID of position to update
        pos_id: PositionId,
        /// Amount of funds to remove from the position
        amount: NonZero<Collateral>,
        /// Crank fee to be paid
        crank_fee: Collateral,
    },
}

impl WorkDescription {
    /// Is it closed position ?
    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,
        }
    }

    /// Is it collect closed position ?
    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,
        }
    }

    /// Is it update position ?
    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,
        }
    }

    /// Is it open position ?
    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,
        }
    }

    /// Is it open position ?
    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"
            ),
        }
    }
}

/// Migration message, currently no fields needed
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}