levana_perpswap_cosmos/contracts/
vault.rs

1//! Vault contract
2use super::cw20::Cw20ReceiveMsg;
3use anyhow::{ensure, Result};
4use cosmwasm_std::{Addr, Api, Uint128};
5use serde::{Deserialize, Deserializer};
6use std::{collections::HashMap, fmt};
7
8/// Message to instantiate the contract
9#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
10#[serde(rename_all = "snake_case")]
11pub struct InstantiateMsg {
12    /// Denomination of the USDC token
13    pub usdc_denom: UsdcAssetInit,
14
15    /// Governance address (as string, validated later)
16    pub governance: String,
17
18    /// Initial allocation percentages to markets
19    pub markets_allocation_bps: HashMap<String, u16>,
20}
21
22/// Denomination of USDC token
23#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
24#[serde(rename_all = "snake_case")]
25pub enum UsdcAsset {
26    /// CW20 USDC
27    CW20(Addr),
28    /// Native USDC
29    Native(String),
30}
31/// Input enum for user-provided asset specification
32#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
33#[serde(rename_all = "snake_case")]
34pub enum UsdcAssetInit {
35    /// CW20 User Input
36    CW20 {
37        /// Address of the CW20 token
38        address: String,
39    },
40    /// Native User Input
41    Native {
42        /// Denomination of the native token
43        denom: String,
44    },
45}
46
47impl fmt::Display for UsdcAsset {
48    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49        match self {
50            UsdcAsset::CW20(addr) => write!(f, "{}", addr),
51            UsdcAsset::Native(denom) => write!(f, "{}", denom),
52        }
53    }
54}
55
56impl UsdcAsset {
57    /// Convert from user input with validation
58    pub fn from_init(api: &dyn Api, init: UsdcAssetInit) -> Result<Self> {
59        match init {
60            UsdcAssetInit::CW20 { address } => {
61                let addr = api.addr_validate(&address)?;
62                Ok(UsdcAsset::CW20(addr))
63            }
64            UsdcAssetInit::Native { denom } => {
65                ensure!(!denom.is_empty(), "Native denom cannot be empty");
66                Ok(UsdcAsset::Native(denom))
67            }
68        }
69    }
70}
71
72/// Configuration structure for the vault
73#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
74#[serde(rename_all = "snake_case")]
75pub struct Config {
76    /// Denomination of the USDC token
77    pub usdc_denom: UsdcAsset,
78
79    /// Address authorized for critical actions (like pausing the contract)
80    pub governance: Addr,
81
82    /// Allocation percentages to markets in basis points (100 bps = 1%)
83    #[serde(deserialize_with = "deserialize_markets_allocation_bps")]
84    pub markets_allocation_bps: HashMap<Addr, u16>,
85
86    /// state::PAUSED
87    pub paused: bool,
88}
89
90/// Executable messages for the contract
91#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
92#[serde(rename_all = "snake_case")]
93pub enum ExecuteMsg {
94    /// Deposit USDC and receive USDCLP
95    Deposit {},
96
97    /// Receive CW20
98    Receive(Cw20ReceiveMsg),
99
100    /// Request withdrawal by burning USDCLP
101    RequestWithdrawal {
102        /// Amount to withdraw
103        amount: Uint128,
104    },
105
106    /// Redistribute excess funds to markets
107    RedistributeFunds {},
108
109    /// Collect yields from markets
110    CollectYield {},
111
112    /// Process a pending withdrawal
113    ProcessWithdrawal {},
114
115    /// Withdraw funds from a market
116    WithdrawFromMarket {
117        /// From Market
118        market: String,
119        /// Amount
120        amount: Uint128,
121    },
122
123    /// Pause the contract in an emergency
124    EmergencyPause {},
125
126    /// Resume contract operations
127    ResumeOperations {},
128
129    /// Update allocation percentages
130    UpdateAllocations {
131        /// New allocations for Markets
132        #[serde(deserialize_with = "deserialize_markets_allocation_bps")]
133        new_allocations: HashMap<Addr, u16>,
134    },
135
136    /// Add a new market to the vault
137    AddMarket {
138        /// Market address to add
139        market: String,
140    },
141}
142
143/// Query messages for the contract
144#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
145#[serde(rename_all = "snake_case")]
146pub enum QueryMsg {
147    /// Query the USDC balance in the vault
148    GetVaultBalance {},
149
150    /// Query a user's pending withdrawal
151    GetPendingWithdrawal {
152        /// User to get pending withdrawal
153        user: String,
154    },
155
156    /// Query total assets (balance + allocations)
157    GetTotalAssets {},
158
159    /// Query market allocations
160    GetMarketAllocations {
161        /// Start from market
162        start_after: Option<String>,
163    },
164
165    /// Query the vault's configuration
166    GetConfig {},
167}
168
169fn deserialize_markets_allocation_bps<'de, D>(
170    deserializer: D,
171) -> Result<HashMap<Addr, u16>, D::Error>
172where
173    D: Deserializer<'de>,
174{
175    let string_map = HashMap::<String, u16>::deserialize(deserializer)?;
176    let addr_map = string_map
177        .into_iter()
178        .map(|(k, v)| (Addr::unchecked(k), v))
179        .collect();
180    Ok(addr_map)
181}