Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[multi-test] Add update and clear admin support to WasmKeeper #812

Merged
merged 4 commits into from Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
62 changes: 62 additions & 0 deletions packages/multi-test/src/app.rs
Expand Up @@ -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::*;

Expand Down
165 changes: 164 additions & 1 deletion packages/multi-test/src/wasm.rs
Expand Up @@ -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<String>,
) -> AnyResult<AppResponse> {
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 clear contract admin: {:?}", data.admin);
chipshort marked this conversation as resolved.
Show resolved Hide resolved
}
// 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,
Expand Down Expand Up @@ -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)),
}
}
Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -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<Empty, Empty>,
contract_addr: &impl ToString,
admin: Option<Addr>,
) {
let api = MockApi::default();
let querier: MockQuerier<Empty> = 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);
}
}