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 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(cw20::BalanceResponse)]
78 Balance { address: String },
79 #[returns(cw20::TokenInfoResponse)]
81 TokenInfo {},
82 #[returns(cw20::MinterResponse)]
85 Minter {},
86 #[returns(cw20::AllowanceResponse)]
89 Allowance { owner: String, spender: String },
90 #[returns(cw20::AllAllowancesResponse)]
93 AllAllowances {
94 owner: String,
95 start_after: Option<String>,
96 limit: Option<u32>,
97 },
98 #[returns(cw20::AllSpenderAllowancesResponse)]
101 AllSpenderAllowances {
102 spender: String,
103 start_after: Option<String>,
104 limit: Option<u32>,
105 },
106 #[returns(cw20::AllAccountsResponse)]
109 AllAccounts {
110 start_after: Option<String>,
111 limit: Option<u32>,
112 },
113 #[returns(cw20::MarketingInfoResponse)]
117 MarketingInfo {},
118 #[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 let mut msg = InstantiateMsg {
136 name: str::repeat("a", 2),
137 ..InstantiateMsg::default()
138 };
139 assert!(!msg.has_valid_name());
140
141 msg.name = str::repeat("a", 3);
143 assert!(msg.has_valid_name());
144
145 msg.name = str::repeat("a", 51);
147 assert!(!msg.has_valid_name());
148 }
149
150 #[test]
151 fn validate_instantiatemsg_symbol() {
152 let mut msg = InstantiateMsg {
154 symbol: str::repeat("a", 2),
155 ..InstantiateMsg::default()
156 };
157 assert!(!msg.has_valid_symbol());
158
159 msg.symbol = str::repeat("a", 3);
161 assert!(msg.has_valid_symbol());
162
163 msg.symbol = str::repeat("a", 13);
165 assert!(!msg.has_valid_symbol());
166
167 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}