cw20_base/
msg.rs

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