abstract_std/account.rs
1//! # Account Account
2//!
3//! `abstract_std::account` implements the contract interface and state lay-out.
4//!
5//! ## Description
6//!
7//! The Account is part of the Core Abstract Account contracts along with the `abstract_std::account` contract.
8//! This contract is responsible for:
9//! - Managing modules instantiation and migrations.
10//! - Managing permissions.
11//! - Upgrading the Account and its modules.
12//! - Providing module name to address resolution.
13//!
14//! **The account should be set as the contract/CosmWasm admin by default on your modules.**
15//! ## Migration
16//! Migrating this contract is done by calling `ExecuteMsg::Upgrade` with `abstract::account` as module.
17//!
18use cosmwasm_schema::QueryResponses;
19use cosmwasm_std::{Binary, Coin, CosmosMsg, Empty};
20
21use crate::objects::{
22 gov_type::{GovAction, GovernanceDetails, TopLevelOwnerResponse},
23 module::ModuleInfo,
24 ownership::Ownership,
25 AccountId,
26};
27use cosmwasm_std::Addr;
28use cw2::ContractVersion;
29
30use state::{AccountInfo, SuspensionStatus};
31
32pub mod state {
33 use std::collections::HashSet;
34
35 use cosmwasm_std::Addr;
36 use cw_storage_plus::{Item, Map};
37
38 use crate::objects::{module::ModuleId, storage_namespaces, AccountId};
39
40 pub type SuspensionStatus = bool;
41
42 /// Abstract Account details.
43 #[cosmwasm_schema::cw_serde]
44 #[derive(Default)]
45 pub struct AccountInfo {
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub name: Option<String>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub description: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub link: Option<String>,
52 }
53
54 impl AccountInfo {
55 pub fn has_info(&self) -> bool {
56 self.name.is_some() || self.description.is_some() || self.link.is_some()
57 }
58 }
59
60 #[cosmwasm_schema::cw_serde]
61 pub struct WhitelistedModules(pub Vec<Addr>);
62
63 pub const WHITELISTED_MODULES: Item<WhitelistedModules> =
64 Item::new(storage_namespaces::account::WHITELISTED_MODULES);
65
66 /// Suspension status
67 pub const SUSPENSION_STATUS: Item<SuspensionStatus> =
68 Item::new(storage_namespaces::account::SUSPENSION_STATUS);
69 /// Info about the Account
70 pub const INFO: Item<AccountInfo> = Item::new(storage_namespaces::account::INFO);
71 /// Enabled Abstract modules
72 pub const ACCOUNT_MODULES: Map<ModuleId, Addr> =
73 Map::new(storage_namespaces::account::ACCOUNT_MODULES);
74 /// Stores the dependency relationship between modules
75 /// map module -> modules that depend on module.
76 pub const DEPENDENTS: Map<ModuleId, HashSet<String>> =
77 Map::new(storage_namespaces::account::DEPENDENTS);
78 /// List of sub-accounts
79 pub const SUB_ACCOUNTS: Map<u32, cosmwasm_std::Empty> =
80 Map::new(storage_namespaces::account::SUB_ACCOUNTS);
81 /// Account Id storage key
82 pub const ACCOUNT_ID: Item<AccountId> = Item::new(storage_namespaces::account::ACCOUNT_ID);
83 /// Temporary state variable that allows for checking access control on admin operation
84 pub const CALLING_TO_AS_ADMIN: Item<Addr> =
85 Item::new(storage_namespaces::account::CALLING_TO_AS_ADMIN);
86 pub const CALLING_TO_AS_ADMIN_WILD_CARD: &str = "calling-to-wild-card";
87
88 #[cfg(feature = "xion")]
89 /// XION temporary state. This is used to make sure that the account only has admin rights when authenticated through XION
90 /// If a call originates from the top level owner account address, there are 2 cases within xion:
91 /// - It's a call made from the account directly.
92 /// In that case, the tx just got through BeforeTx and the admin_auth flag is set during that hook.
93 /// --> [`AUTH_ADMIN`] should be true
94 /// - It's a call made from the account through another module.
95 /// In that case, it means that the execute function on the account was executed successfully
96 /// --> [`AUTH_ADMIN`] flag is not set anymore, because it's removed at the end of each execution
97 ///
98 /// If a module wants to have admin rights, they need to call through the account, but as soon as the account finishes execution, the Auth_Admin flag is set to false, so there is no chance to do admin actions there
99 ///
100 /// This flag can only be set in the beforeTx hook and is always removed in the AfterTx hook
101 pub const AUTH_ADMIN: Item<bool> = Item::new(storage_namespaces::account::AUTH_ADMIN);
102
103 // Additional states, not listed here: cw_gov_ownable::GovOwnership, authenticators, if chain supports it
104}
105
106#[cosmwasm_schema::cw_serde]
107pub struct MigrateMsg {}
108
109/// Account Instantiate Msg
110/// https://github.com/burnt-labs/contracts/blob/main/contracts/account/src/msg.rs
111#[cosmwasm_schema::cw_serde]
112// ANCHOR: init_msg
113pub struct InstantiateMsg<Authenticator = Empty> {
114 /// Code id of the account
115 pub code_id: u64,
116 /// The ownership structure of the Account.
117 pub owner: GovernanceDetails<String>,
118 /// Optionally specify an account-id for this account.
119 /// If provided must be between (u32::MAX/2)..u32::MAX range.
120 pub account_id: Option<AccountId>,
121 /// Optional authenticator for use with the `abstractaccount` cosmos-sdk module.
122 pub authenticator: Option<Authenticator>,
123 /// Optionally claim a namespace on instantiation.
124 /// Any fees will be deducted from the account and should be provided on instantiation.
125 pub namespace: Option<String>,
126 /// Optionally install modules on instantiation.
127 /// Any fees will be deducted from the account and should be provided on instantiation.
128 #[serde(default)]
129 pub install_modules: Vec<ModuleInstallConfig>,
130 /// Optional account name.
131 pub name: Option<String>,
132 /// Optional account description.
133 pub description: Option<String>,
134 /// Optional account link.
135 pub link: Option<String>,
136}
137// ANCHOR_END: init_msg
138
139/// Callback message to set the dependencies after module upgrades.
140#[cosmwasm_schema::cw_serde]
141pub struct CallbackMsg {}
142
143#[cosmwasm_schema::cw_serde]
144#[derive(cw_orch::ExecuteFns)]
145pub enum ExecuteMsg<Authenticator = Empty> {
146 /// Executes the provided messages if sender is whitelisted
147 #[cw_orch(fn_name("execute_msgs"), payable)]
148 Execute {
149 msgs: Vec<CosmosMsg<Empty>>,
150 },
151 /// Execute a message and forward the Response data
152 #[cw_orch(payable)]
153 ExecuteWithData {
154 msg: CosmosMsg<Empty>,
155 },
156 /// Forward execution message to module
157 #[cw_orch(payable)]
158 ExecuteOnModule {
159 module_id: String,
160 exec_msg: Binary,
161 /// Funds attached from account to the module
162 funds: Vec<Coin>,
163 },
164 /// Execute a Wasm Message with Account Admin privileges
165 #[cw_orch(payable)]
166 AdminExecute {
167 addr: String,
168 msg: Binary,
169 },
170 /// Forward execution message to module with Account Admin privileges
171 #[cw_orch(payable)]
172 AdminExecuteOnModule {
173 module_id: String,
174 msg: Binary,
175 },
176 /// Queries the Abstract Ica Client with the provided action query.
177 /// Provides access to different ICA implementations for different ecosystems.
178 IcaAction {
179 /// Query of type `abstract-ica-client::msg::QueryMsg`
180 action_query_msg: Binary,
181 },
182 /// Update Abstract-specific configuration of the module.
183 /// Only callable by the owner.
184 UpdateInternalConfig(InternalConfigAction),
185 /// Install module using module factory, callable by Owner
186 #[cw_orch(payable)]
187 InstallModules {
188 // Module information and Instantiate message to instantiate the contract
189 modules: Vec<ModuleInstallConfig>,
190 },
191 /// Uninstall a module given its ID.
192 UninstallModule {
193 module_id: String,
194 },
195 /// Upgrade the module to a new version
196 /// If module is `abstract::account` then the contract will do a self-migration.
197 /// Self-migration is protected and only possible to the [`crate::objects::module_reference::ModuleReference::Account`] registered in Registry
198 Upgrade {
199 modules: Vec<(ModuleInfo, Option<Binary>)>,
200 },
201 /// Creates a sub-account on the account
202 #[cw_orch(payable)]
203 CreateSubAccount {
204 // Name of the sub-account
205 name: Option<String>,
206 // Description of the account
207 description: Option<String>,
208 // URL linked to the account
209 link: Option<String>,
210 // optionally specify a namespace for the sub-account
211 namespace: Option<String>,
212 // Provide list of module to install after sub-account creation
213 install_modules: Vec<ModuleInstallConfig>,
214 /// If `None`, will create a new local account without asserting account-id.
215 ///
216 /// When provided sequence in 0..2147483648 range: The tx will error
217 /// When provided sequence in 2147483648..u32::MAX range: Signals use of unclaimed Account Id in this range. The tx will error if this account-id already claimed. Useful for instantiate2 address prediction.
218 account_id: Option<u32>,
219 },
220 /// Update info
221 UpdateInfo {
222 name: Option<String>,
223 description: Option<String>,
224 link: Option<String>,
225 },
226 /// Update account statuses
227 UpdateStatus {
228 is_suspended: Option<bool>,
229 },
230 /// Actions called by internal or external sub-accounts
231 UpdateSubAccount(UpdateSubAccountAction),
232 /// Update the contract's ownership. The `action`
233 /// can propose transferring ownership to an account,
234 /// accept a pending ownership transfer, or renounce the ownership
235 /// of the account permanently.
236 UpdateOwnership(GovAction),
237
238 AddAuthMethod {
239 add_authenticator: Authenticator,
240 },
241 RemoveAuthMethod {
242 id: u8,
243 },
244}
245
246#[cosmwasm_schema::cw_serde]
247#[derive(QueryResponses, cw_orch::QueryFns)]
248pub enum QueryMsg {
249 /// Contains the enabled modules
250 /// Returns [`ConfigResponse`]
251 #[returns(ConfigResponse)]
252 Config {},
253 /// Query the versions of modules installed on the account given their `ids`.
254 /// Returns [`ModuleVersionsResponse`]
255 #[returns(ModuleVersionsResponse)]
256 ModuleVersions { ids: Vec<String> },
257 /// Query the addresses of modules installed on the account given their `ids`.
258 /// Returns [`ModuleAddressesResponse`]
259 #[returns(ModuleAddressesResponse)]
260 ModuleAddresses { ids: Vec<String> },
261 /// Query information of all modules installed on the account.
262 /// Returns [`ModuleInfosResponse`]
263 #[returns(ModuleInfosResponse)]
264 ModuleInfos {
265 start_after: Option<String>,
266 limit: Option<u8>,
267 },
268 /// Query the Account info.
269 /// Returns [`InfoResponse`]
270 #[returns(InfoResponse)]
271 Info {},
272 /// Returns [`SubAccountIdsResponse`]
273 #[returns(SubAccountIdsResponse)]
274 SubAccountIds {
275 start_after: Option<u32>,
276 limit: Option<u8>,
277 },
278 /// Returns [`TopLevelOwnerResponse`]
279 #[returns(TopLevelOwnerResponse)]
280 TopLevelOwner {},
281 /// Query the contract's ownership information
282 #[returns(Ownership<String>)]
283 Ownership {},
284
285 /// Query the pubkey associated with this account.
286 #[returns(Binary)]
287 AuthenticatorByID { id: u8 },
288 /// Query the pubkey associated with this account.
289 #[returns(Binary)]
290 AuthenticatorIDs {},
291}
292
293/// Module info and init message
294#[non_exhaustive]
295#[cosmwasm_schema::cw_serde]
296pub struct ModuleInstallConfig {
297 pub module: ModuleInfo,
298 pub init_msg: Option<Binary>,
299}
300
301impl ModuleInstallConfig {
302 pub fn new(module: ModuleInfo, init_msg: Option<Binary>) -> Self {
303 Self { module, init_msg }
304 }
305}
306/// Internal configuration actions accessible from the [`ExecuteMsg::UpdateInternalConfig`] message.
307#[cosmwasm_schema::cw_serde]
308#[non_exhaustive]
309pub enum InternalConfigAction {
310 /// Updates the [`state::ACCOUNT_MODULES`] map
311 /// Only callable by owner.
312 UpdateModuleAddresses {
313 to_add: Vec<(String, String)>,
314 to_remove: Vec<String>,
315 },
316 /// Update the execution whitelist in [`state::WHITELISTED_MODULES`]
317 /// Only callable by owner.
318 UpdateWhitelist {
319 /// Addresses to add to the Account's execution whitelist
320 to_add: Vec<String>,
321 /// Addresses to remove from the Account's execution whitelist
322 to_remove: Vec<String>,
323 },
324}
325
326#[cosmwasm_schema::cw_serde]
327#[non_exhaustive]
328pub enum UpdateSubAccountAction {
329 /// Unregister sub-account
330 /// It will unregister sub-account from the state
331 /// Could be called only by the sub-account itself
332 UnregisterSubAccount { id: u32 },
333 /// Register sub-account
334 /// It will register new sub-account into the state
335 /// Could be called by the sub-account
336 RegisterSubAccount { id: u32 },
337}
338
339#[cosmwasm_schema::cw_serde]
340pub struct ModuleVersionsResponse {
341 pub versions: Vec<ContractVersion>,
342}
343
344#[cosmwasm_schema::cw_serde]
345pub struct ModuleAddressesResponse {
346 pub modules: Vec<(String, Addr)>,
347}
348
349#[cosmwasm_schema::cw_serde]
350pub struct InfoResponse {
351 pub info: AccountInfo,
352}
353
354#[cosmwasm_schema::cw_serde]
355pub struct AccountModuleInfo {
356 pub id: String,
357 pub version: ContractVersion,
358 pub address: Addr,
359}
360
361#[cosmwasm_schema::cw_serde]
362pub struct ModuleInfosResponse {
363 pub module_infos: Vec<AccountModuleInfo>,
364}
365
366#[cosmwasm_schema::cw_serde]
367pub struct SubAccountIdsResponse {
368 pub sub_accounts: Vec<u32>,
369}
370
371#[cosmwasm_schema::cw_serde]
372pub struct ConfigResponse {
373 pub whitelisted_addresses: Vec<Addr>,
374 pub account_id: AccountId,
375 pub is_suspended: SuspensionStatus,
376 pub registry_address: Addr,
377 pub module_factory_address: Addr,
378}
379
380#[cfg(test)]
381mod test {
382 use cw_orch::core::serde_json::json;
383
384 use super::*;
385
386 #[coverage_helper::test]
387 fn minimal_deser_instantiate_test() {
388 let init_msg_binary: InstantiateMsg =
389 cosmwasm_std::from_json(br#"{"code_id": 1, "owner": {"renounced": {}}}"#).unwrap();
390 assert_eq!(
391 init_msg_binary,
392 InstantiateMsg {
393 code_id: 1,
394 owner: GovernanceDetails::Renounced {},
395 authenticator: Default::default(),
396 account_id: Default::default(),
397 namespace: Default::default(),
398 install_modules: Default::default(),
399 name: Default::default(),
400 description: Default::default(),
401 link: Default::default()
402 }
403 );
404
405 let init_msg_string: InstantiateMsg = cosmwasm_std::from_json(
406 json!({
407 "code_id": 1,
408 "owner": GovernanceDetails::Monarchy {
409 monarch: "bob".to_owned()
410 }
411 })
412 .to_string(),
413 )
414 .unwrap();
415 assert_eq!(
416 init_msg_string,
417 InstantiateMsg {
418 code_id: 1,
419 owner: GovernanceDetails::Monarchy {
420 monarch: "bob".to_owned()
421 },
422 authenticator: Default::default(),
423 account_id: Default::default(),
424 namespace: Default::default(),
425 install_modules: Default::default(),
426 name: Default::default(),
427 description: Default::default(),
428 link: Default::default()
429 }
430 )
431 }
432}