levana_perpswap_cosmos/
shutdown.rs

1//! Types for market kill switch and winddown.
2//!
3//! These two mechanisms both allow authorized wallets to shut down parts of the
4//! protocol, either at a market level or the entire protocol. Therefore they
5//! share a set of types here.
6
7use crate::prelude::*;
8use std::collections::HashMap;
9
10use cw_storage_plus::{Key, KeyDeserialize, PrimaryKey};
11use once_cell::sync::Lazy;
12
13/// Which wallet called the shutdown action?
14#[derive(Debug, Clone, Copy)]
15pub enum ShutdownWallet {
16    /// The kill switch wallet
17    KillSwitch,
18    /// The wind down wallet
19    WindDown,
20}
21
22/// Which part of the protocol should be impacted
23#[cw_serde]
24#[derive(enum_iterator::Sequence, Copy, Hash, Eq, PartialOrd, Ord)]
25pub enum ShutdownImpact {
26    /// Ability to open new positions and update existing positions.
27    ///
28    /// Includes: updating trigger orders, creating limit orders.
29    NewTrades,
30    /// Ability to close positions
31    ClosePositions,
32    /// Any owner actions on the market
33    OwnerActions,
34    /// Deposit liquidity, including reinvesting yield
35    DepositLiquidity,
36    /// Withdraw liquidity in any way
37    ///
38    /// Includes withdrawing, claiming yield
39    WithdrawLiquidity,
40    /// Any activities around xLP staking
41    Staking,
42    /// Any activities around unstaking xLP, including collecting
43    Unstaking,
44    /// Transfers of positions tokens
45    TransferPositions,
46    /// Transfers of liquidity tokens, both LP and xLP
47    TransferLp,
48    /// Setting the price
49    SetPrice,
50    /// Transfer DAO fees
51    TransferDaoFees,
52    /// Turning the crank
53    Crank,
54    /// Setting manual price
55    SetManualPrice,
56}
57
58impl ShutdownImpact {
59    /// Check if the wallet in question is allowed to perform the given action
60    pub fn can_perform(self, shutdown_wallet: ShutdownWallet) -> bool {
61        match (shutdown_wallet, self) {
62            (ShutdownWallet::KillSwitch, _) => true,
63            (ShutdownWallet::WindDown, ShutdownImpact::NewTrades) => true,
64            (ShutdownWallet::WindDown, ShutdownImpact::ClosePositions) => false,
65            (ShutdownWallet::WindDown, ShutdownImpact::OwnerActions) => false,
66            (ShutdownWallet::WindDown, ShutdownImpact::DepositLiquidity) => true,
67            (ShutdownWallet::WindDown, ShutdownImpact::WithdrawLiquidity) => false,
68            (ShutdownWallet::WindDown, ShutdownImpact::Staking) => true,
69            (ShutdownWallet::WindDown, ShutdownImpact::Unstaking) => false,
70            (ShutdownWallet::WindDown, ShutdownImpact::TransferPositions) => false,
71            (ShutdownWallet::WindDown, ShutdownImpact::TransferLp) => false,
72            (ShutdownWallet::WindDown, ShutdownImpact::SetPrice) => false,
73            (ShutdownWallet::WindDown, ShutdownImpact::TransferDaoFees) => false,
74            (ShutdownWallet::WindDown, ShutdownImpact::Crank) => false,
75            (ShutdownWallet::WindDown, ShutdownImpact::SetManualPrice) => false,
76        }
77    }
78
79    /// Return an error if not allowed to perform
80    pub fn ensure_can_perform(self, shutdown_wallet: ShutdownWallet) -> Result<()> {
81        if self.can_perform(shutdown_wallet) {
82            Ok(())
83        } else {
84            let msg = format!("{shutdown_wallet:?} cannot perform {self:?}");
85            Err(anyhow!(PerpError::auth(ErrorDomain::Factory, msg)))
86        }
87    }
88
89    /// Determines which shutdown impact, if any, gates the given market action
90    pub fn for_market_execute_msg(
91        msg: &crate::contracts::market::entry::ExecuteMsg,
92    ) -> Option<Self> {
93        use crate::contracts::market::entry::ExecuteMsg;
94        match msg {
95            ExecuteMsg::Owner(_) => Some(Self::OwnerActions),
96            // Ignore here to avoid a double parse, and require this be checked explicitly in the market contract.
97            ExecuteMsg::Receive { .. } => None,
98            ExecuteMsg::OpenPosition { .. } => Some(Self::NewTrades),
99            ExecuteMsg::UpdatePositionAddCollateralImpactLeverage { .. } => Some(Self::NewTrades),
100            ExecuteMsg::UpdatePositionAddCollateralImpactSize { .. } => Some(Self::NewTrades),
101            ExecuteMsg::UpdatePositionRemoveCollateralImpactLeverage { .. } => {
102                Some(Self::NewTrades)
103            }
104            ExecuteMsg::UpdatePositionRemoveCollateralImpactSize { .. } => Some(Self::NewTrades),
105            ExecuteMsg::UpdatePositionLeverage { .. } => Some(Self::NewTrades),
106            ExecuteMsg::UpdatePositionMaxGains { .. } => Some(Self::NewTrades),
107            ExecuteMsg::UpdatePositionTakeProfitPrice { .. } => Some(Self::NewTrades),
108            ExecuteMsg::UpdatePositionStopLossPrice { .. } => Some(Self::NewTrades),
109            #[allow(deprecated)]
110            ExecuteMsg::SetTriggerOrder { .. } => Some(Self::NewTrades),
111            ExecuteMsg::ClosePosition { .. } => Some(Self::ClosePositions),
112            ExecuteMsg::DepositLiquidity { .. } => Some(Self::DepositLiquidity),
113            ExecuteMsg::ReinvestYield { .. } => Some(Self::DepositLiquidity),
114            ExecuteMsg::WithdrawLiquidity { .. } => Some(Self::WithdrawLiquidity),
115            ExecuteMsg::ClaimYield {} => Some(Self::WithdrawLiquidity),
116            ExecuteMsg::StakeLp { .. } => Some(Self::Staking),
117            ExecuteMsg::UnstakeXlp { .. } => Some(Self::Unstaking),
118            ExecuteMsg::StopUnstakingXlp {} => Some(Self::Unstaking),
119            ExecuteMsg::CollectUnstakedLp {} => Some(Self::Unstaking),
120            ExecuteMsg::Crank { .. } => Some(Self::Crank),
121            ExecuteMsg::NftProxy { .. } => Some(Self::TransferPositions),
122            ExecuteMsg::LiquidityTokenProxy { .. } => Some(Self::TransferLp),
123            ExecuteMsg::TransferDaoFees { .. } => Some(Self::TransferDaoFees),
124            ExecuteMsg::CloseAllPositions {} => None,
125            ExecuteMsg::PlaceLimitOrder { .. } => Some(Self::NewTrades),
126            ExecuteMsg::CancelLimitOrder { .. } => Some(Self::ClosePositions),
127            ExecuteMsg::ProvideCrankFunds {} => Some(Self::Crank),
128            ExecuteMsg::SetManualPrice { .. } => Some(Self::SetManualPrice),
129
130            // Since this can only be executed by the contract itself, it's safe to never block it
131            ExecuteMsg::PerformDeferredExec { .. } => None,
132        }
133    }
134}
135
136/// Are we turning off these features or turning them back on?
137#[cw_serde]
138#[derive(Copy)]
139pub enum ShutdownEffect {
140    /// Disable the given portion of the protocol
141    Disable,
142    /// Turn the given portion of the protocol back on
143    Enable,
144}
145
146impl ShutdownImpact {
147    /// Convert into a binary representation using the Debug impl.
148    pub(crate) fn as_bytes(self) -> &'static [u8] {
149        static LOOKUP: Lazy<HashMap<ShutdownImpact, Vec<u8>>> = Lazy::new(|| {
150            enum_iterator::all::<ShutdownImpact>()
151                .map(|x| (x, format!("{x:?}").into_bytes()))
152                .collect()
153        });
154        LOOKUP
155            .get(&self)
156            .expect("Impossible! ShutdownImpact::as_bytes failed")
157    }
158
159    /// Parse this value from a binary representation of the Debug output.
160    pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self> {
161        static LOOKUP: Lazy<HashMap<Vec<u8>, ShutdownImpact>> = Lazy::new(|| {
162            enum_iterator::all::<ShutdownImpact>()
163                .map(|x| (format!("{x:?}").into_bytes(), x))
164                .collect()
165        });
166        LOOKUP.get(bytes).copied().with_context(|| {
167            format!(
168                "Unable to parse as ShutdownImpact: {:?}",
169                std::str::from_utf8(bytes)
170            )
171        })
172    }
173}
174
175impl KeyDeserialize for ShutdownImpact {
176    type Output = ShutdownImpact;
177
178    const KEY_ELEMS: u16 = 1;
179
180    fn from_vec(value: Vec<u8>) -> cosmwasm_std::StdResult<Self::Output> {
181        ShutdownImpact::try_from_bytes(&value)
182            .map_err(|x| cosmwasm_std::StdError::parse_err("ShutdownImpact", x))
183    }
184}
185
186impl<'a> PrimaryKey<'a> for ShutdownImpact {
187    type Prefix = ();
188    type SubPrefix = ();
189    type Suffix = Self;
190    type SuperSuffix = Self;
191
192    fn key(&self) -> Vec<Key> {
193        vec![Key::Ref(self.as_bytes())]
194    }
195}