use abstract_std::ibc::{Callback, IbcResult, ModuleIbcInfo};
use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, Storage};
use cw2::{ContractVersion, CONTRACT};
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, Module, Callback, IbcResult) -> Result<Response, Error>;
pub type ModuleIbcHandlerFn<Module, Error> =
fn(DepsMut, Env, Module, ModuleIbcInfo, Binary) -> Result<Response, Error>;
pub type MigrateHandlerFn<Module, CustomMigrateMsg, Error> =
fn(DepsMut, Env, Module, CustomMigrateMsg) -> 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) 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) ibc_callback_handler: Option<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),
ibc_callback_handler: None,
reply_handlers: [&[], &[]],
dependencies: &[],
execute_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> {
CONTRACT.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_callback(
mut self,
callback: IbcCallbackHandlerFn<Module, Error>,
) -> Self {
self.ibc_callback_handler = Some(callback);
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_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 {
#![allow(clippy::needless_borrows_for_generic_args)]
use cosmwasm_std::Empty;
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(transparent)]
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 SudoMsg = MockSudoMsg;
fn contract(&self) -> &AbstractContract<Self, Self::Error> {
unimplemented!()
}
}
#[coverage_helper::test]
fn test_info() {
let contract = MockAppContract::new("test_contract", "0.1.0", ModuleMetadata::default());
let (name, version, metadata) = contract.info();
assert_eq!(name, "test_contract");
assert_eq!(version, "0.1.0");
assert_eq!(metadata, ModuleMetadata::default());
}
#[coverage_helper::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_handler.is_none());
assert!(contract.instantiate_handler.is_none());
assert!(contract.execute_handler.is_none());
assert!(contract.query_handler.is_none());
assert!(contract.migrate_handler.is_none());
}
#[coverage_helper::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_eq!(contract.dependencies[0].clone(), DEPENDENCY);
}
#[coverage_helper::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());
}
#[coverage_helper::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());
}
#[coverage_helper::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());
}
#[coverage_helper::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());
}
#[coverage_helper::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());
}
#[coverage_helper::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_eq!(contract.reply_handlers[0][0].0, REPLY_ID);
assert!(contract.reply_handlers[1].is_empty());
}
#[coverage_helper::test]
fn test_with_ibc_callback_handlers() {
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_callback(HANDLER);
assert!(contract.ibc_callback_handler.is_some());
}
}