Skip to content
This repository has been archived by the owner on Oct 30, 2023. It is now read-only.

Commit

Permalink
Add ModuleActionWithData variant to Proxy (#369)
Browse files Browse the repository at this point in the history
* add a `ModuleExecuteResponse` endpoint that captures the response data

* rename action and add helper on executor

* cleanup

* update reply to parse data and forward execution data exclusively

* use if let Some
  • Loading branch information
CyberHoward committed Jun 13, 2023
1 parent 38fdaf6 commit e894b71
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 40 deletions.
30 changes: 3 additions & 27 deletions contracts/account/manager/tests/adapters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,16 @@ use abstract_core::objects::module::{ModuleInfo, ModuleVersion, Monetization};
use abstract_core::{adapter::BaseQueryMsgFns, *};
use abstract_interface::*;
use abstract_testing::prelude::{OWNER, TEST_ACCOUNT_ID, TEST_MODULE_ID, TEST_VERSION};
use common::{
add_mock_adapter_install_fee, create_default_account, init_mock_adapter, AResult, TEST_COIN,
};
use cosmwasm_std::{coin, coins, Addr, Coin, Empty};
use common::*;
use cosmwasm_std::{coin, coins};
use cosmwasm_std::{Addr, Coin, Empty};
use cw_orch::deploy::Deploy;
use cw_orch::prelude::*;
// use cw_multi_test::StakingInfo;
use speculoos::{assert_that, result::ResultAssertions, string::StrAssertions};

use crate::common::mock_modules::{BootMockAdapter1V1, BootMockAdapter1V2, V1, V2};

fn install_adapter(manager: &Manager<Mock>, adapter_id: &str) -> AResult {
manager
.install_module(adapter_id, &Empty {}, None)
.map_err(Into::into)
}

fn install_adapter_with_funds(
manager: &Manager<Mock>,
adapter_id: &str,
funds: &[Coin],
) -> AResult {
manager
.install_module(adapter_id, &Empty {}, Some(funds))
.map_err(Into::into)
}

pub(crate) fn uninstall_module(manager: &Manager<Mock>, module_id: &str) -> AResult {
manager
.uninstall_module(module_id.to_string())
.map_err(Into::<CwOrchError>::into)?;
Ok(())
}

#[test]
fn installing_one_adapter_should_succeed() -> AResult {
let sender = Addr::unchecked(common::OWNER);
Expand Down
26 changes: 25 additions & 1 deletion contracts/account/manager/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use abstract_core::version_control::AccountBase;
use abstract_core::{objects::gov_type::GovernanceDetails, PROXY};
use abstract_core::{ACCOUNT_FACTORY, ANS_HOST, MANAGER, MODULE_FACTORY, VERSION_CONTROL};
use abstract_interface::{
Abstract, AccountFactory, AnsHost, Manager, ModuleFactory, Proxy, VCExecFns, VersionControl,
Abstract, AccountFactory, AnsHost, Manager, ManagerExecFns, ModuleFactory, Proxy, VCExecFns,
VersionControl,
};
use abstract_interface::{AbstractAccount, AdapterDeployer};
use abstract_testing::prelude::{TEST_MODULE_NAME, TEST_NAMESPACE};
Expand Down Expand Up @@ -67,3 +68,26 @@ pub(crate) fn add_mock_adapter_install_fee(
)?;
Ok(())
}

pub fn install_adapter(manager: &Manager<Mock>, adapter_id: &str) -> AResult {
manager
.install_module(adapter_id, &Empty {}, None)
.map_err(Into::into)
}

pub fn install_adapter_with_funds(
manager: &Manager<Mock>,
adapter_id: &str,
funds: &[Coin],
) -> AResult {
manager
.install_module(adapter_id, &Empty {}, Some(funds))
.map_err(Into::into)
}

pub fn uninstall_module(manager: &Manager<Mock>, module_id: &str) -> AResult {
manager
.uninstall_module(module_id.to_string())
.map_err(Into::<CwOrchError>::into)?;
Ok(())
}
88 changes: 85 additions & 3 deletions contracts/account/manager/tests/proxy.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod common;
use abstract_adapter::mock::MockExecMsg;
use abstract_core::adapter::AdapterRequestMsg;
use abstract_core::{manager::ManagerModuleInfo, PROXY};
use abstract_interface::*;
use abstract_manager::contract::CONTRACT_VERSION;
use abstract_testing::prelude::TEST_ACCOUNT_ID;
use common::{create_default_account, AResult, TEST_COIN};
use cosmwasm_std::{Addr, Coin, CosmosMsg};
use abstract_testing::prelude::{TEST_ACCOUNT_ID, TEST_MODULE_ID};
use common::{create_default_account, init_mock_adapter, install_adapter, AResult, TEST_COIN};
use cosmwasm_std::{wasm_execute, Addr, Coin, CosmosMsg};
use cw_orch::deploy::Deploy;
use cw_orch::prelude::*;
use speculoos::prelude::*;
Expand Down Expand Up @@ -80,3 +82,83 @@ fn exec_through_manager() -> AResult {

Ok(())
}

#[test]
fn default_without_response_data() -> AResult {
let sender = Addr::unchecked(common::OWNER);
let chain = Mock::new(&sender);
let deployment = Abstract::deploy_on(chain.clone(), Empty {})?;
let account = create_default_account(&deployment.account_factory)?;
let _staking_adapter_one = init_mock_adapter(chain.clone(), &deployment, None)?;

install_adapter(&account.manager, TEST_MODULE_ID)?;

chain.set_balance(
&account.proxy.address()?,
vec![Coin::new(100_000, TEST_COIN)],
)?;

let resp = account.manager.execute_on_module(
TEST_MODULE_ID,
Into::<abstract_core::adapter::ExecuteMsg<MockExecMsg>>::into(MockExecMsg),
)?;
assert_that!(resp.data).is_none();

Ok(())
}

#[test]
fn with_response_data() -> AResult {
let sender = Addr::unchecked(common::OWNER);
let chain = Mock::new(&sender);
let deployment = Abstract::deploy_on(chain.clone(), Empty {})?;
let account = create_default_account(&deployment.account_factory)?;
let staking_adapter = init_mock_adapter(chain.clone(), &deployment, None)?;

install_adapter(&account.manager, TEST_MODULE_ID)?;

staking_adapter
.call_as(&account.manager.address()?)
.execute(
&abstract_core::adapter::ExecuteMsg::<MockExecMsg, Empty>::Base(
abstract_core::adapter::BaseExecuteMsg::UpdateAuthorizedAddresses {
to_add: vec![account.proxy.addr_str()?],
to_remove: vec![],
},
),
None,
)?;

chain.set_balance(
&account.proxy.address()?,
vec![Coin::new(100_000, TEST_COIN)],
)?;

let adapter_addr = account
.manager
.module_info(TEST_MODULE_ID)?
.expect("test module installed");
// proxy should be final executor because of the reply
let resp = account.manager.exec_on_module(
cosmwasm_std::to_binary(&abstract_core::proxy::ExecuteMsg::ModuleActionWithData {
// execute a message on the adapter, which sets some data in its response
msg: wasm_execute(
adapter_addr.address,
&abstract_core::adapter::ExecuteMsg::<MockExecMsg, Empty>::Module(
AdapterRequestMsg {
proxy_address: Some(account.proxy.addr_str()?),
request: MockExecMsg,
},
),
vec![],
)?
.into(),
})?,
PROXY.to_string(),
)?;

let response_data_attr_present = resp.event_attr_value("wasm-abstract", "response_data")?;
assert_that!(response_data_attr_present).is_equal_to("true".to_string());

Ok(())
}
1 change: 1 addition & 0 deletions contracts/account/proxy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ abstract-sdk = { workspace = true }
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true, features = ["stargate"] }
cw-asset = { workspace = true }
cw-utils = { workspace = true }
cw-controllers = { workspace = true }
cw-storage-plus = { workspace = true }
cw2 = { workspace = true }
Expand Down
21 changes: 19 additions & 2 deletions contracts/account/proxy/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::contract::{ProxyResponse, ProxyResult};
use crate::contract::{ProxyResponse, ProxyResult, RESPONSE_REPLY_ID};
use crate::error::ProxyError;
use abstract_core::objects::{oracle::Oracle, price_source::UncheckedPriceSource, AssetEntry};
use abstract_sdk::core::{
ibc_client::ExecuteMsg as IbcClientMsg,
proxy::state::{ADMIN, ANS_HOST, STATE},
IBC_CLIENT,
};
use cosmwasm_std::{wasm_execute, CosmosMsg, DepsMut, Empty, MessageInfo, StdError};
use cosmwasm_std::{wasm_execute, CosmosMsg, DepsMut, Empty, MessageInfo, StdError, SubMsg};

