cw20_base/
contract.rs

1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::Order::Ascending;
4use cosmwasm_std::{
5    to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128,
6};
7
8use cw2::{ensure_from_older_version, set_contract_version};
9use cw20::{
10    BalanceResponse, Cw20Coin, Cw20ReceiveMsg, DownloadLogoResponse, EmbeddedLogo, Logo, LogoInfo,
11    MarketingInfoResponse, MinterResponse, TokenInfoResponse,
12};
13
14use crate::allowances::{
15    execute_burn_from, execute_decrease_allowance, execute_increase_allowance, execute_send_from,
16    execute_transfer_from, query_allowance,
17};
18use crate::enumerable::{query_all_accounts, query_owner_allowances, query_spender_allowances};
19use crate::error::ContractError;
20use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
21use crate::state::{
22    MinterData, TokenInfo, ALLOWANCES, ALLOWANCES_SPENDER, BALANCES, LOGO, MARKETING_INFO,
23    TOKEN_INFO,
24};
25
26// version info for migration info
27const CONTRACT_NAME: &str = "crates.io:cw20-base";
28const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
29
30const LOGO_SIZE_CAP: usize = 5 * 1024;
31
32/// Checks if data starts with XML preamble
33fn verify_xml_preamble(data: &[u8]) -> Result<(), ContractError> {
34    // The easiest way to perform this check would be just match on regex, however regex
35    // compilation is heavy and probably not worth it.
36
37    let preamble = data
38        .split_inclusive(|c| *c == b'>')
39        .next()
40        .ok_or(ContractError::InvalidXmlPreamble {})?;
41
42    const PREFIX: &[u8] = b"<?xml ";
43    const POSTFIX: &[u8] = b"?>";
44
45    if !(preamble.starts_with(PREFIX) && preamble.ends_with(POSTFIX)) {
46        Err(ContractError::InvalidXmlPreamble {})
47    } else {
48        Ok(())
49    }
50
51    // Additionally attributes format could be validated as they are well defined, as well as
52    // comments presence inside of preable, but it is probably not worth it.
53}
54
55/// Validates XML logo
56fn verify_xml_logo(logo: &[u8]) -> Result<(), ContractError> {
57    verify_xml_preamble(logo)?;
58
59    if logo.len() > LOGO_SIZE_CAP {
60        Err(ContractError::LogoTooBig {})
61    } else {
62        Ok(())
63    }
64}
65
66/// Validates png logo
67fn verify_png_logo(logo: &[u8]) -> Result<(), ContractError> {
68    // PNG header format:
69    // 0x89 - magic byte, out of ASCII table to fail on 7-bit systems
70    // "PNG" ascii representation
71    // [0x0d, 0x0a] - dos style line ending
72    // 0x1a - dos control character, stop displaying rest of the file
73    // 0x0a - unix style line ending
74    const HEADER: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a];
75    if logo.len() > LOGO_SIZE_CAP {
76        Err(ContractError::LogoTooBig {})
77    } else if !logo.starts_with(&HEADER) {
78        Err(ContractError::InvalidPngHeader {})
79    } else {
80        Ok(())
81    }
82}
83
84/// Checks if passed logo is correct, and if not, returns an error
85fn verify_logo(logo: &Logo) -> Result<(), ContractError> {
86    match logo {
87        Logo::Embedded(EmbeddedLogo::Svg(logo)) => verify_xml_logo(logo),
88        Logo::Embedded(EmbeddedLogo::Png(logo)) => verify_png_logo(logo),
89        Logo::Url(_) => Ok(()), // Any reasonable url validation would be regex based, probably not worth it
90    }
91}
92
93#[cfg_attr(not(feature = "library"), entry_point)]
94pub fn instantiate(
95    mut deps: DepsMut,
96    _env: Env,
97    _info: MessageInfo,
98    msg: InstantiateMsg,
99) -> Result<Response, ContractError> {
100    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
101    // check valid token info
102    msg.validate()?;
103    // create initial accounts
104    let total_supply = create_accounts(&mut deps, &msg.initial_balances)?;
105
106    if let Some(limit) = msg.get_cap() {
107        if total_supply > limit {
108            return Err(StdError::generic_err("Initial supply greater than cap").into());
109        }
110    }
111
112    let mint = match msg.mint {
113        Some(m) => Some(MinterData {
114            minter: deps.api.addr_validate(&m.minter)?,
115            cap: m.cap,
116        }),
117        None => None,
118    };
119
120    // store token info
121    let data = TokenInfo {
122        name: msg.name,
123        symbol: msg.symbol,
124        decimals: msg.decimals,
125        total_supply,
126        mint,
127    };
128    TOKEN_INFO.save(deps.storage, &data)?;
129
130    if let Some(marketing) = msg.marketing {
131        let logo = if let Some(logo) = marketing.logo {
132            verify_logo(&logo)?;
133            LOGO.save(deps.storage, &logo)?;
134
135            match logo {
136                Logo::Url(url) => Some(LogoInfo::Url(url)),
137                Logo::Embedded(_) => Some(LogoInfo::Embedded),
138            }
139        } else {
140            None
141        };
142
143        let data = MarketingInfoResponse {
144            project: marketing.project,
145            description: marketing.description,
146            marketing: marketing
147                .marketing
148                .map(|addr| deps.api.addr_validate(&addr))
149                .transpose()?,
150            logo,
151        };
152        MARKETING_INFO.save(deps.storage, &data)?;
153    }
154
155    Ok(Response::default())
156}
157
158pub fn create_accounts(
159    deps: &mut DepsMut,
160    accounts: &[Cw20Coin],
161) -> Result<Uint128, ContractError> {
162    validate_accounts(accounts)?;
163
164    let mut total_supply = Uint128::zero();
165    for row in accounts {
166        let address = deps.api.addr_validate(&row.address)?;
167        BALANCES.save(deps.storage, &address, &row.amount)?;
168        total_supply += row.amount;
169    }
170
171    Ok(total_supply)
172}
173
174pub fn validate_accounts(accounts: &[Cw20Coin]) -> Result<(), ContractError> {
175    let mut addresses = accounts.iter().map(|c| &c.address).collect::<Vec<_>>();
176    addresses.sort();
177    addresses.dedup();
178
179    if addresses.len() != accounts.len() {
180        Err(ContractError::DuplicateInitialBalanceAddresses {})
181    } else {
182        Ok(())
183    }
184}
185
186#[cfg_attr(not(feature = "library"), entry_point)]
187pub fn execute(
188    deps: DepsMut,
189    env: Env,
190    info: MessageInfo,
191    msg: ExecuteMsg,
192) -> Result<Response, ContractError> {
193    match msg {
194        ExecuteMsg::Transfer { recipient, amount } => {
195            execute_transfer(deps, env, info, recipient, amount)
196        }
197        ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount),
198        ExecuteMsg::Send {
199            contract,
200            amount,
201            msg,
202        } => execute_send(deps, env, info, contract, amount, msg),
203        ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),
204        ExecuteMsg::IncreaseAllowance {
205            spender,
206            amount,
207            expires,
208        } => execute_increase_allowance(deps, env, info, spender, amount, expires),
209        ExecuteMsg::DecreaseAllowance {
210            spender,
211            amount,
212            expires,
213        } => execute_decrease_allowance(deps, env, info, spender, amount, expires),
214        ExecuteMsg::TransferFrom {
215            owner,
216            recipient,
217            amount,
218        } => execute_transfer_from(deps, env, info, owner, recipient, amount),
219        ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount),
220        ExecuteMsg::SendFrom {
221            owner,
222            contract,
223            amount,
224            msg,
225        } => execute_send_from(deps, env, info, owner, contract, amount, msg),
226        ExecuteMsg::UpdateMarketing {
227            project,
228            description,
229            marketing,
230        } => execute_update_marketing(deps, env, info, project, description, marketing),
231        ExecuteMsg::UploadLogo(logo) => execute_upload_logo(deps, env, info, logo),
232        ExecuteMsg::UpdateMinter { new_minter } => {
233            execute_update_minter(deps, env, info, new_minter)
234        }
235    }
236}
237
238pub fn execute_transfer(
239    deps: DepsMut,
240    _env: Env,
241    info: MessageInfo,
242    recipient: String,
243    amount: Uint128,
244) -> Result<Response, ContractError> {
245    let rcpt_addr = deps.api.addr_validate(&recipient)?;
246
247    BALANCES.update(
248        deps.storage,
249        &info.sender,
250        |balance: Option<Uint128>| -> StdResult<_> {
251            Ok(balance.unwrap_or_default().checked_sub(amount)?)
252        },
253    )?;
254    BALANCES.update(
255        deps.storage,
256        &rcpt_addr,
257        |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
258    )?;
259
260    let res = Response::new()
261        .add_attribute("action", "transfer")
262        .add_attribute("from", info.sender)
263        .add_attribute("to", recipient)
264        .add_attribute("amount", amount);
265    Ok(res)
266}
267
268pub fn execute_burn(
269    deps: DepsMut,
270    _env: Env,
271    info: MessageInfo,
272    amount: Uint128,
273) -> Result<Response, ContractError> {
274    // lower balance
275    BALANCES.update(
276        deps.storage,
277        &info.sender,
278        |balance: Option<Uint128>| -> StdResult<_> {
279            Ok(balance.unwrap_or_default().checked_sub(amount)?)
280        },
281    )?;
282    // reduce total_supply
283    TOKEN_INFO.update(deps.storage, |mut info| -> StdResult<_> {
284        info.total_supply = info.total_supply.checked_sub(amount)?;
285        Ok(info)
286    })?;
287
288    let res = Response::new()
289        .add_attribute("action", "burn")
290        .add_attribute("from", info.sender)
291        .add_attribute("amount", amount);
292    Ok(res)
293}
294
295pub fn execute_mint(
296    deps: DepsMut,
297    _env: Env,
298    info: MessageInfo,
299    recipient: String,
300    amount: Uint128,
301) -> Result<Response, ContractError> {
302    let mut config = TOKEN_INFO
303        .may_load(deps.storage)?
304        .ok_or(ContractError::Unauthorized {})?;
305
306    if config
307        .mint
308        .as_ref()
309        .ok_or(ContractError::Unauthorized {})?
310        .minter
311        != info.sender
312    {
313        return Err(ContractError::Unauthorized {});
314    }
315
316    // update supply and enforce cap
317    config.total_supply += amount;
318    if let Some(limit) = config.get_cap() {
319        if config.total_supply > limit {
320            return Err(ContractError::CannotExceedCap {});
321        }
322    }
323    TOKEN_INFO.save(deps.storage, &config)?;
324
325    // add amount to recipient balance
326    let rcpt_addr = deps.api.addr_validate(&recipient)?;
327    BALANCES.update(
328        deps.storage,
329        &rcpt_addr,
330        |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
331    )?;
332
333    let res = Response::new()
334        .add_attribute("action", "mint")
335        .add_attribute("to", recipient)
336        .add_attribute("amount", amount);
337    Ok(res)
338}
339
340pub fn execute_send(
341    deps: DepsMut,
342    _env: Env,
343    info: MessageInfo,
344    contract: String,
345    amount: Uint128,
346    msg: Binary,
347) -> Result<Response, ContractError> {
348    let rcpt_addr = deps.api.addr_validate(&contract)?;
349
350    // move the tokens to the contract
351    BALANCES.update(
352        deps.storage,
353        &info.sender,
354        |balance: Option<Uint128>| -> StdResult<_> {
355            Ok(balance.unwrap_or_default().checked_sub(amount)?)
356        },
357    )?;
358    BALANCES.update(
359        deps.storage,
360        &rcpt_addr,
361        |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
362    )?;
363
364    let res = Response::new()
365        .add_attribute("action", "send")
366        .add_attribute("from", &info.sender)
367        .add_attribute("to", &contract)
368        .add_attribute("amount", amount)
369        .add_message(
370            Cw20ReceiveMsg {
371                sender: info.sender.into(),
372                amount,
373                msg,
374            }
375            .into_cosmos_msg(contract)?,
376        );
377    Ok(res)
378}
379
380pub fn execute_update_minter(
381    deps: DepsMut,
382    _env: Env,
383    info: MessageInfo,
384    new_minter: Option<String>,
385) -> Result<Response, ContractError> {
386    let mut config = TOKEN_INFO
387        .may_load(deps.storage)?
388        .ok_or(ContractError::Unauthorized {})?;
389
390    let mint = config.mint.as_ref().ok_or(ContractError::Unauthorized {})?;
391    if mint.minter != info.sender {
392        return Err(ContractError::Unauthorized {});
393    }
394
395    let minter_data = new_minter
396        .map(|new_minter| deps.api.addr_validate(&new_minter))
397        .transpose()?
398        .map(|minter| MinterData {
399            minter,
400            cap: mint.cap,
401        });
402
403    config.mint = minter_data;
404
405    TOKEN_INFO.save(deps.storage, &config)?;
406
407    Ok(Response::default()
408        .add_attribute("action", "update_minter")
409        .add_attribute(
410            "new_minter",
411            config
412                .mint
413                .map(|m| m.minter.into_string())
414                .unwrap_or_else(|| "None".to_string()),
415        ))
416}
417
418pub fn execute_update_marketing(
419    deps: DepsMut,
420    _env: Env,
421    info: MessageInfo,
422    project: Option<String>,
423    description: Option<String>,
424    marketing: Option<String>,
425) -> Result<Response, ContractError> {
426    let mut marketing_info = MARKETING_INFO
427        .may_load(deps.storage)?
428        .ok_or(ContractError::Unauthorized {})?;
429
430    if marketing_info
431        .marketing
432        .as_ref()
433        .ok_or(ContractError::Unauthorized {})?
434        != info.sender
435    {
436        return Err(ContractError::Unauthorized {});
437    }
438
439    match project {
440        Some(empty) if empty.trim().is_empty() => marketing_info.project = None,
441        Some(project) => marketing_info.project = Some(project),
442        None => (),
443    }
444
445    match description {
446        Some(empty) if empty.trim().is_empty() => marketing_info.description = None,
447        Some(description) => marketing_info.description = Some(description),
448        None => (),
449    }
450
451    match marketing {
452        Some(empty) if empty.trim().is_empty() => marketing_info.marketing = None,
453        Some(marketing) => marketing_info.marketing = Some(deps.api.addr_validate(&marketing)?),
454        None => (),
455    }
456
457    if marketing_info.project.is_none()
458        && marketing_info.description.is_none()
459        && marketing_info.marketing.is_none()
460        && marketing_info.logo.is_none()
461    {
462        MARKETING_INFO.remove(deps.storage);
463    } else {
464        MARKETING_INFO.save(deps.storage, &marketing_info)?;
465    }
466
467    let res = Response::new().add_attribute("action", "update_marketing");
468    Ok(res)
469}
470
471pub fn execute_upload_logo(
472    deps: DepsMut,
473    _env: Env,
474    info: MessageInfo,
475    logo: Logo,
476) -> Result<Response, ContractError> {
477    let mut marketing_info = MARKETING_INFO
478        .may_load(deps.storage)?
479        .ok_or(ContractError::Unauthorized {})?;
480
481    verify_logo(&logo)?;
482
483    if marketing_info
484        .marketing
485        .as_ref()
486        .ok_or(ContractError::Unauthorized {})?
487        != info.sender
488    {
489        return Err(ContractError::Unauthorized {});
490    }
491
492    LOGO.save(deps.storage, &logo)?;
493
494    let logo_info = match logo {
495        Logo::Url(url) => LogoInfo::Url(url),
496        Logo::Embedded(_) => LogoInfo::Embedded,
497    };
498
499    marketing_info.logo = Some(logo_info);
500    MARKETING_INFO.save(deps.storage, &marketing_info)?;
501
502    let res = Response::new().add_attribute("action", "upload_logo");
503    Ok(res)
504}
505
506#[cfg_attr(not(feature = "library"), entry_point)]
507pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
508    match msg {
509        QueryMsg::Balance { address } => to_json_binary(&query_balance(deps, address)?),
510        QueryMsg::TokenInfo {} => to_json_binary(&query_token_info(deps)?),
511        QueryMsg::Minter {} => to_json_binary(&query_minter(deps)?),
512        QueryMsg::Allowance { owner, spender } => {
513            to_json_binary(&query_allowance(deps, owner, spender)?)
514        }
515        QueryMsg::AllAllowances {
516            owner,
517            start_after,
518            limit,
519        } => to_json_binary(&query_owner_allowances(deps, owner, start_after, limit)?),
520        QueryMsg::AllSpenderAllowances {
521            spender,
522            start_after,
523            limit,
524        } => to_json_binary(&query_spender_allowances(
525            deps,
526            spender,
527            start_after,
528            limit,
529        )?),
530        QueryMsg::AllAccounts { start_after, limit } => {
531            to_json_binary(&query_all_accounts(deps, start_after, limit)?)
532        }
533        QueryMsg::MarketingInfo {} => to_json_binary(&query_marketing_info(deps)?),
534        QueryMsg::DownloadLogo {} => to_json_binary(&query_download_logo(deps)?),
535    }
536}
537
538pub fn query_balance(deps: Deps, address: String) -> StdResult<BalanceResponse> {
539    let address = deps.api.addr_validate(&address)?;
540    let balance = BALANCES
541        .may_load(deps.storage, &address)?
542        .unwrap_or_default();
543    Ok(BalanceResponse { balance })
544}
545
546pub fn query_token_info(deps: Deps) -> StdResult<TokenInfoResponse> {
547    let info = TOKEN_INFO.load(deps.storage)?;
548    let res = TokenInfoResponse {
549        name: info.name,
550        symbol: info.symbol,
551        decimals: info.decimals,
552        total_supply: info.total_supply,
553    };
554    Ok(res)
555}
556
557pub fn query_minter(deps: Deps) -> StdResult<Option<MinterResponse>> {
558    let meta = TOKEN_INFO.load(deps.storage)?;
559    let minter = match meta.mint {
560        Some(m) => Some(MinterResponse {
561            minter: m.minter.into(),
562            cap: m.cap,
563        }),
564        None => None,
565    };
566    Ok(minter)
567}
568
569pub fn query_marketing_info(deps: Deps) -> StdResult<MarketingInfoResponse> {
570    Ok(MARKETING_INFO.may_load(deps.storage)?.unwrap_or_default())
571}
572
573pub fn query_download_logo(deps: Deps) -> StdResult<DownloadLogoResponse> {
574    let logo = LOGO.load(deps.storage)?;
575    match logo {
576        Logo::Embedded(EmbeddedLogo::Svg(logo)) => Ok(DownloadLogoResponse {
577            mime_type: "image/svg+xml".to_owned(),
578            data: logo,
579        }),
580        Logo::Embedded(EmbeddedLogo::Png(logo)) => Ok(DownloadLogoResponse {
581            mime_type: "image/png".to_owned(),
582            data: logo,
583        }),
584        Logo::Url(_) => Err(StdError::not_found("logo")),
585    }
586}
587
588#[cfg_attr(not(feature = "library"), entry_point)]
589pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
590    let original_version =
591        ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
592
593    if original_version < "0.14.0".parse::<semver::Version>().unwrap() {
594        // Build reverse map of allowances per spender
595        let data = ALLOWANCES
596            .range(deps.storage, None, None, Ascending)
597            .collect::<StdResult<Vec<_>>>()?;
598        for ((owner, spender), allowance) in data {
599            ALLOWANCES_SPENDER.save(deps.storage, (&spender, &owner), &allowance)?;
600        }
601    }
602    Ok(Response::default())
603}
604
605#[cfg(test)]
606mod tests {
607    use cosmwasm_std::testing::{
608        mock_dependencies, mock_dependencies_with_balance, mock_env, mock_info,
609    };
610    use cosmwasm_std::{coins, from_json, Addr, CosmosMsg, StdError, SubMsg, WasmMsg};
611
612    use super::*;
613    use crate::msg::InstantiateMarketingInfo;
614
615    fn get_balance<T: Into<String>>(deps: Deps, address: T) -> Uint128 {
616        query_balance(deps, address.into()).unwrap().balance
617    }
618
619    // this will set up the instantiation for other tests
620    fn do_instantiate_with_minter(
621        deps: DepsMut,
622        addr: &str,
623        amount: Uint128,
624        minter: &str,
625        cap: Option<Uint128>,
626    ) -> TokenInfoResponse {
627        _do_instantiate(
628            deps,
629            addr,
630            amount,
631            Some(MinterResponse {
632                minter: minter.to_string(),
633                cap,
634            }),
635        )
636    }
637
638    // this will set up the instantiation for other tests
639    fn do_instantiate(deps: DepsMut, addr: &str, amount: Uint128) -> TokenInfoResponse {
640        _do_instantiate(deps, addr, amount, None)
641    }
642
643    // this will set up the instantiation for other tests
644    fn _do_instantiate(
645        mut deps: DepsMut,
646        addr: &str,
647        amount: Uint128,
648        mint: Option<MinterResponse>,
649    ) -> TokenInfoResponse {
650        let instantiate_msg = InstantiateMsg {
651            name: "Auto Gen".to_string(),
652            symbol: "AUTO".to_string(),
653            decimals: 3,
654            initial_balances: vec![Cw20Coin {
655                address: addr.to_string(),
656                amount,
657            }],
658            mint: mint.clone(),
659            marketing: None,
660        };
661        let info = mock_info("creator", &[]);
662        let env = mock_env();
663        let res = instantiate(deps.branch(), env, info, instantiate_msg).unwrap();
664        assert_eq!(0, res.messages.len());
665
666        let meta = query_token_info(deps.as_ref()).unwrap();
667        assert_eq!(
668            meta,
669            TokenInfoResponse {
670                name: "Auto Gen".to_string(),
671                symbol: "AUTO".to_string(),
672                decimals: 3,
673                total_supply: amount,
674            }
675        );
676        assert_eq!(get_balance(deps.as_ref(), addr), amount);
677        assert_eq!(query_minter(deps.as_ref()).unwrap(), mint,);
678        meta
679    }
680
681    const PNG_HEADER: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a];
682
683    mod instantiate {
684        use super::*;
685
686        #[test]
687        fn basic() {
688            let mut deps = mock_dependencies();
689            let addr = deps.api.addr_make("addr0000");
690            let amount = Uint128::from(11223344u128);
691            let instantiate_msg = InstantiateMsg {
692                name: "Cash Token".to_string(),
693                symbol: "CASH".to_string(),
694                decimals: 9,
695                initial_balances: vec![Cw20Coin {
696                    address: addr.to_string(),
697                    amount,
698                }],
699                mint: None,
700                marketing: None,
701            };
702            let info = mock_info("creator", &[]);
703            let env = mock_env();
704            let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
705            assert_eq!(0, res.messages.len());
706
707            assert_eq!(
708                query_token_info(deps.as_ref()).unwrap(),
709                TokenInfoResponse {
710                    name: "Cash Token".to_string(),
711                    symbol: "CASH".to_string(),
712                    decimals: 9,
713                    total_supply: amount,
714                }
715            );
716            assert_eq!(get_balance(deps.as_ref(), addr), Uint128::new(11223344));
717        }
718
719        #[test]
720        fn mintable() {
721            let mut deps = mock_dependencies();
722            let addr = deps.api.addr_make("addr0000");
723            let amount = Uint128::new(11223344);
724            let minter = deps.api.addr_make("asmodat").to_string();
725            let limit = Uint128::new(511223344);
726            let instantiate_msg = InstantiateMsg {
727                name: "Cash Token".to_string(),
728                symbol: "CASH".to_string(),
729                decimals: 9,
730                initial_balances: vec![Cw20Coin {
731                    address: addr.to_string(),
732                    amount,
733                }],
734                mint: Some(MinterResponse {
735                    minter: minter.clone(),
736                    cap: Some(limit),
737                }),
738                marketing: None,
739            };
740            let info = mock_info("creator", &[]);
741            let env = mock_env();
742            let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
743            assert_eq!(0, res.messages.len());
744
745            assert_eq!(
746                query_token_info(deps.as_ref()).unwrap(),
747                TokenInfoResponse {
748                    name: "Cash Token".to_string(),
749                    symbol: "CASH".to_string(),
750                    decimals: 9,
751                    total_supply: amount,
752                }
753            );
754            assert_eq!(get_balance(deps.as_ref(), addr), Uint128::new(11223344));
755            assert_eq!(
756                query_minter(deps.as_ref()).unwrap(),
757                Some(MinterResponse {
758                    minter,
759                    cap: Some(limit),
760                }),
761            );
762        }
763
764        #[test]
765        fn mintable_over_cap() {
766            let mut deps = mock_dependencies();
767            let amount = Uint128::new(11223344);
768            let minter = deps.api.addr_make("asmodat");
769            let addr = deps.api.addr_make("addr0000");
770            let limit = Uint128::new(11223300);
771            let instantiate_msg = InstantiateMsg {
772                name: "Cash Token".to_string(),
773                symbol: "CASH".to_string(),
774                decimals: 9,
775                initial_balances: vec![Cw20Coin {
776                    address: addr.to_string(),
777                    amount,
778                }],
779                mint: Some(MinterResponse {
780                    minter: minter.to_string(),
781                    cap: Some(limit),
782                }),
783                marketing: None,
784            };
785            let info = mock_info("creator", &[]);
786            let env = mock_env();
787            let err = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap_err();
788            assert_eq!(
789                err,
790                StdError::generic_err("Initial supply greater than cap").into()
791            );
792        }
793
794        mod marketing {
795            use super::*;
796
797            #[test]
798            fn basic() {
799                let mut deps = mock_dependencies();
800
801                let marketing = deps.api.addr_make("marketing");
802
803                let instantiate_msg = InstantiateMsg {
804                    name: "Cash Token".to_string(),
805                    symbol: "CASH".to_string(),
806                    decimals: 9,
807                    initial_balances: vec![],
808                    mint: None,
809                    marketing: Some(InstantiateMarketingInfo {
810                        project: Some("Project".to_owned()),
811                        description: Some("Description".to_owned()),
812                        marketing: Some(marketing.to_string()),
813                        logo: Some(Logo::Url("url".to_owned())),
814                    }),
815                };
816
817                let info = mock_info("creator", &[]);
818                let env = mock_env();
819                let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
820                assert_eq!(0, res.messages.len());
821
822                assert_eq!(
823                    query_marketing_info(deps.as_ref()).unwrap(),
824                    MarketingInfoResponse {
825                        project: Some("Project".to_owned()),
826                        description: Some("Description".to_owned()),
827                        marketing: Some(marketing),
828                        logo: Some(LogoInfo::Url("url".to_owned())),
829                    }
830                );
831
832                let err = query_download_logo(deps.as_ref()).unwrap_err();
833                assert!(
834                    matches!(err, StdError::NotFound { .. }),
835                    "Expected StdError::NotFound, received {err}",
836                );
837            }
838
839            #[test]
840            fn invalid_marketing() {
841                let mut deps = mock_dependencies();
842                let instantiate_msg = InstantiateMsg {
843                    name: "Cash Token".to_string(),
844                    symbol: "CASH".to_string(),
845                    decimals: 9,
846                    initial_balances: vec![],
847                    mint: None,
848                    marketing: Some(InstantiateMarketingInfo {
849                        project: Some("Project".to_owned()),
850                        description: Some("Description".to_owned()),
851                        marketing: Some("m".to_owned()),
852                        logo: Some(Logo::Url("url".to_owned())),
853                    }),
854                };
855
856                let info = mock_info("creator", &[]);
857                let env = mock_env();
858                instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap_err();
859
860                let err = query_download_logo(deps.as_ref()).unwrap_err();
861                assert!(
862                    matches!(err, StdError::NotFound { .. }),
863                    "Expected StdError::NotFound, received {err}",
864                );
865            }
866        }
867    }
868
869    #[test]
870    fn can_mint_by_minter() {
871        let mut deps = mock_dependencies();
872
873        let genesis = deps.api.addr_make("genesis").to_string();
874        let amount = Uint128::new(11223344);
875        let minter = deps.api.addr_make("asmodat").to_string();
876        let limit = Uint128::new(511223344);
877        do_instantiate_with_minter(deps.as_mut(), &genesis, amount, &minter, Some(limit));
878
879        // minter can mint coins to some winner
880        let winner = deps.api.addr_make("winner").to_string();
881        let prize = Uint128::new(222_222_222);
882        let msg = ExecuteMsg::Mint {
883            recipient: winner.clone(),
884            amount: prize,
885        };
886
887        let info = mock_info(minter.as_ref(), &[]);
888        let env = mock_env();
889        let res = execute(deps.as_mut(), env, info, msg).unwrap();
890        assert_eq!(0, res.messages.len());
891        assert_eq!(get_balance(deps.as_ref(), genesis), amount);
892        assert_eq!(get_balance(deps.as_ref(), winner.clone()), prize);
893
894        // Allows minting 0
895        let msg = ExecuteMsg::Mint {
896            recipient: winner.clone(),
897            amount: Uint128::zero(),
898        };
899        let info = mock_info(minter.as_ref(), &[]);
900        let env = mock_env();
901        execute(deps.as_mut(), env, info, msg).unwrap();
902
903        // but if it exceeds cap (even over multiple rounds), it fails
904        // cap is enforced
905        let msg = ExecuteMsg::Mint {
906            recipient: winner,
907            amount: Uint128::new(333_222_222),
908        };
909        let info = mock_info(minter.as_ref(), &[]);
910        let env = mock_env();
911        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
912        assert_eq!(err, ContractError::CannotExceedCap {});
913    }
914
915    #[test]
916    fn others_cannot_mint() {
917        let mut deps = mock_dependencies();
918
919        let genesis = deps.api.addr_make("genesis").to_string();
920        let minter = deps.api.addr_make("minter").to_string();
921        let winner = deps.api.addr_make("winner").to_string();
922
923        do_instantiate_with_minter(deps.as_mut(), &genesis, Uint128::new(1234), &minter, None);
924
925        let msg = ExecuteMsg::Mint {
926            recipient: winner,
927            amount: Uint128::new(222),
928        };
929        let info = mock_info("anyone else", &[]);
930        let env = mock_env();
931        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
932        assert_eq!(err, ContractError::Unauthorized {});
933    }
934
935    #[test]
936    fn minter_can_update_minter_but_not_cap() {
937        let mut deps = mock_dependencies();
938
939        let genesis = deps.api.addr_make("genesis").to_string();
940        let minter = deps.api.addr_make("minter").to_string();
941
942        let cap = Some(Uint128::from(3000000u128));
943        do_instantiate_with_minter(deps.as_mut(), &genesis, Uint128::new(1234), &minter, cap);
944
945        let new_minter = deps.api.addr_make("new_minter").to_string();
946        let msg = ExecuteMsg::UpdateMinter {
947            new_minter: Some(new_minter.clone()),
948        };
949
950        let info = mock_info(&minter, &[]);
951        let env = mock_env();
952        let res = execute(deps.as_mut(), env.clone(), info, msg);
953        assert!(res.is_ok());
954        let query_minter_msg = QueryMsg::Minter {};
955        let res = query(deps.as_ref(), env, query_minter_msg);
956        let mint: MinterResponse = from_json(res.unwrap()).unwrap();
957
958        // Minter cannot update cap.
959        assert!(mint.cap == cap);
960        assert!(mint.minter == new_minter)
961    }
962
963    #[test]
964    fn others_cannot_update_minter() {
965        let mut deps = mock_dependencies();
966
967        let genesis = deps.api.addr_make("genesis").to_string();
968        let minter = deps.api.addr_make("minter").to_string();
969        let new_minter = deps.api.addr_make("new_minter").to_string();
970
971        do_instantiate_with_minter(deps.as_mut(), &genesis, Uint128::new(1234), &minter, None);
972
973        let msg = ExecuteMsg::UpdateMinter {
974            new_minter: Some(new_minter),
975        };
976
977        let info = mock_info("not the minter", &[]);
978        let env = mock_env();
979        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
980        assert_eq!(err, ContractError::Unauthorized {});
981    }
982
983    #[test]
984    fn unset_minter() {
985        let mut deps = mock_dependencies();
986
987        let genesis = deps.api.addr_make("genesis").to_string();
988        let minter = deps.api.addr_make("minter").to_string();
989        let winner = deps.api.addr_make("winner").to_string();
990
991        let cap = None;
992        do_instantiate_with_minter(deps.as_mut(), &genesis, Uint128::new(1234), &minter, cap);
993
994        let msg = ExecuteMsg::UpdateMinter { new_minter: None };
995
996        let info = mock_info(&minter, &[]);
997        let env = mock_env();
998        let res = execute(deps.as_mut(), env.clone(), info, msg);
999        assert!(res.is_ok());
1000        let query_minter_msg = QueryMsg::Minter {};
1001        let res = query(deps.as_ref(), env, query_minter_msg);
1002        let mint: Option<MinterResponse> = from_json(res.unwrap()).unwrap();
1003
1004        // Check that mint information was removed.
1005        assert_eq!(mint, None);
1006
1007        // Check that old minter can no longer mint.
1008        let msg = ExecuteMsg::Mint {
1009            recipient: winner,
1010            amount: Uint128::new(222),
1011        };
1012        let info = mock_info(&minter, &[]);
1013        let env = mock_env();
1014        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
1015        assert_eq!(err, ContractError::Unauthorized {});
1016    }
1017
1018    #[test]
1019    fn no_one_mints_if_minter_unset() {
1020        let mut deps = mock_dependencies();
1021
1022        let genesis = deps.api.addr_make("genesis").to_string();
1023        let winner = deps.api.addr_make("winner").to_string();
1024
1025        do_instantiate(deps.as_mut(), &genesis, Uint128::new(1234));
1026
1027        let msg = ExecuteMsg::Mint {
1028            recipient: winner,
1029            amount: Uint128::new(222),
1030        };
1031        let info = mock_info(&genesis, &[]);
1032        let env = mock_env();
1033        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
1034        assert_eq!(err, ContractError::Unauthorized {});
1035    }
1036
1037    #[test]
1038    fn instantiate_multiple_accounts() {
1039        let mut deps = mock_dependencies();
1040        let amount1 = Uint128::from(11223344u128);
1041        let addr1 = deps.api.addr_make("addr0001").to_string();
1042        let amount2 = Uint128::from(7890987u128);
1043        let addr2 = deps.api.addr_make("addr0002").to_string();
1044        let info = mock_info("creator", &[]);
1045        let env = mock_env();
1046
1047        // Fails with duplicate addresses
1048        let instantiate_msg = InstantiateMsg {
1049            name: "Bash Shell".to_string(),
1050            symbol: "BASH".to_string(),
1051            decimals: 6,
1052            initial_balances: vec![
1053                Cw20Coin {
1054                    address: addr1.clone(),
1055                    amount: amount1,
1056                },
1057                Cw20Coin {
1058                    address: addr1.clone(),
1059                    amount: amount2,
1060                },
1061            ],
1062            mint: None,
1063            marketing: None,
1064        };
1065        let err =
1066            instantiate(deps.as_mut(), env.clone(), info.clone(), instantiate_msg).unwrap_err();
1067        assert_eq!(err, ContractError::DuplicateInitialBalanceAddresses {});
1068
1069        // Works with unique addresses
1070        let instantiate_msg = InstantiateMsg {
1071            name: "Bash Shell".to_string(),
1072            symbol: "BASH".to_string(),
1073            decimals: 6,
1074            initial_balances: vec![
1075                Cw20Coin {
1076                    address: addr1.clone(),
1077                    amount: amount1,
1078                },
1079                Cw20Coin {
1080                    address: addr2.clone(),
1081                    amount: amount2,
1082                },
1083            ],
1084            mint: None,
1085            marketing: None,
1086        };
1087        let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
1088        assert_eq!(0, res.messages.len());
1089        assert_eq!(
1090            query_token_info(deps.as_ref()).unwrap(),
1091            TokenInfoResponse {
1092                name: "Bash Shell".to_string(),
1093                symbol: "BASH".to_string(),
1094                decimals: 6,
1095                total_supply: amount1 + amount2,
1096            }
1097        );
1098        assert_eq!(get_balance(deps.as_ref(), addr1), amount1);
1099        assert_eq!(get_balance(deps.as_ref(), addr2), amount2);
1100    }
1101
1102    #[test]
1103    fn queries_work() {
1104        let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
1105
1106        let addr1 = deps.api.addr_make("addr0001").to_string();
1107        let addr2 = deps.api.addr_make("addr0002").to_string();
1108
1109        let amount1 = Uint128::from(12340000u128);
1110
1111        let expected = do_instantiate(deps.as_mut(), &addr1, amount1);
1112
1113        // check meta query
1114        let loaded = query_token_info(deps.as_ref()).unwrap();
1115        assert_eq!(expected, loaded);
1116
1117        let _info = mock_info("test", &[]);
1118        let env = mock_env();
1119        // check balance query (full)
1120        let data = query(
1121            deps.as_ref(),
1122            env.clone(),
1123            QueryMsg::Balance { address: addr1 },
1124        )
1125        .unwrap();
1126        let loaded: BalanceResponse = from_json(data).unwrap();
1127        assert_eq!(loaded.balance, amount1);
1128
1129        // check balance query (empty)
1130        let data = query(deps.as_ref(), env, QueryMsg::Balance { address: addr2 }).unwrap();
1131        let loaded: BalanceResponse = from_json(data).unwrap();
1132        assert_eq!(loaded.balance, Uint128::zero());
1133    }
1134
1135    #[test]
1136    fn transfer() {
1137        let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
1138        let addr1 = deps.api.addr_make("addr0001").to_string();
1139        let addr2 = deps.api.addr_make("addr0002").to_string();
1140        let amount1 = Uint128::from(12340000u128);
1141        let transfer = Uint128::from(76543u128);
1142        let too_much = Uint128::from(12340321u128);
1143
1144        do_instantiate(deps.as_mut(), &addr1, amount1);
1145
1146        // Allows transferring 0
1147        let info = mock_info(addr1.as_ref(), &[]);
1148        let env = mock_env();
1149        let msg = ExecuteMsg::Transfer {
1150            recipient: addr2.clone(),
1151            amount: Uint128::zero(),
1152        };
1153        execute(deps.as_mut(), env, info, msg).unwrap();
1154
1155        // cannot send more than we have
1156        let info = mock_info(addr1.as_ref(), &[]);
1157        let env = mock_env();
1158        let msg = ExecuteMsg::Transfer {
1159            recipient: addr2.clone(),
1160            amount: too_much,
1161        };
1162        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
1163        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
1164
1165        // cannot send from empty account
1166        let info = mock_info(addr2.as_ref(), &[]);
1167        let env = mock_env();
1168        let msg = ExecuteMsg::Transfer {
1169            recipient: addr1.clone(),
1170            amount: transfer,
1171        };
1172        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
1173        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
1174
1175        // valid transfer
1176        let info = mock_info(addr1.as_ref(), &[]);
1177        let env = mock_env();
1178        let msg = ExecuteMsg::Transfer {
1179            recipient: addr2.clone(),
1180            amount: transfer,
1181        };
1182        let res = execute(deps.as_mut(), env, info, msg).unwrap();
1183        assert_eq!(res.messages.len(), 0);
1184
1185        let remainder = amount1.checked_sub(transfer).unwrap();
1186        assert_eq!(get_balance(deps.as_ref(), addr1), remainder);
1187        assert_eq!(get_balance(deps.as_ref(), addr2), transfer);
1188        assert_eq!(
1189            query_token_info(deps.as_ref()).unwrap().total_supply,
1190            amount1
1191        );
1192    }
1193
1194    #[test]
1195    fn burn() {
1196        let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
1197        let addr1 = deps.api.addr_make("addr0001").to_string();
1198        let amount1 = Uint128::from(12340000u128);
1199        let burn = Uint128::from(76543u128);
1200        let too_much = Uint128::from(12340321u128);
1201
1202        do_instantiate(deps.as_mut(), &addr1, amount1);
1203
1204        // Allows burning 0
1205        let info = mock_info(addr1.as_ref(), &[]);
1206        let env = mock_env();
1207        let msg = ExecuteMsg::Burn {
1208            amount: Uint128::zero(),
1209        };
1210        execute(deps.as_mut(), env, info, msg).unwrap();
1211        assert_eq!(
1212            query_token_info(deps.as_ref()).unwrap().total_supply,
1213            amount1
1214        );
1215
1216        // cannot burn more than we have
1217        let info = mock_info(addr1.as_ref(), &[]);
1218        let env = mock_env();
1219        let msg = ExecuteMsg::Burn { amount: too_much };
1220        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
1221        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
1222        assert_eq!(
1223            query_token_info(deps.as_ref()).unwrap().total_supply,
1224            amount1
1225        );
1226
1227        // valid burn reduces total supply
1228        let info = mock_info(addr1.as_ref(), &[]);
1229        let env = mock_env();
1230        let msg = ExecuteMsg::Burn { amount: burn };
1231        let res = execute(deps.as_mut(), env, info, msg).unwrap();
1232        assert_eq!(res.messages.len(), 0);
1233
1234        let remainder = amount1.checked_sub(burn).unwrap();
1235        assert_eq!(get_balance(deps.as_ref(), addr1), remainder);
1236        assert_eq!(
1237            query_token_info(deps.as_ref()).unwrap().total_supply,
1238            remainder
1239        );
1240    }
1241
1242    #[test]
1243    fn send() {
1244        let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
1245        let addr1 = deps.api.addr_make("addr0001").to_string();
1246        let contract = deps.api.addr_make("contract0001").to_string();
1247        let amount1 = Uint128::from(12340000u128);
1248        let transfer = Uint128::from(76543u128);
1249        let too_much = Uint128::from(12340321u128);
1250        let send_msg = Binary::from(r#"{"some":123}"#.as_bytes());
1251
1252        do_instantiate(deps.as_mut(), &addr1, amount1);
1253
1254        // Allows sending 0
1255        let info = mock_info(addr1.as_ref(), &[]);
1256        let env = mock_env();
1257        let msg = ExecuteMsg::Send {
1258            contract: contract.clone(),
1259            amount: Uint128::zero(),
1260            msg: send_msg.clone(),
1261        };
1262        execute(deps.as_mut(), env, info, msg).unwrap();
1263
1264        // cannot send more than we have
1265        let info = mock_info(addr1.as_ref(), &[]);
1266        let env = mock_env();
1267        let msg = ExecuteMsg::Send {
1268            contract: contract.clone(),
1269            amount: too_much,
1270            msg: send_msg.clone(),
1271        };
1272        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
1273        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
1274
1275        // valid transfer
1276        let info = mock_info(addr1.as_ref(), &[]);
1277        let env = mock_env();
1278        let msg = ExecuteMsg::Send {
1279            contract: contract.clone(),
1280            amount: transfer,
1281            msg: send_msg.clone(),
1282        };
1283        let res = execute(deps.as_mut(), env, info, msg).unwrap();
1284        assert_eq!(res.messages.len(), 1);
1285
1286        // ensure proper send message sent
1287        // this is the message we want delivered to the other side
1288        let binary_msg = Cw20ReceiveMsg {
1289            sender: addr1.clone(),
1290            amount: transfer,
1291            msg: send_msg,
1292        }
1293        .into_json_binary()
1294        .unwrap();
1295        // and this is how it must be wrapped for the vm to process it
1296        assert_eq!(
1297            res.messages[0],
1298            SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
1299                contract_addr: contract.clone(),
1300                msg: binary_msg,
1301                funds: vec![],
1302            }))
1303        );
1304
1305        // ensure balance is properly transferred
1306        let remainder = amount1.checked_sub(transfer).unwrap();
1307        assert_eq!(get_balance(deps.as_ref(), addr1), remainder);
1308        assert_eq!(get_balance(deps.as_ref(), contract), transfer);
1309        assert_eq!(
1310            query_token_info(deps.as_ref()).unwrap().total_supply,
1311            amount1
1312        );
1313    }
1314
1315    mod migration {
1316        use super::*;
1317
1318        use cosmwasm_std::Empty;
1319        use cw20::{AllAllowancesResponse, AllSpenderAllowancesResponse, SpenderAllowanceInfo};
1320        use cw_multi_test::{App, Contract, ContractWrapper, Executor};
1321        use cw_utils::Expiration;
1322
1323        fn cw20_contract() -> Box<dyn Contract<Empty>> {
1324            let contract = ContractWrapper::new(
1325                crate::contract::execute,
1326                crate::contract::instantiate,
1327                crate::contract::query,
1328            )
1329            .with_migrate(crate::contract::migrate);
1330            Box::new(contract)
1331        }
1332
1333        #[test]
1334        fn test_migrate() {
1335            let mut app = App::default();
1336
1337            let sender = app.api().addr_make("sender").to_string();
1338            let spender = app.api().addr_make("spender").to_string();
1339
1340            let cw20_id = app.store_code(cw20_contract());
1341            let cw20_addr = app
1342                .instantiate_contract(
1343                    cw20_id,
1344                    Addr::unchecked("sender"),
1345                    &InstantiateMsg {
1346                        name: "Token".to_string(),
1347                        symbol: "TOKEN".to_string(),
1348                        decimals: 6,
1349                        initial_balances: vec![Cw20Coin {
1350                            address: sender.clone(),
1351                            amount: Uint128::new(100),
1352                        }],
1353                        mint: None,
1354                        marketing: None,
1355                    },
1356                    &[],
1357                    "TOKEN",
1358                    Some(sender.clone()),
1359                )
1360                .unwrap();
1361
1362            // no allowance to start
1363            let allowance: AllAllowancesResponse = app
1364                .wrap()
1365                .query_wasm_smart(
1366                    cw20_addr.to_string(),
1367                    &QueryMsg::AllAllowances {
1368                        owner: sender.clone(),
1369                        start_after: None,
1370                        limit: None,
1371                    },
1372                )
1373                .unwrap();
1374            assert_eq!(allowance, AllAllowancesResponse::default());
1375
1376            // Set allowance
1377            let allow1 = Uint128::new(7777);
1378            let expires = Expiration::AtHeight(123_456);
1379            let msg = CosmosMsg::Wasm(WasmMsg::Execute {
1380                contract_addr: cw20_addr.to_string(),
1381                msg: to_json_binary(&ExecuteMsg::IncreaseAllowance {
1382                    spender: spender.clone(),
1383                    amount: allow1,
1384                    expires: Some(expires),
1385                })
1386                .unwrap(),
1387                funds: vec![],
1388            });
1389            app.execute(Addr::unchecked(&sender), msg).unwrap();
1390
1391            // Now migrate
1392            app.execute(
1393                Addr::unchecked(&sender),
1394                CosmosMsg::Wasm(WasmMsg::Migrate {
1395                    contract_addr: cw20_addr.to_string(),
1396                    new_code_id: cw20_id,
1397                    msg: to_json_binary(&MigrateMsg {}).unwrap(),
1398                }),
1399            )
1400            .unwrap();
1401
1402            // Smoke check that the contract still works.
1403            let balance: cw20::BalanceResponse = app
1404                .wrap()
1405                .query_wasm_smart(
1406                    cw20_addr.clone(),
1407                    &QueryMsg::Balance {
1408                        address: sender.clone(),
1409                    },
1410                )
1411                .unwrap();
1412
1413            assert_eq!(balance.balance, Uint128::new(100));
1414
1415            // Confirm that the allowance per spender is there
1416            let allowance: AllSpenderAllowancesResponse = app
1417                .wrap()
1418                .query_wasm_smart(
1419                    cw20_addr,
1420                    &QueryMsg::AllSpenderAllowances {
1421                        spender,
1422                        start_after: None,
1423                        limit: None,
1424                    },
1425                )
1426                .unwrap();
1427            assert_eq!(
1428                allowance.allowances,
1429                &[SpenderAllowanceInfo {
1430                    owner: sender,
1431                    allowance: allow1,
1432                    expires
1433                }]
1434            );
1435        }
1436    }
1437
1438    mod marketing {
1439        use super::*;
1440
1441        #[test]
1442        fn update_unauthorised() {
1443            let mut deps = mock_dependencies();
1444
1445            let creator = deps.api.addr_make("creator");
1446            let marketing = deps.api.addr_make("marketing");
1447
1448            let instantiate_msg = InstantiateMsg {
1449                name: "Cash Token".to_string(),
1450                symbol: "CASH".to_string(),
1451                decimals: 9,
1452                initial_balances: vec![],
1453                mint: None,
1454                marketing: Some(InstantiateMarketingInfo {
1455                    project: Some("Project".to_owned()),
1456                    description: Some("Description".to_owned()),
1457                    marketing: Some(marketing.to_string()),
1458                    logo: Some(Logo::Url("url".to_owned())),
1459                }),
1460            };
1461
1462            let info = mock_info(creator.as_str(), &[]);
1463
1464            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1465
1466            let err = execute(
1467                deps.as_mut(),
1468                mock_env(),
1469                info,
1470                ExecuteMsg::UpdateMarketing {
1471                    project: Some("New project".to_owned()),
1472                    description: Some("Better description".to_owned()),
1473                    marketing: Some(creator.to_string()),
1474                },
1475            )
1476            .unwrap_err();
1477
1478            assert_eq!(err, ContractError::Unauthorized {});
1479
1480            // Ensure marketing didn't change
1481            assert_eq!(
1482                query_marketing_info(deps.as_ref()).unwrap(),
1483                MarketingInfoResponse {
1484                    project: Some("Project".to_owned()),
1485                    description: Some("Description".to_owned()),
1486                    marketing: Some(marketing),
1487                    logo: Some(LogoInfo::Url("url".to_owned())),
1488                }
1489            );
1490
1491            let err = query_download_logo(deps.as_ref()).unwrap_err();
1492            assert!(
1493                matches!(err, StdError::NotFound { .. }),
1494                "Expected StdError::NotFound, received {err}",
1495            );
1496        }
1497
1498        #[test]
1499        fn update_project() {
1500            let mut deps = mock_dependencies();
1501
1502            let creator = deps.api.addr_make("creator");
1503
1504            let instantiate_msg = InstantiateMsg {
1505                name: "Cash Token".to_string(),
1506                symbol: "CASH".to_string(),
1507                decimals: 9,
1508                initial_balances: vec![],
1509                mint: None,
1510                marketing: Some(InstantiateMarketingInfo {
1511                    project: Some("Project".to_owned()),
1512                    description: Some("Description".to_owned()),
1513                    marketing: Some(creator.to_string()),
1514                    logo: Some(Logo::Url("url".to_owned())),
1515                }),
1516            };
1517
1518            let info = mock_info(creator.as_str(), &[]);
1519
1520            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1521
1522            let res = execute(
1523                deps.as_mut(),
1524                mock_env(),
1525                info,
1526                ExecuteMsg::UpdateMarketing {
1527                    project: Some("New project".to_owned()),
1528                    description: None,
1529                    marketing: None,
1530                },
1531            )
1532            .unwrap();
1533
1534            assert_eq!(res.messages, vec![]);
1535
1536            assert_eq!(
1537                query_marketing_info(deps.as_ref()).unwrap(),
1538                MarketingInfoResponse {
1539                    project: Some("New project".to_owned()),
1540                    description: Some("Description".to_owned()),
1541                    marketing: Some(creator),
1542                    logo: Some(LogoInfo::Url("url".to_owned())),
1543                }
1544            );
1545
1546            let err = query_download_logo(deps.as_ref()).unwrap_err();
1547            assert!(
1548                matches!(err, StdError::NotFound { .. }),
1549                "Expected StdError::NotFound, received {err}",
1550            );
1551        }
1552
1553        #[test]
1554        fn clear_project() {
1555            let mut deps = mock_dependencies();
1556
1557            let creator = deps.api.addr_make("creator");
1558
1559            let instantiate_msg = InstantiateMsg {
1560                name: "Cash Token".to_string(),
1561                symbol: "CASH".to_string(),
1562                decimals: 9,
1563                initial_balances: vec![],
1564                mint: None,
1565                marketing: Some(InstantiateMarketingInfo {
1566                    project: Some("Project".to_owned()),
1567                    description: Some("Description".to_owned()),
1568                    marketing: Some(creator.to_string()),
1569                    logo: Some(Logo::Url("url".to_owned())),
1570                }),
1571            };
1572
1573            let info = mock_info(creator.as_str(), &[]);
1574
1575            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1576
1577            let res = execute(
1578                deps.as_mut(),
1579                mock_env(),
1580                info,
1581                ExecuteMsg::UpdateMarketing {
1582                    project: Some("".to_owned()),
1583                    description: None,
1584                    marketing: None,
1585                },
1586            )
1587            .unwrap();
1588
1589            assert_eq!(res.messages, vec![]);
1590
1591            assert_eq!(
1592                query_marketing_info(deps.as_ref()).unwrap(),
1593                MarketingInfoResponse {
1594                    project: None,
1595                    description: Some("Description".to_owned()),
1596                    marketing: Some(creator),
1597                    logo: Some(LogoInfo::Url("url".to_owned())),
1598                }
1599            );
1600
1601            let err = query_download_logo(deps.as_ref()).unwrap_err();
1602            assert!(
1603                matches!(err, StdError::NotFound { .. }),
1604                "Expected StdError::NotFound, received {err}",
1605            );
1606        }
1607
1608        #[test]
1609        fn update_description() {
1610            let mut deps = mock_dependencies();
1611
1612            let creator = deps.api.addr_make("creator");
1613
1614            let instantiate_msg = InstantiateMsg {
1615                name: "Cash Token".to_string(),
1616                symbol: "CASH".to_string(),
1617                decimals: 9,
1618                initial_balances: vec![],
1619                mint: None,
1620                marketing: Some(InstantiateMarketingInfo {
1621                    project: Some("Project".to_owned()),
1622                    description: Some("Description".to_owned()),
1623                    marketing: Some(creator.to_string()),
1624                    logo: Some(Logo::Url("url".to_owned())),
1625                }),
1626            };
1627
1628            let info = mock_info(creator.as_str(), &[]);
1629
1630            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1631
1632            let res = execute(
1633                deps.as_mut(),
1634                mock_env(),
1635                info,
1636                ExecuteMsg::UpdateMarketing {
1637                    project: None,
1638                    description: Some("Better description".to_owned()),
1639                    marketing: None,
1640                },
1641            )
1642            .unwrap();
1643
1644            assert_eq!(res.messages, vec![]);
1645
1646            assert_eq!(
1647                query_marketing_info(deps.as_ref()).unwrap(),
1648                MarketingInfoResponse {
1649                    project: Some("Project".to_owned()),
1650                    description: Some("Better description".to_owned()),
1651                    marketing: Some(creator),
1652                    logo: Some(LogoInfo::Url("url".to_owned())),
1653                }
1654            );
1655
1656            let err = query_download_logo(deps.as_ref()).unwrap_err();
1657            assert!(
1658                matches!(err, StdError::NotFound { .. }),
1659                "Expected StdError::NotFound, received {err}",
1660            );
1661        }
1662
1663        #[test]
1664        fn clear_description() {
1665            let mut deps = mock_dependencies();
1666
1667            let creator = deps.api.addr_make("creator");
1668
1669            let instantiate_msg = InstantiateMsg {
1670                name: "Cash Token".to_string(),
1671                symbol: "CASH".to_string(),
1672                decimals: 9,
1673                initial_balances: vec![],
1674                mint: None,
1675                marketing: Some(InstantiateMarketingInfo {
1676                    project: Some("Project".to_owned()),
1677                    description: Some("Description".to_owned()),
1678                    marketing: Some(creator.to_string()),
1679                    logo: Some(Logo::Url("url".to_owned())),
1680                }),
1681            };
1682
1683            let info = mock_info(creator.as_str(), &[]);
1684
1685            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1686
1687            let res = execute(
1688                deps.as_mut(),
1689                mock_env(),
1690                info,
1691                ExecuteMsg::UpdateMarketing {
1692                    project: None,
1693                    description: Some("".to_owned()),
1694                    marketing: None,
1695                },
1696            )
1697            .unwrap();
1698
1699            assert_eq!(res.messages, vec![]);
1700
1701            assert_eq!(
1702                query_marketing_info(deps.as_ref()).unwrap(),
1703                MarketingInfoResponse {
1704                    project: Some("Project".to_owned()),
1705                    description: None,
1706                    marketing: Some(creator),
1707                    logo: Some(LogoInfo::Url("url".to_owned())),
1708                }
1709            );
1710
1711            let err = query_download_logo(deps.as_ref()).unwrap_err();
1712            assert!(
1713                matches!(err, StdError::NotFound { .. }),
1714                "Expected StdError::NotFound, received {err}",
1715            );
1716        }
1717
1718        #[test]
1719        fn update_marketing() {
1720            let mut deps = mock_dependencies();
1721
1722            let creator = deps.api.addr_make("creator");
1723            let marketing = deps.api.addr_make("marketing");
1724
1725            let instantiate_msg = InstantiateMsg {
1726                name: "Cash Token".to_string(),
1727                symbol: "CASH".to_string(),
1728                decimals: 9,
1729                initial_balances: vec![],
1730                mint: None,
1731                marketing: Some(InstantiateMarketingInfo {
1732                    project: Some("Project".to_owned()),
1733                    description: Some("Description".to_owned()),
1734                    marketing: Some(creator.to_string()),
1735                    logo: Some(Logo::Url("url".to_owned())),
1736                }),
1737            };
1738
1739            let info = mock_info(creator.as_str(), &[]);
1740
1741            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1742
1743            let res = execute(
1744                deps.as_mut(),
1745                mock_env(),
1746                info,
1747                ExecuteMsg::UpdateMarketing {
1748                    project: None,
1749                    description: None,
1750                    marketing: Some(marketing.to_string()),
1751                },
1752            )
1753            .unwrap();
1754
1755            assert_eq!(res.messages, vec![]);
1756
1757            assert_eq!(
1758                query_marketing_info(deps.as_ref()).unwrap(),
1759                MarketingInfoResponse {
1760                    project: Some("Project".to_owned()),
1761                    description: Some("Description".to_owned()),
1762                    marketing: Some(marketing),
1763                    logo: Some(LogoInfo::Url("url".to_owned())),
1764                }
1765            );
1766
1767            let err = query_download_logo(deps.as_ref()).unwrap_err();
1768            assert!(
1769                matches!(err, StdError::NotFound { .. }),
1770                "Expected StdError::NotFound, received {err}",
1771            );
1772        }
1773
1774        #[test]
1775        fn update_marketing_invalid() {
1776            let mut deps = mock_dependencies();
1777
1778            let creator = deps.api.addr_make("creator");
1779
1780            let instantiate_msg = InstantiateMsg {
1781                name: "Cash Token".to_string(),
1782                symbol: "CASH".to_string(),
1783                decimals: 9,
1784                initial_balances: vec![],
1785                mint: None,
1786                marketing: Some(InstantiateMarketingInfo {
1787                    project: Some("Project".to_owned()),
1788                    description: Some("Description".to_owned()),
1789                    marketing: Some(creator.to_string()),
1790                    logo: Some(Logo::Url("url".to_owned())),
1791                }),
1792            };
1793
1794            let info = mock_info(creator.as_str(), &[]);
1795
1796            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1797
1798            let err = execute(
1799                deps.as_mut(),
1800                mock_env(),
1801                info,
1802                ExecuteMsg::UpdateMarketing {
1803                    project: None,
1804                    description: None,
1805                    marketing: Some("m".to_owned()),
1806                },
1807            )
1808            .unwrap_err();
1809
1810            assert!(
1811                matches!(err, ContractError::Std(_)),
1812                "Expected Std error, received: {err}",
1813            );
1814
1815            assert_eq!(
1816                query_marketing_info(deps.as_ref()).unwrap(),
1817                MarketingInfoResponse {
1818                    project: Some("Project".to_owned()),
1819                    description: Some("Description".to_owned()),
1820                    marketing: Some(creator),
1821                    logo: Some(LogoInfo::Url("url".to_owned())),
1822                }
1823            );
1824
1825            let err = query_download_logo(deps.as_ref()).unwrap_err();
1826            assert!(
1827                matches!(err, StdError::NotFound { .. }),
1828                "Expected StdError::NotFound, received {err}",
1829            );
1830        }
1831
1832        #[test]
1833        fn clear_marketing() {
1834            let mut deps = mock_dependencies();
1835
1836            let creator = deps.api.addr_make("creator");
1837
1838            let instantiate_msg = InstantiateMsg {
1839                name: "Cash Token".to_string(),
1840                symbol: "CASH".to_string(),
1841                decimals: 9,
1842                initial_balances: vec![],
1843                mint: None,
1844                marketing: Some(InstantiateMarketingInfo {
1845                    project: Some("Project".to_owned()),
1846                    description: Some("Description".to_owned()),
1847                    marketing: Some(creator.to_string()),
1848                    logo: Some(Logo::Url("url".to_owned())),
1849                }),
1850            };
1851
1852            let info = mock_info(creator.as_str(), &[]);
1853
1854            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1855
1856            let res = execute(
1857                deps.as_mut(),
1858                mock_env(),
1859                info,
1860                ExecuteMsg::UpdateMarketing {
1861                    project: None,
1862                    description: None,
1863                    marketing: Some("".to_owned()),
1864                },
1865            )
1866            .unwrap();
1867
1868            assert_eq!(res.messages, vec![]);
1869
1870            assert_eq!(
1871                query_marketing_info(deps.as_ref()).unwrap(),
1872                MarketingInfoResponse {
1873                    project: Some("Project".to_owned()),
1874                    description: Some("Description".to_owned()),
1875                    marketing: None,
1876                    logo: Some(LogoInfo::Url("url".to_owned())),
1877                }
1878            );
1879
1880            let err = query_download_logo(deps.as_ref()).unwrap_err();
1881            assert!(
1882                matches!(err, StdError::NotFound { .. }),
1883                "Expected StdError::NotFound, received {err}",
1884            );
1885        }
1886
1887        #[test]
1888        fn update_logo_url() {
1889            let mut deps = mock_dependencies();
1890
1891            let creator = deps.api.addr_make("creator");
1892
1893            let instantiate_msg = InstantiateMsg {
1894                name: "Cash Token".to_string(),
1895                symbol: "CASH".to_string(),
1896                decimals: 9,
1897                initial_balances: vec![],
1898                mint: None,
1899                marketing: Some(InstantiateMarketingInfo {
1900                    project: Some("Project".to_owned()),
1901                    description: Some("Description".to_owned()),
1902                    marketing: Some(creator.to_string()),
1903                    logo: Some(Logo::Url("url".to_owned())),
1904                }),
1905            };
1906
1907            let info = mock_info(creator.as_str(), &[]);
1908
1909            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1910
1911            let res = execute(
1912                deps.as_mut(),
1913                mock_env(),
1914                info,
1915                ExecuteMsg::UploadLogo(Logo::Url("new_url".to_owned())),
1916            )
1917            .unwrap();
1918
1919            assert_eq!(res.messages, vec![]);
1920
1921            assert_eq!(
1922                query_marketing_info(deps.as_ref()).unwrap(),
1923                MarketingInfoResponse {
1924                    project: Some("Project".to_owned()),
1925                    description: Some("Description".to_owned()),
1926                    marketing: Some(creator),
1927                    logo: Some(LogoInfo::Url("new_url".to_owned())),
1928                }
1929            );
1930
1931            let err = query_download_logo(deps.as_ref()).unwrap_err();
1932            assert!(
1933                matches!(err, StdError::NotFound { .. }),
1934                "Expected StdError::NotFound, received {err}",
1935            );
1936        }
1937
1938        #[test]
1939        fn update_logo_png() {
1940            let mut deps = mock_dependencies();
1941
1942            let creator = deps.api.addr_make("creator");
1943
1944            let instantiate_msg = InstantiateMsg {
1945                name: "Cash Token".to_string(),
1946                symbol: "CASH".to_string(),
1947                decimals: 9,
1948                initial_balances: vec![],
1949                mint: None,
1950                marketing: Some(InstantiateMarketingInfo {
1951                    project: Some("Project".to_owned()),
1952                    description: Some("Description".to_owned()),
1953                    marketing: Some(creator.to_string()),
1954                    logo: Some(Logo::Url("url".to_owned())),
1955                }),
1956            };
1957
1958            let info = mock_info(creator.as_str(), &[]);
1959
1960            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
1961
1962            let res = execute(
1963                deps.as_mut(),
1964                mock_env(),
1965                info,
1966                ExecuteMsg::UploadLogo(Logo::Embedded(EmbeddedLogo::Png(PNG_HEADER.into()))),
1967            )
1968            .unwrap();
1969
1970            assert_eq!(res.messages, vec![]);
1971
1972            assert_eq!(
1973                query_marketing_info(deps.as_ref()).unwrap(),
1974                MarketingInfoResponse {
1975                    project: Some("Project".to_owned()),
1976                    description: Some("Description".to_owned()),
1977                    marketing: Some(creator),
1978                    logo: Some(LogoInfo::Embedded),
1979                }
1980            );
1981
1982            assert_eq!(
1983                query_download_logo(deps.as_ref()).unwrap(),
1984                DownloadLogoResponse {
1985                    mime_type: "image/png".to_owned(),
1986                    data: PNG_HEADER.into(),
1987                }
1988            );
1989        }
1990
1991        #[test]
1992        fn update_logo_svg() {
1993            let mut deps = mock_dependencies();
1994
1995            let creator = deps.api.addr_make("creator");
1996
1997            let instantiate_msg = InstantiateMsg {
1998                name: "Cash Token".to_string(),
1999                symbol: "CASH".to_string(),
2000                decimals: 9,
2001                initial_balances: vec![],
2002                mint: None,
2003                marketing: Some(InstantiateMarketingInfo {
2004                    project: Some("Project".to_owned()),
2005                    description: Some("Description".to_owned()),
2006                    marketing: Some(creator.to_string()),
2007                    logo: Some(Logo::Url("url".to_owned())),
2008                }),
2009            };
2010
2011            let info = mock_info(creator.as_str(), &[]);
2012
2013            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
2014
2015            let img = "<?xml version=\"1.0\"?><svg></svg>".as_bytes();
2016            let res = execute(
2017                deps.as_mut(),
2018                mock_env(),
2019                info,
2020                ExecuteMsg::UploadLogo(Logo::Embedded(EmbeddedLogo::Svg(img.into()))),
2021            )
2022            .unwrap();
2023
2024            assert_eq!(res.messages, vec![]);
2025
2026            assert_eq!(
2027                query_marketing_info(deps.as_ref()).unwrap(),
2028                MarketingInfoResponse {
2029                    project: Some("Project".to_owned()),
2030                    description: Some("Description".to_owned()),
2031                    marketing: Some(creator),
2032                    logo: Some(LogoInfo::Embedded),
2033                }
2034            );
2035
2036            assert_eq!(
2037                query_download_logo(deps.as_ref()).unwrap(),
2038                DownloadLogoResponse {
2039                    mime_type: "image/svg+xml".to_owned(),
2040                    data: img.into(),
2041                }
2042            );
2043        }
2044
2045        #[test]
2046        fn update_logo_png_oversized() {
2047            let mut deps = mock_dependencies();
2048
2049            let creator = deps.api.addr_make("creator");
2050
2051            let instantiate_msg = InstantiateMsg {
2052                name: "Cash Token".to_string(),
2053                symbol: "CASH".to_string(),
2054                decimals: 9,
2055                initial_balances: vec![],
2056                mint: None,
2057                marketing: Some(InstantiateMarketingInfo {
2058                    project: Some("Project".to_owned()),
2059                    description: Some("Description".to_owned()),
2060                    marketing: Some(creator.to_string()),
2061                    logo: Some(Logo::Url("url".to_owned())),
2062                }),
2063            };
2064
2065            let info = mock_info(creator.as_str(), &[]);
2066
2067            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
2068
2069            let img = [&PNG_HEADER[..], &[1; 6000][..]].concat();
2070            let err = execute(
2071                deps.as_mut(),
2072                mock_env(),
2073                info,
2074                ExecuteMsg::UploadLogo(Logo::Embedded(EmbeddedLogo::Png(img.into()))),
2075            )
2076            .unwrap_err();
2077
2078            assert_eq!(err, ContractError::LogoTooBig {});
2079
2080            assert_eq!(
2081                query_marketing_info(deps.as_ref()).unwrap(),
2082                MarketingInfoResponse {
2083                    project: Some("Project".to_owned()),
2084                    description: Some("Description".to_owned()),
2085                    marketing: Some(creator),
2086                    logo: Some(LogoInfo::Url("url".to_owned())),
2087                }
2088            );
2089
2090            let err = query_download_logo(deps.as_ref()).unwrap_err();
2091            assert!(
2092                matches!(err, StdError::NotFound { .. }),
2093                "Expected StdError::NotFound, received {err}",
2094            );
2095        }
2096
2097        #[test]
2098        fn update_logo_svg_oversized() {
2099            let mut deps = mock_dependencies();
2100
2101            let creator = deps.api.addr_make("creator");
2102
2103            let instantiate_msg = InstantiateMsg {
2104                name: "Cash Token".to_string(),
2105                symbol: "CASH".to_string(),
2106                decimals: 9,
2107                initial_balances: vec![],
2108                mint: None,
2109                marketing: Some(InstantiateMarketingInfo {
2110                    project: Some("Project".to_owned()),
2111                    description: Some("Description".to_owned()),
2112                    marketing: Some(creator.to_string()),
2113                    logo: Some(Logo::Url("url".to_owned())),
2114                }),
2115            };
2116
2117            let info = mock_info(creator.as_str(), &[]);
2118
2119            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
2120
2121            let img = [
2122                "<?xml version=\"1.0\"?><svg>",
2123                std::str::from_utf8(&[b'x'; 6000]).unwrap(),
2124                "</svg>",
2125            ]
2126            .concat()
2127            .into_bytes();
2128
2129            let err = execute(
2130                deps.as_mut(),
2131                mock_env(),
2132                info,
2133                ExecuteMsg::UploadLogo(Logo::Embedded(EmbeddedLogo::Svg(img.into()))),
2134            )
2135            .unwrap_err();
2136
2137            assert_eq!(err, ContractError::LogoTooBig {});
2138
2139            assert_eq!(
2140                query_marketing_info(deps.as_ref()).unwrap(),
2141                MarketingInfoResponse {
2142                    project: Some("Project".to_owned()),
2143                    description: Some("Description".to_owned()),
2144                    marketing: Some(creator),
2145                    logo: Some(LogoInfo::Url("url".to_owned())),
2146                }
2147            );
2148
2149            let err = query_download_logo(deps.as_ref()).unwrap_err();
2150            assert!(
2151                matches!(err, StdError::NotFound { .. }),
2152                "Expected StdError::NotFound, received {err}",
2153            );
2154        }
2155
2156        #[test]
2157        fn update_logo_png_invalid() {
2158            let mut deps = mock_dependencies();
2159
2160            let creator = deps.api.addr_make("creator");
2161
2162            let instantiate_msg = InstantiateMsg {
2163                name: "Cash Token".to_string(),
2164                symbol: "CASH".to_string(),
2165                decimals: 9,
2166                initial_balances: vec![],
2167                mint: None,
2168                marketing: Some(InstantiateMarketingInfo {
2169                    project: Some("Project".to_owned()),
2170                    description: Some("Description".to_owned()),
2171                    marketing: Some(creator.to_string()),
2172                    logo: Some(Logo::Url("url".to_owned())),
2173                }),
2174            };
2175
2176            let info = mock_info(creator.as_str(), &[]);
2177
2178            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
2179
2180            let img = &[1];
2181            let err = execute(
2182                deps.as_mut(),
2183                mock_env(),
2184                info,
2185                ExecuteMsg::UploadLogo(Logo::Embedded(EmbeddedLogo::Png(img.into()))),
2186            )
2187            .unwrap_err();
2188
2189            assert_eq!(err, ContractError::InvalidPngHeader {});
2190
2191            assert_eq!(
2192                query_marketing_info(deps.as_ref()).unwrap(),
2193                MarketingInfoResponse {
2194                    project: Some("Project".to_owned()),
2195                    description: Some("Description".to_owned()),
2196                    marketing: Some(creator),
2197                    logo: Some(LogoInfo::Url("url".to_owned())),
2198                }
2199            );
2200
2201            let err = query_download_logo(deps.as_ref()).unwrap_err();
2202            assert!(
2203                matches!(err, StdError::NotFound { .. }),
2204                "Expected StdError::NotFound, received {err}",
2205            );
2206        }
2207
2208        #[test]
2209        fn update_logo_svg_invalid() {
2210            let mut deps = mock_dependencies();
2211
2212            let creator = deps.api.addr_make("creator");
2213
2214            let instantiate_msg = InstantiateMsg {
2215                name: "Cash Token".to_string(),
2216                symbol: "CASH".to_string(),
2217                decimals: 9,
2218                initial_balances: vec![],
2219                mint: None,
2220                marketing: Some(InstantiateMarketingInfo {
2221                    project: Some("Project".to_owned()),
2222                    description: Some("Description".to_owned()),
2223                    marketing: Some(creator.to_string()),
2224                    logo: Some(Logo::Url("url".to_owned())),
2225                }),
2226            };
2227
2228            let info = mock_info("creator", &[]);
2229
2230            instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap();
2231
2232            let img = &[1];
2233
2234            let err = execute(
2235                deps.as_mut(),
2236                mock_env(),
2237                info,
2238                ExecuteMsg::UploadLogo(Logo::Embedded(EmbeddedLogo::Svg(img.into()))),
2239            )
2240            .unwrap_err();
2241
2242            assert_eq!(err, ContractError::InvalidXmlPreamble {});
2243
2244            assert_eq!(
2245                query_marketing_info(deps.as_ref()).unwrap(),
2246                MarketingInfoResponse {
2247                    project: Some("Project".to_owned()),
2248                    description: Some("Description".to_owned()),
2249                    marketing: Some(creator),
2250                    logo: Some(LogoInfo::Url("url".to_owned())),
2251                }
2252            );
2253
2254            let err = query_download_logo(deps.as_ref()).unwrap_err();
2255            assert!(
2256                matches!(err, StdError::NotFound { .. }),
2257                "Expected StdError::NotFound, received {err}",
2258            );
2259        }
2260    }
2261}