use abstract_sdk::{
base::{ExecuteEndpoint, Handler, IbcCallbackEndpoint, ModuleIbcEndpoint},
features::ModuleIdentification,
AbstractResponse, AccountVerification,
};
use abstract_std::{
account::state::ACCOUNT_MODULES,
adapter::{AdapterBaseMsg, AdapterExecuteMsg, AdapterRequestMsg, BaseExecuteMsg, ExecuteMsg},
objects::ownership::nested_admin::query_top_level_owner_addr,
};
use cosmwasm_std::{Addr, Deps, DepsMut, Env, MessageInfo, QuerierWrapper, Response, StdResult};
use schemars::JsonSchema;
use serde::Serialize;
use crate::{
error::AdapterError,
state::{AdapterContract, ContractError, MAXIMUM_AUTHORIZED_ADDRESSES},
AdapterResult,
};
impl<
Error: ContractError,
CustomInitMsg,
CustomExecMsg: Serialize + JsonSchema + AdapterExecuteMsg,
CustomQueryMsg,
SudoMsg,
> ExecuteEndpoint
for AdapterContract<Error, CustomInitMsg, CustomExecMsg, CustomQueryMsg, SudoMsg>
{
type ExecuteMsg = ExecuteMsg<CustomExecMsg>;
fn execute(
mut self,
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: Self::ExecuteMsg,
) -> Result<Response, Error> {
match msg {
ExecuteMsg::Module(request) => self.handle_app_msg(deps, env, info, request),
ExecuteMsg::Base(exec_msg) => self
.base_execute(deps, env, info, exec_msg)
.map_err(From::from),
ExecuteMsg::IbcCallback(msg) => self.ibc_callback(deps, env, info, msg),
ExecuteMsg::ModuleIbc(msg) => self.module_ibc(deps, env, info, msg),
}
}
}
fn is_top_level_owner(querier: &QuerierWrapper, account: Addr, sender: &Addr) -> StdResult<bool> {
let owner = query_top_level_owner_addr(querier, account)?;
Ok(owner == sender)
}
impl<Error: ContractError, CustomInitMsg, CustomExecMsg, CustomQueryMsg, SudoMsg>
AdapterContract<Error, CustomInitMsg, CustomExecMsg, CustomQueryMsg, SudoMsg>
{
fn base_execute(
&mut self,
deps: DepsMut,
env: Env,
info: MessageInfo,
message: BaseExecuteMsg,
) -> AdapterResult {
let BaseExecuteMsg {
account_address,
msg,
} = message;
let account_registry = self.account_registry(deps.as_ref())?;
let account = account_registry
.assert_is_account_admin(&env, &info.sender)
.map_err(|_| AdapterError::UnauthorizedAdapterRequest {
adapter: self.module_id().to_string(),
sender: info.sender.to_string(),
})
.or_else(|e| {
match account_address {
Some(requested_account) => {
let account_address = deps.api.addr_validate(&requested_account)?;
let account = account_registry.assert_is_account(&account_address)?;
if is_top_level_owner(&deps.querier, account.addr().clone(), &info.sender)
.unwrap_or(false)
{
Ok(account)
} else {
Err(AdapterError::UnauthorizedAdapterRequest {
adapter: self.module_id().to_string(),
sender: info.sender.to_string(),
})
}
}
None => Err(e),
}
})?;
self.target_account = Some(account);
match msg {
AdapterBaseMsg::UpdateAuthorizedAddresses { to_add, to_remove } => {
self.update_authorized_addresses(deps, info, to_add, to_remove)
}
}
}
fn handle_app_msg(
mut self,
deps: DepsMut,
env: Env,
info: MessageInfo,
request: AdapterRequestMsg<CustomExecMsg>,
) -> Result<Response, Error> {
let sender = &info.sender;
let unauthorized_sender = || AdapterError::UnauthorizedAddressAdapterRequest {
adapter: self.module_id().to_string(),
sender: sender.to_string(),
};
let account_registry = self.account_registry(deps.as_ref())?;
let account = match request.account_address {
Some(requested_account) => {
let account_address = deps.api.addr_validate(&requested_account)?;
let requested_core = account_registry.assert_is_account(&account_address)?;
if requested_core.addr() == sender {
requested_core
} else {
let authorized = self
.authorized_addresses
.load(deps.storage, account_address)
.unwrap_or_default();
if authorized.contains(sender)
|| is_top_level_owner(&deps.querier, requested_core.addr().clone(), sender)
.unwrap_or(false)
{
requested_core
} else {
return Err(unauthorized_sender().into());
}
}
}
None => account_registry
.assert_is_account(sender)
.map_err(|_| unauthorized_sender())?,
};
self.target_account = Some(account);
self.execute_handler()?(deps, env, info, self, request.request)
}
fn update_authorized_addresses(
&self,
deps: DepsMut,
info: MessageInfo,
to_add: Vec<String>,
to_remove: Vec<String>,
) -> AdapterResult {
let account = self.target_account.as_ref().unwrap();
let account_addr = account.addr().clone();
let mut authorized_addrs = self
.authorized_addresses
.may_load(deps.storage, account_addr.clone())?
.unwrap_or_default();
for authorized in to_add {
let authorized_addr = get_addr_from_module_id_or_addr(
deps.as_ref(),
info.sender.clone(),
authorized.clone(),
)?;
if authorized_addrs.contains(&authorized_addr) {
return Err(AdapterError::AuthorizedAddressOrModuleIdAlreadyPresent {
addr_or_module_id: authorized,
});
} else {
authorized_addrs.push(authorized_addr);
}
}
for deauthorized in to_remove {
let deauthorized_addr = get_addr_from_module_id_or_addr(
deps.as_ref(),
info.sender.clone(),
deauthorized.clone(),
)?;
if !authorized_addrs.contains(&deauthorized_addr) {
return Err(AdapterError::AuthorizedAddressOrModuleIdNotPresent {
addr_or_module_id: deauthorized,
});
} else {
authorized_addrs.retain(|addr| deauthorized_addr.ne(addr));
}
}
if authorized_addrs.len() > MAXIMUM_AUTHORIZED_ADDRESSES as usize {
return Err(AdapterError::TooManyAuthorizedAddresses {
max: MAXIMUM_AUTHORIZED_ADDRESSES,
});
}
self.authorized_addresses
.save(deps.storage, account_addr.clone(), &authorized_addrs)?;
Ok(self.custom_response(
"update_authorized_addresses",
vec![("account", account_addr.as_str())],
))
}
}
fn get_addr_from_module_id_or_addr(
deps: Deps,
account: Addr,
addr_or_module_id: String,
) -> Result<Addr, AdapterError> {
if let Ok(Some(addr)) = ACCOUNT_MODULES.query(&deps.querier, account, &addr_or_module_id) {
Ok(addr)
} else if let Ok(addr) = deps.api.addr_validate(addr_or_module_id.as_str()) {
Ok(addr)
} else {
Err(AdapterError::AuthorizedAddressOrModuleIdNotValid { addr_or_module_id })
}
}
#[cfg(test)]
mod tests {
use abstract_std::adapter;
use abstract_testing::prelude::*;
use cosmwasm_std::{testing::*, Addr, Storage};
use super::*;
use crate::mock::{mock_init, AdapterMockResult, MockError, MockExecMsg, MOCK_ADAPTER};
fn execute_as(
deps: &mut MockDeps,
sender: &Addr,
msg: ExecuteMsg<MockExecMsg>,
) -> Result<Response, MockError> {
let env = mock_env_validated(deps.api);
MOCK_ADAPTER.execute(deps.as_mut(), env, message_info(sender, &[]), msg)
}
fn base_execute_as(
deps: &mut MockDeps,
sender: &Addr,
msg: BaseExecuteMsg,
) -> Result<Response, MockError> {
execute_as(deps, sender, adapter::ExecuteMsg::Base(msg))
}
mod update_authorized_addresses {
use super::*;
use crate::mock::TEST_AUTHORIZED_ADDR;
fn load_test_account_authorized_addresses(
storage: &dyn Storage,
account_addr: &Addr,
) -> Vec<Addr> {
MOCK_ADAPTER
.authorized_addresses
.load(storage, account_addr.clone())
.unwrap()
}
#[coverage_helper::test]
fn authorize_address() -> AdapterMockResult {
let mut deps = mock_dependencies();
let account = test_account(deps.api);
deps.querier = abstract_mock_querier_builder(deps.api)
.account(&account, TEST_ACCOUNT_ID)
.set_account_admin_call_to(&account)
.build();
mock_init(&mut deps)?;
let msg = BaseExecuteMsg {
msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
to_add: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
to_remove: vec![],
},
account_address: None,
};
base_execute_as(&mut deps, account.addr(), msg)?;
let api = MOCK_ADAPTER;
assert!(!api.authorized_addresses.is_empty(&deps.storage));
let test_account_authorized_addrs =
load_test_account_authorized_addresses(&deps.storage, account.addr());
assert_eq!(test_account_authorized_addrs.len(), 1);
assert!(
test_account_authorized_addrs.contains(&deps.api.addr_make(TEST_AUTHORIZED_ADDR))
);
Ok(())
}
#[coverage_helper::test]
fn revoke_address_authorization() -> AdapterMockResult {
let mut deps = mock_dependencies();
let account = test_account(deps.api);
deps.querier = abstract_mock_querier_builder(deps.api)
.account(&account, TEST_ACCOUNT_ID)
.set_account_admin_call_to(&account)
.build();
mock_init(&mut deps)?;
let _api = MOCK_ADAPTER;
let msg = BaseExecuteMsg {
account_address: None,
msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
to_add: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
to_remove: vec![],
},
};
base_execute_as(&mut deps, account.addr(), msg)?;
let authorized_addrs =
load_test_account_authorized_addresses(&deps.storage, account.addr());
assert_eq!(authorized_addrs.len(), 1);
let msg = BaseExecuteMsg {
account_address: None,
msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
to_add: vec![],
to_remove: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
},
};
base_execute_as(&mut deps, account.addr(), msg)?;
let authorized_addrs =
load_test_account_authorized_addresses(&deps.storage, account.addr());
assert!(authorized_addrs.is_empty());
Ok(())
}
#[coverage_helper::test]
fn add_existing_authorized_address() -> AdapterMockResult {
let mut deps = mock_dependencies();
let account = test_account(deps.api);
deps.querier = abstract_mock_querier_builder(deps.api)
.account(&account, TEST_ACCOUNT_ID)
.set_account_admin_call_to(&account)
.build();
mock_init(&mut deps)?;
let msg = BaseExecuteMsg {
account_address: None,
msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
to_add: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
to_remove: vec![],
},
};
base_execute_as(&mut deps, account.addr(), msg)?;
let msg = BaseExecuteMsg {
account_address: None,
msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
to_add: vec![deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string()],
to_remove: vec![],
},
};
let res = base_execute_as(&mut deps, account.addr(), msg);
assert!(matches!(
res,
Err(MockError::Adapter(
AdapterError::AuthorizedAddressOrModuleIdAlreadyPresent {
addr_or_module_id: _test_authorized_address_string
}
))
));
Ok(())
}
#[coverage_helper::test]
fn add_module_id_authorized_address() -> AdapterMockResult {
let mut deps = mock_dependencies();
let account = test_account(deps.api);
deps.querier = abstract_mock_querier_builder(deps.api)
.account(&account, TEST_ACCOUNT_ID)
.set_account_admin_call_to(&account)
.build();
let abstr = AbstractMockAddrs::new(deps.api);
mock_init(&mut deps)?;
let _api = MOCK_ADAPTER;
let msg = BaseExecuteMsg {
account_address: None,
msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
to_add: vec![TEST_MODULE_ID.into()],
to_remove: vec![],
},
};
base_execute_as(&mut deps, account.addr(), msg)?;
let authorized_addrs =
load_test_account_authorized_addresses(&deps.storage, account.addr());
assert_eq!(authorized_addrs.len(), 1);
assert_eq!(
authorized_addrs[0].to_string(),
abstr.module_address.to_string()
);
Ok(())
}
#[coverage_helper::test]
fn remove_authorized_address_dne() -> AdapterMockResult {
let mut deps = mock_dependencies();
let account = test_account(deps.api);
deps.querier = abstract_mock_querier_builder(deps.api)
.account(&account, TEST_ACCOUNT_ID)
.set_account_admin_call_to(&account)
.build();
mock_init(&mut deps)?;
let test_authorized_address_string =
deps.api.addr_make(TEST_AUTHORIZED_ADDR).to_string();
let _api = MOCK_ADAPTER;
let msg = BaseExecuteMsg {
account_address: None,
msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
to_add: vec![],
to_remove: vec![test_authorized_address_string.clone()],
},
};
let res = base_execute_as(&mut deps, account.addr(), msg);
assert_eq!(
res,
Err(MockError::Adapter(
AdapterError::AuthorizedAddressOrModuleIdNotPresent {
addr_or_module_id: test_authorized_address_string
}
))
);
Ok(())
}
}
mod execute_app {
use super::*;
use crate::mock::TEST_AUTHORIZED_ADDR;
use abstract_std::{
objects::{account::AccountTrace, AccountId},
registry::Account,
};
use cosmwasm_std::OwnedDeps;
fn setup_with_authorized_addresses(
deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>,
authorized: Vec<&str>,
) {
mock_init(deps).unwrap();
let msg = BaseExecuteMsg {
account_address: None,
msg: AdapterBaseMsg::UpdateAuthorizedAddresses {
to_add: authorized
.into_iter()
.map(|addr| deps.api.addr_make(addr).to_string())
.collect(),
to_remove: vec![],
},
};
let account = test_account(deps.api);
base_execute_as(deps, account.addr(), msg).unwrap();
}
#[coverage_helper::test]
fn unauthorized_addresses_are_unauthorized() {
let mut deps = mock_dependencies();
deps.querier = MockQuerierBuilder::new(deps.api)
.account(&test_account(deps.api), TEST_ACCOUNT_ID)
.set_account_admin_call_to(&test_account(deps.api))
.build();
setup_with_authorized_addresses(&mut deps, vec![]);
let msg = ExecuteMsg::Module(AdapterRequestMsg {
account_address: None,
request: MockExecMsg {},
});
let unauthorized = deps.api.addr_make("someoone");
let res = execute_as(&mut deps, &unauthorized, msg);
assert_unauthorized(res);
}
fn assert_unauthorized(res: Result<Response, MockError>) {
assert!(matches!(
res,
Err(MockError::Adapter(
AdapterError::UnauthorizedAddressAdapterRequest {
sender: _unauthorized,
..
}
))
));
}
#[coverage_helper::test]
fn executing_as_account_account_is_allowed() {
let mut deps = mock_dependencies();
let account = test_account(deps.api);
deps.querier = MockQuerierBuilder::new(deps.api)
.account(&account, TEST_ACCOUNT_ID)
.set_account_admin_call_to(&account)
.build();
setup_with_authorized_addresses(&mut deps, vec![]);
let msg = ExecuteMsg::Module(AdapterRequestMsg {
account_address: None,
request: MockExecMsg {},
});
let res = execute_as(&mut deps, account.addr(), msg);
assert!(res.is_ok());
}
#[coverage_helper::test]
fn executing_as_authorized_address_not_allowed_without_account() {
let mut deps = mock_dependencies();
deps.querier = MockQuerierBuilder::new(deps.api)
.account(&test_account(deps.api), TEST_ACCOUNT_ID)
.set_account_admin_call_to(&test_account(deps.api))
.build();
setup_with_authorized_addresses(&mut deps, vec![TEST_AUTHORIZED_ADDR]);
let msg = ExecuteMsg::Module(AdapterRequestMsg {
account_address: None,
request: MockExecMsg {},
});
let authorized = deps.api.addr_make(TEST_AUTHORIZED_ADDR);
let res = execute_as(&mut deps, &authorized, msg);
assert_unauthorized(res);
}
#[coverage_helper::test]
fn executing_as_authorized_address_is_allowed_via_account() {
let mut deps = mock_dependencies();
let account = test_account(deps.api);
deps.querier = MockQuerierBuilder::new(deps.api)
.account(&account, TEST_ACCOUNT_ID)
.set_account_admin_call_to(&account)
.build();
setup_with_authorized_addresses(&mut deps, vec![TEST_AUTHORIZED_ADDR]);
let msg = ExecuteMsg::Module(AdapterRequestMsg {
account_address: Some(account.addr().to_string()),
request: MockExecMsg {},
});
let authorized = deps.api.addr_make(TEST_AUTHORIZED_ADDR);
let res = execute_as(&mut deps, &authorized, msg);
assert!(res.is_ok());
}
#[coverage_helper::test]
fn executing_as_authorized_address_on_diff_account_should_err() {
let mut deps = mock_dependencies();
let account = test_account(deps.api);
let another_account = Account::new(deps.api.addr_make("some_other_account"));
deps.querier = MockQuerierBuilder::new(deps.api)
.account(&account, TEST_ACCOUNT_ID)
.account(
&another_account,
AccountId::new(69420u32, AccountTrace::Local).unwrap(),
)
.set_account_admin_call_to(&account)
.build();
setup_with_authorized_addresses(&mut deps, vec![TEST_AUTHORIZED_ADDR]);
let msg = ExecuteMsg::Module(AdapterRequestMsg {
account_address: Some(another_account.addr().to_string()),
request: MockExecMsg {},
});
let authorized = deps.api.addr_make(TEST_AUTHORIZED_ADDR);
let res = execute_as(&mut deps, &authorized, msg);
assert_unauthorized(res);
}
}
}