const LIST_SIZE_LIMIT: usize = 15;

Expand All @@ -25,6 +25,23 @@ pub fn execute_module_action(
Ok(ProxyResponse::action("execute_module_action").add_messages(msgs))
}

/// Executes actions forwarded by whitelisted contracts
/// This contracts acts as a proxy contract for the dApps
pub fn execute_module_action_response(
deps: DepsMut,
msg_info: MessageInfo,
msg: CosmosMsg<Empty>,
) -> ProxyResult {
let state = STATE.load(deps.storage)?;
if !state.modules.contains(&msg_info.sender) {
return Err(ProxyError::SenderNotWhitelisted {});
}

let submsg = SubMsg::reply_on_success(msg, RESPONSE_REPLY_ID);

Ok(ProxyResponse::action("execute_module_action_response").add_submessage(submsg))
}

/// Executes IBC actions forwarded by whitelisted contracts
/// Calls the messages on the IBC client (ensuring permission)
pub fn execute_ibc_action(
Expand Down
20 changes: 18 additions & 2 deletions contracts/account/proxy/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::commands::*;
use crate::error::ProxyError;
use crate::queries::*;
use crate::{commands::*, reply};
use abstract_core::objects::module_version::assert_contract_upgrade;
use abstract_core::objects::oracle::Oracle;
use abstract_macros::abstract_response;
Expand All @@ -15,10 +15,13 @@ use abstract_sdk::{
},
feature_objects::AnsHost,
};
use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response};
use cosmwasm_std::{
to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, SubMsgResult,
};
use semver::Version;

pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub(crate) const RESPONSE_REPLY_ID: u64 = 1;

#[abstract_response(PROXY)]
pub struct ProxyResponse;
Expand Down Expand Up @@ -57,6 +60,7 @@ pub fn instantiate(
pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> ProxyResult {
match msg {
ExecuteMsg::ModuleAction { msgs } => execute_module_action(deps, info, msgs),
ExecuteMsg::ModuleActionWithData { msg } => execute_module_action_response(deps, info, msg),
ExecuteMsg::IbcAction { msgs } => execute_ibc_action(deps, info, msgs),
ExecuteMsg::SetAdmin { admin } => set_admin(deps, info, &admin),
ExecuteMsg::AddModule { module } => add_module(deps, info, module),
Expand Down Expand Up @@ -101,6 +105,18 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ProxyResult<Binary> {
.map_err(Into::into)
}

/// This just stores the result for future query
#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> ProxyResult {
match &msg {
Reply {
id: RESPONSE_REPLY_ID,
result: SubMsgResult::Ok(_),
} => reply::forward_response_data(msg),
_ => Err(ProxyError::UnexpectedReply {}),
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
9 changes: 8 additions & 1 deletion contracts/account/proxy/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use abstract_core::AbstractError;
use abstract_sdk::AbstractSdkError;
use cosmwasm_std::{StdError, Uint128};
use cw_asset::AssetError;
use cw_utils::ParseReplyError;
use thiserror::Error;

#[derive(Error, Debug, PartialEq)]
Expand All @@ -21,6 +22,9 @@ pub enum ProxyError {
#[error(transparent)]
Admin(#[from] ::cw_controllers::AdminError),

#[error("{0}")]
Parse(#[from] ParseReplyError),

#[error("Module with address {0} is already whitelisted")]
AlreadyWhitelisted(String),

Expand All @@ -43,12 +47,15 @@ pub enum ProxyError {
BadUpdate(String),

#[error(
"Treasury balance too low, {} requested but it only has {}",
"Account balance too low, {} requested but it only has {}",
requested,
balance
)]
Broke {
balance: Uint128,
requested: Uint128,
},

#[error("Contract got an unexpected Reply")]
UnexpectedReply(),
}
1 change: 1 addition & 0 deletions contracts/account/proxy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod commands;
pub mod contract;
mod error;
mod queries;
pub mod reply;

#[cfg(test)]
mod test_common {
Expand Down
25 changes: 25 additions & 0 deletions contracts/account/proxy/src/reply.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use cosmwasm_std::Reply;

use crate::contract::{ProxyResponse, ProxyResult};

/// Add the message's data to the response
pub fn forward_response_data(result: Reply) -> ProxyResult {
// get the result from the reply
let resp = cw_utils::parse_reply_execute_data(result)?;

// log and add data if needed
let resp = if let Some(data) = resp.data {
ProxyResponse::new(
"forward_response_data_reply",
vec![("response_data", "true")],
)
.set_data(data)
} else {
ProxyResponse::new(
"forward_response_data_reply",
vec![("response_data", "false")],
)
};

Ok(resp)
}
2 changes: 2 additions & 0 deletions packages/abstract-core/src/core/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub enum ExecuteMsg {
SetAdmin { admin: String },
/// Executes the provided messages if sender is whitelisted
ModuleAction { msgs: Vec<CosmosMsg<Empty>> },
/// Execute a message and forward the Response data
ModuleActionWithData { msg: CosmosMsg<Empty> },
/// Execute IBC action on Client
IbcAction { msgs: Vec<IbcClientMsg> },
/// Adds the provided address to whitelisted dapps
Expand Down
7 changes: 4 additions & 3 deletions packages/abstract-interface/src/account/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,16 @@ impl<Chain: CwEnv> Manager<Chain> {
&self,
module: &str,
msg: impl Serialize,
) -> Result<(), crate::AbstractInterfaceError> {
) -> Result<<Chain as cw_orch::prelude::TxHandler>::Response, crate::AbstractInterfaceError>
{
self.execute(
&ExecuteMsg::ExecOnModule {
module_id: module.into(),
exec_msg: to_binary(&msg).unwrap(),
},
None,
)?;
Ok(())
)
.map_err(Into::into)
}

pub fn update_adapter_authorized_addresses(
Expand Down
3 changes: 2 additions & 1 deletion packages/abstract-interface/src/account/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ impl<Chain: CwEnv> Uploadable for Proxy<Chain> {
::proxy::contract::instantiate,
::proxy::contract::query,
)
.with_migrate(::proxy::contract::migrate),
.with_migrate(::proxy::contract::migrate)
.with_reply(::proxy::contract::reply),
)
}
fn wasm(&self) -> WasmPath {
Expand Down
Loading

0 comments on commit e894b71

Please sign in to comment.