abstract_cw20_base/
msg.rs1use 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 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(abstract_cw20::BalanceResponse)]
76 Balance { address: String },
77 #[returns(abstract_cw20::TokenInfoResponse)]
79 TokenInfo {},
80 #[returns(abstract_cw20::MinterResponse)]
83 Minter {},
84 #[returns(abstract_cw20::AllowanceResponse)]
87 Allowance { owner: String, spender: String },
88 #[returns(abstract_cw20::AllAllowancesResponse)]
91 AllAllowances {
92 owner: String,
93 start_after: Option<String>,
94 limit: Option<u32>,
95 },
96 #[returns(abstract_cw20::AllSpenderAllowancesResponse)]
99 AllSpenderAllowances {
100 spender: String,
101 start_after: Option<String>,
102 limit: Option<u32>,
103 },
104 #[returns(abstract_cw20::AllAccountsResponse)]
107 AllAccounts {
108 start_after: Option<String>,
109 limit: Option<u32>,
110 },
111 #[returns(abstract_cw20::MarketingInfoResponse)]
115 MarketingInfo {},
116 #[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 let mut msg = InstantiateMsg {
134 name: str::repeat("a", 2),
135 ..InstantiateMsg::default()
136 };
137 assert!(!msg.has_valid_name());
138
139 msg.name = str::repeat("a", 3);
141 assert!(msg.has_valid_name());
142
143 msg.name = str::repeat("a", 51);
145 assert!(!msg.has_valid_name());
146 }
147
148 #[test]
149 fn validate_instantiatemsg_symbol() {
150 let mut msg = InstantiateMsg {
152 symbol: str::repeat("a", 2),
153 ..InstantiateMsg::default()
154 };
155 assert!(!msg.has_valid_symbol());
156
157 msg.symbol = str::repeat("a", 3);
159 assert!(msg.has_valid_symbol());
160
161 msg.symbol = str::repeat("a", 13);
163 assert!(!msg.has_valid_symbol());
164
165 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}