use abstract_macros::with_abstract_event;
use abstract_std::account::ExecuteMsg;
use cosmwasm_std::{Binary, Coin, CosmosMsg, Deps, ReplyOn, Response, SubMsg};
use super::AbstractApi;
use crate::{
features::{AccountExecutor, ModuleIdentification},
AbstractSdkResult, AccountAction,
};
pub trait Execution: AccountExecutor + ModuleIdentification {
fn executor<'a>(&'a self, deps: Deps<'a>) -> Executor<'a, Self> {
Executor { base: self, deps }
}
}
impl<T> Execution for T where T: AccountExecutor + ModuleIdentification {}
impl<T: Execution> AbstractApi<T> for Executor<'_, T> {
const API_ID: &'static str = "Executor";
fn base(&self) -> &T {
self.base
}
fn deps(&self) -> Deps {
self.deps
}
}
#[derive(Clone)]
pub struct Executor<'a, T: Execution> {
base: &'a T,
deps: Deps<'a>,
}
impl<T: Execution> Executor<'_, T> {
fn execute_with_data(&self, msg: CosmosMsg) -> AbstractSdkResult<ExecutorMsg> {
let msg = self.base.execute_on_account(
self.deps,
&ExecuteMsg::ExecuteWithData { msg },
vec![],
)?;
Ok(ExecutorMsg(msg))
}
pub fn execute(
&self,
actions: impl IntoIterator<Item = impl Into<AccountAction>>,
) -> AbstractSdkResult<ExecutorMsg> {
self.execute_with_funds(actions, vec![])
}
pub fn execute_with_funds(
&self,
actions: impl IntoIterator<Item = impl Into<AccountAction>>,
funds: Vec<Coin>,
) -> AbstractSdkResult<ExecutorMsg> {
let msgs = actions
.into_iter()
.flat_map(|a| a.into().messages())
.collect();
let msg = self
.base
.execute_on_account(self.deps, &ExecuteMsg::Execute { msgs }, funds)?;
Ok(ExecutorMsg(msg))
}
pub fn execute_with_reply(
&self,
actions: impl IntoIterator<Item = impl Into<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,
payload: Binary::default(),
};
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,
payload: Binary::default(),
};
Ok(sub_msg)
}
pub fn execute_with_response(
&self,
actions: impl IntoIterator<Item = impl Into<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 {
#![allow(clippy::needless_borrows_for_generic_args)]
use abstract_std::account::ExecuteMsg;
use abstract_testing::prelude::*;
use cosmwasm_std::*;
use super::*;
use crate::{apis::traits::test::abstract_api_test, mock_module::*};
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::*;
#[coverage_helper::test]
fn empty_actions() {
let (deps, account, stub) = mock_module_setup();
let executor = stub.executor(deps.as_ref());
let messages = vec![];
let actual_res = executor.execute(messages.clone());
assert!(actual_res.is_ok());
let expected = ExecutorMsg(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: account.addr().to_string(),
msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
msgs: flatten_actions(messages),
})
.unwrap(),
funds: vec![],
}));
assert_eq!(actual_res, Ok(expected));
}
#[coverage_helper::test]
fn with_actions() {
let (deps, account, stub) = mock_module_setup();
let executor = stub.executor(deps.as_ref());
let messages = vec![mock_bank_send(coins(100, "juno"))];
let actual_res = executor.execute(messages.clone());
assert!(actual_res.is_ok());
let expected = ExecutorMsg(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: account.addr().to_string(),
msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
msgs: flatten_actions(messages),
})
.unwrap(),
funds: vec![],
}));
assert_eq!(actual_res, Ok(expected));
}
}
mod execute_with_reply {
use super::*;
#[coverage_helper::test]
fn empty_actions() {
let (deps, account, stub) = mock_module_setup();
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!(actual_res.is_ok());
let expected = SubMsg {
id: expected_reply_id,
msg: CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: account.addr().to_string(),
msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
msgs: flatten_actions(empty_actions),
})
.unwrap(),
funds: vec![],
}),
gas_limit: None,
reply_on: expected_reply_on,
payload: Binary::default(),
};
assert_eq!(actual_res, Ok(expected));
}
#[coverage_helper::test]
fn with_actions() {
let (deps, account, stub) = mock_module_setup();
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!(actual_res.is_ok());
let expected = SubMsg {
id: expected_reply_id,
msg: CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: account.addr().to_string(),
msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
msgs: flatten_actions(action),
})
.unwrap(),
funds: vec![],
}),
gas_limit: None,
reply_on: expected_reply_on,
payload: Binary::default(),
};
assert_eq!(actual_res, Ok(expected));
}
}
mod execute_with_response {
use super::*;
#[coverage_helper::test]
fn empty_actions() {
let (deps, account, stub) = mock_module_setup();
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: account.addr().to_string(),
msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
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_eq!(actual_res, Ok(expected));
}
#[coverage_helper::test]
fn with_actions() {
let (deps, account, stub) = mock_module_setup();
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: account.addr().to_string(),
msg: to_json_binary(&ExecuteMsg::<Empty>::Execute {
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_eq!(actual_res, Ok(expected));
}
}
#[coverage_helper::test]
fn abstract_api() {
let (deps, _, app) = mock_module_setup();
let executor = app.executor(deps.as_ref());
abstract_api_test(executor);
}
}