use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{StdError, StdResult, Uint128};
use cw20::{Cw20Coin, Logo, MinterResponse};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use cw20::Cw20ExecuteMsg as ExecuteMsg;
#[cw_serde]
pub struct InstantiateMarketingInfo {
pub project: Option<String>,
pub description: Option<String>,
pub marketing: Option<String>,
pub logo: Option<Logo>,
}
#[cw_serde]
#[cfg_attr(test, derive(Default))]
pub struct InstantiateMsg {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub initial_balances: Vec<Cw20Coin>,
pub mint: Option<MinterResponse>,
pub marketing: Option<InstantiateMarketingInfo>,
}
impl InstantiateMsg {
pub fn get_cap(&self) -> Option<Uint128> {
self.mint.as_ref().and_then(|v| v.cap)
}
pub fn validate(&self) -> StdResult<()> {
if !self.has_valid_name() {
return Err(StdError::generic_err(
"Name is not in the expected format (3-50 UTF-8 bytes)",
));
}
if !self.has_valid_symbol() {
return Err(StdError::generic_err(
"Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}",
));
}
if self.decimals > 18 {
return Err(StdError::generic_err("Decimals must not exceed 18"));
}
Ok(())
}
fn has_valid_name(&self) -> bool {
let bytes = self.name.as_bytes();
if bytes.len() < 3 || bytes.len() > 50 {
return false;
}
true
}
fn has_valid_symbol(&self) -> bool {
let bytes = self.symbol.as_bytes();
if bytes.len() < 3 || bytes.len() > 12 {
return false;
}
for byte in bytes.iter() {
if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
return false;
}
}
true
}
}
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
#[returns(cw20::BalanceResponse)]
Balance { address: String },
#[returns(cw20::TokenInfoResponse)]
TokenInfo {},
#[returns(cw20::MinterResponse)]
Minter {},
#[returns(cw20::AllowanceResponse)]
Allowance { owner: String, spender: String },
#[returns(cw20::AllAllowancesResponse)]
AllAllowances {
owner: String,
start_after: Option<String>,
limit: Option<u32>,
},
#[returns(cw20::AllSpenderAllowancesResponse)]
AllSpenderAllowances {
spender: String,
start_after: Option<String>,
limit: Option<u32>,
},
#[returns(cw20::AllAccountsResponse)]
AllAccounts {
start_after: Option<String>,
limit: Option<u32>,
},
#[returns(cw20::MarketingInfoResponse)]
MarketingInfo {},
#[returns(cw20::DownloadLogoResponse)]
DownloadLogo {},
}
#[derive(Serialize, Deserialize, JsonSchema)]
pub struct MigrateMsg {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_instantiatemsg_name() {
let mut msg = InstantiateMsg {
name: str::repeat("a", 2),
..InstantiateMsg::default()
};
assert!(!msg.has_valid_name());
msg.name = str::repeat("a", 3);
assert!(msg.has_valid_name());
msg.name = str::repeat("a", 51);
assert!(!msg.has_valid_name());
}
#[test]
fn validate_instantiatemsg_symbol() {
let mut msg = InstantiateMsg {
symbol: str::repeat("a", 2),
..InstantiateMsg::default()
};
assert!(!msg.has_valid_symbol());
msg.symbol = str::repeat("a", 3);
assert!(msg.has_valid_symbol());
msg.symbol = str::repeat("a", 13);
assert!(!msg.has_valid_symbol());
let illegal_chars = [[64u8], [91u8], [123u8]];
illegal_chars.iter().for_each(|c| {
let c = std::str::from_utf8(c).unwrap();
msg.symbol = str::repeat(c, 3);
assert!(!msg.has_valid_symbol());
});
}
}