Skip to content

Commit

Permalink
refactored chain manager
Browse files Browse the repository at this point in the history
  • Loading branch information
swelf19 committed Apr 18, 2024
1 parent c6411a8 commit 04e61fe
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 464 deletions.
245 changes: 69 additions & 176 deletions contracts/dao/neutron-chain-manager/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
use crate::cron_module_param_types::{
MsgUpdateParamsCron, ParamsRequestCron, ParamsResponseCron, MSG_TYPE_UPDATE_PARAMS_CRON,
PARAMS_QUERY_PATH_CRON,
ParamsRequestCron, ParamsResponseCron, PARAMS_QUERY_PATH_CRON,
};
use crate::permission::{match_permission_type, Permission, PermissionType, Validator};
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
to_json_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
to_json_binary, Addr, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, StdError,
StdResult,
};
use cw2::set_contract_version;
use neutron_sdk::bindings::msg::{AdminProposal, NeutronMsg, ProposalExecuteMessage};
use neutron_sdk::bindings::msg::NeutronMsg;
use neutron_sdk::proto_types::neutron::cron::QueryParamsRequest;
use neutron_sdk::stargate::aux::make_stargate_query;

use crate::error::ContractError;
use crate::msg::{
ExecuteMsg, InstantiateMsg, MigrateMsg, ProposalExecuteMessageJSON, QueryMsg, Strategy,
};
use crate::state::STRATEGIES;
use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use crate::state::PERMISSIONS;

pub(crate) const CONTRACT_NAME: &str = "crates.io:neutron-chain-manager";
pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
Expand All @@ -30,19 +29,15 @@ pub fn instantiate(
) -> Result<Response, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

if let Strategy::AllowOnly(_) = msg.initial_strategy {
return Err(ContractError::InvalidInitialStrategy {});
}

STRATEGIES.save(
PERMISSIONS.save(
deps.storage,
msg.initial_strategy_address.clone(),
&msg.initial_strategy,
(msg.initial_address.clone(), PermissionType::AllowAll),
&Permission::AllowAll,
)?;

Ok(Response::new()
.add_attribute("action", "instantiate")
.add_attribute("init_allow_all_address", msg.initial_strategy_address))
.add_attribute("init_allow_all_address", msg.initial_address))
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand All @@ -53,29 +48,27 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response<NeutronMsg>, ContractError> {
match msg {
ExecuteMsg::AddStrategy { address, strategy } => {
execute_add_strategy(deps, info, address, strategy)
}
ExecuteMsg::RemoveStrategy { address } => execute_remove_strategy(deps, info, address),
ExecuteMsg::AddPermissions {
address,
permissions,
} => execute_add_permissions(deps, info, address, permissions),
ExecuteMsg::RemovePermissions { address } => execute_remove_strategy(deps, info, address),
ExecuteMsg::ExecuteMessages { messages } => execute_execute_messages(deps, info, messages),
}
}

