something_for_tests/
contract.rs

1#[cfg(not(feature = "library"))]
2use cosmwasm_std::entry_point;
3use cosmwasm_std::{
4    attr, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult,
5    SubMsg, Uint128,
6};
7
8use cw2::set_contract_version;
9use cw20::{BalanceResponse, Cw20Coin, Cw20ReceiveMsg, MinterResponse, TokenInfoResponse};
10
11use crate::allowances::{
12    execute_burn_from, execute_decrease_allowance, execute_increase_allowance, execute_send_from,
13    execute_transfer_from, query_allowance,
14};
15use crate::enumerable::{query_all_accounts, query_all_allowances};
16use crate::error::ContractError;
17use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
18use crate::state::{MinterData, TokenInfo, BALANCES, TOKEN_INFO};
19
20// version info for migration info
21const CONTRACT_NAME: &str = "crates.io:cw20-base";
22const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
23
24#[cfg_attr(not(feature = "library"), entry_point)]
25pub fn instantiate(
26    mut deps: DepsMut,
27    _env: Env,
28    _info: MessageInfo,
29    msg: InstantiateMsg,
30) -> StdResult<Response> {
31    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
32    // check valid token info
33    msg.validate()?;
34    // create initial accounts
35    let total_supply = create_accounts(&mut deps, &msg.initial_balances)?;
36
37    if let Some(limit) = msg.get_cap() {
38        if total_supply > limit {
39            return Err(StdError::generic_err("Initial supply greater than cap"));
40        }
41    }
42
43    let mint = match msg.mint {
44        Some(m) => Some(MinterData {
45            minter: deps.api.addr_validate(&m.minter)?,
46            cap: m.cap,
47        }),
48        None => None,
49    };
50
51    // store token info
52    let data = TokenInfo {
53        name: msg.name,
54        symbol: msg.symbol,
55        decimals: msg.decimals,
56        total_supply,
57        mint,
58    };
59    TOKEN_INFO.save(deps.storage, &data)?;
60    Ok(Response::default())
61}
62
63pub fn create_accounts(deps: &mut DepsMut, accounts: &[Cw20Coin]) -> StdResult<Uint128> {
64    let mut total_supply = Uint128::zero();
65    for row in accounts {
66        let address = deps.api.addr_validate(&row.address)?;
67        BALANCES.save(deps.storage, &address, &row.amount)?;
68        total_supply += row.amount;
69    }
70    Ok(total_supply)
71}
72
73#[cfg_attr(not(feature = "library"), entry_point)]
74pub fn execute(
75    deps: DepsMut,
76    env: Env,
77    info: MessageInfo,
78    msg: ExecuteMsg,
79) -> Result<Response, ContractError> {
80    match msg {
81        ExecuteMsg::Transfer { recipient, amount } => {
82            execute_transfer(deps, env, info, recipient, amount)
83        }
84        ExecuteMsg::Burn { amount } => execute_burn(deps, env, info, amount),
85        ExecuteMsg::Send {
86            contract,
87            amount,
88            msg,
89        } => execute_send(deps, env, info, contract, amount, msg),
90        ExecuteMsg::Mint { recipient, amount } => execute_mint(deps, env, info, recipient, amount),
91        ExecuteMsg::IncreaseAllowance {
92            spender,
93            amount,
94            expires,
95        } => execute_increase_allowance(deps, env, info, spender, amount, expires),
96        ExecuteMsg::DecreaseAllowance {
97            spender,
98            amount,
99            expires,
100        } => execute_decrease_allowance(deps, env, info, spender, amount, expires),
101        ExecuteMsg::TransferFrom {
102            owner,
103            recipient,
104            amount,
105        } => execute_transfer_from(deps, env, info, owner, recipient, amount),
106        ExecuteMsg::BurnFrom { owner, amount } => execute_burn_from(deps, env, info, owner, amount),
107        ExecuteMsg::SendFrom {
108            owner,
109            contract,
110            amount,
111            msg,
112        } => execute_send_from(deps, env, info, owner, contract, amount, msg),
113    }
114}
115
116pub fn execute_transfer(
117    deps: DepsMut,
118    _env: Env,
119    info: MessageInfo,
120    recipient: String,
121    amount: Uint128,
122) -> Result<Response, ContractError> {
123    if amount == Uint128::zero() {
124        return Err(ContractError::InvalidZeroAmount {});
125    }
126
127    let rcpt_addr = deps.api.addr_validate(&recipient)?;
128
129    BALANCES.update(
130        deps.storage,
131        &info.sender,
132        |balance: Option<Uint128>| -> StdResult<_> {
133            Ok(balance.unwrap_or_default().checked_sub(amount)?)
134        },
135    )?;
136    BALANCES.update(
137        deps.storage,
138        &rcpt_addr,
139        |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
140    )?;
141
142    let res = Response {
143        messages: vec![],
144        attributes: vec![
145            attr("action", "transfer"),
146            attr("from", info.sender),
147            attr("to", recipient),
148            attr("amount", amount),
149        ],
150        events: vec![],
151        data: None,
152    };
153    Ok(res)
154}
155
156pub fn execute_burn(
157    deps: DepsMut,
158    _env: Env,
159    info: MessageInfo,
160    amount: Uint128,
161) -> Result<Response, ContractError> {
162    if amount == Uint128::zero() {
163        return Err(ContractError::InvalidZeroAmount {});
164    }
165
166    // lower balance
167    BALANCES.update(
168        deps.storage,
169        &info.sender,
170        |balance: Option<Uint128>| -> StdResult<_> {
171            Ok(balance.unwrap_or_default().checked_sub(amount)?)
172        },
173    )?;
174    // reduce total_supply
175    TOKEN_INFO.update(deps.storage, |mut info| -> StdResult<_> {
176        info.total_supply = info.total_supply.checked_sub(amount)?;
177        Ok(info)
178    })?;
179
180    let res = Response {
181        messages: vec![],
182        attributes: vec![
183            attr("action", "burn"),
184            attr("from", info.sender),
185            attr("amount", amount),
186        ],
187        events: vec![],
188        data: None,
189    };
190    Ok(res)
191}
192
193pub fn execute_mint(
194    deps: DepsMut,
195    _env: Env,
196    info: MessageInfo,
197    recipient: String,
198    amount: Uint128,
199) -> Result<Response, ContractError> {
200    if amount == Uint128::zero() {
201        return Err(ContractError::InvalidZeroAmount {});
202    }
203
204    let mut config = TOKEN_INFO.load(deps.storage)?;
205    if config.mint.is_none() || config.mint.as_ref().unwrap().minter != info.sender {
206        return Err(ContractError::Unauthorized {});
207    }
208
209    // update supply and enforce cap
210    config.total_supply += amount;
211    if let Some(limit) = config.get_cap() {
212        if config.total_supply > limit {
213            return Err(ContractError::CannotExceedCap {});
214        }
215    }
216    TOKEN_INFO.save(deps.storage, &config)?;
217
218    // add amount to recipient balance
219    let rcpt_addr = deps.api.addr_validate(&recipient)?;
220    BALANCES.update(
221        deps.storage,
222        &rcpt_addr,
223        |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
224    )?;
225
226    let res = Response {
227        attributes: vec![
228            attr("action", "mint"),
229            attr("to", recipient),
230            attr("amount", amount),
231        ],
232        ..Response::default()
233    };
234    Ok(res)
235}
236
237pub fn execute_send(
238    deps: DepsMut,
239    _env: Env,
240    info: MessageInfo,
241    contract: String,
242    amount: Uint128,
243    msg: Binary,
244) -> Result<Response, ContractError> {
245    if amount == Uint128::zero() {
246        return Err(ContractError::InvalidZeroAmount {});
247    }
248
249    let rcpt_addr = deps.api.addr_validate(&contract)?;
250
251    // move the tokens to the contract
252    BALANCES.update(
253        deps.storage,
254        &info.sender,
255        |balance: Option<Uint128>| -> StdResult<_> {
256            Ok(balance.unwrap_or_default().checked_sub(amount)?)
257        },
258    )?;
259    BALANCES.update(
260        deps.storage,
261        &rcpt_addr,
262        |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
263    )?;
264
265    let attrs = vec![
266        attr("action", "send"),
267        attr("from", &info.sender),
268        attr("to", &contract),
269        attr("amount", amount),
270    ];
271
272    // create a send message
273    let msg = SubMsg::new(
274        Cw20ReceiveMsg {
275            sender: info.sender.into(),
276            amount,
277            msg,
278        }
279        .into_cosmos_msg(contract)?,
280    );
281
282    let res = Response {
283        messages: vec![msg],
284        attributes: attrs,
285        ..Response::default()
286    };
287    Ok(res)
288}
289
290#[cfg_attr(not(feature = "library"), entry_point)]
291pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
292    match msg {
293        QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?),
294        QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?),
295        QueryMsg::Minter {} => to_binary(&query_minter(deps)?),
296        QueryMsg::Allowance { owner, spender } => {
297            to_binary(&query_allowance(deps, owner, spender)?)
298        }
299        QueryMsg::AllAllowances {
300            owner,
301            start_after,
302            limit,
303        } => to_binary(&query_all_allowances(deps, owner, start_after, limit)?),
304        QueryMsg::AllAccounts { start_after, limit } => {
305            to_binary(&query_all_accounts(deps, start_after, limit)?)
306        }
307    }
308}
309
310pub fn query_balance(deps: Deps, address: String) -> StdResult<BalanceResponse> {
311    let address = deps.api.addr_validate(&address)?;
312    let balance = BALANCES
313        .may_load(deps.storage, &address)?
314        .unwrap_or_default();
315    Ok(BalanceResponse { balance })
316}
317
318pub fn query_token_info(deps: Deps) -> StdResult<TokenInfoResponse> {
319    let info = TOKEN_INFO.load(deps.storage)?;
320    let res = TokenInfoResponse {
321        name: info.name,
322        symbol: info.symbol,
323        decimals: info.decimals,
324        total_supply: info.total_supply,
325    };
326    Ok(res)
327}
328
329pub fn query_minter(deps: Deps) -> StdResult<Option<MinterResponse>> {
330    let meta = TOKEN_INFO.load(deps.storage)?;
331    let minter = match meta.mint {
332        Some(m) => Some(MinterResponse {
333            minter: m.minter.into(),
334            cap: m.cap,
335        }),
336        None => None,
337    };
338    Ok(minter)
339}
340
341#[cfg(test)]
342mod tests {
343    use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
344    use cosmwasm_std::{coins, from_binary, CosmosMsg, StdError, WasmMsg};
345
346    use super::*;
347
348    fn get_balance<T: Into<String>>(deps: Deps, address: T) -> Uint128 {
349        query_balance(deps, address.into()).unwrap().balance
350    }
351
352    // this will set up the instantiation for other tests
353    fn do_instantiate_with_minter(
354        deps: DepsMut,
355        addr: &str,
356        amount: Uint128,
357        minter: &str,
358        cap: Option<Uint128>,
359    ) -> TokenInfoResponse {
360        _do_instantiate(
361            deps,
362            addr,
363            amount,
364            Some(MinterResponse {
365                minter: minter.to_string(),
366                cap,
367            }),
368        )
369    }
370
371    // this will set up the instantiation for other tests
372    fn do_instantiate(deps: DepsMut, addr: &str, amount: Uint128) -> TokenInfoResponse {
373        _do_instantiate(deps, addr, amount, None)
374    }
375
376    // this will set up the instantiation for other tests
377    fn _do_instantiate(
378        mut deps: DepsMut,
379        addr: &str,
380        amount: Uint128,
381        mint: Option<MinterResponse>,
382    ) -> TokenInfoResponse {
383        let instantiate_msg = InstantiateMsg {
384            name: "Auto Gen".to_string(),
385            symbol: "AUTO".to_string(),
386            decimals: 3,
387            initial_balances: vec![Cw20Coin {
388                address: addr.to_string(),
389                amount,
390            }],
391            mint: mint.clone(),
392        };
393        let info = mock_info("creator", &[]);
394        let env = mock_env();
395        let res = instantiate(deps.branch(), env, info, instantiate_msg).unwrap();
396        assert_eq!(0, res.messages.len());
397
398        let meta = query_token_info(deps.as_ref()).unwrap();
399        assert_eq!(
400            meta,
401            TokenInfoResponse {
402                name: "Auto Gen".to_string(),
403                symbol: "AUTO".to_string(),
404                decimals: 3,
405                total_supply: amount,
406            }
407        );
408        assert_eq!(get_balance(deps.as_ref(), addr), amount);
409        assert_eq!(query_minter(deps.as_ref()).unwrap(), mint,);
410        meta
411    }
412
413    #[test]
414    fn proper_instantiation() {
415        let mut deps = mock_dependencies(&[]);
416        let amount = Uint128::from(11223344u128);
417        let instantiate_msg = InstantiateMsg {
418            name: "Cash Token".to_string(),
419            symbol: "CASH".to_string(),
420            decimals: 9,
421            initial_balances: vec![Cw20Coin {
422                address: String::from("addr0000"),
423                amount,
424            }],
425            mint: None,
426        };
427        let info = mock_info("creator", &[]);
428        let env = mock_env();
429        let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
430        assert_eq!(0, res.messages.len());
431
432        assert_eq!(
433            query_token_info(deps.as_ref()).unwrap(),
434            TokenInfoResponse {
435                name: "Cash Token".to_string(),
436                symbol: "CASH".to_string(),
437                decimals: 9,
438                total_supply: amount,
439            }
440        );
441        assert_eq!(
442            get_balance(deps.as_ref(), "addr0000"),
443            Uint128::new(11223344)
444        );
445    }
446
447    #[test]
448    fn instantiate_mintable() {
449        let mut deps = mock_dependencies(&[]);
450        let amount = Uint128::new(11223344);
451        let minter = String::from("asmodat");
452        let limit = Uint128::new(511223344);
453        let instantiate_msg = InstantiateMsg {
454            name: "Cash Token".to_string(),
455            symbol: "CASH".to_string(),
456            decimals: 9,
457            initial_balances: vec![Cw20Coin {
458                address: "addr0000".into(),
459                amount,
460            }],
461            mint: Some(MinterResponse {
462                minter: minter.clone(),
463                cap: Some(limit),
464            }),
465        };
466        let info = mock_info("creator", &[]);
467        let env = mock_env();
468        let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
469        assert_eq!(0, res.messages.len());
470
471        assert_eq!(
472            query_token_info(deps.as_ref()).unwrap(),
473            TokenInfoResponse {
474                name: "Cash Token".to_string(),
475                symbol: "CASH".to_string(),
476                decimals: 9,
477                total_supply: amount,
478            }
479        );
480        assert_eq!(
481            get_balance(deps.as_ref(), "addr0000"),
482            Uint128::new(11223344)
483        );
484        assert_eq!(
485            query_minter(deps.as_ref()).unwrap(),
486            Some(MinterResponse {
487                minter,
488                cap: Some(limit),
489            }),
490        );
491    }
492
493    #[test]
494    fn instantiate_mintable_over_cap() {
495        let mut deps = mock_dependencies(&[]);
496        let amount = Uint128::new(11223344);
497        let minter = String::from("asmodat");
498        let limit = Uint128::new(11223300);
499        let instantiate_msg = InstantiateMsg {
500            name: "Cash Token".to_string(),
501            symbol: "CASH".to_string(),
502            decimals: 9,
503            initial_balances: vec![Cw20Coin {
504                address: String::from("addr0000"),
505                amount,
506            }],
507            mint: Some(MinterResponse {
508                minter,
509                cap: Some(limit),
510            }),
511        };
512        let info = mock_info("creator", &[]);
513        let env = mock_env();
514        let err = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap_err();
515        assert_eq!(
516            err,
517            StdError::generic_err("Initial supply greater than cap")
518        );
519    }
520
521    #[test]
522    fn can_mint_by_minter() {
523        let mut deps = mock_dependencies(&[]);
524
525        let genesis = String::from("genesis");
526        let amount = Uint128::new(11223344);
527        let minter = String::from("asmodat");
528        let limit = Uint128::new(511223344);
529        do_instantiate_with_minter(deps.as_mut(), &genesis, amount, &minter, Some(limit));
530
531        // minter can mint coins to some winner
532        let winner = String::from("lucky");
533        let prize = Uint128::new(222_222_222);
534        let msg = ExecuteMsg::Mint {
535            recipient: winner.clone(),
536            amount: prize,
537        };
538
539        let info = mock_info(minter.as_ref(), &[]);
540        let env = mock_env();
541        let res = execute(deps.as_mut(), env, info, msg).unwrap();
542        assert_eq!(0, res.messages.len());
543        assert_eq!(get_balance(deps.as_ref(), genesis), amount);
544        assert_eq!(get_balance(deps.as_ref(), winner.clone()), prize);
545
546        // but cannot mint nothing
547        let msg = ExecuteMsg::Mint {
548            recipient: winner.clone(),
549            amount: Uint128::zero(),
550        };
551        let info = mock_info(minter.as_ref(), &[]);
552        let env = mock_env();
553        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
554        assert_eq!(err, ContractError::InvalidZeroAmount {});
555
556        // but if it exceeds cap (even over multiple rounds), it fails
557        // cap is enforced
558        let msg = ExecuteMsg::Mint {
559            recipient: winner,
560            amount: Uint128::new(333_222_222),
561        };
562        let info = mock_info(minter.as_ref(), &[]);
563        let env = mock_env();
564        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
565        assert_eq!(err, ContractError::CannotExceedCap {});
566    }
567
568    #[test]
569    fn others_cannot_mint() {
570        let mut deps = mock_dependencies(&[]);
571        do_instantiate_with_minter(
572            deps.as_mut(),
573            &String::from("genesis"),
574            Uint128::new(1234),
575            &String::from("minter"),
576            None,
577        );
578
579        let msg = ExecuteMsg::Mint {
580            recipient: String::from("lucky"),
581            amount: Uint128::new(222),
582        };
583        let info = mock_info("anyone else", &[]);
584        let env = mock_env();
585        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
586        assert_eq!(err, ContractError::Unauthorized {});
587    }
588
589    #[test]
590    fn no_one_mints_if_minter_unset() {
591        let mut deps = mock_dependencies(&[]);
592        do_instantiate(deps.as_mut(), &String::from("genesis"), Uint128::new(1234));
593
594        let msg = ExecuteMsg::Mint {
595            recipient: String::from("lucky"),
596            amount: Uint128::new(222),
597        };
598        let info = mock_info("genesis", &[]);
599        let env = mock_env();
600        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
601        assert_eq!(err, ContractError::Unauthorized {});
602    }
603
604    #[test]
605    fn instantiate_multiple_accounts() {
606        let mut deps = mock_dependencies(&[]);
607        let amount1 = Uint128::from(11223344u128);
608        let addr1 = String::from("addr0001");
609        let amount2 = Uint128::from(7890987u128);
610        let addr2 = String::from("addr0002");
611        let instantiate_msg = InstantiateMsg {
612            name: "Bash Shell".to_string(),
613            symbol: "BASH".to_string(),
614            decimals: 6,
615            initial_balances: vec![
616                Cw20Coin {
617                    address: addr1.clone(),
618                    amount: amount1,
619                },
620                Cw20Coin {
621                    address: addr2.clone(),
622                    amount: amount2,
623                },
624            ],
625            mint: None,
626        };
627        let info = mock_info("creator", &[]);
628        let env = mock_env();
629        let res = instantiate(deps.as_mut(), env, info, instantiate_msg).unwrap();
630        assert_eq!(0, res.messages.len());
631
632        assert_eq!(
633            query_token_info(deps.as_ref()).unwrap(),
634            TokenInfoResponse {
635                name: "Bash Shell".to_string(),
636                symbol: "BASH".to_string(),
637                decimals: 6,
638                total_supply: amount1 + amount2,
639            }
640        );
641        assert_eq!(get_balance(deps.as_ref(), addr1), amount1);
642        assert_eq!(get_balance(deps.as_ref(), addr2), amount2);
643    }
644
645    #[test]
646    fn queries_work() {
647        let mut deps = mock_dependencies(&coins(2, "token"));
648        let addr1 = String::from("addr0001");
649        let amount1 = Uint128::from(12340000u128);
650
651        let expected = do_instantiate(deps.as_mut(), &addr1, amount1);
652
653        // check meta query
654        let loaded = query_token_info(deps.as_ref()).unwrap();
655        assert_eq!(expected, loaded);
656
657        let _info = mock_info("test", &[]);
658        let env = mock_env();
659        // check balance query (full)
660        let data = query(
661            deps.as_ref(),
662            env.clone(),
663            QueryMsg::Balance { address: addr1 },
664        )
665        .unwrap();
666        let loaded: BalanceResponse = from_binary(&data).unwrap();
667        assert_eq!(loaded.balance, amount1);
668
669        // check balance query (empty)
670        let data = query(
671            deps.as_ref(),
672            env,
673            QueryMsg::Balance {
674                address: String::from("addr0002"),
675            },
676        )
677        .unwrap();
678        let loaded: BalanceResponse = from_binary(&data).unwrap();
679        assert_eq!(loaded.balance, Uint128::zero());
680    }
681
682    #[test]
683    fn transfer() {
684        let mut deps = mock_dependencies(&coins(2, "token"));
685        let addr1 = String::from("addr0001");
686        let addr2 = String::from("addr0002");
687        let amount1 = Uint128::from(12340000u128);
688        let transfer = Uint128::from(76543u128);
689        let too_much = Uint128::from(12340321u128);
690
691        do_instantiate(deps.as_mut(), &addr1, amount1);
692
693        // cannot transfer nothing
694        let info = mock_info(addr1.as_ref(), &[]);
695        let env = mock_env();
696        let msg = ExecuteMsg::Transfer {
697            recipient: addr2.clone(),
698            amount: Uint128::zero(),
699        };
700        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
701        assert_eq!(err, ContractError::InvalidZeroAmount {});
702
703        // cannot send more than we have
704        let info = mock_info(addr1.as_ref(), &[]);
705        let env = mock_env();
706        let msg = ExecuteMsg::Transfer {
707            recipient: addr2.clone(),
708            amount: too_much,
709        };
710        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
711        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
712
713        // cannot send from empty account
714        let info = mock_info(addr2.as_ref(), &[]);
715        let env = mock_env();
716        let msg = ExecuteMsg::Transfer {
717            recipient: addr1.clone(),
718            amount: transfer,
719        };
720        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
721        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
722
723        // valid transfer
724        let info = mock_info(addr1.as_ref(), &[]);
725        let env = mock_env();
726        let msg = ExecuteMsg::Transfer {
727            recipient: addr2.clone(),
728            amount: transfer,
729        };
730        let res = execute(deps.as_mut(), env, info, msg).unwrap();
731        assert_eq!(res.messages.len(), 0);
732
733        let remainder = amount1.checked_sub(transfer).unwrap();
734        assert_eq!(get_balance(deps.as_ref(), addr1), remainder);
735        assert_eq!(get_balance(deps.as_ref(), addr2), transfer);
736        assert_eq!(
737            query_token_info(deps.as_ref()).unwrap().total_supply,
738            amount1
739        );
740    }
741
742    #[test]
743    fn burn() {
744        let mut deps = mock_dependencies(&coins(2, "token"));
745        let addr1 = String::from("addr0001");
746        let amount1 = Uint128::from(12340000u128);
747        let burn = Uint128::from(76543u128);
748        let too_much = Uint128::from(12340321u128);
749
750        do_instantiate(deps.as_mut(), &addr1, amount1);
751
752        // cannot burn nothing
753        let info = mock_info(addr1.as_ref(), &[]);
754        let env = mock_env();
755        let msg = ExecuteMsg::Burn {
756            amount: Uint128::zero(),
757        };
758        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
759        assert_eq!(err, ContractError::InvalidZeroAmount {});
760        assert_eq!(
761            query_token_info(deps.as_ref()).unwrap().total_supply,
762            amount1
763        );
764
765        // cannot burn more than we have
766        let info = mock_info(addr1.as_ref(), &[]);
767        let env = mock_env();
768        let msg = ExecuteMsg::Burn { amount: too_much };
769        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
770        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
771        assert_eq!(
772            query_token_info(deps.as_ref()).unwrap().total_supply,
773            amount1
774        );
775
776        // valid burn reduces total supply
777        let info = mock_info(addr1.as_ref(), &[]);
778        let env = mock_env();
779        let msg = ExecuteMsg::Burn { amount: burn };
780        let res = execute(deps.as_mut(), env, info, msg).unwrap();
781        assert_eq!(res.messages.len(), 0);
782
783        let remainder = amount1.checked_sub(burn).unwrap();
784        assert_eq!(get_balance(deps.as_ref(), addr1), remainder);
785        assert_eq!(
786            query_token_info(deps.as_ref()).unwrap().total_supply,
787            remainder
788        );
789    }
790
791    #[test]
792    fn send() {
793        let mut deps = mock_dependencies(&coins(2, "token"));
794        let addr1 = String::from("addr0001");
795        let contract = String::from("addr0002");
796        let amount1 = Uint128::from(12340000u128);
797        let transfer = Uint128::from(76543u128);
798        let too_much = Uint128::from(12340321u128);
799        let send_msg = Binary::from(r#"{"some":123}"#.as_bytes());
800
801        do_instantiate(deps.as_mut(), &addr1, amount1);
802
803        // cannot send nothing
804        let info = mock_info(addr1.as_ref(), &[]);
805        let env = mock_env();
806        let msg = ExecuteMsg::Send {
807            contract: contract.clone(),
808            amount: Uint128::zero(),
809            msg: send_msg.clone(),
810        };
811        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
812        assert_eq!(err, ContractError::InvalidZeroAmount {});
813
814        // cannot send more than we have
815        let info = mock_info(addr1.as_ref(), &[]);
816        let env = mock_env();
817        let msg = ExecuteMsg::Send {
818            contract: contract.clone(),
819            amount: too_much,
820            msg: send_msg.clone(),
821        };
822        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
823        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
824
825        // valid transfer
826        let info = mock_info(addr1.as_ref(), &[]);
827        let env = mock_env();
828        let msg = ExecuteMsg::Send {
829            contract: contract.clone(),
830            amount: transfer,
831            msg: send_msg.clone(),
832        };
833        let res = execute(deps.as_mut(), env, info, msg).unwrap();
834        assert_eq!(res.messages.len(), 1);
835
836        // ensure proper send message sent
837        // this is the message we want delivered to the other side
838        let binary_msg = Cw20ReceiveMsg {
839            sender: addr1.clone(),
840            amount: transfer,
841            msg: send_msg,
842        }
843        .into_binary()
844        .unwrap();
845        // and this is how it must be wrapped for the vm to process it
846        assert_eq!(
847            res.messages[0],
848            SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
849                contract_addr: contract.clone(),
850                msg: binary_msg,
851                funds: vec![],
852            }))
853        );
854
855        // ensure balance is properly transferred
856        let remainder = amount1.checked_sub(transfer).unwrap();
857        assert_eq!(get_balance(deps.as_ref(), addr1), remainder);
858        assert_eq!(get_balance(deps.as_ref(), contract), transfer);
859        assert_eq!(
860            query_token_info(deps.as_ref()).unwrap().total_supply,
861            amount1
862        );
863    }
864}