abstract_proxy/
contract.rs

1use abstract_macros::abstract_response;
2use abstract_sdk::{
3    feature_objects::AnsHost,
4    std::{
5        objects::account::ACCOUNT_ID,
6        proxy::{
7            state::{State, ADMIN, ANS_HOST, STATE},
8            AssetConfigResponse, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
9        },
10        PROXY,
11    },
12};
13use abstract_std::objects::{
14    module_version::assert_contract_upgrade, oracle::Oracle, price_source::UncheckedPriceSource,
15};
16use cosmwasm_std::{
17    to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, SubMsgResult,
18};
19use semver::Version;
20
21use crate::{commands::*, error::ProxyError, queries::*, reply};
22
23pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
24pub(crate) const RESPONSE_REPLY_ID: u64 = 1;
25
26#[abstract_response(PROXY)]
27pub struct ProxyResponse;
28
29/// The result type for the proxy contract.
30pub type ProxyResult<T = Response> = Result<T, ProxyError>;
31
32/*
33    The proxy is the bank account of the account. It owns the liquidity and acts as a proxy contract.
34    Whitelisted dApps construct messages for this contract. The dApps are controlled by the Manager.
35*/
36
37#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
38pub fn instantiate(
39    mut deps: DepsMut,
40    _env: Env,
41    _info: MessageInfo,
42    msg: InstantiateMsg,
43) -> ProxyResult {
44    // Use CW2 to set the contract version, this is needed for migrations
45    cw2::set_contract_version(deps.storage, PROXY, CONTRACT_VERSION)?;
46
47    let manager_addr = deps.api.addr_validate(&msg.manager_addr)?;
48    ACCOUNT_ID.save(deps.storage, &msg.account_id)?;
49    STATE.save(
50        deps.storage,
51        &State {
52            modules: vec![manager_addr.clone()],
53        },
54    )?;
55    let ans_host = AnsHost {
56        address: deps.api.addr_validate(&msg.ans_host_address)?,
57    };
58    ANS_HOST.save(deps.storage, &ans_host)?;
59    let admin_addr = Some(manager_addr);
60    ADMIN.set(deps.branch(), admin_addr)?;
61
62    if let Some(base_asset) = msg.base_asset {
63        let oracle = Oracle::new();
64        oracle.update_assets(
65            deps,
66            &ans_host,
67            vec![(base_asset, UncheckedPriceSource::None)],
68            vec![],
69        )?;
70    }
71    Ok(Response::default())
72}
73
74#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
75pub fn execute(deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg) -> ProxyResult {
76    match msg {
77        ExecuteMsg::ModuleAction { msgs } => execute_module_action(deps, info, msgs),
78        ExecuteMsg::ModuleActionWithData { msg } => execute_module_action_response(deps, info, msg),
79        ExecuteMsg::IbcAction { msg } => execute_ibc_action(deps, info, msg),
80        ExecuteMsg::SetAdmin { admin } => set_admin(deps, info, &admin),
81        ExecuteMsg::AddModules { modules } => add_modules(deps, info, modules),
82        ExecuteMsg::RemoveModule { module } => remove_module(deps, info, module),
83        ExecuteMsg::UpdateAssets { to_add, to_remove } => {
84            update_assets(deps, info, to_add, to_remove)
85        }
86    }
87}
88
89#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
90pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> ProxyResult {
91    let version: Version = CONTRACT_VERSION.parse().unwrap();
92
93    assert_contract_upgrade(deps.storage, PROXY, version)?;
94    cw2::set_contract_version(deps.storage, PROXY, CONTRACT_VERSION)?;
95    Ok(Response::default())
96}
97
98#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
99pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ProxyResult<Binary> {
100    match msg {
101        QueryMsg::Config {} => to_json_binary(&query_config(deps)?),
102        QueryMsg::TotalValue {} => to_json_binary(&query_total_value(deps, env)?),
103        QueryMsg::HoldingAmount { identifier } => {
104            to_json_binary(&query_holding_amount(deps, env, identifier)?)
105        }
106        QueryMsg::TokenValue { identifier } => {
107            to_json_binary(&query_token_value(deps, env, identifier)?)
108        }
109        QueryMsg::AssetConfig { identifier } => to_json_binary(&AssetConfigResponse {
110            price_source: Oracle::new().asset_config(deps, &identifier)?,
111        }),
112        QueryMsg::AssetsConfig { start_after, limit } => {
113            to_json_binary(&query_oracle_asset_config(deps, start_after, limit)?)
114        }
115        QueryMsg::AssetsInfo { start_after, limit } => {
116            to_json_binary(&query_oracle_asset_info(deps, start_after, limit)?)
117        }
118        QueryMsg::BaseAsset {} => to_json_binary(&query_base_asset(deps)?),
119    }
120    .map_err(Into::into)
121}
122
123/// This just stores the result for future query
124#[cfg_attr(feature = "export", cosmwasm_std::entry_point)]
125pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> ProxyResult {
126    match &msg {
127        Reply {
128            id: RESPONSE_REPLY_ID,
129            result: SubMsgResult::Ok(_),
130        } => reply::forward_response_data(msg),
131        _ => Err(ProxyError::UnexpectedReply {}),
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use cosmwasm_std::testing::*;
138    use speculoos::prelude::*;
139
140    use super::*;
141    use crate::{contract, test_common::*};
142
143    mod migrate {
144        use abstract_std::AbstractError;
145
146        use super::*;
147
148        #[test]
149        fn disallow_same_version() -> ProxyResult<()> {
150            let mut deps = mock_dependencies();
151            mock_init(deps.as_mut());
152
153            let version: Version = CONTRACT_VERSION.parse().unwrap();
154
155            let res = contract::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
156
157            assert_that!(res).is_err().is_equal_to(ProxyError::Abstract(
158                AbstractError::CannotDowngradeContract {
159                    contract: PROXY.to_string(),
160                    from: version.clone(),
161                    to: version,
162                },
163            ));
164
165            Ok(())
166        }
167
168        #[test]
169        fn disallow_downgrade() -> ProxyResult<()> {
170            let mut deps = mock_dependencies();
171            mock_init(deps.as_mut());
172
173            let big_version = "999.999.999";
174            cw2::set_contract_version(deps.as_mut().storage, PROXY, big_version)?;
175
176            let version: Version = CONTRACT_VERSION.parse().unwrap();
177
178            let res = contract::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
179
180            assert_that!(res).is_err().is_equal_to(ProxyError::Abstract(
181                AbstractError::CannotDowngradeContract {
182                    contract: PROXY.to_string(),
183                    from: big_version.parse().unwrap(),
184                    to: version,
185                },
186            ));
187
188            Ok(())
189        }
190
191        #[test]
192        fn disallow_name_change() -> ProxyResult<()> {
193            let mut deps = mock_dependencies();
194            mock_init(deps.as_mut());
195
196            let old_version = "0.0.0";
197            let old_name = "old:contract";
198            cw2::set_contract_version(deps.as_mut().storage, old_name, old_version)?;
199
200            let res = contract::migrate(deps.as_mut(), mock_env(), MigrateMsg {});
201
202            assert_that!(res).is_err().is_equal_to(ProxyError::Abstract(
203                AbstractError::ContractNameMismatch {
204                    from: old_name.parse().unwrap(),
205                    to: PROXY.parse().unwrap(),
206                },
207            ));
208
209            Ok(())
210        }
211
212        #[test]
213        fn works() -> ProxyResult<()> {
214            let mut deps = mock_dependencies();
215            mock_init(deps.as_mut());
216
217            let version: Version = CONTRACT_VERSION.parse().unwrap();
218
219            let small_version = Version {
220                minor: version.minor - 1,
221                ..version.clone()
222            }
223            .to_string();
224            cw2::set_contract_version(deps.as_mut().storage, PROXY, small_version)?;
225
226            let res = contract::migrate(deps.as_mut(), mock_env(), MigrateMsg {})?;
227            assert_that!(res.messages).has_length(0);
228
229            assert_that!(cw2::get_contract_version(&deps.storage)?.version)
230                .is_equal_to(version.to_string());
231            Ok(())
232        }
233    }
234}