Skip to content

Commit d538013

Browse files
Kayanskiadairrr
andauthored
Audit Fixes 1: XION Authentication fix and test (#546)
Co-authored-by: Interchain Adair <32375605+adairrr@users.noreply.github.com>
1 parent a647229 commit d538013

File tree

6 files changed

+230
-33
lines changed

6 files changed

+230
-33
lines changed

framework/contracts/account/src/contract.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -269,24 +269,24 @@ pub fn execute(mut deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg)
269269

270270
match msg {
271271
// ## Execution ##
272-
ExecuteMsg::Execute { msgs } => execute_msgs(deps, &info.sender, msgs),
272+
ExecuteMsg::Execute { msgs } => execute_msgs(deps, env, &info.sender, msgs),
273273
ExecuteMsg::AdminExecute { addr, msg } => {
274274
let addr = deps.api.addr_validate(&addr)?;
275275
admin_execute(deps, info, addr, msg)
276276
}
277277
ExecuteMsg::ExecuteWithData { msg } => {
278-
execute_msgs_with_data(deps, &info.sender, msg)
278+
execute_msgs_with_data(deps, env, &info.sender, msg)
279279
}
280280
ExecuteMsg::ExecuteOnModule {
281281
module_id,
282282
exec_msg,
283283
funds,
284-
} => execute_on_module(deps, info, module_id, exec_msg, funds),
284+
} => execute_on_module(deps, env, info, module_id, exec_msg, funds),
285285
ExecuteMsg::AdminExecuteOnModule { module_id, msg } => {
286286
admin_execute_on_module(deps, info, module_id, msg)
287287
}
288288
ExecuteMsg::IcaAction { action_query_msg } => {
289-
ica_action(deps, info, action_query_msg)
289+
ica_action(deps, env, info, action_query_msg)
290290
}
291291

292292
// ## Configuration ##
@@ -354,10 +354,9 @@ pub fn execute(mut deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg)
354354
unreachable!("Update status case is reached above")
355355
}
356356
ExecuteMsg::AddAuthMethod { add_authenticator } => {
357-
add_auth_method(deps, env, add_authenticator)
357+
add_auth_method(deps, env, info, add_authenticator)
358358
}
359-
#[allow(unused)]
360-
ExecuteMsg::RemoveAuthMethod { id } => remove_auth_method(deps, env, id),
359+
ExecuteMsg::RemoveAuthMethod { id } => remove_auth_method(deps, env, info, id),
361360
}
362361
}
363362
}?;

framework/contracts/account/src/execution.rs

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,15 @@ use crate::{
1616
};
1717

