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    /// This field provides the new code id of the contract
109    /// This is only necessary for migrations from XION accounts.
110    pub code_id: Option<u64>,
111}
112
113/// Account Instantiate Msg
114/// https://github.com/burnt-labs/contracts/blob/main/contracts/account/src/msg.rs
115#[cosmwasm_schema::cw_serde]
116// ANCHOR: init_msg
117pub struct InstantiateMsg<Authenticator = Empty> {
118    /// Code id of the account
119    pub code_id: u64,
120    /// The ownership structure of the Account.
121    pub owner: Option<GovernanceDetails<String>>,
122    /// Optionally specify an account-id for this account.
123    /// If provided must be between (u32::MAX/2)..u32::MAX range.
124    pub account_id: Option<AccountId>,
125    /// Optional authenticator for use with the `abstractaccount` cosmos-sdk module.
126    pub authenticator: Option<Authenticator>,
127    /// Optionally claim a namespace on instantiation.
128    /// Any fees will be deducted from the account and should be provided on instantiation.
129    pub namespace: Option<String>,
130    /// Optionally install modules on instantiation.
131    /// Any fees will be deducted from the account and should be provided on instantiation.
132    #[serde(default)]
133    pub install_modules: Vec<ModuleInstallConfig>,
134    /// Optional account name.
135    pub name: Option<String>,
136    /// Optional account description.
137    pub description: Option<String>,
138    /// Optional account link.
139    pub link: Option<String>,
140}
141// ANCHOR_END: init_msg
142
143/// Callback message to set the dependencies after module upgrades.
144#[cosmwasm_schema::cw_serde]
145pub struct CallbackMsg {}
146
147#[cosmwasm_schema::cw_serde]
148#[derive(cw_orch::ExecuteFns)]
149pub enum ExecuteMsg<Authenticator = Empty> {
150    /// Executes the provided messages if sender is whitelisted
151    #[cw_orch(fn_name("execute_msgs"), payable)]
152    Execute {
153        msgs: Vec<CosmosMsg<Empty>>,
154    },
155    /// Execute a message and forward the Response data
156    #[cw_orch(payable)]
157    ExecuteWithData {
158        msg: CosmosMsg<Empty>,
159    },
160    /// Forward execution message to module
161    #[cw_orch(payable)]
162    ExecuteOnModule {
163        module_id: String,
164        exec_msg: Binary,
165        /// Funds attached from account to the module
166        funds: Vec<Coin>,
167    },
168    /// Execute a Wasm Message with Account Admin privileges    
169    #[cw_orch(payable)]
170    AdminExecute {
171        addr: String,
172        msg: Binary,
173    },
174    /// Forward execution message to module with Account Admin privileges
175    #[cw_orch(payable)]
176    AdminExecuteOnModule {
177        module_id: String,
178        msg: Binary,
179    },
180    /// Queries the Abstract Ica Client with the provided action query.
181    /// Provides access to different ICA implementations for different ecosystems.
182    IcaAction {
183        /// Query of type `abstract-ica-client::msg::QueryMsg`
184        action_query_msg: Binary,
185    },
186    /// Update Abstract-specific configuration of the module.
187    /// Only callable by the owner.
188    UpdateInternalConfig(InternalConfigAction),
189    /// Install module using module factory, callable by Owner
190    #[cw_orch(payable)]
191    InstallModules {
192        // Module information and Instantiate message to instantiate the contract
193        modules: Vec<ModuleInstallConfig>,
194    },
195    /// Uninstall a module given its ID.
196    UninstallModule {
197        module_id: String,
198    },
199    /// Upgrade the module to a new version
200    /// If module is `abstract::account` then the contract will do a self-migration.
201    /// Self-migration is protected and only possible to the [`crate::objects::module_reference::ModuleReference::Account`] registered in Registry
202    Upgrade {
203        modules: Vec<(ModuleInfo, Option<Binary>)>,
204    },
205    /// Creates a sub-account on the account
206    #[cw_orch(payable)]
207    CreateSubAccount {
208        // Name of the sub-account
209        name: Option<String>,
210        // Description of the account
211        description: Option<String>,
212        // URL linked to the account
213        link: Option<String>,
214        // optionally specify a namespace for the sub-account
215        namespace: Option<String>,
216        // Provide list of module to install after sub-account creation
217        install_modules: Vec<ModuleInstallConfig>,
218        /// If `None`, will create a new local account without asserting account-id.
219        ///
220        /// When provided sequence in 0..2147483648 range: The tx will error
221        /// 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.
222        account_id: Option<u32>,
223    },
224    /// Update info
225    UpdateInfo {
226        name: Option<String>,
227        description: Option<String>,
228        link: Option<String>,
229    },
230    /// Update account statuses
231    UpdateStatus {
232        is_suspended: Option<bool>,
233    },
234    /// Actions called by internal or external sub-accounts
235    UpdateSubAccount(UpdateSubAccountAction),
236    /// Update the contract's ownership. The `action`
237    /// can propose transferring ownership to an account,
238    /// accept a pending ownership transfer, or renounce the ownership
239    /// of the account permanently.
240    UpdateOwnership(GovAction),
241
242    AddAuthMethod {
243        add_authenticator: Authenticator,
244    },
245    RemoveAuthMethod {
246        id: u8,
247    },
248}
249
250#[cosmwasm_schema::cw_serde]
251#[derive(QueryResponses, cw_orch::QueryFns)]
252pub enum QueryMsg {
253    /// Contains the enabled modules
254    /// Returns [`ConfigResponse`]
255    #[returns(ConfigResponse)]
256    Config {},
257    /// Query the versions of modules installed on the account given their `ids`.
258    /// Returns [`ModuleVersionsResponse`]
259    #[returns(ModuleVersionsResponse)]
260    ModuleVersions { ids: Vec<String> },
261    /// Query the addresses of modules installed on the account given their `ids`.
262    /// Returns [`ModuleAddressesResponse`]
263    #[returns(ModuleAddressesResponse)]
264    ModuleAddresses { ids: Vec<String> },
265    /// Query information of all modules installed on the account.
266    /// Returns [`ModuleInfosResponse`]
267    #[returns(ModuleInfosResponse)]
268    ModuleInfos {
269        start_after: Option<String>,
270        limit: Option<u8>,
271    },
272    /// Query the Account info.
273    /// Returns [`InfoResponse`]
274    #[returns(InfoResponse)]
275    Info {},
276    /// Returns [`SubAccountIdsResponse`]
277    #[returns(SubAccountIdsResponse)]
278    SubAccountIds {
279        start_after: Option<u32>,
280        limit: Option<u8>,
281    },
282    /// Returns [`TopLevelOwnerResponse`]
283    #[returns(TopLevelOwnerResponse)]
284    TopLevelOwner {},
285    /// Query the contract's ownership information
286    #[returns(Ownership<String>)]
287    Ownership {},
288
289    /// Query the pubkey associated with this account.
290    #[returns(Binary)]
291    AuthenticatorByID { id: u8 },
292    /// Query the pubkey associated with this account.
293    #[returns(Binary)]
294    AuthenticatorIDs {},
295}
296
297/// Module info and init message
298#[non_exhaustive]
299#[cosmwasm_schema::cw_serde]
300pub struct ModuleInstallConfig {
301    pub module: ModuleInfo,
302    pub init_msg: Option<Binary>,
303}
304
305impl ModuleInstallConfig {
306    pub fn new(module: ModuleInfo, init_msg: Option<Binary>) -> Self {
307        Self { module, init_msg }
308    }
309}
310/// Internal configuration actions accessible from the [`ExecuteMsg::UpdateInternalConfig`] message.
311#[cosmwasm_schema::cw_serde]
312#[non_exhaustive]
313pub enum InternalConfigAction {
314    /// Updates the [`state::ACCOUNT_MODULES`] map
315    /// Only callable by owner.
316    UpdateModuleAddresses {
317        to_add: Vec<(String, String)>,
318        to_remove: Vec<String>,
319    },
320    /// Update the execution whitelist in [`state::WHITELISTED_MODULES`]
321    /// Only callable by owner.
322    UpdateWhitelist {
323        /// Addresses to add to the Account's execution whitelist
324        to_add: Vec<String>,
325        /// Addresses to remove from the Account's execution whitelist
326        to_remove: Vec<String>,
327    },
328}
329
330#[cosmwasm_schema::cw_serde]
331#[non_exhaustive]
332pub enum UpdateSubAccountAction {
333    /// Unregister sub-account
334    /// It will unregister sub-account from the state
335    /// Could be called only by the sub-account itself
336    UnregisterSubAccount { id: u32 },
337    /// Register sub-account
338    /// It will register new sub-account into the state
339    /// Could be called by the sub-account
340    RegisterSubAccount { id: u32 },
341}
342
343#[cosmwasm_schema::cw_serde]
344pub struct ModuleVersionsResponse {
345    pub versions: Vec<ContractVersion>,
346}
347
348#[cosmwasm_schema::cw_serde]
349pub struct ModuleAddressesResponse {
350    pub modules: Vec<(String, Addr)>,
351}
352
353#[cosmwasm_schema::cw_serde]
354pub struct InfoResponse {
355    pub info: AccountInfo,
356}
357
358#[cosmwasm_schema::cw_serde]
359pub struct AccountModuleInfo {
360    pub id: String,
361    pub version: ContractVersion,
362    pub address: Addr,
363}
364
365#[cosmwasm_schema::cw_serde]
366pub struct ModuleInfosResponse {
367    pub module_infos: Vec<AccountModuleInfo>,
368}
369
370#[cosmwasm_schema::cw_serde]
371pub struct SubAccountIdsResponse {
372    pub sub_accounts: Vec<u32>,
373}
374
375#[cosmwasm_schema::cw_serde]
376pub struct ConfigResponse {
377    pub whitelisted_addresses: Vec<Addr>,
378    pub account_id: AccountId,
379    pub is_suspended: SuspensionStatus,
380    pub registry_address: Addr,
381    pub module_factory_address: Addr,
382}
383
384#[cfg(test)]
385mod test {
386    use cw_orch::core::serde_json::json;
387
388    use super::*;
389
390    #[coverage_helper::test]
391    fn minimal_deser_instantiate_test() {
392        let init_msg_binary: InstantiateMsg =
393            cosmwasm_std::from_json(br#"{"code_id": 1, "owner": {"renounced": {}}}"#).unwrap();
394        assert_eq!(
395            init_msg_binary,
396            InstantiateMsg {
397                code_id: 1,
398                owner: Some(GovernanceDetails::Renounced {}),
399                authenticator: Default::default(),
400                account_id: Default::default(),
401                namespace: Default::default(),
402                install_modules: Default::default(),
403                name: Default::default(),
404                description: Default::default(),
405                link: Default::default()
406            }
407        );
408
409        let init_msg_string: InstantiateMsg = cosmwasm_std::from_json(
410            json!({
411                "code_id": 1,
412                "owner": GovernanceDetails::Monarchy {
413                    monarch: "bob".to_owned()
414                }
415            })
416            .to_string(),
417        )
418        .unwrap();
419        assert_eq!(
420            init_msg_string,
421            InstantiateMsg {
422                code_id: 1,
423                owner: Some(GovernanceDetails::Monarchy {
424                    monarch: "bob".to_owned()
425                }),
426                authenticator: Default::default(),
427                account_id: Default::default(),
428                namespace: Default::default(),
429                install_modules: Default::default(),
430                name: Default::default(),
431                description: Default::default(),
432                link: Default::default()
433            }
434        )
435    }
436}