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