andromeda_std/os/
adodb.rs

1use std::str::FromStr;
2
3use cosmwasm_schema::{cw_serde, QueryResponses};
4use cosmwasm_std::{ensure, Addr, Api, Uint128};
5use schemars::JsonSchema;
6use semver::Version;
7use serde::{Deserialize, Serialize};
8
9use crate::{ado_base::ownership::OwnershipMessage, error::ContractError};
10
11#[cw_serde]
12pub struct InstantiateMsg {
13    pub kernel_address: String,
14    pub owner: Option<String>,
15}
16
17#[cw_serde]
18pub enum ExecuteMsg {
19    Publish {
20        code_id: u64,
21        ado_type: String,
22        action_fees: Option<Vec<ActionFee>>,
23        version: String,
24        publisher: Option<String>,
25    },
26    Unpublish {
27        ado_type: String,
28        version: String,
29    },
30    UpdateActionFees {
31        ado_type: String,
32        action_fees: Vec<ActionFee>,
33    },
34    RemoveActionFees {
35        ado_type: String,
36        actions: Vec<String>,
37    },
38    UpdatePublisher {
39        ado_type: String,
40        publisher: String,
41    },
42    // Base message
43    Ownership(OwnershipMessage),
44}
45
46#[cw_serde]
47pub struct ActionFee {
48    pub action: String,
49    pub asset: String,
50    pub amount: Uint128,
51    pub receiver: Option<Addr>,
52}
53
54impl ActionFee {
55    pub fn new(action: String, asset: String, amount: Uint128) -> Self {
56        Self {
57            action,
58            asset,
59            amount,
60            receiver: None,
61        }
62    }
63
64    pub fn with_receive(&self, receiver: Addr) -> Self {
65        Self {
66            action: self.action.clone(),
67            asset: self.asset.clone(),
68            amount: self.amount,
69            receiver: Some(receiver),
70        }
71    }
72
73    /// Valiades the provided asset for an action fee
74    /// An asset is valid if it fits the format "cw20:address" or "native:denom"
75    /// If the asset type is cw20 the address is also validated
76    /// TODO: Add denom validation in future cosmwasm version
77    pub fn validate_asset(&self, api: &dyn Api) -> Result<(), ContractError> {
78        let asset_split = self.asset.split(':').collect::<Vec<&str>>();
79        // Ensure asset is in the format "cw20:address" or "native:denom"
80        // This is double validated as the asset type in the ADODB contract for fees is validated as cw20:* or native:*
81        ensure!(
82            asset_split.len() == 2 && !asset_split.is_empty(),
83            ContractError::InvalidAsset {
84                asset: self.asset.clone()
85            }
86        );
87        let asset_type = asset_split[0];
88        ensure!(
89            asset_type == "cw20" || asset_type == "native",
90            ContractError::InvalidAsset {
91                asset: self.asset.clone()
92            }
93        );
94
95        if asset_type == "cw20" {
96            api.addr_validate(asset_split[1])?;
97        }
98
99        Ok(())
100    }
101
102    /// Gets the asset string without the asset type
103    ///
104    /// i.e. **cw20:address** would return **"address"** or native:denom would return **"denom"**
105    pub fn get_asset_string(&self) -> Result<&str, ContractError> {
106        ensure!(
107            self.asset.contains(':'),
108            ContractError::InvalidAsset {
109                asset: self.asset.clone()
110            }
111        );
112        match self.asset.split(':').last() {
113            Some(asset) => Ok(asset),
114            None => Err(ContractError::InvalidAsset {
115                asset: self.asset.clone(),
116            }),
117        }
118    }
119}
120
121#[cw_serde]
122pub struct ADOMetadata {
123    pub publisher: String,
124    pub latest_version: String,
125}
126
127#[cw_serde]
128#[derive(QueryResponses)]
129pub enum QueryMsg {
130    #[returns(u64)]
131    CodeId { key: String },
132    // #[returns(Vec<u64>)]
133    // UnpublishedCodeIds {},
134    #[returns(IsUnpublishedCodeIdResponse)]
135    IsUnpublishedCodeId { code_id: u64 },
136    #[returns(Option<String>)]
137    #[serde(rename = "ado_type")]
138    ADOType { code_id: u64 },
139    #[returns(Vec<String>)]
140    #[serde(rename = "all_ado_types")]
141    AllADOTypes {
142        start_after: Option<String>,
143        limit: Option<u32>,
144    },
145    #[returns(Vec<String>)]
146    #[serde(rename = "ado_versions")]
147    ADOVersions {
148        ado_type: String,
149        start_after: Option<String>,
150        limit: Option<u32>,
151    },
152    // #[returns(Vec<String>)]
153    // #[serde(rename = "unpublished_ado_versions")]
154    // UnpublishedADOVersions { ado_type: String },
155    #[returns(Option<ADOMetadata>)]
156    #[serde(rename = "ado_metadata")]
157    ADOMetadata { ado_type: String },
158    #[returns(Option<ActionFee>)]
159    ActionFee { ado_type: String, action: String },
160    #[returns(Option<ActionFee>)]
161    ActionFeeByCodeId { code_id: u64, action: String },
162    // Base queries
163    #[returns(crate::ado_base::version::VersionResponse)]
164    Version {},
165    #[returns(crate::ado_base::ado_type::TypeResponse)]
166    Type {},
167    #[returns(crate::ado_base::ownership::ContractOwnerResponse)]
168    Owner {},
169    #[returns(crate::ado_base::kernel_address::KernelAddressResponse)]
170    KernelAddress {},
171}
172
173#[cw_serde]
174pub struct IsUnpublishedCodeIdResponse {
175    pub is_unpublished_code_id: bool,
176}
177
178#[derive(
179    Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, JsonSchema,
180)]
181pub struct ADOVersion(String);
182
183impl ADOVersion {
184    #[inline]
185    pub fn as_str(&self) -> &str {
186        self.0.as_str()
187    }
188
189    #[inline]
190    pub fn as_bytes(&self) -> &[u8] {
191        self.0.as_bytes()
192    }
193
194    #[inline]
195    pub fn into_string(self) -> String {
196        self.0
197    }
198
199    #[inline]
200    pub fn from_string(string: impl Into<String>) -> ADOVersion {
201        ADOVersion(string.into())
202    }
203
204    #[inline]
205    pub fn from_type(ado_type: impl Into<String>) -> ADOVersion {
206        ADOVersion(ado_type.into())
207    }
208
209    #[inline]
210    pub fn with_version(&self, version: impl Into<String>) -> ADOVersion {
211        let mut ado_version = self.clone();
212        // Remove any previous version string if present
213        ado_version.0 = ado_version.get_type();
214        ado_version.0.push('@');
215        ado_version.0.push_str(&version.into());
216        ado_version
217    }
218
219    /// Validates a given ADOVersion
220    ///
221    /// A valid ADOVersion must:
222    /// 1. Be non-empty
223    /// 2. Have at most one `@` symbol
224    ///
225    /// ### Examples
226    /// - `ado_type@0.1.0`
227    /// - `ado_type`
228    /// - `ado_type@latest`
229    pub fn validate(&self) -> bool {
230        !self.clone().into_string().is_empty()
231            && self.clone().into_string().split('@').count() <= 2
232            && (self.get_version() == "latest"
233                || Version::from_str(self.get_version().as_str()).is_ok())
234    }
235
236    /// Gets the version for the given ADOVersion
237    ///
238    /// Returns `"latest"` if no version provided
239    pub fn get_version(&self) -> String {
240        match self
241            .clone()
242            .into_string()
243            .split('@')
244            .collect::<Vec<&str>>()
245            .len()
246        {
247            1 => "latest".to_string(),
248            _ => self.clone().into_string().split('@').collect::<Vec<&str>>()[1].to_string(),
249        }
250    }
251
252    /// Gets the type for the given ADOVersion
253    pub fn get_type(&self) -> String {
254        self.clone().into_string().split('@').collect::<Vec<&str>>()[0].to_string()
255    }
256
257    /// Gets the type for the given ADOVersion
258    pub fn get_tuple(&self) -> (String, String) {
259        (self.get_type(), self.get_version())
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use cosmwasm_std::testing::mock_dependencies;
266
267    use super::*;
268
269    #[test]
270    fn test_validate() {
271        let ado_version = ADOVersion::from_string("valid_version");
272        assert!(ado_version.validate());
273
274        let ado_version = ADOVersion::from_string("valid_version@0.1.0");
275        assert!(ado_version.validate());
276
277        let ado_version = ADOVersion::from_string("");
278        assert!(!ado_version.validate());
279
280        let ado_version = ADOVersion::from_string("not@valid@version");
281        assert!(!ado_version.validate());
282    }
283
284    #[test]
285    fn test_get_version() {
286        let ado_version = ADOVersion::from_string("ado_type");
287        assert_eq!(ado_version.get_version(), "latest");
288
289        let ado_version = ADOVersion::from_string("ado_type@0.1.0");
290        assert_eq!(ado_version.get_version(), "0.1.0");
291
292        let ado_version = ADOVersion::from_string("ado_type@latest");
293        assert_eq!(ado_version.get_version(), "latest");
294    }
295
296    #[test]
297    fn test_get_type() {
298        let ado_version = ADOVersion::from_string("ado_type");
299        assert_eq!(ado_version.get_type(), "ado_type");
300
301        let ado_version = ADOVersion::from_string("ado_type@0.1.0");
302        assert_eq!(ado_version.get_type(), "ado_type");
303
304        let ado_version = ADOVersion::from_string("ado_type@latest");
305        assert_eq!(ado_version.get_type(), "ado_type");
306    }
307
308    #[test]
309    fn test_action_fee_asset() {
310        let deps = mock_dependencies();
311        let action_fee = ActionFee::new(
312            "action".to_string(),
313            "cw20:address".to_string(),
314            Uint128::zero(),
315        );
316        assert!(action_fee.validate_asset(deps.as_ref().api).is_ok());
317
318        let action_fee = ActionFee::new(
319            "action".to_string(),
320            "native:denom".to_string(),
321            Uint128::zero(),
322        );
323        assert!(action_fee.validate_asset(deps.as_ref().api).is_ok());
324
325        let action_fee =
326            ActionFee::new("action".to_string(), "cw20:aw".to_string(), Uint128::zero());
327        assert!(action_fee.validate_asset(deps.as_ref().api).is_err());
328
329        let action_fee =
330            ActionFee::new("action".to_string(), "invalid".to_string(), Uint128::zero());
331        assert!(action_fee.validate_asset(deps.as_ref().api).is_err());
332    }
333}