use abstract_std::ibc::{IbcResponseMsg, ModuleIbcMsg};
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, Storage};
use cw2::{ContractVersion, CONTRACT};
use cw_storage_plus::Item;
use super::handler::Handler;
use crate::{std::objects::dependency::StaticDependency, AbstractSdkError, AbstractSdkResult};
pub type ModuleId = &'static str;
pub type VersionString = &'static str;
pub type ModuleMetadata = Option<&'static str>;
pub type InstantiateHandlerFn<Module, CustomInitMsg, Error> =
fn(DepsMut, Env, MessageInfo, Module, CustomInitMsg) -> Result<Response, Error>;
pub type ExecuteHandlerFn<Module, CustomExecMsg, Error> =
fn(DepsMut, Env, MessageInfo, Module, CustomExecMsg) -> Result<Response, Error>;
pub type QueryHandlerFn<Module, CustomQueryMsg, Error> =
fn(Deps, Env, &Module, CustomQueryMsg) -> Result<Binary, Error>;
pub type IbcCallbackHandlerFn<Module, Error> =
fn(DepsMut, Env, MessageInfo, Module, IbcResponseMsg) -> Result<Response, Error>;
pub type ModuleIbcHandlerFn<Module, Error> =
fn(DepsMut, Env, Module, ModuleIbcMsg) -> Result<Response, Error>;
pub type MigrateHandlerFn<Module, CustomMigrateMsg, Error> =
fn(DepsMut, Env, Module, CustomMigrateMsg) -> Result<Response, Error>;
pub type ReceiveHandlerFn<Module, ReceiveMsg, Error> =
fn(DepsMut, Env, MessageInfo, Module, ReceiveMsg) -> Result<Response, Error>;
pub type SudoHandlerFn<Module, CustomSudoMsg, Error> =
fn(DepsMut, Env, Module, CustomSudoMsg) -> Result<Response, Error>;
pub type ReplyHandlerFn<Module, Error> = fn(DepsMut, Env, Module, Reply) -> Result<Response, Error>;
const MAX_REPLY_COUNT: usize = 2;
pub struct AbstractContract<Module: Handler + 'static, Error: From<AbstractSdkError> + 'static> {
pub(crate) info: (ModuleId, VersionString, ModuleMetadata),
pub(crate) version: Item<'static, ContractVersion>,
pub(crate) dependencies: &'static [StaticDependency],
pub(crate) instantiate_handler:
Option<InstantiateHandlerFn<Module, <Module as Handler>::CustomInitMsg, Error>>,
pub(crate) execute_handler:
Option<ExecuteHandlerFn<Module, <Module as Handler>::CustomExecMsg, Error>>,
pub(crate) query_handler:
Option<QueryHandlerFn<Module, <Module as Handler>::CustomQueryMsg, Error>>,
pub(crate) migrate_handler:
Option<MigrateHandlerFn<Module, <Module as Handler>::CustomMigrateMsg, Error>>,
pub(crate) sudo_handler: Option<SudoHandlerFn<Module, <Module as Handler>::SudoMsg, Error>>,
pub reply_handlers: [&'static [(u64, ReplyHandlerFn<Module, Error>)]; MAX_REPLY_COUNT],
pub(crate) receive_handler:
Option<ReceiveHandlerFn<Module, <Module as Handler>::ReceiveMsg, Error>>,
pub(crate) ibc_callback_handlers:
&'static [(&'static str, IbcCallbackHandlerFn<Module, Error>)],
pub(crate) module_ibc_handler: Option<ModuleIbcHandlerFn<Module, Error>>,
}
impl<Module, Error: From<AbstractSdkError>> AbstractContract<Module, Error>
where
Module: Handler,
{
pub const fn new(name: ModuleId, version: VersionString, metadata: ModuleMetadata) -> Self {
Self {
info: (name, version, metadata),
version: CONTRACT,
ibc_callback_handlers: &[],
reply_handlers: [&[], &[]],
dependencies: &[],
execute_handler: None,
receive_handler: None,
migrate_handler: None,
sudo_handler: None,
instantiate_handler: None,
query_handler: None,
module_ibc_handler: None,
}
}
pub fn version(&self, store: &dyn Storage) -> AbstractSdkResult<ContractVersion> {
self.version.load(store).map_err(Into::into)
}
pub fn info(&self) -> (ModuleId, VersionString, ModuleMetadata) {
self.info
}
pub const fn with_dependencies(mut self, dependencies: &'static [StaticDependency]) -> Self {
self.dependencies = dependencies;
self
}
pub const fn with_replies(
mut self,
reply_handlers: [&'static [(u64, ReplyHandlerFn<Module, Error>)]; MAX_REPLY_COUNT],
) -> Self {
self.reply_handlers = reply_handlers;
self
}
pub const fn with_ibc_callbacks(
mut self,
callbacks: &'static [(&'static str, IbcCallbackHandlerFn<Module, Error>)],
) -> Self {
self.ibc_callback_handlers = callbacks;
self
}
pub const fn with_module_ibc(
mut self,
module_handler: ModuleIbcHandlerFn<Module, Error>,
) -> Self {
self.module_ibc_handler = Some(module_handler);
self
}
pub const fn with_instantiate(
mut self,
instantiate_handler: InstantiateHandlerFn<
Module,
<Module as Handler>::CustomInitMsg,
Error,
>,
) -> Self {
self.instantiate_handler = Some(instantiate_handler);
self
}
pub const fn with_migrate(
mut self,
migrate_handler: MigrateHandlerFn<Module, <Module as Handler>::CustomMigrateMsg, Error>,
) -> Self {
self.migrate_handler = Some(migrate_handler);
self
}
pub const fn with_sudo(
mut self,
sudo_handler: SudoHandlerFn<Module, <Module as Handler>::SudoMsg, Error>,
) -> Self {
self.sudo_handler = Some(sudo_handler);
self
}
pub const fn with_receive(
mut self,
receive_handler: ReceiveHandlerFn<Module, <Module as Handler>::ReceiveMsg, Error>,
) -> Self {
self.receive_handler = Some(receive_handler);
self
}
pub const fn with_execute(
mut self,
execute_handler: ExecuteHandlerFn<Module, <Module as Handler>::CustomExecMsg, Error>,
) -> Self {
self.execute_handler = Some(execute_handler);
self
}
pub const fn with_query(
mut self,
query_handler: QueryHandlerFn<Module, <Module as Handler>::CustomQueryMsg, Error>,
) -> Self {
self.query_handler = Some(query_handler);
self
}
}
#[cfg(test)]
mod test {
use cosmwasm_std::Empty;
use speculoos::assert_that;
use super::*;
#[cosmwasm_schema::cw_serde]
struct MockInitMsg;
#[cosmwasm_schema::cw_serde]
struct MockExecMsg;
#[cosmwasm_schema::cw_serde]
struct MockQueryMsg;
#[cosmwasm_schema::cw_serde]
struct MockMigrateMsg;
#[cosmwasm_schema::cw_serde]
struct MockReceiveMsg;
#[cosmwasm_schema::cw_serde]
struct MockSudoMsg;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MockError {
#[error("{0}")]
Sdk(#[from] AbstractSdkError),
}
struct MockModule;
type MockAppContract = AbstractContract<MockModule, MockError>;
impl Handler for MockModule {
type Error = MockError;
type CustomInitMsg = MockInitMsg;
type CustomExecMsg = MockExecMsg;
type CustomQueryMsg = MockQueryMsg;
type CustomMigrateMsg = MockMigrateMsg;
type ReceiveMsg = MockReceiveMsg;
type SudoMsg = MockSudoMsg;
fn contract(&self) -> &AbstractContract<Self, Self::Error> {
unimplemented!()
}
}
#[test]
fn test_info() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default());
let (name, version, metadata) = contract.info();
assert_that!(&name).is_equal_to("test_contract");
assert_that!(&version).is_equal_to("0.1.0");
assert_that!(metadata).is_equal_to(ModuleMetadata::default());
}
#[test]
fn test_with_empty() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_dependencies(&[]);
assert!(contract.reply_handlers.iter().all(|x| x.is_empty()));
assert!(contract.dependencies.is_empty());
assert!(contract.ibc_callback_handlers.is_empty());
assert!(contract.instantiate_handler.is_none());
assert!(contract.receive_handler.is_none());
assert!(contract.execute_handler.is_none());
assert!(contract.query_handler.is_none());
assert!(contract.migrate_handler.is_none());
}
#[test]
fn test_with_dependencies() {
const VERSION: &str = "0.1.0";
const DEPENDENCY: StaticDependency = StaticDependency::new("test", &[VERSION]);
const DEPENDENCIES: &[StaticDependency] = &[DEPENDENCY];
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_dependencies(DEPENDENCIES);
assert_that!(contract.dependencies[0].clone()).is_equal_to(DEPENDENCY);
}
#[test]
fn test_with_instantiate() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_instantiate(|_, _, _, _, _| {
Ok(Response::default().add_attribute("test", "instantiate"))
});
assert!(contract.instantiate_handler.is_some());
}
#[test]
fn test_with_receive() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_receive(|_, _, _, _, _| Ok(Response::default().add_attribute("test", "receive")));
assert!(contract.receive_handler.is_some());
}
#[test]
fn test_with_sudo() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_sudo(|_, _, _, _| Ok(Response::default().add_attribute("test", "sudo")));
assert!(contract.sudo_handler.is_some());
}
#[test]
fn test_with_execute() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_execute(|_, _, _, _, _| Ok(Response::default().add_attribute("test", "execute")));
assert!(contract.execute_handler.is_some());
}
#[test]
fn test_with_query() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_query(|_, _, _, _| Ok(cosmwasm_std::to_json_binary(&Empty {}).unwrap()));
assert!(contract.query_handler.is_some());
}
#[test]
fn test_with_migrate() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_migrate(|_, _, _, _| Ok(Response::default().add_attribute("test", "migrate")));
assert!(contract.migrate_handler.is_some());
}
#[test]
fn test_with_reply_handlers() {
const REPLY_ID: u64 = 50u64;
const HANDLER: ReplyHandlerFn<MockModule, MockError> =
|_, _, _, _| Ok(Response::default().add_attribute("test", "reply"));
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_replies([&[(REPLY_ID, HANDLER)], &[]]);
assert_that!(contract.reply_handlers[0][0].0).is_equal_to(REPLY_ID);
assert!(contract.reply_handlers[1].is_empty());
}
#[test]
fn test_with_ibc_callback_handlers() {
const IBC_ID: &str = "aoeu";
const HANDLER: IbcCallbackHandlerFn<MockModule, MockError> =
|_, _, _, _, _| Ok(Response::default().add_attribute("test", "ibc"));
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default())
.with_ibc_callbacks(&[(IBC_ID, HANDLER)]);
assert_that!(contract.ibc_callback_handlers[0].0).is_equal_to(IBC_ID);
}
}