diff --git a/sylvia/src/types.rs b/sylvia/src/types.rs index cbf12d38..b3999c6e 100644 --- a/sylvia/src/types.rs +++ b/sylvia/src/types.rs @@ -1,4 +1,5 @@ //! Module providing utilities to build and use sylvia contracts. + use cosmwasm_std::{Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo, WasmMsg}; #[cfg(feature = "sv_replies")] use cosmwasm_std::{Event, MsgResponse}; @@ -431,9 +432,25 @@ impl<'a, Contract: ?Sized> Remote<'a, Contract> { } } + /// Creates a new instance of [ExecutorBuilder] from underlying contract address. pub fn executor(&self) -> ExecutorBuilder<(EmptyExecutorBuilderState, Contract)> { ExecutorBuilder::<(EmptyExecutorBuilderState, Contract)>::new(&self.addr) } + + /// Creates a new instance of [WasmMsg::UpdateAdmin] from underlying contract address and provided admin address. + pub fn update_admin(&self, new_admin: &str) -> WasmMsg { + WasmMsg::UpdateAdmin { + contract_addr: self.addr.to_string(), + admin: new_admin.to_string(), + } + } + + /// Creates a new instance of [WasmMsg::ClearAdmin] from underlying contract address. + pub fn clear_admin(&self) -> WasmMsg { + WasmMsg::ClearAdmin { + contract_addr: self.addr.to_string(), + } + } } impl<'a, Contract: ?Sized> AsRef for Remote<'a, Contract> { diff --git a/sylvia/tests/remote.rs b/sylvia/tests/remote.rs index 1c433228..f7468e66 100644 --- a/sylvia/tests/remote.rs +++ b/sylvia/tests/remote.rs @@ -156,7 +156,9 @@ pub mod unsigned_contract { } } -// Making sure `Remote` can be stored in `#[cw_serde]` types +// Making sure `Remote` can be stored in `#[cw_serde]` types. +// This is intentionally a dead code. +// https://github.com/CosmWasm/sylvia/issues/181 #[cw_serde] #[allow(dead_code)] pub struct ContractStorage { @@ -257,12 +259,52 @@ pub mod manager { Ok(count) } + + #[sv::msg(exec)] + fn update_admin( + &self, + ctx: ExecCtx, + new_admin: String, + ) -> Result, StdError> { + let wasm = self + .remote_counter + .load(ctx.deps.storage)? + .interface_remote + .update_admin(&new_admin); + let resp = Response::new().add_message(wasm); + Ok(resp) + } + + #[sv::msg(exec)] + fn clear_admin( + &self, + ctx: ExecCtx, + ) -> Result, StdError> { + let wasm = self + .remote_counter + .load(ctx.deps.storage)? + .interface_remote + .clear_admin(); + let resp = Response::new().add_message(wasm); + Ok(resp) + } + + #[sv::msg(query)] + fn counter_contract(&self, ctx: QueryCtx) -> Result { + Ok(self + .remote_counter + .load(ctx.deps.storage)? + .interface_remote + .as_ref() + .clone()) + } } } #[cfg(test)] mod tests { - use cw_multi_test::{BasicApp, IntoBech32}; + use cosmwasm_std::{CosmosMsg, WasmMsg}; + use cw_multi_test::{BasicApp, Executor, IntoAddr}; use sylvia::cw_std::{Addr, StdError}; use sylvia::multitest::{App, Proxy}; use sylvia::types::Remote; @@ -277,7 +319,6 @@ mod tests { use crate::{ExampleMsg, ExampleQuery}; type ExampleApp = BasicApp; - const OWNER: &str = "owner"; #[test] fn remote_generation() { @@ -302,11 +343,12 @@ mod tests { assert_eq!(&addr, borrowed_remote.as_ref()); } - fn setup( - app: &App, + fn setup<'a>( + app: &'a App, + owner: &'a Addr, ) -> ( - Proxy>, - Proxy>, + Proxy<'a, ExampleApp, ManagerContract>, + Proxy<'a, ExampleApp, ManagerContract>, ) { // Manager operating on signed numbers let signed_counter_code_id = SignedCounterCodeId::store_code(app); @@ -314,7 +356,7 @@ mod tests { let signed_counter_contract = signed_counter_code_id .instantiate() .with_label("Signed counter contract") - .call(&OWNER.into_bech32()) + .call(owner) .unwrap(); let manager_code_id = ManagerCodeId::store_code(app); @@ -322,7 +364,7 @@ mod tests { let signed_manager_contract = manager_code_id .instantiate(signed_counter_contract.contract_addr.clone()) .with_label("Manager contract") - .call(&OWNER.into_bech32()) + .call(owner) .unwrap(); // Manager operating on unsigned numbers @@ -331,7 +373,8 @@ mod tests { let unsigned_counter_contract = unsigned_counter_code_id .instantiate() .with_label("Unsigned counter contract") - .call(&OWNER.into_bech32()) + .with_admin(Some(owner.as_str())) + .call(owner) .unwrap(); let manager_code_id = ManagerCodeId::store_code(app); @@ -339,7 +382,18 @@ mod tests { let unsigned_manager_contract = manager_code_id .instantiate(unsigned_counter_contract.contract_addr.clone()) .with_label("Manager contract") - .call(&OWNER.into_bech32()) + .call(owner) + .unwrap(); + + // Set manager contract as an admin of the counter contract + app.app_mut() + .execute( + owner.clone(), + CosmosMsg::Wasm(WasmMsg::UpdateAdmin { + contract_addr: unsigned_counter_contract.contract_addr.to_string(), + admin: unsigned_manager_contract.contract_addr.to_string(), + }), + ) .unwrap(); (signed_manager_contract, unsigned_manager_contract) @@ -347,23 +401,81 @@ mod tests { #[test] fn call_remote() { + let owner = "owner".into_addr(); let app = App::>::custom(|_, _, _| {}); - let (signed_manager_contract, unsigned_manager_contract) = setup(&app); + let (signed_manager_contract, unsigned_manager_contract) = setup(&app, &owner); assert_eq!(signed_manager_contract.count().unwrap(), 0); - signed_manager_contract - .add(5) - .call(&OWNER.into_bech32()) - .unwrap(); + signed_manager_contract.add(5).call(&owner).unwrap(); assert_eq!(signed_manager_contract.count().unwrap(), 5); assert_eq!(unsigned_manager_contract.count().unwrap(), 0); + unsigned_manager_contract.add(5).call(&owner).unwrap(); + assert_eq!(unsigned_manager_contract.count().unwrap(), 5); + } + + #[test] + fn update_admin() { + let owner = "owner".into_addr(); + let app = App::>::custom(|_, _, _| {}); + let (_, unsigned_manager_contract) = setup(&app, &owner); + let new_admin = "new_admin".into_addr(); + + let unsigned_counter_contract_addr = unsigned_manager_contract.counter_contract().unwrap(); + + // Initial admin should be the manager_contract + let contract_info = app + .querier() + .query_wasm_contract_info(unsigned_counter_contract_addr.clone()) + .unwrap(); + assert_eq!( + contract_info.admin, + Some(unsigned_manager_contract.contract_addr.clone()) + ); + + // Add new admin unsigned_manager_contract - .add(5) - .call(&OWNER.into_bech32()) + .update_admin(new_admin.to_string()) + .call(&owner) .unwrap(); - assert_eq!(unsigned_manager_contract.count().unwrap(), 5); + + let contract_info = app + .querier() + .query_wasm_contract_info(unsigned_counter_contract_addr) + .unwrap(); + assert_eq!(contract_info.admin, Some(new_admin.clone())); + } + + #[test] + fn clear_admin() { + let owner = "owner".into_addr(); + let app = App::>::custom(|_, _, _| {}); + let (_, unsigned_manager_contract) = setup(&app, &owner); + + let unsigned_counter_contract_addr = unsigned_manager_contract.counter_contract().unwrap(); + + // Initial admin should be the manager_contract + let contract_info = app + .querier() + .query_wasm_contract_info(unsigned_counter_contract_addr.clone()) + .unwrap(); + assert_eq!( + contract_info.admin, + Some(unsigned_manager_contract.contract_addr.clone()) + ); + + // Clear admin + unsigned_manager_contract + .clear_admin() + .call(&owner) + .unwrap(); + + let contract_info = app + .querier() + .query_wasm_contract_info(unsigned_counter_contract_addr.clone()) + .unwrap(); + assert_eq!(contract_info.admin, None); } }