pub fn execute_add_strategy(
pub fn execute_add_permissions(
deps: DepsMut,
info: MessageInfo,
address: Addr,
strategy: Strategy,
permissions: Vec<Permission>,
) -> Result<Response<NeutronMsg>, ContractError> {
is_authorized(deps.as_ref(), info.sender.clone())?;

// We add the new strategy, and then we check that it did not replace
// the only existing ALLOW_ALL strategy.
STRATEGIES.save(deps.storage, address.clone(), &strategy)?;
if let Strategy::AllowOnly(_) = strategy {
if no_admins_left(deps.as_ref())? {
return Err(ContractError::InvalidDemotion {});
}
for p in &permissions {
PERMISSIONS.save(deps.storage, (address.clone(), p.to_owned().into()), p)?;
}

Ok(Response::new()
Expand All @@ -90,9 +83,16 @@ pub fn execute_remove_strategy(
) -> Result<Response<NeutronMsg>, ContractError> {
is_authorized(deps.as_ref(), info.sender.clone())?;

// First we remove the strategy, then we check that it was not the only
// ALLOW_ALL strategy we had.
STRATEGIES.remove(deps.storage, address.clone());
// First we remove all the permissions, then we check that it was not the only
// ALLOW_ALL permission we had.
let permission_types = PERMISSIONS
.prefix(address.clone())
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<Result<Vec<PermissionType>, StdError>>()?;
for permission in permission_types {
PERMISSIONS.remove(deps.storage, (address.clone(), permission))
}

if no_admins_left(deps.as_ref())? {
return Err(ContractError::InvalidDemotion {});
}
Expand All @@ -107,165 +107,54 @@ pub fn execute_execute_messages(
info: MessageInfo,
messages: Vec<CosmosMsg<NeutronMsg>>,
) -> Result<Response<NeutronMsg>, ContractError> {
// If the sender doesn't have a strategy associated with them, abort immediately.
if !STRATEGIES.has(deps.storage, info.sender.clone()) {
return Err(ContractError::Unauthorized {});
}

let response = Response::new()
.add_attribute("action", "execute_execute_messages")
.add_attribute("address", info.sender.clone());

let strategy = STRATEGIES.load(deps.storage, info.sender)?;
match strategy {
Strategy::AllowAll => Ok(response
// check the sender is AllowAll account
if PERMISSIONS.has(
deps.storage,
(info.sender.clone(), PermissionType::AllowAll),
) {
return Ok(response
.add_attribute("strategy", "allow_all")
.add_messages(messages)),
Strategy::AllowOnly(_) => {
check_allow_only_permissions(deps.as_ref(), strategy.clone(), messages.clone())?;
Ok(response
.add_attribute("strategy", "allow_only")
.add_messages(messages))
}
.add_messages(messages));
};

// сопоставляем космос сообщения с типами требуемых разрешений
let permissions_types: Vec<(PermissionType, Validator)> = messages
.iter()
.map(|msg| match_permission_type(msg))
.collect::<Result<Vec<(PermissionType, Validator)>, ContractError>>()?;

for (p, v) in permissions_types {
let permission = PERMISSIONS.load(deps.storage, (info.sender.clone(), p))?;
v.validate(deps.as_ref(), permission)?
}

Ok(response
.add_attribute("strategy", "allow_only")
.add_messages(messages))
}

fn is_authorized(deps: Deps, address: Addr) -> Result<(), ContractError> {
match STRATEGIES.load(deps.storage, address) {
Ok(Strategy::AllowAll) => Ok(()),
_ => Err(ContractError::Unauthorized {}),
if PERMISSIONS.has(deps.storage, (address, PermissionType::AllowAll)) {
Ok(())
} else {
Err(ContractError::Unauthorized {})
}
}

/// This function returns true if there is no more allow_all strategies left.
fn no_admins_left(deps: Deps) -> Result<bool, ContractError> {
let not_found: bool = !STRATEGIES
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<Result<Vec<(Addr, Strategy)>, _>>()?
let not_found: bool = !PERMISSIONS
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<Result<Vec<(Addr, PermissionType)>, _>>()?
.into_iter()
.any(|(_, strategy)| matches!(strategy, Strategy::AllowAll));

.any(|(_, perm_type)| perm_type == PermissionType::AllowAll);
Ok(not_found)
}

/// For every message, check whether we have the permission to execute it.
/// Any missing permission aborts the execution. Trying to execute any
/// unknown message aborts the execution.
fn check_allow_only_permissions(
deps: Deps,
strategy: Strategy,
messages: Vec<CosmosMsg<NeutronMsg>>,
) -> Result<(), ContractError> {
for msg in messages.clone() {
if let CosmosMsg::Custom(neutron_msg) = msg {
check_neutron_msg(deps, strategy.clone(), neutron_msg)?
} else {
return Err(ContractError::Unauthorized {});
}
}

Ok(())
}

fn check_neutron_msg(
deps: Deps,
strategy: Strategy,
neutron_msg: NeutronMsg,
) -> Result<(), ContractError> {
match neutron_msg {
NeutronMsg::AddSchedule { .. } => {
if !strategy.has_cron_add_schedule_permission() {
return Err(ContractError::Unauthorized {});
}
}
NeutronMsg::RemoveSchedule { name: _ } => {
if !strategy.has_cron_remove_schedule_permission() {
return Err(ContractError::Unauthorized {});
}
}
NeutronMsg::SubmitAdminProposal { admin_proposal } => {
check_submit_admin_proposal_message(deps, strategy, admin_proposal)?;
}
_ => {
return Err(ContractError::Unauthorized {});
}
}

Ok(())
}

fn check_submit_admin_proposal_message(
deps: Deps,
strategy: Strategy,
proposal: AdminProposal,
) -> Result<(), ContractError> {
match proposal {
AdminProposal::ParamChangeProposal(proposal) => {
for param_change in proposal.param_changes {
if !strategy.has_param_change_permission(param_change) {
return Err(ContractError::Unauthorized {});
}
}
}
AdminProposal::ProposalExecuteMessage(proposal) => {
check_proposal_execute_message(deps, strategy.clone(), proposal)?;
}
_ => {
return Err(ContractError::Unauthorized {});
}
}

Ok(())
}

/// Processes ProposalExecuteMessage messages. Message type has to be checked
/// as a string; after that, you can parse the JSON payload into a specific
/// message.
fn check_proposal_execute_message(
deps: Deps,
strategy: Strategy,
proposal: ProposalExecuteMessage,
) -> Result<(), ContractError> {
let typed_proposal: ProposalExecuteMessageJSON =
serde_json_wasm::from_str(proposal.message.as_str())?;

if typed_proposal.type_field.as_str() == MSG_TYPE_UPDATE_PARAMS_CRON {
check_cron_update_msg_params(deps, strategy, proposal)?;
}

Ok(())
}
/// Checks that the strategy owner is authorised to change the parameters of the
/// cron module. We query the current values for each parameter & compare them to
/// the values in the proposal; all modifications must be allowed by the strategy.
fn check_cron_update_msg_params(
deps: Deps,
strategy: Strategy,
proposal: ProposalExecuteMessage,
) -> Result<(), ContractError> {
let msg_update_params: MsgUpdateParamsCron =
serde_json_wasm::from_str(proposal.message.as_str())?;

let cron_update_param_permission = strategy
.get_cron_update_param_permission()
.ok_or(ContractError::Unauthorized {})?;

let cron_params = get_cron_params(deps, ParamsRequestCron {})?;
if cron_params.params.limit != msg_update_params.params.limit
&& !cron_update_param_permission.limit
{
return Err(ContractError::Unauthorized {});
}

if cron_params.params.security_address != msg_update_params.params.security_address
&& !cron_update_param_permission.security_address
{
return Err(ContractError::Unauthorized {});
}

Ok(())
}

/// Queries the parameters of the cron module.
pub fn get_cron_params(deps: Deps, req: ParamsRequestCron) -> StdResult<ParamsResponseCron> {
make_stargate_query(deps, PARAMS_QUERY_PATH_CRON, QueryParamsRequest::from(req))
Expand All @@ -274,17 +163,21 @@ pub fn get_cron_params(deps: Deps, req: ParamsRequestCron) -> StdResult<ParamsRe
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Strategies {} => to_json_binary(&query_strategies(deps)?),
QueryMsg::Permissions {} => to_json_binary(&query_strategies(deps)?),
}
}

/// No pagination is added because it's unlikely that there is going
/// to be more than 10 strategies.
pub fn query_strategies(deps: Deps) -> StdResult<Vec<(Addr, Strategy)>> {
let all_strategies: Vec<(Addr, Strategy)> = STRATEGIES
pub fn query_strategies(deps: Deps) -> StdResult<Vec<(Addr, Permission)>> {
// TODO group by addresses
let all_permissions: Vec<(Addr, Permission)> = PERMISSIONS
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.collect::<Result<Vec<(Addr, Strategy)>, _>>()?;
Ok(all_strategies)
.collect::<Result<Vec<((Addr, PermissionType), Permission)>, _>>()?
.into_iter()
.map(|((a, _), p)| return (a, p))
.collect();
Ok(all_permissions)
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand Down
6 changes: 3 additions & 3 deletions contracts/dao/neutron-chain-manager/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),

#[error("Initial strategy of a wrong type was submitted")]
InvalidInitialStrategy {},

#[error("Unauthorized")]
Unauthorized {},

#[error("PermissionType not found: {0}")]
PermissionTypeNotFound(String),

// This error is returned when you try to remove the only existing
// ALLOW_ALL strategy.
#[error("An invalid demotion was attempted")]
Expand Down
1 change: 1 addition & 0 deletions contracts/dao/neutron-chain-manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub mod state;
mod cron_module_param_types;
#[cfg(test)]
mod testing;
mod permission;
Loading

0 comments on commit 04e61fe

Please sign in to comment.