github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/dev/wasm/cw20-base/src/allowances.rs (about)

     1  use cosmwasm_std::{
     2      attr, Addr, Binary, BlockInfo, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult,
     3      Storage, Uint128,
     4  };
     5  use cw20::{AllowanceResponse, Cw20ReceiveMsg, Expiration};
     6  
     7  use crate::error::ContractError;
     8  use crate::state::{ALLOWANCES, BALANCES, TOKEN_INFO};
     9  
    10  pub 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  
    45  pub 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      // load value and delete if it hits 0, or update otherwise
    60      let mut allowance = ALLOWANCES.load(deps.storage, key)?;
    61      if amount < allowance.allowance {
    62          // update the new amount
    63          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  
    84  // this can be used to update a lower allowance - call bucket.update with proper keys
    85  pub 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                      // deduct the allowance if enough
    99                      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  
   111  pub 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 before doing anything else have enough allowance
   123      deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
   124  
   125      BALANCES.update(
   126          deps.storage,
   127          &owner_addr,
   128          |balance: Option<Uint128>| -> StdResult<_> {
   129              Ok(balance.unwrap_or_default().checked_sub(amount)?)
   130          },
   131      )?;
   132      BALANCES.update(
   133          deps.storage,
   134          &rcpt_addr,
   135          |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
   136      )?;
   137  
   138      let res = Response::new().add_attributes(vec![
   139          attr("action", "transfer_from"),
   140          attr("from", owner),
   141          attr("to", recipient),
   142          attr("by", info.sender),
   143          attr("amount", amount),
   144      ]);
   145      Ok(res)
   146  }
   147  
   148  pub fn execute_burn_from(
   149      deps: DepsMut,
   150  
   151      env: Env,
   152      info: MessageInfo,
   153      owner: String,
   154      amount: Uint128,
   155  ) -> Result<Response, ContractError> {
   156      let owner_addr = deps.api.addr_validate(&owner)?;
   157  
   158      // deduct allowance before doing anything else have enough allowance
   159      deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
   160  
   161      // lower balance
   162      BALANCES.update(
   163          deps.storage,
   164          &owner_addr,
   165          |balance: Option<Uint128>| -> StdResult<_> {
   166              Ok(balance.unwrap_or_default().checked_sub(amount)?)
   167          },
   168      )?;
   169      // reduce total_supply
   170      TOKEN_INFO.update(deps.storage, |mut meta| -> StdResult<_> {
   171          meta.total_supply = meta.total_supply.checked_sub(amount)?;
   172          Ok(meta)
   173      })?;
   174  
   175      let res = Response::new().add_attributes(vec![
   176          attr("action", "burn_from"),
   177          attr("from", owner),
   178          attr("by", info.sender),
   179          attr("amount", amount),
   180      ]);
   181      Ok(res)
   182  }
   183  
   184  pub fn execute_send_from(
   185      deps: DepsMut,
   186      env: Env,
   187      info: MessageInfo,
   188      owner: String,
   189      contract: String,
   190      amount: Uint128,
   191      msg: Binary,
   192  ) -> Result<Response, ContractError> {
   193      let rcpt_addr = deps.api.addr_validate(&contract)?;
   194      let owner_addr = deps.api.addr_validate(&owner)?;
   195  
   196      // deduct allowance before doing anything else have enough allowance
   197      deduct_allowance(deps.storage, &owner_addr, &info.sender, &env.block, amount)?;
   198  
   199      // move the tokens to the contract
   200      BALANCES.update(
   201          deps.storage,
   202          &owner_addr,
   203          |balance: Option<Uint128>| -> StdResult<_> {
   204              Ok(balance.unwrap_or_default().checked_sub(amount)?)
   205          },
   206      )?;
   207      BALANCES.update(
   208          deps.storage,
   209          &rcpt_addr,
   210          |balance: Option<Uint128>| -> StdResult<_> { Ok(balance.unwrap_or_default() + amount) },
   211      )?;
   212  
   213      let attrs = vec![
   214          attr("action", "send_from"),
   215          attr("from", &owner),
   216          attr("to", &contract),
   217          attr("by", &info.sender),
   218          attr("amount", amount),
   219      ];
   220  
   221      // create a send message
   222      let msg = Cw20ReceiveMsg {
   223          sender: info.sender.into(),
   224          amount,
   225          msg,
   226      }
   227      .into_cosmos_msg(contract)?;
   228  
   229      let res = Response::new().add_message(msg).add_attributes(attrs);
   230      Ok(res)
   231  }
   232  
   233  pub fn query_allowance(deps: Deps, owner: String, spender: String) -> StdResult<AllowanceResponse> {
   234      let owner_addr = deps.api.addr_validate(&owner)?;
   235      let spender_addr = deps.api.addr_validate(&spender)?;
   236      let allowance = ALLOWANCES
   237          .may_load(deps.storage, (&owner_addr, &spender_addr))?
   238          .unwrap_or_default();
   239      Ok(allowance)
   240  }
   241  
   242  #[cfg(test)]
   243  mod tests {
   244      use super::*;
   245  
   246      use cosmwasm_std::testing::{mock_dependencies_with_balance, mock_env, mock_info};
   247      use cosmwasm_std::{coins, CosmosMsg, SubMsg, Timestamp, WasmMsg};
   248      use cw20::{Cw20Coin, TokenInfoResponse};
   249  
   250      use crate::contract::{execute, instantiate, query_balance, query_token_info};
   251      use crate::msg::{ExecuteMsg, InstantiateMsg};
   252  
   253      fn get_balance<T: Into<String>>(deps: Deps, address: T) -> Uint128 {
   254          query_balance(deps, address.into()).unwrap().balance
   255      }
   256  
   257      // this will set up the instantiation for other tests
   258      fn do_instantiate<T: Into<String>>(
   259          mut deps: DepsMut,
   260          addr: T,
   261          amount: Uint128,
   262      ) -> TokenInfoResponse {
   263          let instantiate_msg = InstantiateMsg {
   264              name: "Auto Gen".to_string(),
   265              symbol: "AUTO".to_string(),
   266              decimals: 3,
   267              initial_balances: vec![Cw20Coin {
   268                  address: addr.into(),
   269                  amount,
   270              }],
   271              mint: None,
   272              marketing: None,
   273          };
   274          let info = mock_info("creator", &[]);
   275          let env = mock_env();
   276          instantiate(deps.branch(), env, info, instantiate_msg).unwrap();
   277          query_token_info(deps.as_ref()).unwrap()
   278      }
   279  
   280      #[test]
   281      fn increase_decrease_allowances() {
   282          let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
   283  
   284          let owner = String::from("addr0001");
   285          let spender = String::from("addr0002");
   286          let info = mock_info(owner.as_ref(), &[]);
   287          let env = mock_env();
   288          do_instantiate(deps.as_mut(), owner.clone(), Uint128::new(12340000));
   289  
   290          // no allowance to start
   291          let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
   292          assert_eq!(allowance, AllowanceResponse::default());
   293  
   294          // set allowance with height expiration
   295          let allow1 = Uint128::new(7777);
   296          let expires = Expiration::AtHeight(5432);
   297          let msg = ExecuteMsg::IncreaseAllowance {
   298              spender: spender.clone(),
   299              amount: allow1,
   300              expires: Some(expires),
   301          };
   302          execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
   303  
   304          // ensure it looks good
   305          let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
   306          assert_eq!(
   307              allowance,
   308              AllowanceResponse {
   309                  allowance: allow1,
   310                  expires
   311              }
   312          );
   313  
   314          // decrease it a bit with no expire set - stays the same
   315          let lower = Uint128::new(4444);
   316          let allow2 = allow1.checked_sub(lower).unwrap();
   317          let msg = ExecuteMsg::DecreaseAllowance {
   318              spender: spender.clone(),
   319              amount: lower,
   320              expires: None,
   321          };
   322          execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
   323          let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
   324          assert_eq!(
   325              allowance,
   326              AllowanceResponse {
   327                  allowance: allow2,
   328                  expires
   329              }
   330          );
   331  
   332          // increase it some more and override the expires
   333          let raise = Uint128::new(87654);
   334          let allow3 = allow2 + raise;
   335          let new_expire = Expiration::AtTime(Timestamp::from_seconds(8888888888));
   336          let msg = ExecuteMsg::IncreaseAllowance {
   337              spender: spender.clone(),
   338              amount: raise,
   339              expires: Some(new_expire),
   340          };
   341          execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
   342          let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
   343          assert_eq!(
   344              allowance,
   345              AllowanceResponse {
   346                  allowance: allow3,
   347                  expires: new_expire
   348              }
   349          );
   350  
   351          // decrease it below 0
   352          let msg = ExecuteMsg::DecreaseAllowance {
   353              spender: spender.clone(),
   354              amount: Uint128::new(99988647623876347),
   355              expires: None,
   356          };
   357          execute(deps.as_mut(), env, info, msg).unwrap();
   358          let allowance = query_allowance(deps.as_ref(), owner, spender).unwrap();
   359          assert_eq!(allowance, AllowanceResponse::default());
   360      }
   361  
   362      #[test]
   363      fn allowances_independent() {
   364          let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
   365  
   366          let owner = String::from("addr0001");
   367          let spender = String::from("addr0002");
   368          let spender2 = String::from("addr0003");
   369          let info = mock_info(owner.as_ref(), &[]);
   370          let env = mock_env();
   371          do_instantiate(deps.as_mut(), &owner, Uint128::new(12340000));
   372  
   373          // no allowance to start
   374          assert_eq!(
   375              query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(),
   376              AllowanceResponse::default()
   377          );
   378          assert_eq!(
   379              query_allowance(deps.as_ref(), owner.clone(), spender2.clone()).unwrap(),
   380              AllowanceResponse::default()
   381          );
   382          assert_eq!(
   383              query_allowance(deps.as_ref(), spender.clone(), spender2.clone()).unwrap(),
   384              AllowanceResponse::default()
   385          );
   386  
   387          // set allowance with height expiration
   388          let allow1 = Uint128::new(7777);
   389          let expires = Expiration::AtHeight(5432);
   390          let msg = ExecuteMsg::IncreaseAllowance {
   391              spender: spender.clone(),
   392              amount: allow1,
   393              expires: Some(expires),
   394          };
   395          execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
   396  
   397          // set other allowance with no expiration
   398          let allow2 = Uint128::new(87654);
   399          let msg = ExecuteMsg::IncreaseAllowance {
   400              spender: spender2.clone(),
   401              amount: allow2,
   402              expires: None,
   403          };
   404          execute(deps.as_mut(), env, info, msg).unwrap();
   405  
   406          // check they are proper
   407          let expect_one = AllowanceResponse {
   408              allowance: allow1,
   409              expires,
   410          };
   411          let expect_two = AllowanceResponse {
   412              allowance: allow2,
   413              expires: Expiration::Never {},
   414          };
   415          assert_eq!(
   416              query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(),
   417              expect_one
   418          );
   419          assert_eq!(
   420              query_allowance(deps.as_ref(), owner.clone(), spender2.clone()).unwrap(),
   421              expect_two
   422          );
   423          assert_eq!(
   424              query_allowance(deps.as_ref(), spender.clone(), spender2.clone()).unwrap(),
   425              AllowanceResponse::default()
   426          );
   427  
   428          // also allow spender -> spender2 with no interference
   429          let info = mock_info(spender.as_ref(), &[]);
   430          let env = mock_env();
   431          let allow3 = Uint128::new(1821);
   432          let expires3 = Expiration::AtTime(Timestamp::from_seconds(3767626296));
   433          let msg = ExecuteMsg::IncreaseAllowance {
   434              spender: spender2.clone(),
   435              amount: allow3,
   436              expires: Some(expires3),
   437          };
   438          execute(deps.as_mut(), env, info, msg).unwrap();
   439          let expect_three = AllowanceResponse {
   440              allowance: allow3,
   441              expires: expires3,
   442          };
   443          assert_eq!(
   444              query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap(),
   445              expect_one
   446          );
   447          assert_eq!(
   448              query_allowance(deps.as_ref(), owner, spender2.clone()).unwrap(),
   449              expect_two
   450          );
   451          assert_eq!(
   452              query_allowance(deps.as_ref(), spender, spender2).unwrap(),
   453              expect_three
   454          );
   455      }
   456  
   457      #[test]
   458      fn no_self_allowance() {
   459          let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
   460  
   461          let owner = String::from("addr0001");
   462          let info = mock_info(owner.as_ref(), &[]);
   463          let env = mock_env();
   464          do_instantiate(deps.as_mut(), &owner, Uint128::new(12340000));
   465  
   466          // self-allowance
   467          let msg = ExecuteMsg::IncreaseAllowance {
   468              spender: owner.clone(),
   469              amount: Uint128::new(7777),
   470              expires: None,
   471          };
   472          let err = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err();
   473          assert_eq!(err, ContractError::CannotSetOwnAccount {});
   474  
   475          // decrease self-allowance
   476          let msg = ExecuteMsg::DecreaseAllowance {
   477              spender: owner,
   478              amount: Uint128::new(7777),
   479              expires: None,
   480          };
   481          let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
   482          assert_eq!(err, ContractError::CannotSetOwnAccount {});
   483      }
   484  
   485      #[test]
   486      fn transfer_from_respects_limits() {
   487          let mut deps = mock_dependencies_with_balance(&[]);
   488          let owner = String::from("addr0001");
   489          let spender = String::from("addr0002");
   490          let rcpt = String::from("addr0003");
   491  
   492          let start = Uint128::new(999999);
   493          do_instantiate(deps.as_mut(), &owner, start);
   494  
   495          // provide an allowance
   496          let allow1 = Uint128::new(77777);
   497          let msg = ExecuteMsg::IncreaseAllowance {
   498              spender: spender.clone(),
   499              amount: allow1,
   500              expires: None,
   501          };
   502          let info = mock_info(owner.as_ref(), &[]);
   503          let env = mock_env();
   504          execute(deps.as_mut(), env, info, msg).unwrap();
   505  
   506          // valid transfer of part of the allowance
   507          let transfer = Uint128::new(44444);
   508          let msg = ExecuteMsg::TransferFrom {
   509              owner: owner.clone(),
   510              recipient: rcpt.clone(),
   511              amount: transfer,
   512          };
   513          let info = mock_info(spender.as_ref(), &[]);
   514          let env = mock_env();
   515          let res = execute(deps.as_mut(), env, info, msg).unwrap();
   516          assert_eq!(res.attributes[0], attr("action", "transfer_from"));
   517  
   518          // make sure money arrived
   519          assert_eq!(
   520              get_balance(deps.as_ref(), owner.clone()),
   521              start.checked_sub(transfer).unwrap()
   522          );
   523          assert_eq!(get_balance(deps.as_ref(), rcpt.clone()), transfer);
   524  
   525          // ensure it looks good
   526          let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
   527          let expect = AllowanceResponse {
   528              allowance: allow1.checked_sub(transfer).unwrap(),
   529              expires: Expiration::Never {},
   530          };
   531          assert_eq!(expect, allowance);
   532  
   533          // cannot send more than the allowance
   534          let msg = ExecuteMsg::TransferFrom {
   535              owner: owner.clone(),
   536              recipient: rcpt.clone(),
   537              amount: Uint128::new(33443),
   538          };
   539          let info = mock_info(spender.as_ref(), &[]);
   540          let env = mock_env();
   541          let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
   542          assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
   543  
   544          // let us increase limit, but set the expiration (default env height is 12_345)
   545          let info = mock_info(owner.as_ref(), &[]);
   546          let env = mock_env();
   547          let msg = ExecuteMsg::IncreaseAllowance {
   548              spender: spender.clone(),
   549              amount: Uint128::new(1000),
   550              expires: Some(Expiration::AtHeight(env.block.height)),
   551          };
   552          execute(deps.as_mut(), env, info, msg).unwrap();
   553  
   554          // we should now get the expiration error
   555          let msg = ExecuteMsg::TransferFrom {
   556              owner,
   557              recipient: rcpt,
   558              amount: Uint128::new(33443),
   559          };
   560          let info = mock_info(spender.as_ref(), &[]);
   561          let env = mock_env();
   562          let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
   563          assert_eq!(err, ContractError::Expired {});
   564      }
   565  
   566      #[test]
   567      fn burn_from_respects_limits() {
   568          let mut deps = mock_dependencies_with_balance(&[]);
   569          let owner = String::from("addr0001");
   570          let spender = String::from("addr0002");
   571  
   572          let start = Uint128::new(999999);
   573          do_instantiate(deps.as_mut(), &owner, start);
   574  
   575          // provide an allowance
   576          let allow1 = Uint128::new(77777);
   577          let msg = ExecuteMsg::IncreaseAllowance {
   578              spender: spender.clone(),
   579              amount: allow1,
   580              expires: None,
   581          };
   582          let info = mock_info(owner.as_ref(), &[]);
   583          let env = mock_env();
   584          execute(deps.as_mut(), env, info, msg).unwrap();
   585  
   586          // valid burn of part of the allowance
   587          let transfer = Uint128::new(44444);
   588          let msg = ExecuteMsg::BurnFrom {
   589              owner: owner.clone(),
   590              amount: transfer,
   591          };
   592          let info = mock_info(spender.as_ref(), &[]);
   593          let env = mock_env();
   594          let res = execute(deps.as_mut(), env, info, msg).unwrap();
   595          assert_eq!(res.attributes[0], attr("action", "burn_from"));
   596  
   597          // make sure money burnt
   598          assert_eq!(
   599              get_balance(deps.as_ref(), owner.clone()),
   600              start.checked_sub(transfer).unwrap()
   601          );
   602  
   603          // ensure it looks good
   604          let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
   605          let expect = AllowanceResponse {
   606              allowance: allow1.checked_sub(transfer).unwrap(),
   607              expires: Expiration::Never {},
   608          };
   609          assert_eq!(expect, allowance);
   610  
   611          // cannot burn more than the allowance
   612          let msg = ExecuteMsg::BurnFrom {
   613              owner: owner.clone(),
   614              amount: Uint128::new(33443),
   615          };
   616          let info = mock_info(spender.as_ref(), &[]);
   617          let env = mock_env();
   618          let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
   619          assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
   620  
   621          // let us increase limit, but set the expiration (default env height is 12_345)
   622          let info = mock_info(owner.as_ref(), &[]);
   623          let env = mock_env();
   624          let msg = ExecuteMsg::IncreaseAllowance {
   625              spender: spender.clone(),
   626              amount: Uint128::new(1000),
   627              expires: Some(Expiration::AtHeight(env.block.height)),
   628          };
   629          execute(deps.as_mut(), env, info, msg).unwrap();
   630  
   631          // we should now get the expiration error
   632          let msg = ExecuteMsg::BurnFrom {
   633              owner,
   634              amount: Uint128::new(33443),
   635          };
   636          let info = mock_info(spender.as_ref(), &[]);
   637          let env = mock_env();
   638          let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
   639          assert_eq!(err, ContractError::Expired {});
   640      }
   641  
   642      #[test]
   643      fn send_from_respects_limits() {
   644          let mut deps = mock_dependencies_with_balance(&[]);
   645          let owner = String::from("addr0001");
   646          let spender = String::from("addr0002");
   647          let contract = String::from("cool-dex");
   648          let send_msg = Binary::from(r#"{"some":123}"#.as_bytes());
   649  
   650          let start = Uint128::new(999999);
   651          do_instantiate(deps.as_mut(), &owner, start);
   652  
   653          // provide an allowance
   654          let allow1 = Uint128::new(77777);
   655          let msg = ExecuteMsg::IncreaseAllowance {
   656              spender: spender.clone(),
   657              amount: allow1,
   658              expires: None,
   659          };
   660          let info = mock_info(owner.as_ref(), &[]);
   661          let env = mock_env();
   662          execute(deps.as_mut(), env, info, msg).unwrap();
   663  
   664          // valid send of part of the allowance
   665          let transfer = Uint128::new(44444);
   666          let msg = ExecuteMsg::SendFrom {
   667              owner: owner.clone(),
   668              amount: transfer,
   669              contract: contract.clone(),
   670              msg: send_msg.clone(),
   671          };
   672          let info = mock_info(spender.as_ref(), &[]);
   673          let env = mock_env();
   674          let res = execute(deps.as_mut(), env, info, msg).unwrap();
   675          assert_eq!(res.attributes[0], attr("action", "send_from"));
   676          assert_eq!(1, res.messages.len());
   677  
   678          // we record this as sent by the one who requested, not the one who was paying
   679          let binary_msg = Cw20ReceiveMsg {
   680              sender: spender.clone(),
   681              amount: transfer,
   682              msg: send_msg.clone(),
   683          }
   684          .into_binary()
   685          .unwrap();
   686          assert_eq!(
   687              res.messages[0],
   688              SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
   689                  contract_addr: contract.clone(),
   690                  msg: binary_msg,
   691                  funds: vec![],
   692              }))
   693          );
   694  
   695          // make sure money sent
   696          assert_eq!(
   697              get_balance(deps.as_ref(), owner.clone()),
   698              start.checked_sub(transfer).unwrap()
   699          );
   700          assert_eq!(get_balance(deps.as_ref(), contract.clone()), transfer);
   701  
   702          // ensure it looks good
   703          let allowance = query_allowance(deps.as_ref(), owner.clone(), spender.clone()).unwrap();
   704          let expect = AllowanceResponse {
   705              allowance: allow1.checked_sub(transfer).unwrap(),
   706              expires: Expiration::Never {},
   707          };
   708          assert_eq!(expect, allowance);
   709  
   710          // cannot send more than the allowance
   711          let msg = ExecuteMsg::SendFrom {
   712              owner: owner.clone(),
   713              amount: Uint128::new(33443),
   714              contract: contract.clone(),
   715              msg: send_msg.clone(),
   716          };
   717          let info = mock_info(spender.as_ref(), &[]);
   718          let env = mock_env();
   719          let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
   720          assert!(matches!(err, ContractError::Std(StdError::Overflow { .. })));
   721  
   722          // let us increase limit, but set the expiration to current block (expired)
   723          let info = mock_info(owner.as_ref(), &[]);
   724          let env = mock_env();
   725          let msg = ExecuteMsg::IncreaseAllowance {
   726              spender: spender.clone(),
   727              amount: Uint128::new(1000),
   728              expires: Some(Expiration::AtHeight(env.block.height)),
   729          };
   730          execute(deps.as_mut(), env, info, msg).unwrap();
   731  
   732          // we should now get the expiration error
   733          let msg = ExecuteMsg::SendFrom {
   734              owner,
   735              amount: Uint128::new(33443),
   736              contract,
   737              msg: send_msg,
   738          };
   739          let info = mock_info(spender.as_ref(), &[]);
   740          let env = mock_env();
   741          let err = execute(deps.as_mut(), env, info, msg).unwrap_err();
   742          assert_eq!(err, ContractError::Expired {});
   743      }
   744  }