From 97356d78f8aaa1eb0d731886fb793de508f5b930 Mon Sep 17 00:00:00 2001 From: peartes Date: Tue, 17 Dec 2024 16:00:36 +0100 Subject: [PATCH 1/2] feat: merge main --- contracts/account/src/error.rs | 3 ++ contracts/account/src/execute.rs | 10 +++++ contracts/treasury/src/contract.rs | 16 +++++-- contracts/treasury/src/error.rs | 3 ++ contracts/treasury/src/execute.rs | 72 +++++++++++++++++++++++++++--- contracts/treasury/src/msg.rs | 4 +- contracts/treasury/src/state.rs | 2 + 7 files changed, 99 insertions(+), 11 deletions(-) diff --git a/contracts/account/src/error.rs b/contracts/account/src/error.rs index bd17ab6..1580514 100644 --- a/contracts/account/src/error.rs +++ b/contracts/account/src/error.rs @@ -84,6 +84,9 @@ pub enum ContractError { #[error(transparent)] FromUTF8(#[from] std::string::FromUtf8Error), + + #[error("Not found: {msg}")] + NotFound { msg: String }, } pub type ContractResult = Result; diff --git a/contracts/account/src/execute.rs b/contracts/account/src/execute.rs index 233b924..79713f2 100644 --- a/contracts/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -242,6 +242,7 @@ pub fn save_authenticator( } pub fn remove_auth_method(deps: DepsMut, env: Env, id: u8) -> ContractResult { + // Ensure there are more than 1 authenticator before removing if AUTHENTICATORS .keys(deps.storage, None, None, Order::Ascending) .count() @@ -250,7 +251,16 @@ pub fn remove_auth_method(deps: DepsMut, env: Env, id: u8) -> ContractResult ContractResult { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + // Validate the admin address + let admin_addr = if let Some(addr) = msg.admin { + deps.api.addr_validate(&addr.as_str())? + } else { + return Err(ContractError::Unauthorized); + }; execute::init( deps, info, - msg.admin, + Some(admin_addr), msg.type_urls, msg.grant_configs, msg.fee_config, @@ -36,7 +42,11 @@ pub fn execute( authz_granter, authz_grantee, } => execute::deploy_fee_grant(deps, env, authz_granter, authz_grantee), - ExecuteMsg::UpdateAdmin { new_admin } => execute::update_admin(deps, info, new_admin), + ExecuteMsg::ProposeAdmin { new_admin } => { + execute::propose_admin(deps, info, new_admin.into_string()) + } + ExecuteMsg::AcceptAdmin {} => execute::accept_admin(deps, info), + ExecuteMsg::CancelProposedAdmin {} => execute::cancel_proposed_admin(deps, info), ExecuteMsg::UpdateGrantConfig { msg_type_url, grant_config, diff --git a/contracts/treasury/src/error.rs b/contracts/treasury/src/error.rs index 96961ff..8c89414 100644 --- a/contracts/treasury/src/error.rs +++ b/contracts/treasury/src/error.rs @@ -32,6 +32,9 @@ pub enum ContractError { #[error("unauthorized")] Unauthorized, + + #[error("Not found: {msg}")] + NotFound { msg: String }, } pub type ContractResult = Result; diff --git a/contracts/treasury/src/execute.rs b/contracts/treasury/src/execute.rs index 67e7c4c..e73bc49 100644 --- a/contracts/treasury/src/execute.rs +++ b/contracts/treasury/src/execute.rs @@ -1,10 +1,10 @@ use crate::error::ContractError::{ - AuthzGrantMismatch, AuthzGrantNotFound, ConfigurationMismatch, Unauthorized, + AuthzGrantMismatch, AuthzGrantNotFound, ConfigurationMismatch, NotFound, Unauthorized, }; use crate::error::ContractResult; use crate::grant::allowance::format_allowance; use crate::grant::{FeeConfig, GrantConfig}; -use crate::state::{Params, ADMIN, FEE_CONFIG, GRANT_CONFIGS, PARAMS}; +use crate::state::{Params, ADMIN, FEE_CONFIG, GRANT_CONFIGS, PARAMS, PENDING_ADMIN}; use cosmos_sdk_proto::cosmos::authz::v1beta1::{QueryGrantsRequest, QueryGrantsResponse}; use cosmos_sdk_proto::cosmos::feegrant::v1beta1::QueryAllowanceRequest; use cosmos_sdk_proto::prost::Message; @@ -46,22 +46,71 @@ pub fn init( )) } -pub fn update_admin(deps: DepsMut, info: MessageInfo, new_admin: Addr) -> ContractResult { +pub fn propose_admin( + deps: DepsMut, + info: MessageInfo, + new_admin: String, +) -> ContractResult { + // Load the current admin let admin = ADMIN.load(deps.storage)?; + + // Check if the caller is the current admin if admin != info.sender { return Err(Unauthorized); } - ADMIN.save(deps.storage, &new_admin)?; + // Validate the new admin address + let validated_admin = deps.api.addr_validate(&new_admin)?; + + // Save the proposed new admin to PENDING_ADMIN + PENDING_ADMIN.save(deps.storage, &validated_admin)?; Ok( - Response::new().add_event(Event::new("updated_treasury_admin").add_attributes(vec![ - ("old admin", admin.into_string()), - ("new admin", new_admin.into_string()), + Response::new().add_event(Event::new("proposed_new_admin").add_attributes(vec![ + ("proposed_admin", validated_admin.to_string()), + ("proposer", admin.to_string()), ])), ) } +pub fn accept_admin(deps: DepsMut, info: MessageInfo) -> ContractResult { + // Load the pending admin + let pending_admin = PENDING_ADMIN.load(deps.storage)?; + + // Verify the sender is the pending admin + if pending_admin != info.sender { + return Err(Unauthorized); + } + + // Update the ADMIN storage with the new admin + ADMIN.save(deps.storage, &pending_admin)?; + + // Clear the PENDING_ADMIN + PENDING_ADMIN.remove(deps.storage); + + Ok(Response::new().add_event( + Event::new("accepted_new_admin") + .add_attributes(vec![("new_admin", pending_admin.to_string())]), + )) +} + +pub fn cancel_proposed_admin(deps: DepsMut, info: MessageInfo) -> ContractResult { + // Load the current admin + let admin = ADMIN.load(deps.storage)?; + + // Check if the caller is the current admin + if admin != info.sender { + return Err(Unauthorized); + } + + // Remove the pending admin + PENDING_ADMIN.remove(deps.storage); + + Ok(Response::new().add_event( + Event::new("cancelled_proposed_admin").add_attribute("action", "cancel_proposed_admin"), + )) +} + pub fn update_grant_config( deps: DepsMut, info: MessageInfo, @@ -90,11 +139,20 @@ pub fn remove_grant_config( info: MessageInfo, msg_type_url: String, ) -> ContractResult { + // Check if the sender is the admin let admin = ADMIN.load(deps.storage)?; if admin != info.sender { return Err(Unauthorized); } + // Validate that the key exists + if !GRANT_CONFIGS.has(deps.storage, msg_type_url.clone()) { + return Err(NotFound { + msg: format!("Grant config for '{}' does not exist", msg_type_url), + }); + } + + // Remove the grant config GRANT_CONFIGS.remove(deps.storage, msg_type_url.clone()); Ok(Response::new().add_event( diff --git a/contracts/treasury/src/msg.rs b/contracts/treasury/src/msg.rs index 85b707d..95f0891 100644 --- a/contracts/treasury/src/msg.rs +++ b/contracts/treasury/src/msg.rs @@ -13,9 +13,11 @@ pub struct InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { - UpdateAdmin { + ProposeAdmin { new_admin: Addr, }, + AcceptAdmin {}, + CancelProposedAdmin {}, UpdateGrantConfig { msg_type_url: String, grant_config: GrantConfig, diff --git a/contracts/treasury/src/state.rs b/contracts/treasury/src/state.rs index 192c4b0..c85e241 100644 --- a/contracts/treasury/src/state.rs +++ b/contracts/treasury/src/state.rs @@ -10,6 +10,8 @@ pub const FEE_CONFIG: Item = Item::new("fee_config"); pub const ADMIN: Item = Item::new("admin"); +pub const PENDING_ADMIN: Item = Item::new("pending_admin"); + #[cw_serde] pub struct Params { pub display_url: String, From 3689e6e7b1aaa3e973c7354ebf23141a5412aaf7 Mon Sep 17 00:00:00 2001 From: peartes Date: Thu, 30 Jan 2025 23:10:57 +0100 Subject: [PATCH 2/2] fix: failing lint --- contracts/treasury/src/contract.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/treasury/src/contract.rs b/contracts/treasury/src/contract.rs index 06438e2..9628375 100644 --- a/contracts/treasury/src/contract.rs +++ b/contracts/treasury/src/contract.rs @@ -16,7 +16,7 @@ pub fn instantiate( cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; // Validate the admin address let admin_addr = if let Some(addr) = msg.admin { - deps.api.addr_validate(&addr.as_str())? + deps.api.addr_validate(addr.as_str())? } else { return Err(ContractError::Unauthorized); };