abstract_ibc_host/
account_commands.rs

1use abstract_sdk::{
2    feature_objects::{AnsHost, RegistryContract},
3    std::{objects::ChannelEntry, ICS20},
4    Resolve,
5};
6use abstract_std::{
7    account::{self, ModuleInstallConfig},
8    ibc::PACKET_LIFETIME,
9    native_addrs,
10    objects::{module::ModuleInfo, module_reference::ModuleReference, AccountId, TruncatedChainId},
11    registry::Account,
12    ACCOUNT,
13};
14use cosmwasm_std::{
15    instantiate2_address, to_json_binary, wasm_execute, Coin, CosmosMsg, Deps, DepsMut, Empty, Env,
16    IbcMsg, Response, SubMsg, WasmMsg,
17};
18
19use crate::{
20    contract::{HostResponse, HostResult},
21    endpoints::reply::{INIT_BEFORE_ACTION_REPLY_ID, RESPONSE_REPLY_ID},
22    HostError,
23};
24
25/// Creates and registers account for remote Account
26#[allow(clippy::too_many_arguments)]
27pub fn receive_register(
28    deps: DepsMut,
29    env: Env,
30    account_id: AccountId,
31    name: Option<String>,
32    description: Option<String>,
33    link: Option<String>,
34    namespace: Option<String>,
35    install_modules: Vec<ModuleInstallConfig>,
36    with_reply: bool,
37    funds: Vec<Coin>,
38) -> HostResult {
39    let abstract_code_id =
40        native_addrs::abstract_code_id(&deps.querier, env.contract.address.clone())?;
41
42    let registry = RegistryContract::new(deps.as_ref(), abstract_code_id)?;
43    // verify that the origin last chain is the chain related to this channel, and that it is not `Local`
44    account_id.trace().verify_remote()?;
45    let salt = cosmwasm_std::to_json_binary(&account_id)?;
46
47    let account_module_info = ModuleInfo::from_id_latest(ACCOUNT)?;
48    let ModuleReference::Account(code_id) = registry
49        .query_module(account_module_info.clone(), &deps.querier)?
50        .reference
51    else {
52        return Err(HostError::RegistryError(
53            abstract_std::objects::registry::RegistryError::InvalidReference(account_module_info),
54        ));
55    };
56    let checksum = deps.querier.query_wasm_code_info(code_id)?.checksum;
57    let self_canon_addr = deps.api.addr_canonicalize(env.contract.address.as_str())?;
58
59    let create_account_msg = account::InstantiateMsg::<cosmwasm_std::Empty> {
60        code_id,
61        owner: Some(
62            abstract_std::objects::gov_type::GovernanceDetails::External {
63                governance_address: env.contract.address.into_string(),
64                governance_type: "abstract-ibc".into(), // at least 4 characters
65            },
66        ),
67        name,
68        description,
69        link,
70        // provide the origin chain id
71        account_id: Some(account_id.clone()),
72        install_modules,
73        namespace,
74        authenticator: None,
75    };
76
77    let account_canon_addr =
78        instantiate2_address(checksum.as_slice(), &self_canon_addr, salt.as_slice())?;
79    let account_addr = deps.api.addr_humanize(&account_canon_addr)?;
80
81    // create the message to instantiate the remote account
82    let account_creation_message = WasmMsg::Instantiate2 {
83        admin: Some(account_addr.to_string()),
84        code_id,
85        label: account_id.to_string(),
86        msg: to_json_binary(&create_account_msg)?,
87        funds,
88        salt,
89    };
90
91    // If we were ordered to have a reply after account creation
92    let sub_msg = if with_reply {
93        SubMsg::reply_on_success(account_creation_message, INIT_BEFORE_ACTION_REPLY_ID)
94    } else {
95        SubMsg::new(account_creation_message)
96    };
97
98    Ok(Response::new()
99        .add_submessage(sub_msg)
100        .add_attribute("action", "register"))
101}
102
103/// Execute account message on local account.
104pub fn receive_dispatch(
105    _deps: DepsMut,
106    account: Account,
107    account_msgs: Vec<account::ExecuteMsg>,
108) -> HostResult {
109    // execute the message on the account
110    let msgs = account_msgs
111        .into_iter()
112        .map(|msg| wasm_execute(account.addr(), &msg, vec![]))
113        .collect::<Result<Vec<_>, _>>()?;
114
115    let response = Response::new()
116        .add_attribute("action", "receive_dispatch")
117        // This is used to forward the data of the calling message
118        // This means that only the last present data of will be forwarded
119        .add_submessages(
120            msgs.into_iter()
121                .map(|m| SubMsg::reply_on_success(m.clone(), RESPONSE_REPLY_ID)),
122        );
123
124    Ok(response)
125}
126
127/// processes PacketMsg::SendAllBack variant
128pub fn receive_send_all_back(
129    deps: DepsMut,
130    env: Env,
131    account: Account,
132    client_account_address: String,
133    src_chain: TruncatedChainId,
134) -> HostResult {
135    let wasm_msg = send_all_back(
136        deps.as_ref(),
137        env,
138        account,
139        client_account_address,
140        src_chain,
141    )?;
142
143    Ok(HostResponse::action("receive_dispatch").add_message(wasm_msg))
144}
145
146/// construct the msg to send all the assets back
147pub fn send_all_back(
148    deps: Deps,
149    env: Env,
150    account: Account,
151    client_account_address: String,
152    src_chain: TruncatedChainId,
153) -> Result<CosmosMsg, HostError> {
154    // get the ICS20 channel information
155    let abstract_code_id =
156        native_addrs::abstract_code_id(&deps.querier, env.contract.address.clone())?;
157
158    let ans = AnsHost::new(deps, abstract_code_id)?;
159    let ics20_channel_entry = ChannelEntry {
160        connected_chain: src_chain,
161        protocol: ICS20.to_string(),
162    };
163    let ics20_channel_id = ics20_channel_entry.resolve(&deps.querier, &ans)?;
164    // get all the coins for the account
165    let coins = deps.querier.query_all_balances(account.addr())?;
166    // Construct ics20 messages to send all the coins back
167    let mut msgs: Vec<CosmosMsg> = vec![];
168    for coin in coins {
169        msgs.push(
170            IbcMsg::Transfer {
171                channel_id: ics20_channel_id.clone(),
172                to_address: client_account_address.to_string(),
173                amount: coin,
174                timeout: env.block.time.plus_seconds(PACKET_LIFETIME).into(),
175                memo: None,
176            }
177            .into(),
178        )
179    }
180    // call the message to send everything back through the account
181    let account_msg = wasm_execute(
182        account.into_addr(),
183        &account::ExecuteMsg::<Empty>::Execute { msgs },
184        vec![],
185    )?;
186    Ok(account_msg.into())
187}
188
189/// get the account from the registry contract
190pub fn get_account(deps: Deps, env: &Env, account_id: &AccountId) -> Result<Account, HostError> {
191    let abstract_code_id =
192        native_addrs::abstract_code_id(&deps.querier, env.contract.address.clone())?;
193
194    let registry = RegistryContract::new(deps, abstract_code_id)?;
195    let account = registry.account(account_id, &deps.querier)?;
196    Ok(account)
197}