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  }