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
26const CONTRACT_NAME: &str = "crates.io:cw20-base";
28const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
29
30const LOGO_SIZE_CAP: usize = 5 * 1024;
31
32fn verify_xml_preamble(data: &[u8]) -> Result<(), ContractError> {
34 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 }
54
55fn 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
66fn verify_png_logo(logo: &[u8]) -> Result<(), ContractError> {
68 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
84fn 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(()), }
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 msg.validate()?;
103 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 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 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 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 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 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 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 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 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 fn do_instantiate(deps: DepsMut, addr: &str, amount: Uint128) -> TokenInfoResponse {
640 _do_instantiate(deps, addr, amount, None)
641 }
642
643 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 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 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 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 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 assert_eq!(mint, None);
1006
1007 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let binary_msg = Cw20ReceiveMsg {
1289 sender: addr1.clone(),
1290 amount: transfer,
1291 msg: send_msg,
1292 }
1293 .into_json_binary()
1294 .unwrap();
1295 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 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 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 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 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 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 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 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}