1use abstract_sdk::features::ModuleIdentification;
2use abstract_sdk::{
3 base::{
4 AbstractContract, ExecuteHandlerFn, Handler, IbcCallbackHandlerFn, InstantiateHandlerFn,
5 ModuleIbcHandlerFn, QueryHandlerFn, ReplyHandlerFn, SudoHandlerFn,
6 },
7 namespaces::BASE_STATE,
8 std::registry::Account,
9 AbstractSdkError,
10};
11use abstract_std::{
12 adapter::AdapterState,
13 objects::{dependency::StaticDependency, module::ModuleInfo},
14 AbstractError, AbstractResult,
15};
16use cosmwasm_std::{Addr, Empty, StdError, StdResult, Storage};
17use cw_storage_plus::{Item, Map};
18
19use crate::AdapterError;
20
21pub const AUTHORIZED_ADDRESSES_NAMESPACE: &str = "authorized_addresses";
22pub const MAXIMUM_AUTHORIZED_ADDRESSES: u32 = 15;
23
24pub trait ContractError:
25 From<cosmwasm_std::StdError>
26 + From<AdapterError>
27 + From<AbstractSdkError>
28 + From<AbstractError>
29 + 'static
30{
31}
32impl<T> ContractError for T where
33 T: From<cosmwasm_std::StdError>
34 + From<AdapterError>
35 + From<AbstractSdkError>
36 + From<AbstractError>
37 + 'static
38{
39}
40
41pub struct AdapterContract<
43 Error: ContractError,
44 CustomInitMsg: 'static,
45 CustomExecMsg: 'static,
46 CustomQueryMsg: 'static,
47 SudoMsg: 'static = Empty,
48> where
49 Self: Handler,
50{
51 pub(crate) contract: AbstractContract<Self, Error>,
52 pub(crate) base_state: Item<AdapterState>,
53 pub authorized_addresses: Map<Addr, Vec<Addr>>,
55 pub target_account: Option<Account>,
57}
58
59impl<Error: ContractError, CustomInitMsg, CustomExecMsg, CustomQueryMsg, SudoMsg>
61 AdapterContract<Error, CustomInitMsg, CustomExecMsg, CustomQueryMsg, SudoMsg>
62{
63 pub const fn new(
64 name: &'static str,
65 version: &'static str,
66 metadata: Option<&'static str>,
67 ) -> Self {
68 Self {
69 contract: AbstractContract::new(name, version, metadata),
70 base_state: Item::new(BASE_STATE),
71 authorized_addresses: Map::new(AUTHORIZED_ADDRESSES_NAMESPACE),
72 target_account: None,
73 }
74 }
75
76 pub fn version(&self) -> &'static str {
77 self.contract.info().1
78 }
79
80 pub fn module_info(&self) -> AbstractResult<ModuleInfo> {
81 ModuleInfo::from_id(self.module_id(), self.version().into())
82 }
83
84 pub fn state(&self, store: &dyn Storage) -> StdResult<AdapterState> {
85 self.base_state.load(store)
86 }
87
88 pub fn target(&self) -> Result<&Addr, AdapterError> {
91 Ok(self
92 .target_account
93 .as_ref()
94 .ok_or_else(|| StdError::generic_err("No target Account specified to execute on."))?
95 .addr())
96 }
97 pub const fn with_dependencies(mut self, dependencies: &'static [StaticDependency]) -> Self {
99 self.contract = self.contract.with_dependencies(dependencies);
100 self
101 }
102
103 pub const fn with_instantiate(
104 mut self,
105 instantiate_handler: InstantiateHandlerFn<Self, CustomInitMsg, Error>,
106 ) -> Self {
107 self.contract = self.contract.with_instantiate(instantiate_handler);
108 self
109 }
110
111 pub const fn with_execute(
112 mut self,
113 execute_handler: ExecuteHandlerFn<Self, CustomExecMsg, Error>,
114 ) -> Self {
115 self.contract = self.contract.with_execute(execute_handler);
116 self
117 }
118
119 pub const fn with_query(
120 mut self,
121 query_handler: QueryHandlerFn<Self, CustomQueryMsg, Error>,
122 ) -> Self {
123 self.contract = self.contract.with_query(query_handler);
124 self
125 }
126
127 pub const fn with_replies(
128 mut self,
129 reply_handlers: &'static [(u64, ReplyHandlerFn<Self, Error>)],
130 ) -> Self {
131 self.contract = self.contract.with_replies([&[], reply_handlers]);
132 self
133 }
134
135 pub const fn with_sudo(mut self, sudo_handler: SudoHandlerFn<Self, SudoMsg, Error>) -> Self {
136 self.contract = self.contract.with_sudo(sudo_handler);
137 self
138 }
139
140 pub const fn with_ibc_callback(mut self, callback: IbcCallbackHandlerFn<Self, Error>) -> Self {
142 self.contract = self.contract.with_ibc_callback(callback);
143 self
144 }
145
146 pub const fn with_module_ibc(
148 mut self,
149 module_handler: ModuleIbcHandlerFn<Self, Error>,
150 ) -> Self {
151 self.contract = self.contract.with_module_ibc(module_handler);
152 self
153 }
154}
155
156#[cfg(test)]
157mod tests {
158
159 use abstract_testing::prelude::*;
160 use cosmwasm_std::Response;
161
162 use super::*;
163 use crate::mock::{AdapterMockResult, MOCK_ADAPTER, TEST_METADATA};
164
165 #[coverage_helper::test]
166 fn set_and_get_target() -> AdapterMockResult {
167 let mut mock = MOCK_ADAPTER;
168 let target = Addr::unchecked("target");
169 mock.target_account = Some(Account::new(target.clone()));
170 assert_eq!(mock.target()?, &target);
171 Ok(())
172 }
173
174 #[coverage_helper::test]
175 fn builder_functions() {
176 crate::mock::MockAdapterContract::new(TEST_MODULE_ID, TEST_VERSION, Some(TEST_METADATA))
177 .with_instantiate(|_, _, _, _, _| Ok(Response::new().set_data("mock_init".as_bytes())))
178 .with_execute(|_, _, _, _, _| Ok(Response::new().set_data("mock_exec".as_bytes())))
179 .with_query(|_, _, _, _| cosmwasm_std::to_json_binary("mock_query").map_err(Into::into))
180 .with_sudo(|_, _, _, _| Ok(Response::new().set_data("mock_sudo".as_bytes())))
181 .with_ibc_callback(|_, _, _, _, _| {
182 Ok(Response::new().set_data("mock_callback".as_bytes()))
183 })
184 .with_replies(&[(1u64, |_, _, _, msg| {
185 #[allow(deprecated)]
186 Ok(Response::new().set_data(msg.result.unwrap().data.unwrap()))
187 })]);
188 }
189}