pub use fadroma_proc_auth::*;
use serde::{Serialize, Deserialize};
use crate::{
dsl::*,
core::Canonize,
storage::SingleItem,
schemars::JsonSchema,
cosmwasm_std::{
self,
Deps, DepsMut, Response, MessageInfo,
CanonicalAddr, StdResult, StdError, Addr
}
};
crate::namespace!(pub AdminNs, b"ltp5P6sFZT");
pub const STORE: SingleItem<CanonicalAddr, AdminNs> = SingleItem::new();
crate::namespace!(pub PendingAdminNs, b"b5QaJXDibK");
pub const PENDING_ADMIN: SingleItem<CanonicalAddr, PendingAdminNs> = SingleItem::new();
#[interface]
pub trait Admin {
type Error: std::fmt::Display;
#[execute]
fn change_admin(mode: Option<Mode>) -> Result<Response, Self::Error>;
#[query]
fn admin() -> Result<Option<Addr>, Self::Error>;
}
#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Debug, Clone)]
pub enum Mode {
Immediate { new_admin: String },
TwoStep { new_admin: String }
}
pub fn init(
deps: DepsMut,
address: Option<&str>,
info: &MessageInfo
) -> StdResult<CanonicalAddr> {
let admin = if let Some(addr) = address {
&addr
} else {
info.sender.as_str()
};
let admin = admin.canonize(deps.api)?;
STORE.save(deps.storage, &admin)?;
Ok(admin)
}
pub fn assert(deps: Deps, info: &MessageInfo) -> StdResult<()> {
let admin = STORE.load_humanize(deps)?;
if let Some(admin) = admin {
if admin == info.sender {
return Ok(());
}
}
Err(StdError::generic_err("Unauthorized"))
}
#[derive(Clone, Copy, Debug)]
pub struct DefaultImpl;
impl Admin for DefaultImpl {
type Error = StdError;
#[execute]
fn change_admin(mode: Option<Mode>) -> StdResult<Response> {
if let Some(mode) = mode {
assert(deps.as_ref(), &info)?;
match mode {
Mode::Immediate { new_admin } =>
STORE.canonize_and_save(deps, new_admin.as_str())?,
Mode::TwoStep { new_admin } =>
PENDING_ADMIN.canonize_and_save(deps, new_admin.as_str())?,
}
} else {
if let Some(pending) = PENDING_ADMIN.load_humanize(deps.as_ref())? {
if pending == info.sender {
STORE.canonize_and_save(deps, pending.as_str())?;
} else {
return Err(StdError::generic_err("Unauthorized"));
}
} else {
return Err(StdError::generic_err("No address is currently expected to accept the admin role."));
}
}
Ok(Response::new())
}
#[query]
fn admin() -> StdResult<Option<Addr>> {
STORE.load_humanize(deps)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
admin,
cosmwasm_std::{
StdError,
testing::{mock_dependencies, mock_env, mock_info},
}
};
#[test]
fn test_init_admin() {
let ref mut deps = mock_dependencies();
let admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
assert!(admin.is_none());
let admin = "admin";
admin::init(deps.as_mut(), Some(admin), &mock_info("Tio Macaco", &[])).unwrap();
let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
assert_eq!(stored_admin.unwrap(), admin);
}
#[test]
fn test_init_default_admin() {
let ref mut deps = mock_dependencies();
let admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
assert!(admin.is_none());
let admin = "admin";
admin::init(deps.as_mut(), None, &mock_info(admin, &[])).unwrap();
let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
assert_eq!(stored_admin.unwrap(), admin);
}
#[test]
fn test_change_invariants_prior_to_change() {
let ref mut deps = mock_dependencies();
let admin = "admin";
admin::init(deps.as_mut(), None, &mock_info(admin, &[])).unwrap();
let new_admin = "new_admin";
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info("What about me?", &[]),
Some(Mode::Immediate { new_admin: new_admin.into() })
).unwrap_err();
assert_unauthorized(&err);
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info("What about me?", &[]),
Some(Mode::TwoStep { new_admin: new_admin.into() })
).unwrap_err();
assert_unauthorized(&err);
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info("What about me?", &[]),
None
).unwrap_err();
assert_no_pending(&err);
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
None
).unwrap_err();
assert_no_pending(&err);
let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
assert_eq!(stored_admin.unwrap(), admin);
}
#[test]
fn test_change_admin_immediate() {
let ref mut deps = mock_dependencies();
let admin = "admin";
admin::init(deps.as_mut(), None, &mock_info(admin, &[])).unwrap();
let new_admin = "new_admin";
DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
Some(Mode::Immediate { new_admin: new_admin.into() })
).unwrap();
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
Some(Mode::Immediate { new_admin: new_admin.into() })
).unwrap_err();
assert_unauthorized(&err);
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
Some(Mode::TwoStep { new_admin: new_admin.into() })
).unwrap_err();
assert_unauthorized(&err);
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
None
).unwrap_err();
assert_no_pending(&err);
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(new_admin, &[]),
None
).unwrap_err();
assert_no_pending(&err);
let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
assert_eq!(stored_admin.unwrap(), new_admin);
}
#[test]
fn test_change_admin_two_step() {
let ref mut deps = mock_dependencies();
let admin = "admin";
admin::init(deps.as_mut(), None, &mock_info(admin, &[])).unwrap();
let new_admin = "new_admin";
let new_admin2 = "new_admin2";
DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
Some(Mode::TwoStep { new_admin: new_admin.into() })
).unwrap();
DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
Some(Mode::TwoStep { new_admin: new_admin2.into() })
).unwrap();
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(new_admin, &[]),
None
).unwrap_err();
assert_unauthorized(&err);
DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(new_admin2, &[]),
None
).unwrap();
let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
assert_eq!(stored_admin.unwrap(), new_admin2);
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
Some(Mode::TwoStep { new_admin: new_admin.into() })
).unwrap_err();
assert_unauthorized(&err);
let err = DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(admin, &[]),
Some(Mode::Immediate { new_admin: new_admin.into() })
).unwrap_err();
assert_unauthorized(&err);
DefaultImpl::change_admin(
deps.as_mut(),
mock_env(),
mock_info(new_admin2, &[]),
Some(Mode::Immediate { new_admin: new_admin.into() })
).unwrap();
let stored_admin = DefaultImpl::admin(deps.as_ref(), mock_env()).unwrap();
assert_eq!(stored_admin.unwrap(), new_admin);
}
fn assert_unauthorized(err: &StdError) {
match err {
StdError::GenericErr { msg } => assert_eq!(msg, "Unauthorized"),
_ => panic!("Expected \"StdError::GenericErr\"")
};
}
fn assert_no_pending(err: &StdError) {
match err {
StdError::GenericErr { msg } => assert_eq!(msg, "No address is currently expected to accept the admin role."),
_ => panic!("Expected \"StdError::GenericErr\"")
};
}
}