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
20const 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 msg.validate()?;
34 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 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 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 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 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 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 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 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 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 fn do_instantiate(deps: DepsMut, addr: &str, amount: Uint128) -> TokenInfoResponse {
373 _do_instantiate(deps, addr, amount, None)
374 }
375
376 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let binary_msg = Cw20ReceiveMsg {
839 sender: addr1.clone(),
840 amount: transfer,
841 msg: send_msg,
842 }
843 .into_binary()
844 .unwrap();
845 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 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}