1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4use cosmwasm_std::{StdError, StdResult, Uint128};
5use cw20::{Cw20Coin, MinterResponse};
6
7#[derive(Serialize, Deserialize, JsonSchema)]
9pub struct InstantiateMsg {
10 pub name: String,
11 pub symbol: String,
12 pub decimals: u8,
13 pub initial_balances: Vec<Cw20Coin>,
14 pub mint: Option<MinterResponse>,
15}
16
17impl InstantiateMsg {
18 pub fn get_cap(&self) -> Option<Uint128> {
19 self.mint.as_ref().and_then(|v| v.cap)
20 }
21
22 pub fn validate(&self) -> StdResult<()> {
23 if !is_valid_name(&self.name) {
25 return Err(StdError::generic_err(
26 "Name is not in the expected format (3-50 UTF-8 bytes)",
27 ));
28 }
29 if !is_valid_symbol(&self.symbol) {
30 return Err(StdError::generic_err(
31 "Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}",
32 ));
33 }
34 if self.decimals > 18 {
35 return Err(StdError::generic_err("Decimals must not exceed 18"));
36 }
37 Ok(())
38 }
39}
40
41fn is_valid_name(name: &str) -> bool {
42 let bytes = name.as_bytes();
43 if bytes.len() < 3 || bytes.len() > 50 {
44 return false;
45 }
46 true
47}
48
49fn is_valid_symbol(symbol: &str) -> bool {
50 let bytes = symbol.as_bytes();
51 if bytes.len() < 3 || bytes.len() > 12 {
52 return false;
53 }
54 for byte in bytes.iter() {
55 if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
56 return false;
57 }
58 }
59 true
60}
61
62#[cfg(test)]
63mod test {
64 use super::*;
65
66 #[test]
67 fn get_cap() {
68 let msg = InstantiateMsg {
69 decimals: 6u8,
70 initial_balances: vec![],
71 mint: Some(MinterResponse {
72 cap: Some(Uint128::from(1u128)),
73 minter: "minter0000".to_string(),
74 }),
75 name: "test_token".to_string(),
76 symbol: "TNT".to_string(),
77 };
78
79 assert_eq!(msg.get_cap(), Some(Uint128::from(1u128)))
80 }
81
82 #[test]
83 fn validate() {
84 let valid_msg = InstantiateMsg {
85 decimals: 6u8,
86 initial_balances: vec![],
87 mint: Some(MinterResponse {
88 cap: Some(Uint128::from(1u128)),
89 minter: "minter0000".to_string(),
90 }),
91 name: "test_token".to_string(),
92 symbol: "TNT".to_string(),
93 };
94
95 assert_eq!(valid_msg.validate(), Ok(()));
96
97 let name_invalid_msg = InstantiateMsg {
98 decimals: 6u8,
99 initial_balances: vec![],
100 mint: Some(MinterResponse {
101 cap: Some(Uint128::from(1u128)),
102 minter: "minter0000".to_string(),
103 }),
104 name: "a".to_string(),
105 symbol: "TNT".to_string(),
106 };
107
108 assert_eq!(
109 name_invalid_msg.validate(),
110 Err(StdError::generic_err(
111 "Name is not in the expected format (3-50 UTF-8 bytes)",
112 ))
113 );
114
115 let symbol_invalid_msg = InstantiateMsg {
116 decimals: 6u8,
117 initial_balances: vec![],
118 mint: Some(MinterResponse {
119 cap: Some(Uint128::from(1u128)),
120 minter: "minter0000".to_string(),
121 }),
122 name: "test_token".to_string(),
123 symbol: "TN".to_string(),
124 };
125
126 assert_eq!(
127 symbol_invalid_msg.validate(),
128 Err(StdError::generic_err(
129 "Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}",
130 ))
131 );
132
133 let decimal_invalid_msg = InstantiateMsg {
134 decimals: 20u8,
135 initial_balances: vec![],
136 mint: Some(MinterResponse {
137 cap: Some(Uint128::from(1u128)),
138 minter: "minter0000".to_string(),
139 }),
140 name: "test_token".to_string(),
141 symbol: "TNT".to_string(),
142 };
143
144 assert_eq!(
145 decimal_invalid_msg.validate(),
146 Err(StdError::generic_err("Decimals must not exceed 18"))
147 );
148 }
149}