1use cosmwasm_std::{
2 attr, Addr, Binary, BlockInfo, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult,
3 Storage, SubMsg, Uint128,
4};
5use cw20::{AllowanceResponse, Cw20ReceiveMsg, Expiration};
6
7use crate::error::ContractError;
8use crate::state::{ALLOWANCES, BALANCES, TOKEN_INFO};
9
10pub fn execute_increase_allowance(
11 deps: DepsMut,
12 _env: Env,
13 info: MessageInfo,
14 spender: String,
15 amount: Uint128,
16 expires: Option<Expiration>,
17) -> Result<Response, ContractError> {
18 let spender_addr = deps.api.addr_validate(&spender)?;
19 if spender_addr == info.sender {
20 return Err(ContractError::CannotSetOwnAccount {});
21 }
22
23 ALLOWANCES.update(
24 deps.storage,
25 (&info.sender, &spender_addr),
26 |allow| -> StdResult<_> {
27 let mut val = allow.unwrap_or_default();
28 if let Some(exp) = expires {
29 val.expires = exp;
30 }
31 val.allowance += amount;
32 Ok(val)
33 },
34 )?;
35
36 let res = Response {
37 attributes: vec![
38 attr("action", "increase_allowance"),
39 attr("owner", info.sender),
40 attr("spender", spender),
41 attr("amount", amount),
42 ],
43 ..Response::default()
44 };
45 Ok(res)
46}
47
48pub fn execute_decrease_allowance(
49 deps: DepsMut,
50 _env: Env,
51 info: MessageInfo,
52 spender: String,
53 amount: Uint128,
54 expires: Option<Expiration>,
55) -> Result<Response, ContractError> {
56 let spender_addr = deps.api.addr_validate(&spender)?;
57 if spender_addr == info.sender {
58 return Err(ContractError::CannotSetOwnAccount {});
59 }
60
61 let key = (&info.sender, &spender_addr);
62 let mut allowance = ALLOWANCES.load(deps.storage, key)?;
64 if amount < allowance.allowance {
65 allowance.allowance = allowance
67 .allowance
68 .checked_sub(amount)
69 .map_err(StdError::overflow)?;
70 if let Some(exp) = expires {
71 allowance.expires = exp;
72 }
73 ALLOWANCES.save(deps.storage, key, &allowance)?;
74 } else {
75 ALLOWANCES.remove(deps.storage, key);
76 }
77
78 let res = Response {
79 attributes: vec![
80 attr("action", "decrease_allowance"),
81 attr("owner", info.sender),
82 attr("spender", spender),
83 attr("amount", amount),
84 ],
85 ..Response::default()
86 };
87 Ok(res)
88}
89
90pub fn deduct_allowance(
92 storage: &mut dyn Storage,
93 owner: &Addr,
94 spender: &Addr,
95 block: &BlockInfo,
96 amount: Uint128,
97) -> Result<AllowanceResponse, ContractError> {
98 ALLOWANCES.update(storage, (owner, spender), |current| {
99 match current {
100 Some(mut a) => {
101 if a.expires.is_expired(block) {
102 Err(ContractError::Expired {})
103 } else {
104 a.allowance = a
106 .allowance
107 .checked_sub(amount)
108 .map_err(StdError::overflow)?;
109 Ok(a)
110 }
111 }
112 None => Err(ContractError::NoAllowance {}),
113 }
114 })
115}
116
117pub fn execute_transfer_from(
118 deps: DepsMut,
119 env: Env,
120 info: MessageInfo,
121 owner: String,
122 recipient: String,
123 amount: Uint128,
124) -> Result<Response, ContractError> {
125 let rcpt_addr = deps.api.addr_validate(&recipient)?;
126 let owner_addr = deps.api.addr_validate(&owner)?;
127
128 deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
130
131 BALANCES.update(
132 deps.storage,
133 &owner_addr,
134 |balance: Option<Uint128>| -> StdResult<_> {
135 Ok(balance.unwrap_or_default().checked_sub(amount)?)
136 },
137 )?;
138 BALANCES.update(
139 deps.storage,
140 &rcpt_addr,
141 |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
142 )?;
143
144 let res = Response {
145 attributes: vec![
146 attr("action", "transfer_from"),
147 attr("from", owner),
148 attr("to", recipient),
149 attr("by", info.sender),
150 attr("amount", amount),
151 ],
152 ..Response::default()
153 };
154 Ok(res)
155}
156
157pub fn execute_burn_from(
158 deps: DepsMut,
159
160 env: Env,
161 info: MessageInfo,
162 owner: String,
163 amount: Uint128,
164) -> Result<Response, ContractError> {
165 let owner_addr = deps.api.addr_validate(&owner)?;
166
167 deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
169
170 BALANCES.update(
172 deps.storage,
173 &owner_addr,
174 |balance: Option<Uint128>| -> StdResult<_> {
175 Ok(balance.unwrap_or_default().checked_sub(amount)?)
176 },
177 )?;
178 TOKEN_INFO.update(deps.storage, |mut meta| -> StdResult<_> {
180 meta.total_supply = meta.total_supply.checked_sub(amount)?;
181 Ok(meta)
182 })?;
183
184 let res = Response {
185 attributes: vec![
186 attr("action", "burn_from"),
187 attr("from", owner),
188 attr("by", info.sender),
189 attr("amount", amount),
190 ],
191 ..Response::default()
192 };
193 Ok(res)
194}
195
196pub fn execute_send_from(
197 deps: DepsMut,
198 env: Env,
199 info: MessageInfo,
200 owner: String,
201 contract: String,
202 amount: Uint128,
203 msg: Binary,
204) -> Result<Response, ContractError> {
205 let rcpt_addr = deps.api.addr_validate(&contract)?;
206 let owner_addr = deps.api.addr_validate(&owner)?;
207
208 deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
210
211 BALANCES.update(
213 deps.storage,
214 &owner_addr,
215 |balance: Option<Uint128>| -> StdResult<_> {
216 Ok(balance.unwrap_or_default().checked_sub(amount)?)
217 },
218 )?;
219 BALANCES.update(
220 deps.storage,
221 &rcpt_addr,
222 |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
223 )?;
224
225 let attrs = vec![
226 attr("action", "send_from"),
227 attr("from", &owner),
228 attr("to", &contract),
229 attr("by", &info.sender),
230 attr("amount", amount),
231 ];
232
233 let msg = SubMsg::new(
235 Cw20ReceiveMsg {
236 sender: info.sender.into(),
237 amount,
238 msg,
239 }
240 .into_cosmos_msg(contract)?,
241 );
242
243 let res = Response {
244 messages: vec![msg],
245 attributes: attrs,
246 ..Response::default()
247 };
248 Ok(res)
249}
250
251pub fn query_allowance(deps: Deps, owner: String, spender: String) -> StdResult<AllowanceResponse> {
252 let owner_addr = deps.api.addr_validate(&owner)?;
253 let spender_addr = deps.api.addr_validate(&spender)?;
254 let allowance = ALLOWANCES
255 .may_load(deps.storage, (&owner_addr, &spender_addr))?
256 .unwrap_or_default();
257 Ok(allowance)
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
265 use cosmwasm_std::{coins, CosmosMsg, Timestamp, WasmMsg};
266 use cw20::{Cw20Coin, TokenInfoResponse};
267
268 use crate::contract::{execute, instantiate, query_balance, query_token_info};
269 use crate::msg::{ExecuteMsg, InstantiateMsg};
270
271 fn get_balance<T: Into<String>>(deps: Deps, address: T) -> Uint128 {
272 query_balance(deps, address.into()).unwrap().balance
273 }
274
275 fn do_instantiate<T: Into<String>>(
277 mut deps: DepsMut,
278 addr: T,
279 amount: Uint128,
280 ) -> TokenInfoResponse {
281 let instantiate_msg = InstantiateMsg {
282 name: "Auto Gen".to_string(),
283 symbol: "AUTO".to_string(),
284 decimals: 3,
285 initial_balances: vec![Cw20Coin {
286 address: addr.into(),
287 amount,
288 }],
289 mint: None,
290 };
291 let info = mock_info("creator", &[]);
292 let env = mock_env();
293 instantiate(deps.branch(), env, info, instantiate_msg).unwrap();
294 query_token_info(deps.as_ref()).unwrap()
295 }
296
297 #[test]
298 fn increase_decrease_allowances() {
299 let mut deps = mock_dependencies(&coins(2, "token"));
300
301 let owner = String::from("addr0001");
302 let spender = String::from("addr0002");
303 let info = mock_info(owner.as_ref(), &[]);
304 let env = mock_env();
305 do_instantiate(deps.as_mut(), owner.clone(), Uint128::new(12340000));
306
307 let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
309 assert_eq!(allowance, AllowanceResponse::default());
310
311 let allow1 = Uint128::new(7777);
313 let expires = Expiration::AtHeight(5432);
314 let msg = ExecuteMsg::IncreaseAllowance {
315 spender: spender.clone(),
316 amount: allow1,
317 expires: Some(expires),
318 };
319 execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
320
321 let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
323 assert_eq!(
324 allowance,
325 AllowanceResponse {
326 allowance: allow1,
327 expires
328 }
329 );
330
331 let lower = Uint128::new(4444);
333 let allow2 = allow1.checked_sub(lower).unwrap();
334 let msg = ExecuteMsg::DecreaseAllowance {
335 spender: spender.clone(),
336 amount: lower,
337 expires: None,
338 };
339 execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
340 let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
341 assert_eq!(
342 allowance,
343 AllowanceResponse {
344 allowance: allow2,
345 expires
346 }
347 );
348
349 let raise = Uint128::new(87654);
351 let allow3 = allow2 + raise;
352 let new_expire = Expiration::AtTime(Timestamp::from_seconds(8888888888));
353 let msg = ExecuteMsg::IncreaseAllowance {
354 spender: spender.clone(),
355 amount: raise,
356 expires: Some(new_expire),
357 };
358 execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
359 let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
360 assert_eq!(
361 allowance,
362 AllowanceResponse {
363 allowance: allow3,
364 expires: new_expire
365 }
366 );
367
368 let msg = ExecuteMsg::DecreaseAllowance {
370 spender: spender.clone(),
371 amount: Uint128::new(99988647623876347),
372 expires: None,
373 };
374 execute(deps.as_mut(), env, info, msg).unwrap();
375 let allowance = query_allowance(deps.as_ref(), owner, spender).unwrap();
376 assert_eq!(allowance, AllowanceResponse::default());
377 }
378
379 #[test]
380 fn allowances_independent() {
381 let mut deps = mock_dependencies(&coins(2, "token"));
382
383 let owner = String::from("addr0001");
384 let spender = String::from("addr0002");
385 let spender2 = String::from("addr0003");
386 let info = mock_info(owner.as_ref(), &[]);
387 let env = mock_env();
388 do_instantiate(deps.as_mut(), &owner, Uint128::new(12340000));
389
390 assert_eq!(
392 query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(),
393 AllowanceResponse::default()
394 );
395 assert_eq!(
396 query_allowance(deps.as_ref(), owner.clone(), spender2.clone()).unwrap(),
397 AllowanceResponse::default()
398 );
399 assert_eq!(
400 query_allowance(deps.as_ref(), spender.clone(), spender2.clone()).unwrap(),
401 AllowanceResponse::default()
402 );
403
404 let allow1 = Uint128::new(7777);
406 let expires = Expiration::AtHeight(5432);
407 let msg = ExecuteMsg::IncreaseAllowance {
408 spender: spender.clone(),
409 amount: allow1,
410 expires: Some(expires),
411 };
412 execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
413
414 let allow2 = Uint128::new(87654);
416 let msg = ExecuteMsg::IncreaseAllowance {
417 spender: spender2.clone(),
418 amount: allow2,
419 expires: None,
420 };
421 execute(deps.as_mut(), env, info, msg).unwrap();
422
423 let expect_one = AllowanceResponse {
425 allowance: allow1,
426 expires,
427 };
428 let expect_two = AllowanceResponse {
429 allowance: allow2,
430 expires: Expiration::Never {},
431 };
432 assert_eq!(
433 query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(),
434 expect_one
435 );
436 assert_eq!(
437 query_allowance(deps.as_ref(), owner.clone(), spender2.clone()).unwrap(),
438 expect_two
439 );
440 assert_eq!(
441 query_allowance(deps.as_ref(), spender.clone(), spender2.clone()).unwrap(),
442 AllowanceResponse::default()
443 );
444
445 let info = mock_info(spender.as_ref(), &[]);
447 let env = mock_env();
448 let allow3 = Uint128::new(1821);
449 let expires3 = Expiration::AtTime(Timestamp::from_seconds(3767626296));
450 let msg = ExecuteMsg::IncreaseAllowance {
451 spender: spender2.clone(),
452 amount: allow3,
453 expires: Some(expires3),
454 };
455 execute(deps.as_mut(), env, info, msg).unwrap();
456 let expect_three = AllowanceResponse {
457 allowance: allow3,
458 expires: expires3,
459 };
460 assert_eq!(
461 query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(),
462 expect_one
463 );
464 assert_eq!(
465 query_allowance(deps.as_ref(), owner, spender2.clone()).unwrap(),
466 expect_two
467 );
468 assert_eq!(
469 query_allowance(deps.as_ref(), spender, spender2).unwrap(),
470 expect_three
471 );
472 }
473
474 #[test]
475 fn no_self_allowance() {
476 let mut deps = mock_dependencies(&coins(2, "token"));
477
478 let owner = String::from("addr0001");
479 let info = mock_info(owner.as_ref(), &[]);
480 let env = mock_env();
481 do_instantiate(deps.as_mut(), &owner, Uint128::new(12340000));
482
483 let msg = ExecuteMsg::IncreaseAllowance {
485 spender: owner.clone(),
486 amount: Uint128::new(7777),
487 expires: None,
488 };
489 let err = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err();
490 assert_eq!(err, ContractError::CannotSetOwnAccount {});
491
492 let msg = ExecuteMsg::DecreaseAllowance {
494 spender: owner,
495 amount: Uint128::new(7777),
496 expires: None,
497 };
498 let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
499 assert_eq!(err, ContractError::CannotSetOwnAccount {});
500 }
501
502 #[test]
503 fn transfer_from_respects_limits() {
504 let mut deps = mock_dependencies(&[]);
505 let owner = String::from("addr0001");
506 let spender = String::from("addr0002");
507 let rcpt = String::from("addr0003");
508
509 let start = Uint128::new(999999);
510 do_instantiate(deps.as_mut(), &owner, start);
511
512 let allow1 = Uint128::new(77777);
514 let msg = ExecuteMsg::IncreaseAllowance {
515 spender: spender.clone(),
516 amount: allow1,
517 expires: None,
518 };
519 let info = mock_info(owner.as_ref(), &[]);
520 let env = mock_env();
521 execute(deps.as_mut(), env, info, msg).unwrap();
522
523 let transfer = Uint128::new(44444);
525 let msg = ExecuteMsg::TransferFrom {
526 owner: owner.clone(),
527 recipient: rcpt.clone(),
528 amount: transfer,
529 };
530 let info = mock_info(spender.as_ref(), &[]);
531 let env = mock_env();
532 let res = execute(deps.as_mut(), env, info, msg).unwrap();
533 assert_eq!(res.attributes[0], attr("action", "transfer_from"));
534
535 assert_eq!(
537 get_balance(deps.as_ref(), owner.clone()),
538 start.checked_sub(transfer).unwrap()
539 );
540 assert_eq!(get_balance(deps.as_ref(), rcpt.clone()), transfer);
541
542 let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
544 let expect = AllowanceResponse {
545 allowance: allow1.checked_sub(transfer).unwrap(),
546 expires: Expiration::Never {},
547 };
548 assert_eq!(expect, allowance);
549
550 let msg = ExecuteMsg::TransferFrom {
552 owner: owner.clone(),
553 recipient: rcpt.clone(),
554 amount: Uint128::new(33443),
555 };
556 let info = mock_info(spender.as_ref(), &[]);
557 let env = mock_env();
558 let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
559 assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
560
561 let info = mock_info(owner.as_ref(), &[]);
563 let env = mock_env();
564 let msg = ExecuteMsg::IncreaseAllowance {
565 spender: spender.clone(),
566 amount: Uint128::new(1000),
567 expires: Some(Expiration::AtHeight(env.block.height)),
568 };
569 execute(deps.as_mut(), env, info, msg).unwrap();
570
571 let msg = ExecuteMsg::TransferFrom {
573 owner,
574 recipient: rcpt,
575 amount: Uint128::new(33443),
576 };
577 let info = mock_info(spender.as_ref(), &[]);
578 let env = mock_env();
579 let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
580 assert_eq!(err, ContractError::Expired {});
581 }
582
583 #[test]
584 fn burn_from_respects_limits() {
585 let mut deps = mock_dependencies(&[]);
586 let owner = String::from("addr0001");
587 let spender = String::from("addr0002");
588
589 let start = Uint128::new(999999);
590 do_instantiate(deps.as_mut(), &owner, start);
591
592 let allow1 = Uint128::new(77777);
594 let msg = ExecuteMsg::IncreaseAllowance {
595 spender: spender.clone(),
596 amount: allow1,
597 expires: None,
598 };
599 let info = mock_info(owner.as_ref(), &[]);
600 let env = mock_env();
601 execute(deps.as_mut(), env, info, msg).unwrap();
602
603 let transfer = Uint128::new(44444);
605 let msg = ExecuteMsg::BurnFrom {
606 owner: owner.clone(),
607 amount: transfer,
608 };
609 let info = mock_info(spender.as_ref(), &[]);
610 let env = mock_env();
611 let res = execute(deps.as_mut(), env, info, msg).unwrap();
612 assert_eq!(res.attributes[0], attr("action", "burn_from"));
613
614 assert_eq!(
616 get_balance(deps.as_ref(), owner.clone()),
617 start.checked_sub(transfer).unwrap()
618 );
619
620 let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
622 let expect = AllowanceResponse {
623 allowance: allow1.checked_sub(transfer).unwrap(),
624 expires: Expiration::Never {},
625 };
626 assert_eq!(expect, allowance);
627
628 let msg = ExecuteMsg::BurnFrom {
630 owner: owner.clone(),
631 amount: Uint128::new(33443),
632 };
633 let info = mock_info(spender.as_ref(), &[]);
634 let env = mock_env();
635 let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
636 assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
637
638 let info = mock_info(owner.as_ref(), &[]);
640 let env = mock_env();
641 let msg = ExecuteMsg::IncreaseAllowance {
642 spender: spender.clone(),
643 amount: Uint128::new(1000),
644 expires: Some(Expiration::AtHeight(env.block.height)),
645 };
646 execute(deps.as_mut(), env, info, msg).unwrap();
647
648 let msg = ExecuteMsg::BurnFrom {
650 owner,
651 amount: Uint128::new(33443),
652 };
653 let info = mock_info(spender.as_ref(), &[]);
654 let env = mock_env();
655 let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
656 assert_eq!(err, ContractError::Expired {});
657 }
658
659 #[test]
660 fn send_from_respects_limits() {
661 let mut deps = mock_dependencies(&[]);
662 let owner = String::from("addr0001");
663 let spender = String::from("addr0002");
664 let contract = String::from("cool-dex");
665 let send_msg = Binary::from(r#"{"some":123}"#.as_bytes());
666
667 let start = Uint128::new(999999);
668 do_instantiate(deps.as_mut(), &owner, start);
669
670 let allow1 = Uint128::new(77777);
672 let msg = ExecuteMsg::IncreaseAllowance {
673 spender: spender.clone(),
674 amount: allow1,
675 expires: None,
676 };
677 let info = mock_info(owner.as_ref(), &[]);
678 let env = mock_env();
679 execute(deps.as_mut(), env, info, msg).unwrap();
680
681 let transfer = Uint128::new(44444);
683 let msg = ExecuteMsg::SendFrom {
684 owner: owner.clone(),
685 amount: transfer,
686 contract: contract.clone(),
687 msg: send_msg.clone(),
688 };
689 let info = mock_info(spender.as_ref(), &[]);
690 let env = mock_env();
691 let res = execute(deps.as_mut(), env, info, msg).unwrap();
692 assert_eq!(res.attributes[0], attr("action", "send_from"));
693 assert_eq!(1, res.messages.len());
694
695 let binary_msg = Cw20ReceiveMsg {
697 sender: spender.clone(),
698 amount: transfer,
699 msg: send_msg.clone(),
700 }
701 .into_binary()
702 .unwrap();
703 assert_eq!(
704 res.messages[0],
705 SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
706 contract_addr: contract.clone(),
707 msg: binary_msg,
708 funds: vec![],
709 }))
710 );
711
712 assert_eq!(
714 get_balance(deps.as_ref(), owner.clone()),
715 start.checked_sub(transfer).unwrap()
716 );
717 assert_eq!(get_balance(deps.as_ref(), contract.clone()), transfer);
718
719 let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
721 let expect = AllowanceResponse {
722 allowance: allow1.checked_sub(transfer).unwrap(),
723 expires: Expiration::Never {},
724 };
725 assert_eq!(expect, allowance);
726
727 let msg = ExecuteMsg::SendFrom {
729 owner: owner.clone(),
730 amount: Uint128::new(33443),
731 contract: contract.clone(),
732 msg: send_msg.clone(),
733 };
734 let info = mock_info(spender.as_ref(), &[]);
735 let env = mock_env();
736 let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
737 assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
738
739 let info = mock_info(owner.as_ref(), &[]);
741 let env = mock_env();
742 let msg = ExecuteMsg::IncreaseAllowance {
743 spender: spender.clone(),
744 amount: Uint128::new(1000),
745 expires: Some(Expiration::AtHeight(env.block.height)),
746 };
747 execute(deps.as_mut(), env, info, msg).unwrap();
748
749 let msg = ExecuteMsg::SendFrom {
751 owner,
752 amount: Uint128::new(33443),
753 contract,
754 msg: send_msg,
755 };
756 let info = mock_info(spender.as_ref(), &[]);
757 let env = mock_env();
758 let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
759 assert_eq!(err, ContractError::Expired {});
760 }
761}