use super::{AbstractApi, ApiIdentification};
use crate::{
features::{AccountIdentification, ModuleIdentification},
AbstractSdkResult, AccountAction,
};
use abstract_core::proxy::ExecuteMsg;
use abstract_macros::with_abstract_event;
use cosmwasm_std::{wasm_execute, CosmosMsg, Deps, ReplyOn, Response, SubMsg};
pub trait Execution: AccountIdentification + ModuleIdentification {
fn executor<'a>(&'a self, deps: Deps<'a>) -> Executor<Self> {
Executor { base: self, deps }
}
}
impl<T> Execution for T where T: AccountIdentification + ModuleIdentification {}
impl<'a, T: Execution> AbstractApi<T> for Executor<'a, T> {
fn base(&self) -> &T {
self.base
}
fn deps(&self) -> Deps {
self.deps
}
}
impl<'a, T: Execution> ApiIdentification for Executor<'a, T> {
fn api_id() -> String {
"Executor".to_owned()
}
}
#[derive(Clone)]
pub struct Executor<'a, T: Execution> {
base: &'a T,
deps: Deps<'a>,
}
impl<'a, T: Execution> Executor<'a, T> {
fn execute_with_data(&self, msg: CosmosMsg) -> AbstractSdkResult<ExecutorMsg> {
let msg = wasm_execute(
self.base.proxy_address(self.deps)?.to_string(),
&ExecuteMsg::ModuleActionWithData { msg },
vec![],
)?
.into();
Ok(ExecutorMsg(msg))
}
pub fn execute(&self, actions: Vec<AccountAction>) -> AbstractSdkResult<ExecutorMsg> {
let msgs = actions.into_iter().flat_map(|a| a.messages()).collect();
let msg: CosmosMsg = wasm_execute(
self.base.proxy_address(self.deps)?.to_string(),
&ExecuteMsg::ModuleAction { msgs },
vec![],
)?
.into();
Ok(ExecutorMsg(msg))
}
pub fn execute_with_reply(
&self,
actions: Vec<AccountAction>,
reply_on: ReplyOn,
id: u64,
) -> AbstractSdkResult<SubMsg> {
let msg = self.execute(actions)?;
let sub_msg = SubMsg {
id,
msg: msg.into(),
gas_limit: None,
reply_on,
};
Ok(sub_msg)
}
pub fn execute_with_reply_and_data(
&self,
actions: CosmosMsg,
reply_on: ReplyOn,
id: u64,
) -> AbstractSdkResult<SubMsg> {
let msg = self.execute_with_data(actions)?;
let sub_msg = SubMsg {
id,
msg: msg.into(),
gas_limit: None,
reply_on,
};
Ok(sub_msg)
}
pub fn execute_with_response(
&self,
actions: Vec<AccountAction>,
action: &str,
) -> AbstractSdkResult<Response> {
let msg = self.execute(actions)?;
let resp = Response::default();
Ok(with_abstract_event!(resp, self.base.module_id(), action).add_message(msg))
}
}
#[must_use = "ExecutorMsg should be provided to Response::add_message"]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq, Eq))]
pub struct ExecutorMsg(CosmosMsg);
impl From<ExecutorMsg> for CosmosMsg {
fn from(val: ExecutorMsg) -> Self {
val.0
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::mock_module::*;
use abstract_core::proxy::ExecuteMsg;
use abstract_testing::prelude::*;
use cosmwasm_std::{testing::*, *};
use speculoos::prelude::*;
fn mock_bank_send(amount: Vec<Coin>) -> AccountAction {
AccountAction::from(CosmosMsg::Bank(BankMsg::Send {
to_address: "to_address".to_string(),
amount,
}))
}
fn flatten_actions(actions: Vec<AccountAction>) -> Vec<CosmosMsg> {
actions.into_iter().flat_map(|a| a.messages()).collect()
}
mod execute {
use super::*;
use cosmwasm_std::to_json_binary;
#[test]
fn empty_actions() {
let deps = mock_dependencies();
let stub = MockModule::new();
let executor = stub.executor(deps.as_ref());
let messages = vec![];
let actual_res = executor.execute(messages.clone());
assert_that!(actual_res).is_ok();
let expected = ExecutorMsg(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: TEST_PROXY.to_string(),
msg: to_json_binary(&ExecuteMsg::ModuleAction {
msgs: flatten_actions(messages),
})
.unwrap(),
funds: vec![],
}));
assert_that!(actual_res.unwrap()).is_equal_to(expected);
}
#[test]
fn with_actions() {
let deps = mock_dependencies();
let stub = MockModule::new();
let executor = stub.executor(deps.as_ref());
let messages = vec![mock_bank_send(coins(100, "juno"))];
let actual_res = executor.execute(messages.clone());
assert_that!(actual_res).is_ok();
let expected = ExecutorMsg(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: TEST_PROXY.to_string(),
msg: to_json_binary(&ExecuteMsg::ModuleAction {
msgs: flatten_actions(messages),
})
.unwrap(),
funds: vec![],
}));
assert_that!(actual_res.unwrap()).is_equal_to(expected);
}
}
mod execute_with_reply {
use super::*;
#[test]
fn empty_actions() {
let deps = mock_dependencies();
let stub = MockModule::new();
let executor = stub.executor(deps.as_ref());
let empty_actions = vec![];
let expected_reply_on = ReplyOn::Success;
let expected_reply_id = 10952;
let actual_res = executor.execute_with_reply(
empty_actions.clone(),
expected_reply_on.clone(),
expected_reply_id,
);
assert_that!(actual_res).is_ok();
let expected = SubMsg {
id: expected_reply_id,
msg: CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: TEST_PROXY.to_string(),
msg: to_json_binary(&ExecuteMsg::ModuleAction {
msgs: flatten_actions(empty_actions),
})
.unwrap(),
funds: vec![],
}),
gas_limit: None,
reply_on: expected_reply_on,
};
assert_that!(actual_res.unwrap()).is_equal_to(expected);
}
#[test]
fn with_actions() {
let deps = mock_dependencies();
let stub = MockModule::new();
let executor = stub.executor(deps.as_ref());
let action = vec![mock_bank_send(coins(1, "denom"))];
let expected_reply_on = ReplyOn::Never;
let expected_reply_id = 1;
let actual_res = executor.execute_with_reply(
action.clone(),
expected_reply_on.clone(),
expected_reply_id,
);
assert_that!(actual_res).is_ok();
let expected = SubMsg {
id: expected_reply_id,
msg: CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: TEST_PROXY.to_string(),
msg: to_json_binary(&ExecuteMsg::ModuleAction {
msgs: flatten_actions(action),
})
.unwrap(),
funds: vec![],
}),
gas_limit: None,
reply_on: expected_reply_on,
};
assert_that!(actual_res.unwrap()).is_equal_to(expected);
}
}
mod execute_with_response {
use super::*;
use cosmwasm_std::coins;
#[test]
fn empty_actions() {
let deps = mock_dependencies();
let stub = MockModule::new();
let executor = stub.executor(deps.as_ref());
let empty_actions = vec![];
let expected_action = "THIS IS AN ACTION";
let actual_res = executor.execute_with_response(empty_actions.clone(), expected_action);
let expected_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: TEST_PROXY.to_string(),
msg: to_json_binary(&ExecuteMsg::ModuleAction {
msgs: flatten_actions(empty_actions),
})
.unwrap(),
funds: vec![],
});
let expected = Response::new()
.add_event(
Event::new("abstract")
.add_attribute("contract", stub.module_id())
.add_attribute("action", expected_action),
)
.add_message(expected_msg);
assert_that!(actual_res).is_ok().is_equal_to(expected);
}
#[test]
fn with_actions() {
let deps = mock_dependencies();
let stub = MockModule::new();
let executor = stub.executor(deps.as_ref());
let action = vec![mock_bank_send(coins(1, "denom"))];
let expected_action = "provide liquidity";
let actual_res = executor.execute_with_response(action.clone(), expected_action);
let expected_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: TEST_PROXY.to_string(),
msg: to_json_binary(&ExecuteMsg::ModuleAction {
msgs: flatten_actions(action),
})
.unwrap(),
funds: vec![],
});
let expected = Response::new()
.add_event(
Event::new("abstract")
.add_attribute("contract", stub.module_id())
.add_attribute("action", expected_action),
)
.add_message(expected_msg);
assert_that!(actual_res).is_ok().is_equal_to(expected);
}
}
}