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