1818
/// Check that sender either whitelisted or governance
19-
pub(crate) fn assert_whitelisted_or_owner(deps: &mut DepsMut, sender: &Addr) -> AccountResult<()> {
20-
#[cfg(feature = "xion")]
21-
{
22-
if let Some(is_admin) = crate::state::AUTH_ADMIN.may_load(deps.storage)? {
23-
// Clear auth if it was set
24-
crate::state::AUTH_ADMIN.remove(deps.storage);
25-
if is_admin {
26-
return Ok(());
27-
}
28-
}
29-
}
19+
pub(crate) fn assert_whitelisted_owner_or_self(
20+
deps: &mut DepsMut,
21+
env: &Env,
22+
sender: &Addr,
23+
) -> AccountResult<()> {
3024
let whitelisted_modules = WHITELISTED_MODULES.load(deps.storage)?;
3125
if whitelisted_modules.0.contains(sender)
3226
|| ownership::assert_nested_owner(deps.storage, &deps.querier, sender).is_ok()
27+
|| sender == env.contract.address
3328
{
3429
Ok(())
3530
} else {
@@ -41,10 +36,11 @@ pub(crate) fn assert_whitelisted_or_owner(deps: &mut DepsMut, sender: &Addr) ->
4136
/// Permission: Module
4237
pub fn execute_msgs(
4338
mut deps: DepsMut,
39+
env: Env,
4440
msg_sender: &Addr,
4541
msgs: Vec<CosmosMsg<Empty>>,
4642
) -> AccountResult {
47-
assert_whitelisted_or_owner(&mut deps, msg_sender)?;
43+
assert_whitelisted_owner_or_self(&mut deps, &env, msg_sender)?;
4844

4945
Ok(AccountResponse::action("execute_module_action").add_messages(msgs))
5046
}
@@ -53,10 +49,11 @@ pub fn execute_msgs(
5349
/// Permission: Module
5450
pub fn execute_msgs_with_data(
5551
mut deps: DepsMut,
52+
env: Env,
5653
msg_sender: &Addr,
5754
msg: CosmosMsg<Empty>,
5855
) -> AccountResult {
59-
assert_whitelisted_or_owner(&mut deps, msg_sender)?;
56+
assert_whitelisted_owner_or_self(&mut deps, &env, msg_sender)?;
6057

6158
let submsg = SubMsg::reply_on_success(msg, FORWARD_RESPONSE_REPLY_ID);
6259

@@ -67,6 +64,7 @@ pub fn execute_msgs_with_data(
6764
/// This is a simple wrapper around [`ExecuteMsg::Execute`](abstract_std::account::ExecuteMsg::Execute).
6865
pub fn execute_on_module(
6966
deps: DepsMut,
67+
env: Env,
7068
info: MessageInfo,
7169
module_id: String,
7270
exec_msg: Binary,
@@ -75,6 +73,7 @@ pub fn execute_on_module(
7573
let module_addr = load_module_addr(deps.storage, &module_id)?;
7674
execute_msgs(
7775
deps,
76+
env,
7877
&info.sender,
7978
vec![CosmosMsg::Wasm(WasmMsg::Execute {
8079
contract_addr: module_addr.into(),
@@ -119,10 +118,12 @@ pub fn admin_execute_on_module(
119118
pub fn add_auth_method(
120119
_deps: DepsMut,
121120
_env: Env,
121+
_info: MessageInfo,
122122
#[allow(unused_mut)] mut _auth: crate::msg::Authenticator,
123123
) -> AccountResult {
124124
#[cfg(feature = "xion")]
125125
{
126+
ownership::assert_nested_owner(_deps.storage, &_deps.querier, &_info.sender)?;
126127
abstract_xion::execute::add_auth_method(_deps, &_env, &mut _auth).map_err(Into::into)
127128
}
128129
#[cfg(not(feature = "xion"))]
@@ -131,9 +132,10 @@ pub fn add_auth_method(
131132
}
132133
}
133134

134-
pub fn remove_auth_method(_deps: DepsMut, _env: Env, _id: u8) -> AccountResult {
135+
pub fn remove_auth_method(_deps: DepsMut, _env: Env, _info: MessageInfo, _id: u8) -> AccountResult {
135136
#[cfg(feature = "xion")]
136137
{
138+
ownership::assert_nested_owner(_deps.storage, &_deps.querier, &_info.sender)?;
137139
abstract_xion::execute::remove_auth_method(_deps, _env, _id).map_err(Into::into)
138140
}
139141
#[cfg(not(feature = "xion"))]
@@ -149,8 +151,13 @@ pub fn remove_auth_method(_deps: DepsMut, _env: Env, _id: u8) -> AccountResult {
149151
/// It then fires a smart-query on that address of type [`QueryMsg::IcaAction`](abstract_ica::msg::QueryMsg).
150152
///
151153
/// The resulting `Vec<CosmosMsg>` are then executed on the account contract.
152-
pub fn ica_action(mut deps: DepsMut, msg_info: MessageInfo, action_query: Binary) -> AccountResult {
153-
assert_whitelisted_or_owner(&mut deps, &msg_info.sender)?;
154+
pub fn ica_action(
155+
mut deps: DepsMut,
156+
env: Env,
157+
msg_info: MessageInfo,
158+
action_query: Binary,
159+
) -> AccountResult {
160+
assert_whitelisted_owner_or_self(&mut deps, &env, &msg_info.sender)?;
154161

155162
let ica_client_address = ACCOUNT_MODULES
156163
.may_load(deps.storage, ICA_CLIENT)?
@@ -176,11 +183,45 @@ mod test {
176183
use crate::contract::execute;
177184
use crate::error::AccountError;
178185
use crate::test_common::mock_init;
186+
use abstract_sdk::namespaces::OWNERSHIP_STORAGE_KEY;
179187
use abstract_std::account::{state::*, *};
188+
use abstract_std::objects::gov_type::GovernanceDetails;
189+
use abstract_std::objects::ownership::Ownership;
180190
use abstract_std::{account, IBC_CLIENT};
181191
use abstract_testing::prelude::*;
182-
use cosmwasm_std::testing::*;
183192
use cosmwasm_std::{coins, CosmosMsg, SubMsg};
193+
use cosmwasm_std::{testing::*, Addr};
194+
use cw_storage_plus::Item;
195+
196+
#[coverage_helper::test]
197+
fn abstract_account_can_execute_on_itself() -> anyhow::Result<()> {
198+
let mut deps = mock_dependencies();
199+
deps.querier = abstract_mock_querier(deps.api);
200+
mock_init(&mut deps)?;
201+
202+
let env = mock_env_validated(deps.api);
203+
// We set the contract as owner.
204+
// We can't make it through execute msgs, because of XION signatures are too messy to reproduce in tests
205+
let ownership = Ownership {
206+
owner: GovernanceDetails::AbstractAccount {
207+
address: env.contract.address.clone(),
208+
}
209+
.verify(deps.as_ref())?,
210+
pending_owner: None,
211+
pending_expiry: None,
212+
};
213+
const OWNERSHIP: Item<Ownership<Addr>> = Item::new(OWNERSHIP_STORAGE_KEY);
214+
OWNERSHIP.save(deps.as_mut().storage, &ownership)?;
215+
216+
// Module calls nested admin calls on account, making it admin
217+
let info = message_info(&env.contract.address, &[]);
218+
219+
let msg = ExecuteMsg::Execute { msgs: vec![] };
220+
221+
execute(deps.as_mut(), env.clone(), info, msg).unwrap();
222+
223+
Ok(())
224+
}
184225

185226
mod execute_action {
186227

framework/contracts/account/src/modules.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use abstract_std::{
1010
module::{Module, ModuleInfo, ModuleVersion},
1111
module_factory::ModuleFactoryContract,
1212
module_reference::ModuleReference,
13-
ownership::{self},
13+
ownership,
1414
registry::RegistryContract,
1515
salt::generate_instantiate_salt,
1616
storage_namespaces,
@@ -349,7 +349,6 @@ mod tests {
349349
use abstract_std::objects::dependency::Dependency;
350350
use abstract_testing::prelude::*;
351351
use cosmwasm_std::{testing::*, Addr, Order, StdError, Storage};
352-
use ownership::GovOwnershipError;
353352

354353
fn load_account_modules(storage: &dyn Storage) -> Result<Vec<(String, Addr)>, StdError> {
355354
ACCOUNT_MODULES
@@ -384,6 +383,8 @@ mod tests {
384383
}
385384

386385
mod update_module_addresses {
386+
use abstract_std::objects::ownership::GovOwnershipError;
387+
387388
use super::*;
388389

389390
#[coverage_helper::test]

framework/contracts/account/src/modules/migration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use abstract_std::{
88
dependency::Dependency,
99
module::ModuleInfo,
1010
module_reference::ModuleReference,
11-
ownership::{self},
11+
ownership,
1212
registry::{RegistryContract, RegistryError},
1313
storage_namespaces,
1414
},

framework/contracts/account/tests/xion.rs

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
11
#![cfg(feature = "xion")]
22

3+
use abstract_account::contract::execute;
34
use abstract_account::contract::instantiate;
5+
use abstract_account::contract::AccountResult;
6+
use abstract_account::error::AccountError;
7+
use abstract_account::msg::ExecuteMsg;
8+
use abstract_account::state::AUTH_ADMIN;
9+
use abstract_std::account;
410
use abstract_std::account::InstantiateMsg;
11+
use abstract_std::account::InternalConfigAction;
12+
use abstract_std::objects::ownership::GovOwnershipError;
13+
use abstract_std::objects::ownership::GovernanceDetails;
14+
use abstract_std::objects::ownership::Ownership;
15+
use abstract_std::objects::storage_namespaces::OWNERSHIP_STORAGE_KEY;
16+
use abstract_std::objects::AccountId;
17+
use abstract_std::objects::AccountTrace;
518
use abstract_std::registry::state::LOCAL_ACCOUNT_SEQUENCE;
19+
use abstract_testing::abstract_mock_querier;
620
use abstract_testing::abstract_mock_querier_builder;
21+
use abstract_testing::mock_env_validated;
722
use abstract_testing::prelude::AbstractMockAddrs;
23+
use abstract_testing::prelude::*;
824
use abstract_xion::testing::util;
925
use abstract_xion::testing::wrap_message;
1026
use base64::{engine::general_purpose, Engine as _};
11-
use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env};
12-
use cosmwasm_std::{Addr, Api, Binary};
27+
use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env, MockApi};
28+
use cosmwasm_std::CosmosMsg;
29+
use cosmwasm_std::Response;
30+
use cosmwasm_std::{wasm_execute, Addr, Api, Binary, DepsMut, Empty, Env, OwnedDeps, WasmMsg};
31+
use cw_storage_plus::Item;
1332

1433
#[test]
1534
fn test_derive_addr() {
@@ -123,3 +142,116 @@ fn test_init_sign_arb() {
123142
)
124143
.unwrap();
125144
}
145+
146+
/// Initialize the account with the test owner as the owner
147+
pub(crate) fn mock_init(
148+
deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier, Empty>,
149+
) -> AccountResult {
150+
let abstr = AbstractMockAddrs::new(deps.api);
151+
152+
let info = message_info(&abstr.owner, &[]);
153+
let env = mock_env_validated(deps.api);
154+
155+
abstract_account::contract::instantiate(
156+
deps.as_mut(),
157+
env,
158+
info,
159+
account::InstantiateMsg {
160+
code_id: 1,
161+
account_id: Some(AccountId::new(1, AccountTrace::Local).unwrap()),
162+
owner: GovernanceDetails::Monarchy {
163+
monarch: abstr.owner.to_string(),
164+
},
165+
namespace: None,
166+
name: Some("test".to_string()),
167+
description: None,
168+
link: None,
169+
install_modules: vec![],
170+
authenticator: None,
171+
},
172+
)
173+
}
174+
175+
const OWNERSHIP: Item<Ownership<Addr>> = Item::new(OWNERSHIP_STORAGE_KEY);
176+
177+
#[test]
178+
fn xion_account_auth_itself() -> anyhow::Result<()> {
179+
let mut deps = mock_dependencies();
180+
deps.querier = abstract_mock_querier(deps.api);
181+
mock_init(&mut deps)?;
182+
183+
let env = mock_env_validated(deps.api);
184+
// We set the contract as owner.
185+
// We can't make it through execute msgs, because of XION signatures are too messy to reproduce in tests
186+
let ownership = Ownership {
187+
owner: GovernanceDetails::AbstractAccount {
188+
address: env.contract.address.clone(),
189+
}
190+
.verify(deps.as_ref())?,
191+
pending_owner: None,
192+
pending_expiry: None,
193+
};
194+
OWNERSHIP.save(deps.as_mut().storage, &ownership)?;
195+
196+
let whitelisted = deps.api.addr_make("whitelisted");
197+
let not_whitelisted_yet = deps.api.addr_make("not_whitelisted");
198+
199+
// We whitelist a module
200+
AUTH_ADMIN.save(deps.as_mut().storage, &true)?;
201+
execute(
202+
deps.as_mut(),
203+
env.clone(),
204+
message_info(&env.contract.address, &[]),
205+
ExecuteMsg::UpdateInternalConfig(InternalConfigAction::UpdateWhitelist {
206+
to_add: vec![whitelisted.to_string()],
207+
to_remove: vec![],
208+
}),
209+
)?;
210+
211+
// Module calls nested admin calls on account, making it admin
212+
let info = message_info(&whitelisted, &[]);
213+
214+
let msg = ExecuteMsg::Execute {
215+
msgs: vec![wasm_execute(
216+
&env.contract.address,
217+
&ExecuteMsg::Execute {
218+
msgs: vec![wasm_execute(
219+
&env.contract.address,
220+
&ExecuteMsg::UpdateInternalConfig(InternalConfigAction::UpdateWhitelist {
221+
to_add: vec![not_whitelisted_yet.to_string()],
222+
to_remove: vec![],
223+
}),
224+
vec![],
225+
)?
226+
.into()],
227+
},
228+
vec![],
229+
)?
230+
.into()],
231+
};
232+
233+
let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
234+
235+
// Execute all messages
236+
let res = execute_from_res(deps.as_mut(), &env, res).unwrap();
237+
// This should error because this action is triggered at the top by an external module
238+
let res = execute_from_res(deps.as_mut(), &env, res).unwrap_err();
239+
240+
assert_eq!(res, AccountError::Ownership(GovOwnershipError::NotOwner));
241+
Ok(())
242+
}
243+
244+
fn execute_from_res(deps: DepsMut, env: &Env, res: Response) -> AccountResult<Response> {
245+
// Execute all messages
246+
let info = message_info(&env.contract.address, &[]);
247+
if let CosmosMsg::Wasm(WasmMsg::Execute {
248+
contract_addr: _,
249+
msg,
250+
funds: _,
251+
}) = res.messages[0].msg.clone()
252+
{
253+
execute(deps, env.clone(), info, from_json(&msg)?).map_err(Into::into)
254+
} else {
255+
panic!("Wrong message received");
256+
}
257+
}

0 commit comments

Comments
 (0)