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 }