astroport_xastro_token/
contract.rs

1use cosmwasm_std::{
2    attr, entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response,
3    StdError, StdResult, Uint128,
4};
5use cw20::{
6    AllAccountsResponse, BalanceResponse, Cw20Coin, Cw20ReceiveMsg, EmbeddedLogo, Logo, LogoInfo,
7    MarketingInfoResponse,
8};
9use cw20_base::allowances::{
10    deduct_allowance, execute_decrease_allowance, execute_increase_allowance, query_allowance,
11};
12
13use crate::state::{capture_total_supply_history, check_minter, get_total_supply_at, BALANCES};
14use astroport::asset::addr_opt_validate;
15use astroport::xastro_token::{InstantiateMsg, MigrateMsg, QueryMsg};
16use cw2::{get_contract_version, set_contract_version};
17use cw20_base::contract::{
18    execute_update_marketing, execute_upload_logo, query_download_logo, query_marketing_info,
19    query_minter, query_token_info,
20};
21use cw20_base::enumerable::query_owner_allowances;
22use cw20_base::msg::ExecuteMsg;
23use cw20_base::state::{MinterData, TokenInfo, LOGO, MARKETING_INFO, TOKEN_INFO};
24use cw20_base::ContractError;
25use cw_storage_plus::Bound;
26
27/// Contract name that is used for migration.
28const CONTRACT_NAME: &str = "astroport-xastro-token";
29/// Contract version that is used for migration.
30const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
31
32// Settings for pagination.
33const MAX_LIMIT: u32 = 30;
34const DEFAULT_LIMIT: u32 = 10;
35
36const LOGO_SIZE_CAP: usize = 5 * 1024;
37
38/// Checks if data starts with XML preamble
39fn verify_xml_preamble(data: &[u8]) -> Result<(), ContractError> {
40    // The easiest way to perform this check would be just match on regex, however regex
41    // compilation is heavy and probably not worth it.
42
43    let preamble = data
44        .split_inclusive(|c| *c == b'>')
45        .next()
46        .ok_or(ContractError::InvalidXmlPreamble {})?;
47
48    const PREFIX: &[u8] = b"<?xml ";
49    const POSTFIX: &[u8] = b"?>";
50
51    if !(preamble.starts_with(PREFIX) && preamble.ends_with(POSTFIX)) {
52        Err(ContractError::InvalidXmlPreamble {})
53    } else {
54        Ok(())
55    }
56
57    // Additionally attributes format could be validated as they are well defined, as well as
58    // comments presence inside of preable, but it is probably not worth it.
59}
60
61/// Validates XML logo
62fn verify_xml_logo(logo: &[u8]) -> Result<(), ContractError> {
63    verify_xml_preamble(logo)?;
64
65    if logo.len() > LOGO_SIZE_CAP {
66        Err(ContractError::LogoTooBig {})
67    } else {
68        Ok(())
69    }
70}
71
72/// Validates png logo
73fn verify_png_logo(logo: &[u8]) -> Result<(), ContractError> {
74    // PNG header format:
75    // 0x89 - magic byte, out of ASCII table to fail on 7-bit systems
76    // "PNG" ascii representation
77    // [0x0d, 0x0a] - dos style line ending
78    // 0x1a - dos control character, stop displaying rest of the file
79    // 0x0a - unix style line ending
80    const HEADER: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a];
81    if logo.len() > LOGO_SIZE_CAP {
82        Err(ContractError::LogoTooBig {})
83    } else if !logo.starts_with(&HEADER) {
84        Err(ContractError::InvalidPngHeader {})
85    } else {
86        Ok(())
87    }
88}
89
90/// Checks if passed logo is correct, and if not, returns an error
91fn verify_logo(logo: &Logo) -> Result<(), ContractError> {
92    match logo {
93        Logo::Embedded(EmbeddedLogo::Svg(logo)) => verify_xml_logo(logo),
94        Logo::Embedded(EmbeddedLogo::Png(logo)) => verify_png_logo(logo),
95        Logo::Url(_) => Ok(()), // Any reasonable url validation would be regex based, probably not worth it
96    }
97}
98
99/// Creates a new contract with the specified parameters in the [`InstantiateMsg`].
100#[cfg_attr(not(feature = "library"), entry_point)]
101pub fn instantiate(
102    mut deps: DepsMut,
103    env: Env,
104    _info: MessageInfo,
105    msg: InstantiateMsg,
106) -> Result<Response, ContractError> {
107    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
108
109    // Check valid token info
110    msg.validate()?;
111
112    // Create initial accounts
113    let total_supply = create_accounts(&mut deps, &env, &msg.initial_balances)?;
114
115    if !total_supply.is_zero() {
116        capture_total_supply_history(deps.storage, &env, total_supply)?;
117    }
118
119    // Check supply cap
120    if let Some(limit) = msg.get_cap() {
121        if total_supply > limit {
122            return Err(StdError::generic_err("Initial supply greater than cap").into());
123        }
124    }
125
126    let mint = match msg.mint {
127        Some(m) => Some(MinterData {
128            minter: deps.api.addr_validate(&m.minter)?,
129            cap: m.cap,
130        }),
131        None => None,
132    };
133
134    // Store token info
135    let data = TokenInfo {
136        name: msg.name,
137        symbol: msg.symbol,
138        decimals: msg.decimals,
139        total_supply,
140        mint,
141    };
142    TOKEN_INFO.save(deps.storage, &data)?;
143
144    if let Some(marketing) = msg.marketing {
145        let logo = if let Some(logo) = marketing.logo {
146            verify_logo(&logo)?;
147            LOGO.save(deps.storage, &logo)?;
148
149            match logo {
150                Logo::Url(url) => Some(LogoInfo::Url(url)),
151                Logo::Embedded(_) => Some(LogoInfo::Embedded),
152            }
153        } else {
154            None
155        };
156
157        let data = MarketingInfoResponse {
158            project: marketing.project,
159            description: marketing.description,
160            marketing: marketing
161                .marketing
162                .map(|addr| deps.api.addr_validate(&addr))
163                .transpose()?,
164            logo,
165        };
166        MARKETING_INFO.save(deps.storage, &data)?;
167    }
168
169    Ok(Response::default())
170}
171
172/// Mints tokens for specific accounts.
173///
174/// * **accounts** array with accounts for which to mint tokens.
175pub fn create_accounts(deps: &mut DepsMut, env: &Env, accounts: &[Cw20Coin]) -> StdResult<Uint128> {
176    let mut total_supply = Uint128::zero();
177
178    for row in accounts {
179        let address = deps.api.addr_validate(&row.address)?;
180        BALANCES.save(deps.storage, &address, &row.amount, env.block.height)?;
181        total_supply += row.amount;
182    }
183
184    Ok(total_supply)
185}
186
187/// Exposes execute functions available in the contract.
188///
189/// ## Variants
190/// * **ExecuteMsg::Transfer { recipient, amount }** Transfers tokens to recipient.
191///
192/// * **ExecuteMsg::Burn { amount }** Burns tokens.
193///
194/// * **ExecuteMsg::Send { contract, amount, msg }** Sends tokens to contract and executes message.
195///
196/// * **ExecuteMsg::Mint { recipient, amount }** Mints tokens.
197///
198/// * **ExecuteMsg::IncreaseAllowance { spender, amount, expires }** Increases allowance.
199///
200/// * **ExecuteMsg::DecreaseAllowance { spender, amount, expires }** Decreases allowance.
201///
202/// * **ExecuteMsg::TransferFrom { owner, recipient, amount }** Transfers tokens from.
203///
204/// * **ExecuteMsg::BurnFrom { owner, amount }** Burns tokens from.
205///
206/// * **ExecuteMsg::SendFrom { owner, contract, amount, msg }** Sends tokens from.
207///
208/// * **ExecuteMsg::UpdateMarketing { project, description, marketing }** Updates marketing info.
209///
210/// * **ExecuteMsg::UploadLogo(logo)** Uploads logo.
211#[cfg_attr(not(feature = "library"), entry_point)]
212pub fn execute(
213    deps: DepsMut,
214    env: Env,
215    info: MessageInfo,
216    msg: ExecuteMsg,
217) -> Result<Response, ContractError> {
218    match msg {
219        ExecuteMsg::Transfer { recipient, amount } => {
220            execute_transfer(deps, env, info, recipient, amount)
221        }
222        ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount),
223        ExecuteMsg::Send {
224            contract,
225            amount,
226            msg,
227        } => execute_send(deps, env, info, contract, amount, msg),
228        ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),
229        ExecuteMsg::IncreaseAllowance {
230            spender,
231            amount,
232            expires,
233        } => execute_increase_allowance(deps, env, info, spender, amount, expires),
234        ExecuteMsg::DecreaseAllowance {
235            spender,
236            amount,
237            expires,
238        } => execute_decrease_allowance(deps, env, info, spender, amount, expires),
239        ExecuteMsg::TransferFrom {
240            owner,
241            recipient,
242            amount,
243        } => execute_transfer_from(deps, env, info, owner, recipient, amount),
244        ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount),
245        ExecuteMsg::SendFrom {
246            owner,
247            contract,
248            amount,
249            msg,
250        } => execute_send_from(deps, env, info, owner, contract, amount, msg),
251        ExecuteMsg::UpdateMarketing {
252            project,
253            description,
254            marketing,
255        } => execute_update_marketing(deps, env, info, project, description, marketing),
256        ExecuteMsg::UploadLogo(logo) => execute_upload_logo(deps, env, info, logo),
257        _ => Err(StdError::generic_err("Unsupported execute message").into()),
258    }
259}
260
261/// Executes a token transfer.
262pub fn execute_transfer(
263    deps: DepsMut,
264    env: Env,
265    info: MessageInfo,
266    recipient: String,
267    amount: Uint128,
268) -> Result<Response, ContractError> {
269    if amount == Uint128::zero() {
270        return Err(ContractError::InvalidZeroAmount {});
271    }
272
273    let rcpt_addr = deps.api.addr_validate(&recipient)?;
274
275    BALANCES.update(
276        deps.storage,
277        &info.sender,
278        env.block.height,
279        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
280    )?;
281    BALANCES.update(
282        deps.storage,
283        &rcpt_addr,
284        env.block.height,
285        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
286    )?;
287
288    Ok(Response::new().add_attributes(vec![
289        attr("action", "transfer"),
290        attr("from", info.sender),
291        attr("to", rcpt_addr),
292        attr("amount", amount),
293    ]))
294}
295
296/// Burns a token.
297///
298/// * **amount** amount of tokens that the function caller wants to burn from their own account.
299pub fn execute_burn(
300    deps: DepsMut,
301    env: Env,
302    info: MessageInfo,
303    amount: Uint128,
304) -> Result<Response, ContractError> {
305    if amount == Uint128::zero() {
306        return Err(ContractError::InvalidZeroAmount {});
307    }
308
309    let config = TOKEN_INFO.load(deps.storage)?;
310    check_minter(&info.sender, &config)?;
311
312    // Lower the sender's balance
313    BALANCES.update(
314        deps.storage,
315        &info.sender,
316        env.block.height,
317        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
318    )?;
319
320    // Reduce total_supply
321    let token_info = TOKEN_INFO.update(deps.storage, |mut info| -> StdResult<_> {
322        info.total_supply = info.total_supply.checked_sub(amount)?;
323        Ok(info)
324    })?;
325
326    capture_total_supply_history(deps.storage, &env, token_info.total_supply)?;
327
328    let res = Response::new().add_attributes(vec![
329        attr("action", "burn"),
330        attr("from", info.sender),
331        attr("amount", amount),
332    ]);
333    Ok(res)
334}
335
336/// Mints a token.
337pub fn execute_mint(
338    deps: DepsMut,
339    env: Env,
340    info: MessageInfo,
341    recipient: String,
342    amount: Uint128,
343) -> Result<Response, ContractError> {
344    if amount == Uint128::zero() {
345        return Err(ContractError::InvalidZeroAmount {});
346    }
347
348    let mut config = TOKEN_INFO.load(deps.storage)?;
349    check_minter(&info.sender, &config)?;
350
351    // Update supply and enforce cap
352    config.total_supply = config
353        .total_supply
354        .checked_add(amount)
355        .map_err(StdError::from)?;
356    if let Some(limit) = config.get_cap() {
357        if config.total_supply > limit {
358            return Err(ContractError::CannotExceedCap {});
359        }
360    }
361
362    TOKEN_INFO.save(deps.storage, &config)?;
363
364    capture_total_supply_history(deps.storage, &env, config.total_supply)?;
365
366    // Add amount to recipient balance
367    let rcpt_addr = deps.api.addr_validate(&recipient)?;
368    BALANCES.update(
369        deps.storage,
370        &rcpt_addr,
371        env.block.height,
372        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
373    )?;
374
375    Ok(Response::new().add_attributes(vec![
376        attr("action", "mint"),
377        attr("to", rcpt_addr),
378        attr("amount", amount),
379    ]))
380}
381
382/// Executes a token send.
383///
384/// * **contract** token contract.
385///
386/// * **amount** amount of tokens to send.
387///
388/// * **msg** internal serialized message.
389pub fn execute_send(
390    deps: DepsMut,
391    env: Env,
392    info: MessageInfo,
393    contract: String,
394    amount: Uint128,
395    msg: Binary,
396) -> Result<Response, ContractError> {
397    if amount == Uint128::zero() {
398        return Err(ContractError::InvalidZeroAmount {});
399    }
400
401    let rcpt_addr = deps.api.addr_validate(&contract)?;
402
403    // Move the tokens to the contract
404    BALANCES.update(
405        deps.storage,
406        &info.sender,
407        env.block.height,
408        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
409    )?;
410    BALANCES.update(
411        deps.storage,
412        &rcpt_addr,
413        env.block.height,
414        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
415    )?;
416
417    let res = Response::new()
418        .add_attributes(vec![
419            attr("action", "send"),
420            attr("from", &info.sender),
421            attr("to", &rcpt_addr),
422            attr("amount", amount),
423        ])
424        .add_message(
425            Cw20ReceiveMsg {
426                sender: info.sender.into(),
427                amount,
428                msg,
429            }
430            .into_cosmos_msg(contract)?,
431        );
432    Ok(res)
433}
434
435/// Executes a transfer from.
436///
437/// * **owner** account from which to transfer tokens.
438///
439/// * **recipient** transfer recipient.
440///
441/// * **amount** amount to transfer.
442pub fn execute_transfer_from(
443    deps: DepsMut,
444    env: Env,
445    info: MessageInfo,
446    owner: String,
447    recipient: String,
448    amount: Uint128,
449) -> Result<Response, ContractError> {
450    let rcpt_addr = deps.api.addr_validate(&recipient)?;
451    let owner_addr = deps.api.addr_validate(&owner)?;
452
453    // Deduct allowance before doing anything else
454    deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
455
456    BALANCES.update(
457        deps.storage,
458        &owner_addr,
459        env.block.height,
460        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
461    )?;
462    BALANCES.update(
463        deps.storage,
464        &rcpt_addr,
465        env.block.height,
466        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_add(amount)?) },
467    )?;
468
469    let res = Response::new().add_attributes(vec![
470        attr("action", "transfer_from"),
471        attr("from", owner),
472        attr("to", recipient),
473        attr("by", info.sender),
474        attr("amount", amount),
475    ]);
476    Ok(res)
477}
478
479/// Executes a burn from.
480///
481/// * **owner** account from which to burn tokens.
482///
483/// * **amount** amount of tokens to burn.
484pub fn execute_burn_from(
485    deps: DepsMut,
486    env: Env,
487    info: MessageInfo,
488    owner: String,
489    amount: Uint128,
490) -> Result<Response, ContractError> {
491    let owner_addr = deps.api.addr_validate(&owner)?;
492
493    let config = TOKEN_INFO.load(deps.storage)?;
494    check_minter(&info.sender, &config)?;
495
496    // Deduct allowance before doing anything else
497    deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
498
499    // Lower balance
500    BALANCES.update(
501        deps.storage,
502        &owner_addr,
503        env.block.height,
504        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
505    )?;
506
507    // Reduce total_supply
508    let token_info = TOKEN_INFO.update(deps.storage, |mut meta| -> StdResult<_> {
509        meta.total_supply = meta.total_supply.checked_sub(amount)?;
510        Ok(meta)
511    })?;
512
513    capture_total_supply_history(deps.storage, &env, token_info.total_supply)?;
514
515    let res = Response::new().add_attributes(vec![
516        attr("action", "burn_from"),
517        attr("from", owner),
518        attr("by", info.sender),
519        attr("amount", amount),
520    ]);
521    Ok(res)
522}
523
524/// Executes a send from.
525///
526/// * **owner** account from which to send tokens.
527///
528/// * **contract** token contract address.
529///
530/// * **amount** amount of tokens to send.
531///
532/// * **msg** internal serialized message.
533pub fn execute_send_from(
534    deps: DepsMut,
535    env: Env,
536    info: MessageInfo,
537    owner: String,
538    contract: String,
539    amount: Uint128,
540    msg: Binary,
541) -> Result<Response, ContractError> {
542    let rcpt_addr = deps.api.addr_validate(&contract)?;
543    let owner_addr = deps.api.addr_validate(&owner)?;
544
545    // Deduct allowance before doing anything else
546    deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
547
548    // Move the tokens to the contract
549    BALANCES.update(
550        deps.storage,
551        &owner_addr,
552        env.block.height,
553        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_sub(amount)?) },
554    )?;
555    BALANCES.update(
556        deps.storage,
557        &rcpt_addr,
558        env.block.height,
559        |balance| -> StdResult<_> { Ok(balance.unwrap_or_default().checked_add(amount)?) },
560    )?;
561
562    let res = Response::new()
563        .add_attributes(vec![
564            attr("action", "send_from"),
565            attr("from", &owner),
566            attr("to", &contract),
567            attr("by", &info.sender),
568            attr("amount", amount),
569        ])
570        .add_message(
571            Cw20ReceiveMsg {
572                sender: info.sender.into(),
573                amount,
574                msg,
575            }
576            .into_cosmos_msg(contract)?,
577        );
578    Ok(res)
579}
580
581/// Exposes all the queries available in the contract.
582///
583/// ## Queries
584/// * **Balance { address: String }** Returns the current balance of the given address, 0 if unset.
585/// Uses a [`BalanceResponse`] object.
586///
587/// * **BalanceAt { address, block }** Returns the balance of the given address at the given block
588/// using a [`BalanceResponse`] object.
589///
590/// * **TotalSupplyAt { block }** Returns the total supply at the given block.
591///
592/// * **TokenInfo {}** Returns the token metadata - name, decimals, supply, etc
593/// using a [`cw20::TokenInfoResponse`] object.
594///
595/// * **Minter {}** Returns the address that can mint tokens and the hard cap on the total amount of tokens using
596/// a [`cw20::MinterResponse`] object.
597///
598/// * **QueryMsg::Allowance { owner, spender }** Returns how much the spender can use from the owner account, 0 if unset.
599/// Uses a [`cw20::AllowanceResponse`] object.
600///
601/// * **QueryMsg::AllAllowances { owner, start_after, limit }** Returns all allowances this owner has approved
602/// using a [`cw20::AllAllowancesResponse`] object.
603///
604/// * **QueryMsg::AllAccounts { start_after, limit }** Returns all accounts that have a balance
605/// using a [`cw20::AllAccountsResponse`] object.
606///
607/// * **QueryMsg::MarketingInfo {}** Returns the token metadata
608/// using a [`cw20::MarketingInfoResponse`] object.
609///
610/// * **QueryMsg::DownloadLogo {}** Downloads the embedded logo data (if stored on-chain)
611/// and returns the result using a [`cw20::DownloadLogoResponse`] object.
612#[cfg_attr(not(feature = "library"), entry_point)]
613pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
614    match msg {
615        QueryMsg::Balance { address } => to_json_binary(&query_balance(deps, address)?),
616        QueryMsg::BalanceAt { address, block } => {
617            to_json_binary(&query_balance_at(deps, address, block)?)
618        }
619        QueryMsg::TotalSupplyAt { block } => {
620            to_json_binary(&get_total_supply_at(deps.storage, block)?)
621        }
622        QueryMsg::TokenInfo {} => to_json_binary(&query_token_info(deps)?),
623        QueryMsg::Minter {} => to_json_binary(&query_minter(deps)?),
624        QueryMsg::Allowance { owner, spender } => {
625            to_json_binary(&query_allowance(deps, owner, spender)?)
626        }
627        QueryMsg::AllAllowances {
628            owner,
629            start_after,
630            limit,
631        } => to_json_binary(&query_owner_allowances(deps, owner, start_after, limit)?),
632        QueryMsg::AllAccounts { start_after, limit } => {
633            to_json_binary(&query_all_accounts(deps, start_after, limit)?)
634        }
635        QueryMsg::MarketingInfo {} => to_json_binary(&query_marketing_info(deps)?),
636        QueryMsg::DownloadLogo {} => to_json_binary(&query_download_logo(deps)?),
637    }
638}
639
640/// Returns the specified account's balance.
641pub fn query_balance(deps: Deps, address: String) -> StdResult<BalanceResponse> {
642    let address = deps.api.addr_validate(&address)?;
643    let balance = BALANCES
644        .may_load(deps.storage, &address)?
645        .unwrap_or_default();
646    Ok(BalanceResponse { balance })
647}
648
649/// Returns the balance of the given address at the given block.
650pub fn query_balance_at(deps: Deps, address: String, block: u64) -> StdResult<BalanceResponse> {
651    let address = deps.api.addr_validate(&address)?;
652    let balance = BALANCES
653        .may_load_at_height(deps.storage, &address, block)?
654        .unwrap_or_default();
655    Ok(BalanceResponse { balance })
656}
657
658/// Returns the current balances of multiple accounts.
659///
660/// * **start_after** account from which to start querying for balances.
661///
662/// * **limit** amount of account balances to return.
663pub fn query_all_accounts(
664    deps: Deps,
665    start_after: Option<String>,
666    limit: Option<u32>,
667) -> StdResult<AllAccountsResponse> {
668    let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
669    let start = addr_opt_validate(deps.api, &start_after)?;
670    let start = start.as_ref().map(Bound::exclusive);
671
672    let accounts = BALANCES
673        .keys(deps.storage, start, None, Order::Ascending)
674        .take(limit)
675        .map(|addr| addr.map(Into::into))
676        .collect::<StdResult<_>>()?;
677
678    Ok(AllAccountsResponse { accounts })
679}
680
681#[cfg_attr(not(feature = "library"), entry_point)]
682pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
683    let contract_version = get_contract_version(deps.storage)?;
684
685    match contract_version.contract.as_ref() {
686        "astroport-xastro-token" => match contract_version.version.as_ref() {
687            "1.0.0" | "1.0.1" | "1.0.2" => {}
688            _ => {
689                return Err(StdError::generic_err(
690                    "Cannot migrate. Unsupported contract version",
691                ))
692            }
693        },
694        _ => {
695            return Err(StdError::generic_err(
696                "Cannot migrate. Unsupported contract name",
697            ))
698        }
699    }
700
701    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
702
703    Ok(Response::default()
704        .add_attribute("previous_contract_name", &contract_version.contract)
705        .add_attribute("previous_contract_version", &contract_version.version)
706        .add_attribute("new_contract_name", CONTRACT_NAME)
707        .add_attribute("new_contract_version", CONTRACT_VERSION))
708}
709
710#[cfg(test)]
711mod tests {
712    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
713    use cosmwasm_std::{Addr, StdError};
714
715    use super::*;
716    use astroport::xastro_token::InstantiateMarketingInfo;
717
718    mod marketing {
719        use cw20::DownloadLogoResponse;
720        use cw20_base::contract::{query_download_logo, query_marketing_info};
721
722        use super::*;
723
724        #[test]
725        fn basic() {
726            let mut deps = mock_dependencies();
727            let instantiate_msg = InstantiateMsg {
728                name: "Cash Token".to_string(),
729                symbol: "CASH".to_string(),
730                decimals: 9,
731                initial_balances: vec![],
732                mint: None,
733                marketing: Some(InstantiateMarketingInfo {
734                    project: Some("Project".to_owned()),
735                    description: Some("Description".to_owned()),
736                    marketing: Some("marketing".to_owned()),
737                    logo: Some(Logo::Url("url".to_owned())),
738                }),
739            };
740
741            let info = mock_info("creator", &[]);
742            let env = mock_env();
743            let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
744            assert_eq!(0, res.messages.len());
745
746            assert_eq!(
747                query_marketing_info(deps.as_ref()).unwrap(),
748                MarketingInfoResponse {
749                    project: Some("Project".to_owned()),
750                    description: Some("Description".to_owned()),
751                    marketing: Some(Addr::unchecked("marketing")),
752                    logo: Some(LogoInfo::Url("url".to_owned())),
753                }
754            );
755
756            let err = query_download_logo(deps.as_ref()).unwrap_err();
757            assert!(
758                matches!(err, StdError::NotFound { .. }),
759                "Expected StdError::NotFound, received {}",
760                err
761            );
762        }
763
764        #[test]
765        fn svg() {
766            let mut deps = mock_dependencies();
767            let img = "<?xml version=\"1.0\"?><svg></svg>".as_bytes();
768            let instantiate_msg = InstantiateMsg {
769                name: "Cash Token".to_string(),
770                symbol: "CASH".to_string(),
771                decimals: 9,
772                initial_balances: vec![],
773                mint: None,
774                marketing: Some(InstantiateMarketingInfo {
775                    project: Some("Project".to_owned()),
776                    description: Some("Description".to_owned()),
777                    marketing: Some("marketing".to_owned()),
778                    logo: Some(Logo::Embedded(EmbeddedLogo::Svg(img.into()))),
779                }),
780            };
781
782            let info = mock_info("creator", &[]);
783            let env = mock_env();
784            let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
785            assert_eq!(0, res.messages.len());
786
787            assert_eq!(
788                query_marketing_info(deps.as_ref()).unwrap(),
789                MarketingInfoResponse {
790                    project: Some("Project".to_owned()),
791                    description: Some("Description".to_owned()),
792                    marketing: Some(Addr::unchecked("marketing")),
793                    logo: Some(LogoInfo::Embedded),
794                }
795            );
796
797            let res: DownloadLogoResponse = query_download_logo(deps.as_ref()).unwrap();
798            assert_eq! {
799                res,
800                DownloadLogoResponse{
801                    data: img.into(),
802                    mime_type: "image/svg+xml".to_owned(),
803                }
804            }
805        }
806
807        #[test]
808        fn png() {
809            let mut deps = mock_dependencies();
810            const PNG_HEADER: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a];
811            let instantiate_msg = InstantiateMsg {
812                name: "Cash Token".to_string(),
813                symbol: "CASH".to_string(),
814                decimals: 9,
815                initial_balances: vec![],
816                mint: None,
817                marketing: Some(InstantiateMarketingInfo {
818                    project: Some("Project".to_owned()),
819                    description: Some("Description".to_owned()),
820                    marketing: Some("marketing".to_owned()),
821                    logo: Some(Logo::Embedded(EmbeddedLogo::Png(PNG_HEADER.into()))),
822                }),
823            };
824
825            let info = mock_info("creator", &[]);
826            let env = mock_env();
827            let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
828            assert_eq!(0, res.messages.len());
829
830            assert_eq!(
831                query_marketing_info(deps.as_ref()).unwrap(),
832                MarketingInfoResponse {
833                    project: Some("Project".to_owned()),
834                    description: Some("Description".to_owned()),
835                    marketing: Some(Addr::unchecked("marketing")),
836                    logo: Some(LogoInfo::Embedded),
837                }
838            );
839
840            let res: DownloadLogoResponse = query_download_logo(deps.as_ref()).unwrap();
841            assert_eq! {
842                res,
843                DownloadLogoResponse{
844                    data: PNG_HEADER.into(),
845                    mime_type: "image/png".to_owned(),
846                }
847            }
848        }
849    }
850}