cw20_base/
allowances.rs

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::{ALLOWANCES, ALLOWANCES_SPENDER, 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    let update_fn = |allow: Option<AllowanceResponse>| -> Result<_, _> {
24        let mut val = allow.unwrap_or_default();
25        if let Some(exp) = expires {
26            if exp.is_expired(&env.block) {
27                return Err(ContractError::InvalidExpiration {});
28            }
29            val.expires = exp;
30        }
31        val.allowance += amount;
32        Ok(val)
33    };
34    ALLOWANCES.update(deps.storage, (&info.sender, &spender_addr), update_fn)?;
35    ALLOWANCES_SPENDER.update(deps.storage, (&spender_addr, &info.sender), update_fn)?;
36
37    let res = Response::new().add_attributes(vec![
38        attr("action", "increase_allowance"),
39        attr("owner", info.sender),
40        attr("spender", spender),
41        attr("amount", amount),
42    ]);
43    Ok(res)
44}
45
46pub fn execute_decrease_allowance(
47    deps: DepsMut,
48    env: Env,
49    info: MessageInfo,
50    spender: String,
51    amount: Uint128,
52    expires: Option<Expiration>,
53) -> Result<Response, ContractError> {
54    let spender_addr = deps.api.addr_validate(&spender)?;
55    if spender_addr == info.sender {
56        return Err(ContractError::CannotSetOwnAccount {});
57    }
58
59    let key = (&info.sender, &spender_addr);
60
61    fn reverse<'a>(t: (&'a Addr, &'a Addr)) -> (&'a Addr, &'a Addr) {
62        (t.1, t.0)
63    }
64
65    // load value and delete if it hits 0, or update otherwise
66    let mut allowance = ALLOWANCES.load(deps.storage, key)?;
67    if amount < allowance.allowance {
68        // update the new amount
69        allowance.allowance = allowance
70            .allowance
71            .checked_sub(amount)
72            .map_err(StdError::overflow)?;
73        if let Some(exp) = expires {
74            if exp.is_expired(&env.block) {
75                return Err(ContractError::InvalidExpiration {});
76            }
77            allowance.expires = exp;
78        }
79        ALLOWANCES.save(deps.storage, key, &allowance)?;
80        ALLOWANCES_SPENDER.save(deps.storage, reverse(key), &allowance)?;
81    } else {
82        ALLOWANCES.remove(deps.storage, key);
83        ALLOWANCES_SPENDER.remove(deps.storage, reverse(key));
84    }
85
86    let res = Response::new().add_attributes(vec![
87        attr("action", "decrease_allowance"),
88        attr("owner", info.sender),
89        attr("spender", spender),
90        attr("amount", amount),
91    ]);
92    Ok(res)
93}
94
95// this can be used to update a lower allowance - call bucket.update with proper keys
96pub fn deduct_allowance(
97    storage: &mut dyn Storage,
98    owner: &Addr,
99    spender: &Addr,
100    block: &BlockInfo,
101    amount: Uint128,
102) -> Result<AllowanceResponse, ContractError> {
103    let update_fn = |current: Option<AllowanceResponse>| -> _ {
104        match current {
105            Some(mut a) => {
106                if a.expires.is_expired(block) {
107                    Err(ContractError::Expired {})
108                } else {
109                    // deduct the allowance if enough
110                    a.allowance = a
111                        .allowance
112                        .checked_sub(amount)
113                        .map_err(StdError::overflow)?;
114                    Ok(a)
115                }
116            }
117            None => Err(ContractError::NoAllowance {}),
118        }
119    };
120    ALLOWANCES.update(storage, (owner, spender), update_fn)?;
121    ALLOWANCES_SPENDER.update(storage, (spender, owner), update_fn)
122}
123
124pub fn execute_transfer_from(
125    deps: DepsMut,
126    env: Env,
127    info: MessageInfo,
128    owner: String,
129    recipient: String,
130    amount: Uint128,
131) -> Result<Response, ContractError> {
132    let rcpt_addr = deps.api.addr_validate(&recipient)?;
133    let owner_addr = deps.api.addr_validate(&owner)?;
134
135    // deduct allowance before doing anything else have enough allowance
136    deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
137
138    BALANCES.update(
139        deps.storage,
140        &owner_addr,
141        |balance: Option<Uint128>| -> StdResult<_> {
142            Ok(balance.unwrap_or_default().checked_sub(amount)?)
143        },
144    )?;
145    BALANCES.update(
146        deps.storage,
147        &rcpt_addr,
148        |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
149    )?;
150
151    let res = Response::new().add_attributes(vec![
152        attr("action", "transfer_from"),
153        attr("from", owner),
154        attr("to", recipient),
155        attr("by", info.sender),
156        attr("amount", amount),
157    ]);
158    Ok(res)
159}
160
161pub fn execute_burn_from(
162    deps: DepsMut,
163
164    env: Env,
165    info: MessageInfo,
166    owner: String,
167    amount: Uint128,
168) -> Result<Response, ContractError> {
169    let owner_addr = deps.api.addr_validate(&owner)?;
170
171    // deduct allowance before doing anything else have enough allowance
172    deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
173
174    // lower balance
175    BALANCES.update(
176        deps.storage,
177        &owner_addr,
178        |balance: Option<Uint128>| -> StdResult<_> {
179            Ok(balance.unwrap_or_default().checked_sub(amount)?)
180        },
181    )?;
182    // reduce total_supply
183    TOKEN_INFO.update(deps.storage, |mut meta| -> StdResult<_> {
184        meta.total_supply = meta.total_supply.checked_sub(amount)?;
185        Ok(meta)
186    })?;
187
188    let res = Response::new().add_attributes(vec![
189        attr("action", "burn_from"),
190        attr("from", owner),
191        attr("by", info.sender),
192        attr("amount", amount),
193    ]);
194    Ok(res)
195}
196
197pub fn execute_send_from(
198    deps: DepsMut,
199    env: Env,
200    info: MessageInfo,
201    owner: String,
202    contract: String,
203    amount: Uint128,
204    msg: Binary,
205) -> Result<Response, ContractError> {
206    let rcpt_addr = deps.api.addr_validate(&contract)?;
207    let owner_addr = deps.api.addr_validate(&owner)?;
208
209    // deduct allowance before doing anything else have enough allowance
210    deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
211
212    // move the tokens to the contract
213    BALANCES.update(
214        deps.storage,
215        &owner_addr,
216        |balance: Option<Uint128>| -> StdResult<_> {
217            Ok(balance.unwrap_or_default().checked_sub(amount)?)
218        },
219    )?;
220    BALANCES.update(
221        deps.storage,
222        &rcpt_addr,
223        |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
224    )?;
225
226    let attrs = vec![
227        attr("action", "send_from"),
228        attr("from", &owner),
229        attr("to", &contract),
230        attr("by", &info.sender),
231        attr("amount", amount),
232    ];
233
234    // create a send message
235    let msg = Cw20ReceiveMsg {
236        sender: info.sender.into(),
237        amount,
238        msg,
239    }
240    .into_cosmos_msg(contract)?;
241
242    let res = Response::new().add_message(msg).add_attributes(attrs);
243    Ok(res)
244}
245
246pub fn query_allowance(deps: Deps, owner: String, spender: String) -> StdResult<AllowanceResponse> {
247    let owner_addr = deps.api.addr_validate(&owner)?;
248    let spender_addr = deps.api.addr_validate(&spender)?;
249    let allowance = ALLOWANCES
250        .may_load(deps.storage, (&owner_addr, &spender_addr))?
251        .unwrap_or_default();
252    Ok(allowance)
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    use cosmwasm_std::testing::{mock_dependencies_with_balance, mock_env, mock_info};
260    use cosmwasm_std::{coins, CosmosMsg, SubMsg, Timestamp, WasmMsg};
261    use cw20::{Cw20Coin, TokenInfoResponse};
262
263    use crate::contract::{execute, instantiate, query_balance, query_token_info};
264    use crate::msg::{ExecuteMsg, InstantiateMsg};
265
266    fn get_balance<T: Into<String>>(deps: Deps, address: T) -> Uint128 {
267        query_balance(deps, address.into()).unwrap().balance
268    }
269
270    // this will set up the instantiation for other tests
271    fn do_instantiate<T: Into<String>>(
272        mut deps: DepsMut,
273        addr: T,
274        amount: Uint128,
275    ) -> TokenInfoResponse {
276        let instantiate_msg = InstantiateMsg {
277            name: "Auto Gen".to_string(),
278            symbol: "AUTO".to_string(),
279            decimals: 3,
280            initial_balances: vec![Cw20Coin {
281                address: addr.into(),
282                amount,
283            }],
284            mint: None,
285            marketing: None,
286        };
287        let info = mock_info("creator", &[]);
288        let env = mock_env();
289        instantiate(deps.branch(), env, info, instantiate_msg).unwrap();
290        query_token_info(deps.as_ref()).unwrap()
291    }
292
293    #[test]
294    fn increase_decrease_allowances() {
295        let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
296
297        let owner = deps.api.addr_make("addr0001").to_string();
298        let spender = deps.api.addr_make("addr0002").to_string();
299        let info = mock_info(owner.as_ref(), &[]);
300        let env = mock_env();
301        do_instantiate(deps.as_mut(), owner.clone(), Uint128::new(12340000));
302
303        // no allowance to start
304        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
305        assert_eq!(allowance, AllowanceResponse::default());
306
307        // set allowance with height expiration
308        let allow1 = Uint128::new(7777);
309        let expires = Expiration::AtHeight(123_456);
310        let msg = ExecuteMsg::IncreaseAllowance {
311            spender: spender.clone(),
312            amount: allow1,
313            expires: Some(expires),
314        };
315        execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
316
317        // ensure it looks good
318        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
319        assert_eq!(
320            allowance,
321            AllowanceResponse {
322                allowance: allow1,
323                expires
324            }
325        );
326
327        // decrease it a bit with no expire set - stays the same
328        let lower = Uint128::new(4444);
329        let allow2 = allow1.checked_sub(lower).unwrap();
330        let msg = ExecuteMsg::DecreaseAllowance {
331            spender: spender.clone(),
332            amount: lower,
333            expires: None,
334        };
335        execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
336        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
337        assert_eq!(
338            allowance,
339            AllowanceResponse {
340                allowance: allow2,
341                expires
342            }
343        );
344
345        // increase it some more and override the expires
346        let raise = Uint128::new(87654);
347        let allow3 = allow2 + raise;
348        let new_expire = Expiration::AtTime(Timestamp::from_seconds(8888888888));
349        let msg = ExecuteMsg::IncreaseAllowance {
350            spender: spender.clone(),
351            amount: raise,
352            expires: Some(new_expire),
353        };
354        execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
355        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
356        assert_eq!(
357            allowance,
358            AllowanceResponse {
359                allowance: allow3,
360                expires: new_expire
361            }
362        );
363
364        // decrease it below 0
365        let msg = ExecuteMsg::DecreaseAllowance {
366            spender: spender.clone(),
367            amount: Uint128::new(99988647623876347),
368            expires: None,
369        };
370        execute(deps.as_mut(), env, info, msg).unwrap();
371        let allowance = query_allowance(deps.as_ref(), owner, spender).unwrap();
372        assert_eq!(allowance, AllowanceResponse::default());
373    }
374
375    #[test]
376    fn allowances_independent() {
377        let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
378
379        let owner = deps.api.addr_make("addr0001").to_string();
380        let spender = deps.api.addr_make("addr0002").to_string();
381        let spender2 = deps.api.addr_make("addr0003").to_string();
382        let info = mock_info(owner.as_ref(), &[]);
383        let env = mock_env();
384        do_instantiate(deps.as_mut(), &owner, Uint128::new(12340000));
385
386        // no allowance to start
387        assert_eq!(
388            query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(),
389            AllowanceResponse::default()
390        );
391        assert_eq!(
392            query_allowance(deps.as_ref(), owner.clone(), spender2.clone()).unwrap(),
393            AllowanceResponse::default()
394        );
395        assert_eq!(
396            query_allowance(deps.as_ref(), spender.clone(), spender2.clone()).unwrap(),
397            AllowanceResponse::default()
398        );
399
400        // set allowance with height expiration
401        let allow1 = Uint128::new(7777);
402        let expires = Expiration::AtHeight(123_456);
403        let msg = ExecuteMsg::IncreaseAllowance {
404            spender: spender.clone(),
405            amount: allow1,
406            expires: Some(expires),
407        };
408        execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
409
410        // set other allowance with no expiration
411        let allow2 = Uint128::new(87654);
412        let msg = ExecuteMsg::IncreaseAllowance {
413            spender: spender2.clone(),
414            amount: allow2,
415            expires: None,
416        };
417        execute(deps.as_mut(), env, info, msg).unwrap();
418
419        // check they are proper
420        let expect_one = AllowanceResponse {
421            allowance: allow1,
422            expires,
423        };
424        let expect_two = AllowanceResponse {
425            allowance: allow2,
426            expires: Expiration::Never {},
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.clone(), spender2.clone()).unwrap(),
434            expect_two
435        );
436        assert_eq!(
437            query_allowance(deps.as_ref(), spender.clone(), spender2.clone()).unwrap(),
438            AllowanceResponse::default()
439        );
440
441        // also allow spender -> spender2 with no interference
442        let info = mock_info(spender.as_ref(), &[]);
443        let env = mock_env();
444        let allow3 = Uint128::new(1821);
445        let expires3 = Expiration::AtTime(Timestamp::from_seconds(3767626296));
446        let msg = ExecuteMsg::IncreaseAllowance {
447            spender: spender2.clone(),
448            amount: allow3,
449            expires: Some(expires3),
450        };
451        execute(deps.as_mut(), env, info, msg).unwrap();
452        let expect_three = AllowanceResponse {
453            allowance: allow3,
454            expires: expires3,
455        };
456        assert_eq!(
457            query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(),
458            expect_one
459        );
460        assert_eq!(
461            query_allowance(deps.as_ref(), owner, spender2.clone()).unwrap(),
462            expect_two
463        );
464        assert_eq!(
465            query_allowance(deps.as_ref(), spender, spender2).unwrap(),
466            expect_three
467        );
468    }
469
470    #[test]
471    fn no_self_allowance() {
472        let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
473
474        let owner = deps.api.addr_make("addr0001").to_string();
475        let info = mock_info(owner.as_ref(), &[]);
476        let env = mock_env();
477        do_instantiate(deps.as_mut(), &owner, Uint128::new(12340000));
478
479        // self-allowance
480        let msg = ExecuteMsg::IncreaseAllowance {
481            spender: owner.clone(),
482            amount: Uint128::new(7777),
483            expires: None,
484        };
485        let err = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err();
486        assert_eq!(err, ContractError::CannotSetOwnAccount {});
487
488        // decrease self-allowance
489        let msg = ExecuteMsg::DecreaseAllowance {
490            spender: owner,
491            amount: Uint128::new(7777),
492            expires: None,
493        };
494        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
495        assert_eq!(err, ContractError::CannotSetOwnAccount {});
496    }
497
498    #[test]
499    fn transfer_from_respects_limits() {
500        let mut deps = mock_dependencies_with_balance(&[]);
501        let owner = deps.api.addr_make("addr0001").to_string();
502        let spender = deps.api.addr_make("addr0002").to_string();
503        let rcpt = deps.api.addr_make("addr0003").to_string();
504
505        let start = Uint128::new(999999);
506        do_instantiate(deps.as_mut(), &owner, start);
507
508        // provide an allowance
509        let allow1 = Uint128::new(77777);
510        let msg = ExecuteMsg::IncreaseAllowance {
511            spender: spender.clone(),
512            amount: allow1,
513            expires: None,
514        };
515        let info = mock_info(owner.as_ref(), &[]);
516        let env = mock_env();
517        execute(deps.as_mut(), env, info, msg).unwrap();
518
519        // valid transfer of part of the allowance
520        let transfer = Uint128::new(44444);
521        let msg = ExecuteMsg::TransferFrom {
522            owner: owner.clone(),
523            recipient: rcpt.clone(),
524            amount: transfer,
525        };
526        let info = mock_info(spender.as_ref(), &[]);
527        let env = mock_env();
528        let res = execute(deps.as_mut(), env, info, msg).unwrap();
529        assert_eq!(res.attributes[0], attr("action", "transfer_from"));
530
531        // make sure money arrived
532        assert_eq!(
533            get_balance(deps.as_ref(), owner.clone()),
534            start.checked_sub(transfer).unwrap()
535        );
536        assert_eq!(get_balance(deps.as_ref(), rcpt.clone()), transfer);
537
538        // ensure it looks good
539        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
540        let expect = AllowanceResponse {
541            allowance: allow1.checked_sub(transfer).unwrap(),
542            expires: Expiration::Never {},
543        };
544        assert_eq!(expect, allowance);
545
546        // cannot send more than the allowance
547        let msg = ExecuteMsg::TransferFrom {
548            owner: owner.clone(),
549            recipient: rcpt.clone(),
550            amount: Uint128::new(33443),
551        };
552        let info = mock_info(spender.as_ref(), &[]);
553        let env = mock_env();
554        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
555        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
556
557        // let us increase limit, but set the expiration to expire in the next block
558        let info = mock_info(owner.as_ref(), &[]);
559        let mut env = mock_env();
560        let msg = ExecuteMsg::IncreaseAllowance {
561            spender: spender.clone(),
562            amount: Uint128::new(1000),
563            expires: Some(Expiration::AtHeight(env.block.height + 1)),
564        };
565        execute(deps.as_mut(), env.clone(), info, msg).unwrap();
566
567        env.block.height += 1;
568
569        // we should now get the expiration error
570        let msg = ExecuteMsg::TransferFrom {
571            owner,
572            recipient: rcpt,
573            amount: Uint128::new(33443),
574        };
575        let info = mock_info(spender.as_ref(), &[]);
576        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
577        assert_eq!(err, ContractError::Expired {});
578    }
579
580    #[test]
581    fn burn_from_respects_limits() {
582        let mut deps = mock_dependencies_with_balance(&[]);
583        let owner = deps.api.addr_make("addr0001").to_string();
584        let spender = deps.api.addr_make("addr0002").to_string();
585
586        let start = Uint128::new(999999);
587        do_instantiate(deps.as_mut(), &owner, start);
588
589        // provide an allowance
590        let allow1 = Uint128::new(77777);
591        let msg = ExecuteMsg::IncreaseAllowance {
592            spender: spender.clone(),
593            amount: allow1,
594            expires: None,
595        };
596        let info = mock_info(owner.as_ref(), &[]);
597        let env = mock_env();
598        execute(deps.as_mut(), env, info, msg).unwrap();
599
600        // valid burn of part of the allowance
601        let transfer = Uint128::new(44444);
602        let msg = ExecuteMsg::BurnFrom {
603            owner: owner.clone(),
604            amount: transfer,
605        };
606        let info = mock_info(spender.as_ref(), &[]);
607        let env = mock_env();
608        let res = execute(deps.as_mut(), env, info, msg).unwrap();
609        assert_eq!(res.attributes[0], attr("action", "burn_from"));
610
611        // make sure money burnt
612        assert_eq!(
613            get_balance(deps.as_ref(), owner.clone()),
614            start.checked_sub(transfer).unwrap()
615        );
616
617        // ensure it looks good
618        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
619        let expect = AllowanceResponse {
620            allowance: allow1.checked_sub(transfer).unwrap(),
621            expires: Expiration::Never {},
622        };
623        assert_eq!(expect, allowance);
624
625        // cannot burn more than the allowance
626        let msg = ExecuteMsg::BurnFrom {
627            owner: owner.clone(),
628            amount: Uint128::new(33443),
629        };
630        let info = mock_info(spender.as_ref(), &[]);
631        let env = mock_env();
632        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
633        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
634
635        // let us increase limit, but set the expiration to expire in the next block
636        let info = mock_info(owner.as_ref(), &[]);
637        let mut env = mock_env();
638        let msg = ExecuteMsg::IncreaseAllowance {
639            spender: spender.clone(),
640            amount: Uint128::new(1000),
641            expires: Some(Expiration::AtHeight(env.block.height + 1)),
642        };
643        execute(deps.as_mut(), env.clone(), info, msg).unwrap();
644
645        // increase block height, so the limit is expired now
646        env.block.height += 1;
647
648        // we should now get the expiration error
649        let msg = ExecuteMsg::BurnFrom {
650            owner,
651            amount: Uint128::new(33443),
652        };
653        let info = mock_info(spender.as_ref(), &[]);
654        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
655        assert_eq!(err, ContractError::Expired {});
656    }
657
658    #[test]
659    fn send_from_respects_limits() {
660        let mut deps = mock_dependencies_with_balance(&[]);
661        let owner = deps.api.addr_make("addr0001").to_string();
662        let spender = deps.api.addr_make("addr0002").to_string();
663        let contract = deps.api.addr_make("addr0003").to_string();
664        let send_msg = Binary::from(r#"{"some":123}"#.as_bytes());
665
666        let start = Uint128::new(999999);
667        do_instantiate(deps.as_mut(), &owner, start);
668
669        // provide an allowance
670        let allow1 = Uint128::new(77777);
671        let msg = ExecuteMsg::IncreaseAllowance {
672            spender: spender.clone(),
673            amount: allow1,
674            expires: None,
675        };
676        let info = mock_info(owner.as_ref(), &[]);
677        let env = mock_env();
678        execute(deps.as_mut(), env, info, msg).unwrap();
679
680        // valid send of part of the allowance
681        let transfer = Uint128::new(44444);
682        let msg = ExecuteMsg::SendFrom {
683            owner: owner.clone(),
684            amount: transfer,
685            contract: contract.clone(),
686            msg: send_msg.clone(),
687        };
688        let info = mock_info(spender.as_ref(), &[]);
689        let env = mock_env();
690        let res = execute(deps.as_mut(), env, info, msg).unwrap();
691        assert_eq!(res.attributes[0], attr("action", "send_from"));
692        assert_eq!(1, res.messages.len());
693
694        // we record this as sent by the one who requested, not the one who was paying
695        let binary_msg = Cw20ReceiveMsg {
696            sender: spender.clone(),
697            amount: transfer,
698            msg: send_msg.clone(),
699        }
700        .into_json_binary()
701        .unwrap();
702        assert_eq!(
703            res.messages[0],
704            SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
705                contract_addr: contract.clone(),
706                msg: binary_msg,
707                funds: vec![],
708            }))
709        );
710
711        // make sure money sent
712        assert_eq!(
713            get_balance(deps.as_ref(), owner.clone()),
714            start.checked_sub(transfer).unwrap()
715        );
716        assert_eq!(get_balance(deps.as_ref(), contract.clone()), transfer);
717
718        // ensure it looks good
719        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
720        let expect = AllowanceResponse {
721            allowance: allow1.checked_sub(transfer).unwrap(),
722            expires: Expiration::Never {},
723        };
724        assert_eq!(expect, allowance);
725
726        // cannot send more than the allowance
727        let msg = ExecuteMsg::SendFrom {
728            owner: owner.clone(),
729            amount: Uint128::new(33443),
730            contract: contract.clone(),
731            msg: send_msg.clone(),
732        };
733        let info = mock_info(spender.as_ref(), &[]);
734        let env = mock_env();
735        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
736        assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
737
738        // let us increase limit, but set the expiration to the next block
739        let info = mock_info(owner.as_ref(), &[]);
740        let mut env = mock_env();
741        let msg = ExecuteMsg::IncreaseAllowance {
742            spender: spender.clone(),
743            amount: Uint128::new(1000),
744            expires: Some(Expiration::AtHeight(env.block.height + 1)),
745        };
746        execute(deps.as_mut(), env.clone(), info, msg).unwrap();
747
748        // increase block height, so the limit is expired now
749        env.block.height += 1;
750
751        // we should now get the expiration error
752        let msg = ExecuteMsg::SendFrom {
753            owner,
754            amount: Uint128::new(33443),
755            contract,
756            msg: send_msg,
757        };
758        let info = mock_info(spender.as_ref(), &[]);
759        let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
760        assert_eq!(err, ContractError::Expired {});
761    }
762
763    #[test]
764    fn no_past_expiration() {
765        let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
766
767        let owner = deps.api.addr_make("addr0001").to_string();
768        let spender = deps.api.addr_make("addr0002").to_string();
769        let info = mock_info(owner.as_ref(), &[]);
770        let env = mock_env();
771        do_instantiate(deps.as_mut(), owner.clone(), Uint128::new(12340000));
772
773        // set allowance with height expiration at current block height
774        let expires = Expiration::AtHeight(env.block.height);
775        let msg = ExecuteMsg::IncreaseAllowance {
776            spender: spender.clone(),
777            amount: Uint128::new(7777),
778            expires: Some(expires),
779        };
780
781        // ensure it is rejected
782        assert_eq!(
783            Err(ContractError::InvalidExpiration {}),
784            execute(deps.as_mut(), env.clone(), info.clone(), msg)
785        );
786
787        // set allowance with time expiration in the past
788        let expires = Expiration::AtTime(env.block.time.minus_seconds(1));
789        let msg = ExecuteMsg::IncreaseAllowance {
790            spender: spender.clone(),
791            amount: Uint128::new(7777),
792            expires: Some(expires),
793        };
794
795        // ensure it is rejected
796        assert_eq!(
797            Err(ContractError::InvalidExpiration {}),
798            execute(deps.as_mut(), env.clone(), info.clone(), msg)
799        );
800
801        // set allowance with height expiration at next block height
802        let expires = Expiration::AtHeight(env.block.height + 1);
803        let allow = Uint128::new(7777);
804        let msg = ExecuteMsg::IncreaseAllowance {
805            spender: spender.clone(),
806            amount: allow,
807            expires: Some(expires),
808        };
809
810        execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
811
812        // ensure it looks good
813        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
814        assert_eq!(
815            allowance,
816            AllowanceResponse {
817                allowance: allow,
818                expires
819            }
820        );
821
822        // set allowance with time expiration in the future
823        let expires = Expiration::AtTime(env.block.time.plus_seconds(10));
824        let allow = Uint128::new(7777);
825        let msg = ExecuteMsg::IncreaseAllowance {
826            spender: spender.clone(),
827            amount: allow,
828            expires: Some(expires),
829        };
830
831        execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
832
833        // ensure it looks good
834        let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
835        assert_eq!(
836            allowance,
837            AllowanceResponse {
838                allowance: allow + allow, // we increased twice
839                expires
840            }
841        );
842
843        // decrease with height expiration at current block height
844        let expires = Expiration::AtHeight(env.block.height);
845        let allow = Uint128::new(7777);
846        let msg = ExecuteMsg::IncreaseAllowance {
847            spender: spender.clone(),
848            amount: allow,
849            expires: Some(expires),
850        };
851
852        // ensure it is rejected
853        assert_eq!(
854            Err(ContractError::InvalidExpiration {}),
855            execute(deps.as_mut(), env.clone(), info.clone(), msg)
856        );
857
858        // decrease with height expiration at next block height
859        let expires = Expiration::AtHeight(env.block.height + 1);
860        let allow = Uint128::new(7777);
861        let msg = ExecuteMsg::DecreaseAllowance {
862            spender: spender.clone(),
863            amount: allow,
864            expires: Some(expires),
865        };
866
867        execute(deps.as_mut(), env, info, msg).unwrap();
868
869        // ensure it looks good
870        let allowance = query_allowance(deps.as_ref(), owner, spender).unwrap();
871        assert_eq!(
872            allowance,
873            AllowanceResponse {
874                allowance: allow,
875                expires
876            }
877        );
878    }
879}