diff --git a/packages/multi-test/src/app.rs b/packages/multi-test/src/app.rs index 68995265c..8227bff24 100644 --- a/packages/multi-test/src/app.rs +++ b/packages/multi-test/src/app.rs @@ -1828,6 +1828,68 @@ mod test { assert_eq!(state.beneficiary, random); } + #[test] + fn send_update_admin_works() { + // The plan: + // create a hackatom contract + // check admin set properly + // update admin succeeds if admin + // update admin fails if not (new) admin + // check admin set properly + let owner = Addr::unchecked("owner"); + let owner2 = Addr::unchecked("owner2"); + let beneficiary = Addr::unchecked("beneficiary"); + + let mut app = App::default(); + + // create a hackatom contract with some funds + let contract_id = app.store_code(hackatom::contract()); + let contract = app + .instantiate_contract( + contract_id, + owner.clone(), + &hackatom::InstantiateMsg { + beneficiary: beneficiary.as_str().to_owned(), + }, + &[], + "Hackatom", + Some(owner.to_string()), + ) + .unwrap(); + + // check admin set properly + let info = app.contract_data(&contract).unwrap(); + assert_eq!(info.admin, Some(owner.clone())); + + // transfer adminship to owner2 + app.execute( + owner.clone(), + CosmosMsg::Wasm(WasmMsg::UpdateAdmin { + contract_addr: contract.to_string(), + admin: owner2.to_string(), + }), + ) + .unwrap(); + + // check admin set properly + let info = app.contract_data(&contract).unwrap(); + assert_eq!(info.admin, Some(owner2.clone())); + + // update admin fails if not owner2 + app.execute( + owner.clone(), + CosmosMsg::Wasm(WasmMsg::UpdateAdmin { + contract_addr: contract.to_string(), + admin: owner.to_string(), + }), + ) + .unwrap_err(); + + // check admin still the same + let info = app.contract_data(&contract).unwrap(); + assert_eq!(info.admin, Some(owner2)); + } + mod reply_data_overwrite { use super::*; diff --git a/packages/multi-test/src/wasm.rs b/packages/multi-test/src/wasm.rs index 618e2739d..54afe9681 100644 --- a/packages/multi-test/src/wasm.rs +++ b/packages/multi-test/src/wasm.rs @@ -327,6 +327,34 @@ where } } + /// unified logic for UpdateAdmin and ClearAdmin messages + fn update_admin( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + sender: Addr, + contract_addr: &str, + new_admin: Option, + ) -> AnyResult { + let contract_addr = api.addr_validate(contract_addr)?; + let admin = new_admin.map(|a| api.addr_validate(&a)).transpose()?; + + // check admin status + let mut data = self.load_contract(storage, &contract_addr)?; + if data.admin != Some(sender) { + bail!("Only admin can update the contract admin: {:?}", data.admin); + } + // update admin field + data.admin = admin; + self.save_contract(storage, &contract_addr, &data)?; + + // no custom event here + Ok(AppResponse { + data: None, + events: vec![], + }) + } + // this returns the contract address as well, so we can properly resend the data fn execute_wasm( &self, @@ -474,6 +502,13 @@ where res.data = execute_response(res.data); Ok(res) } + WasmMsg::UpdateAdmin { + contract_addr, + admin, + } => self.update_admin(api, storage, sender, &contract_addr, Some(admin)), + WasmMsg::ClearAdmin { contract_addr } => { + self.update_admin(api, storage, sender, &contract_addr, None) + } msg => bail!(Error::UnsupportedWasmMsg(msg)), } } @@ -906,7 +941,8 @@ mod test { use crate::app::Router; use crate::bank::BankKeeper; use crate::module::FailingModule; - use crate::test_helpers::contracts::{error, payout}; + use crate::test_helpers::contracts::{caller, error, payout}; + use crate::test_helpers::EmptyMsg; use crate::transactions::StorageTransaction; use super::*; @@ -1356,4 +1392,131 @@ mod test { assert_payout(&keeper, &mut wasm_storage, &contract2, &payout2); assert_payout(&keeper, &mut wasm_storage, &contract3, &payout3); } + + fn assert_admin( + storage: &dyn Storage, + keeper: &WasmKeeper, + contract_addr: &impl ToString, + admin: Option, + ) { + let api = MockApi::default(); + let querier: MockQuerier = MockQuerier::new(&[]); + // query + let data = keeper + .query( + &api, + storage, + &querier, + &mock_env().block, + WasmQuery::ContractInfo { + contract_addr: contract_addr.to_string(), + }, + ) + .unwrap(); + let res: ContractInfoResponse = from_slice(&data).unwrap(); + assert_eq!(res.admin, admin.as_ref().map(Addr::to_string)); + } + + #[test] + fn update_clear_admin_works() { + let api = MockApi::default(); + let mut keeper = WasmKeeper::new(); + let block = mock_env().block; + let code_id = keeper.store_code(caller::contract()); + + let mut wasm_storage = MockStorage::new(); + + let admin: Addr = Addr::unchecked("admin"); + let new_admin: Addr = Addr::unchecked("new_admin"); + let normal_user: Addr = Addr::unchecked("normal_user"); + + let contract_addr = keeper + .register_contract( + &mut wasm_storage, + code_id, + Addr::unchecked("creator"), + admin.clone(), + "label".to_owned(), + 1000, + ) + .unwrap(); + + // init the contract + let info = mock_info("admin", &[]); + let init_msg = to_vec(&EmptyMsg {}).unwrap(); + let res = keeper + .call_instantiate( + contract_addr.clone(), + &api, + &mut wasm_storage, + &mock_router(), + &block, + info, + init_msg, + ) + .unwrap(); + assert_eq!(0, res.messages.len()); + + assert_admin(&wasm_storage, &keeper, &contract_addr, Some(admin.clone())); + + // non-admin should not be allowed to become admin on their own + keeper + .execute_wasm( + &api, + &mut wasm_storage, + &mock_router(), + &block, + normal_user.clone(), + WasmMsg::UpdateAdmin { + contract_addr: contract_addr.to_string(), + admin: normal_user.to_string(), + }, + ) + .unwrap_err(); + + // should still be admin + assert_admin(&wasm_storage, &keeper, &contract_addr, Some(admin.clone())); + + // admin should be allowed to transfers adminship + let res = keeper + .execute_wasm( + &api, + &mut wasm_storage, + &mock_router(), + &block, + admin, + WasmMsg::UpdateAdmin { + contract_addr: contract_addr.to_string(), + admin: new_admin.to_string(), + }, + ) + .unwrap(); + assert_eq!(res.events.len(), 0); + + // new_admin should now be admin + assert_admin( + &wasm_storage, + &keeper, + &contract_addr, + Some(new_admin.clone()), + ); + + // new_admin should now be able to clear to admin + let res = keeper + .execute_wasm( + &api, + &mut wasm_storage, + &mock_router(), + &block, + new_admin, + WasmMsg::ClearAdmin { + contract_addr: contract_addr.to_string(), + }, + ) + .unwrap(); + assert_eq!(res.events.len(), 0); + + // should have no admin now + assert_admin(&wasm_storage, &keeper, &contract_addr, None); + } }