abstract_cw20_base/
msg.rs

1use abstract_cw20::{Cw20Coin, Logo, MinterResponse};
2use cosmwasm_schema::{cw_serde, QueryResponses};
3use cosmwasm_std::{StdError, StdResult, Uint128};
4
5pub use abstract_cw20::Cw20ExecuteMsg as ExecuteMsg;
6
7#[cw_serde]
8pub struct InstantiateMarketingInfo {
9    pub project: Option<String>,
10    pub description: Option<String>,
11    pub marketing: Option<String>,
12    pub logo: Option<Logo>,
13}
14
15#[cw_serde]
16#[cfg_attr(test, derive(Default))]
17pub struct InstantiateMsg {
18    pub name: String,
19    pub symbol: String,
20    pub decimals: u8,
21    pub initial_balances: Vec<Cw20Coin>,
22    pub mint: Option<MinterResponse>,
23    pub marketing: Option<InstantiateMarketingInfo>,
24}
25
26impl InstantiateMsg {
27    pub fn get_cap(&self) -> Option<Uint128> {
28        self.mint.as_ref().and_then(|v| v.cap)
29    }
30
31    pub fn validate(&self) -> StdResult<()> {
32        // Check name, symbol, decimals
33        if !self.has_valid_name() {
34            return Err(StdError::generic_err(
35                "Name is not in the expected format (3-50 UTF-8 bytes)",
36            ));
37        }
38        if !self.has_valid_symbol() {
39            return Err(StdError::generic_err(
40                "Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}",
41            ));
42        }
43        if self.decimals > 18 {
44            return Err(StdError::generic_err("Decimals must not exceed 18"));
45        }
46        Ok(())
47    }
48
49    fn has_valid_name(&self) -> bool {
50        let bytes = self.name.as_bytes();
51        if bytes.len() < 3 || bytes.len() > 50 {
52            return false;
53        }
54        true
55    }
56
57    fn has_valid_symbol(&self) -> bool {
58        let bytes = self.symbol.as_bytes();
59        if bytes.len() < 3 || bytes.len() > 12 {
60            return false;
61        }
62        for byte in bytes.iter() {
63            if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
64                return false;
65            }
66        }
67        true
68    }
69}
70
71#[cw_serde]
72#[derive(QueryResponses, cw_orch::QueryFns)]
73pub enum QueryMsg {
74    /// Returns the current balance of the given address, 0 if unset.
75    #[returns(abstract_cw20::BalanceResponse)]
76    Balance { address: String },
77    /// Returns metadata on the contract - name, decimals, supply, etc.
78    #[returns(abstract_cw20::TokenInfoResponse)]
79    TokenInfo {},
80    /// Only with "mintable" extension.
81    /// Returns who can mint and the hard cap on maximum tokens after minting.
82    #[returns(abstract_cw20::MinterResponse)]
83    Minter {},
84    /// Only with "allowance" extension.
85    /// Returns how much spender can use from owner account, 0 if unset.
86    #[returns(abstract_cw20::AllowanceResponse)]
87    Allowance { owner: String, spender: String },
88    /// Only with "enumerable" extension (and "allowances")
89    /// Returns all allowances this owner has approved. Supports pagination.
90    #[returns(abstract_cw20::AllAllowancesResponse)]
91    AllAllowances {
92        owner: String,
93        start_after: Option<String>,
94        limit: Option<u32>,
95    },
96    /// Only with "enumerable" extension (and "allowances")
97    /// Returns all allowances this spender has been granted. Supports pagination.
98    #[returns(abstract_cw20::AllSpenderAllowancesResponse)]
99    AllSpenderAllowances {
100        spender: String,
101        start_after: Option<String>,
102        limit: Option<u32>,
103    },
104    /// Only with "enumerable" extension
105    /// Returns all accounts that have balances. Supports pagination.
106    #[returns(abstract_cw20::AllAccountsResponse)]
107    AllAccounts {
108        start_after: Option<String>,
109        limit: Option<u32>,
110    },
111    /// Only with "marketing" extension
112    /// Returns more metadata on the contract to display in the client:
113    /// - description, logo, project url, etc.
114    #[returns(abstract_cw20::MarketingInfoResponse)]
115    MarketingInfo {},
116    /// Only with "marketing" extension
117    /// Downloads the embedded logo data (if stored on chain). Errors if no logo data is stored for this
118    /// contract.
119    #[returns(abstract_cw20::DownloadLogoResponse)]
120    DownloadLogo {},
121}
122
123#[cw_serde]
124pub struct MigrateMsg {}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn validate_instantiatemsg_name() {
132        // Too short
133        let mut msg = InstantiateMsg {
134            name: str::repeat("a", 2),
135            ..InstantiateMsg::default()
136        };
137        assert!(!msg.has_valid_name());
138
139        // In the correct length range
140        msg.name = str::repeat("a", 3);
141        assert!(msg.has_valid_name());
142
143        // Too long
144        msg.name = str::repeat("a", 51);
145        assert!(!msg.has_valid_name());
146    }
147
148    #[test]
149    fn validate_instantiatemsg_symbol() {
150        // Too short
151        let mut msg = InstantiateMsg {
152            symbol: str::repeat("a", 2),
153            ..InstantiateMsg::default()
154        };
155        assert!(!msg.has_valid_symbol());
156
157        // In the correct length range
158        msg.symbol = str::repeat("a", 3);
159        assert!(msg.has_valid_symbol());
160
161        // Too long
162        msg.symbol = str::repeat("a", 13);
163        assert!(!msg.has_valid_symbol());
164
165        // Has illegal char
166        let illegal_chars = [[64u8], [91u8], [123u8]];
167        illegal_chars.iter().for_each(|c| {
168            let c = std::str::from_utf8(c).unwrap();
169            msg.symbol = str::repeat(c, 3);
170            assert!(!msg.has_valid_symbol());
171        });
172    }
173}