use astroport::fee_granter::{Config, ExecuteMsg, InstantiateMsg, QueryMsg}; use astroport_fee_granter::contract::{execute, instantiate}; use astroport_fee_granter::error::ContractError; use astroport_fee_granter::query::query; use astroport_fee_granter::state::MAX_ADMINS; use cosmwasm_std::{coins, Addr, Empty}; use cw_multi_test::{App, Contract, ContractWrapper, Executor}; fn fee_granter_contract() -> Box> { Box::new(ContractWrapper::new_with_empty(execute, instantiate, query)) } const GAS_DENOM: &str = "inj"; #[test] fn test_init() { let owner = Addr::unchecked("owner"); let mut app = App::new(|router, _, store| { router .bank .init_balance(store, &owner, coins(1000000, GAS_DENOM)) .unwrap(); }); let fee_granter_code_id = app.store_code(fee_granter_contract()); let mut init_msg = InstantiateMsg { owner: owner.to_string(), admins: vec![ "admin1".to_string(), "admin2".to_string(), "admin3".to_string(), ], gas_denom: GAS_DENOM.to_string(), }; let err = app .instantiate_contract( fee_granter_code_id, owner.clone(), &init_msg, &[], "Test contract", None, ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), format!("Generic error: Maximum allowed number of admins is {MAX_ADMINS}") ); init_msg.admins = vec!["admin1".to_string(), "admin2".to_string()]; let fee_granter = app .instantiate_contract( fee_granter_code_id, owner.clone(), &init_msg, &[], "Test contract", None, ) .unwrap(); app.send_tokens(owner.clone(), fee_granter.clone(), &coins(10, GAS_DENOM)) .unwrap(); let inj_bal = app .wrap() .query_balance(&fee_granter, GAS_DENOM) .unwrap() .amount .u128(); assert_eq!(inj_bal, 10); let inj_bal_before = app .wrap() .query_balance(&owner, GAS_DENOM) .unwrap() .amount .u128(); app.execute_contract( owner.clone(), fee_granter.clone(), &ExecuteMsg::TransferCoins { amount: 5u128.into(), receiver: None, }, &[], ) .unwrap(); let inj_bal_after = app .wrap() .query_balance(&owner, GAS_DENOM) .unwrap() .amount .u128(); assert_eq!(inj_bal_after - inj_bal_before, 5); let receiver_addr = "receiver".to_string(); app.execute_contract( owner.clone(), fee_granter.clone(), &ExecuteMsg::TransferCoins { amount: 5u128.into(), receiver: Some(receiver_addr.clone()), }, &[], ) .unwrap(); let inj_bal_receiver = app .wrap() .query_balance(&receiver_addr, GAS_DENOM) .unwrap() .amount .u128(); assert_eq!(inj_bal_receiver, 5); let err = app .execute_contract( owner, fee_granter, &ExecuteMsg::TransferCoins { amount: 0u8.into(), receiver: Some(receiver_addr.clone()), }, &[], ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), format!("Generic error: Can't send 0 amount") ); } #[test] fn test_update_admins() { let owner = Addr::unchecked("owner"); let admin = Addr::unchecked("admin"); let mut app = App::new(|router, _, store| { router .bank .init_balance(store, &owner, coins(1000000, GAS_DENOM)) .unwrap(); }); let fee_granter_code_id = app.store_code(fee_granter_contract()); let err = app .instantiate_contract( fee_granter_code_id, owner.clone(), &InstantiateMsg { owner: owner.to_string(), admins: vec![admin.to_string(), "user1".to_string(), "user2".to_string()], gas_denom: GAS_DENOM.to_string(), }, &[], "Test contract", None, ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), format!("Generic error: Maximum allowed number of admins is {MAX_ADMINS}") ); let err = app .instantiate_contract( fee_granter_code_id, owner.clone(), &InstantiateMsg { owner: owner.to_string(), admins: vec![admin.to_string(), admin.to_string()], gas_denom: GAS_DENOM.to_string(), }, &[], "Test contract", None, ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), format!("Generic error: Admin {admin} already exists") ); let fee_granter = app .instantiate_contract( fee_granter_code_id, owner.clone(), &InstantiateMsg { owner: owner.to_string(), admins: vec![admin.to_string()], gas_denom: GAS_DENOM.to_string(), }, &[], "Test contract", None, ) .unwrap(); app.send_tokens(owner.clone(), admin.clone(), &coins(10, GAS_DENOM)) .unwrap(); app.send_tokens(owner.clone(), fee_granter.clone(), &coins(5, GAS_DENOM)) .unwrap(); // Admin can only create, revoke grants and transfer coins app.execute_contract( admin.clone(), fee_granter.clone(), &ExecuteMsg::TransferCoins { amount: 5u128.into(), receiver: None, }, &[], ) .unwrap(); let err = app .execute_contract( owner.clone(), fee_granter.clone(), &ExecuteMsg::UpdateAdmins { add: vec!["admin2".to_string(), "admin3".to_string()], remove: vec![], }, &[], ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), format!("Generic error: Maximum allowed number of admins is {MAX_ADMINS}") ); // Stargate messages are not implemented in cw-multitest thus we assert that we receive exact cw-multitest error let err = app .execute_contract( admin.clone(), fee_granter.clone(), &ExecuteMsg::Grant { grantee_contract: "test".to_string(), amount: 10u128.into(), bypass_amount_check: false, }, &coins(10, GAS_DENOM), ) .unwrap_err(); assert!( err.root_cause().to_string().contains("Unexpected exec msg"), "{err}" ); // only owner is able to update admins let err = app .execute_contract( admin.clone(), fee_granter.clone(), &ExecuteMsg::UpdateAdmins { add: vec!["admin2".to_string()], remove: vec![], }, &[], ) .unwrap_err(); assert_eq!(ContractError::Unauthorized {}, err.downcast().unwrap()); let err = app .execute_contract( owner.clone(), fee_granter.clone(), &ExecuteMsg::UpdateAdmins { add: vec![admin.to_string()], remove: vec![], }, &[], ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), format!("Generic error: Admin {} already exists", admin) ); app.execute_contract( owner.clone(), fee_granter.clone(), &ExecuteMsg::UpdateAdmins { add: vec!["admin2".to_string()], remove: vec![], }, &[], ) .unwrap(); app.execute_contract( owner, fee_granter, &ExecuteMsg::UpdateAdmins { add: vec![], remove: vec!["admin2".to_string(), "random".to_string()], // random is not admin thus it should be ignored }, &[], ) .unwrap(); } #[test] fn test_change_ownership() { let owner = Addr::unchecked("owner"); let admin = Addr::unchecked("admin"); let mut app = App::default(); let fee_granter_code_id = app.store_code(fee_granter_contract()); let fee_granter = app .instantiate_contract( fee_granter_code_id, owner.clone(), &InstantiateMsg { owner: owner.to_string(), admins: vec![admin.to_string()], gas_denom: GAS_DENOM.to_string(), }, &[], "Test contract", None, ) .unwrap(); let new_owner = Addr::unchecked("new_owner".to_string()); // New owner let msg = ExecuteMsg::ProposeNewOwner { owner: new_owner.to_string(), expires_in: 100, // seconds }; // Unauthorized check let err = app .execute_contract(Addr::unchecked("not_owner"), fee_granter.clone(), &msg, &[]) .unwrap_err(); assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); // Claim before proposal let err = app .execute_contract( new_owner.clone(), fee_granter.clone(), &ExecuteMsg::ClaimOwnership {}, &[], ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), "Generic error: Ownership proposal not found" ); // Propose new owner app.execute_contract(owner, fee_granter.clone(), &msg, &[]) .unwrap(); // Claim from invalid addr let err = app .execute_contract( Addr::unchecked("invalid_addr"), fee_granter.clone(), &ExecuteMsg::ClaimOwnership {}, &[], ) .unwrap_err(); assert_eq!(err.root_cause().to_string(), "Generic error: Unauthorized"); // Claim ownership app.execute_contract( new_owner.clone(), fee_granter.clone(), &ExecuteMsg::ClaimOwnership {}, &[], ) .unwrap(); let res: Config = app .wrap() .query_wasm_smart(&fee_granter, &QueryMsg::Config {}) .unwrap(); assert_eq!(res.owner, new_owner) }