andromeda_std/ado_contract/
execute.rs

1use crate::ado_contract::ADOContract;
2use crate::amp::addresses::AndrAddr;
3use crate::amp::messages::AMPPkt;
4use crate::common::context::ExecuteContext;
5use crate::common::reply::ReplyId;
6use crate::error::from_semver;
7use crate::os::{aos_querier::AOSQuerier, economics::ExecuteMsg as EconomicsExecuteMsg};
8use crate::{
9    ado_base::{AndromedaMsg, InstantiateMsg},
10    error::ContractError,
11};
12use cosmwasm_std::{
13    attr, ensure, from_json, to_json_binary, Addr, Api, ContractInfoResponse, CosmosMsg, Deps,
14    DepsMut, Env, MessageInfo, QuerierWrapper, Response, Storage, SubMsg, WasmMsg,
15};
16use cw2::{get_contract_version, set_contract_version};
17use semver::Version;
18use serde::de::DeserializeOwned;
19use serde::Serialize;
20
21type ExecuteContextFunction<M, E = ContractError> = fn(ExecuteContext, M) -> Result<Response, E>;
22
23impl<'a> ADOContract<'a> {
24    pub fn instantiate(
25        &self,
26        storage: &mut dyn Storage,
27        env: Env,
28        api: &dyn Api,
29        querier: &QuerierWrapper,
30        info: MessageInfo,
31        msg: InstantiateMsg,
32    ) -> Result<Response, ContractError> {
33        let ado_type = if msg.ado_type.starts_with("crates.io:andromeda-") {
34            msg.ado_type.strip_prefix("crates.io:andromeda-").unwrap()
35        } else if msg.ado_type.starts_with("crates.io:") {
36            msg.ado_type.strip_prefix("crates.io:").unwrap()
37        } else {
38            &msg.ado_type
39        };
40        cw2::set_contract_version(storage, ado_type, msg.ado_version)?;
41        let mut owner = api.addr_validate(&msg.owner.unwrap_or(info.sender.to_string()))?;
42        self.original_publisher.save(storage, &info.sender)?;
43        self.block_height.save(storage, &env.block.height)?;
44        self.ado_type.save(storage, &ado_type.to_string())?;
45        self.kernel_address
46            .save(storage, &api.addr_validate(&msg.kernel_address)?)?;
47        let mut attributes = vec![
48            attr("method", "instantiate"),
49            attr("type", ado_type),
50            attr("kernel_address", msg.kernel_address),
51        ];
52
53        // We do not want to store app contracts for the kernel, exit early if current contract is kernel
54        let is_kernel_contract = ado_type.contains("kernel");
55        if is_kernel_contract {
56            self.owner.save(storage, &owner)?;
57            attributes.push(attr("owner", owner));
58            return Ok(Response::new().add_attributes(attributes));
59        }
60
61        // Check if the sender is an app contract to allow for automatic storage of app contrcat reference
62        let maybe_contract_info = querier.query_wasm_contract_info(info.sender.clone());
63        let is_sender_contract = maybe_contract_info.is_ok();
64        if is_sender_contract {
65            let ContractInfoResponse { code_id, .. } = maybe_contract_info?;
66            let sender_ado_type = AOSQuerier::ado_type_getter(
67                querier,
68                &self.get_adodb_address(storage, querier)?,
69                code_id,
70            )?;
71            let is_sender_app = Some("app-contract".to_string()) == sender_ado_type;
72            // Automatically save app contract reference if creator is an app contract
73            if is_sender_app {
74                self.app_contract
75                    .save(storage, &Addr::unchecked(info.sender.to_string()))?;
76                let app_owner = AOSQuerier::ado_owner_getter(querier, &info.sender)?;
77                owner = app_owner;
78                attributes.push(attr("app_contract", info.sender.to_string()));
79            }
80        }
81
82        self.owner.save(storage, &owner)?;
83        attributes.push(attr("owner", owner));
84        Ok(Response::new().add_attributes(attributes))
85    }
86
87    /// Handles execution of ADO specific messages.
88    pub fn execute(
89        &self,
90        ctx: ExecuteContext,
91        msg: impl Serialize,
92    ) -> Result<Response, ContractError> {
93        let msg = to_json_binary(&msg)?;
94        match from_json::<AndromedaMsg>(&msg) {
95            Ok(msg) => match msg {
96                AndromedaMsg::Ownership(msg) => {
97                    self.execute_ownership(ctx.deps, ctx.env, ctx.info, msg)
98                }
99                AndromedaMsg::UpdateAppContract { address } => {
100                    self.execute_update_app_contract(ctx.deps, ctx.info, address, None)
101                }
102                AndromedaMsg::UpdateKernelAddress { address } => {
103                    self.update_kernel_address(ctx.deps, ctx.info, address)
104                }
105                #[cfg(feature = "modules")]
106                AndromedaMsg::RegisterModule { module } => {
107                    self.validate_module_address(&ctx.deps.as_ref(), &module)?;
108                    self.execute_register_module(
109                        ctx.deps.storage,
110                        ctx.info.sender.as_str(),
111                        module,
112                        true,
113                    )
114                }
115                #[cfg(feature = "modules")]
116                AndromedaMsg::DeregisterModule { module_idx } => {
117                    self.execute_deregister_module(ctx.deps, ctx.info, module_idx)
118                }
119                #[cfg(feature = "modules")]
120                AndromedaMsg::AlterModule { module_idx, module } => {
121                    self.validate_module_address(&ctx.deps.as_ref(), &module)?;
122                    self.execute_alter_module(ctx.deps, ctx.info, module_idx, module)
123                }
124                AndromedaMsg::Permissioning(msg) => self.execute_permissioning(ctx, msg),
125                AndromedaMsg::AMPReceive(_) => panic!("AMP Receive should be handled separately"),
126            },
127            _ => Err(ContractError::NotImplemented { msg: None }),
128        }
129    }
130
131    pub fn migrate(
132        &self,
133        deps: DepsMut,
134        contract_name: &str,
135        contract_version: &str,
136    ) -> Result<Response, ContractError> {
137        // New version
138        let version: Version = contract_version.parse().map_err(from_semver)?;
139
140        // Old version
141        let stored = get_contract_version(deps.storage)?;
142        let storage_version: Version = stored.version.parse().map_err(from_semver)?;
143        let contract_name = if contract_name.starts_with("crates.io:andromeda-") {
144            contract_name.strip_prefix("crates.io:andromeda-").unwrap()
145        } else if contract_name.starts_with("crates.io:") {
146            contract_name.strip_prefix("crates.io:").unwrap()
147        } else {
148            contract_name
149        };
150        ensure!(
151            stored.contract == contract_name,
152            ContractError::CannotMigrate {
153                previous_contract: stored.contract,
154            }
155        );
156
157        // New version has to be newer/greater than the old version
158        ensure!(
159            storage_version < version,
160            ContractError::CannotMigrate {
161                previous_contract: stored.version,
162            }
163        );
164
165        set_contract_version(deps.storage, contract_name, contract_version)?;
166        Ok(Response::default())
167    }
168    /// Validates all provided `AndrAddr` addresses.
169    ///
170    /// Requires the VFS address to be set if any address is a VFS path.
171    /// Automatically validates all stored modules.
172    pub fn validate_andr_addresses(
173        &self,
174        deps: &Deps,
175        addresses: Vec<AndrAddr>,
176    ) -> Result<(), ContractError> {
177        let vfs_address = self.get_vfs_address(deps.storage, &deps.querier);
178        match vfs_address {
179            Ok(vfs_address) => {
180                #[cfg(feature = "modules")]
181                {
182                    let mut addresses = addresses.clone();
183                    let modules = self.load_modules(deps.storage)?;
184                    if !modules.is_empty() {
185                        let andr_addresses: Vec<AndrAddr> =
186                            modules.into_iter().map(|m| m.address).collect();
187                        addresses.extend(andr_addresses);
188                    }
189                }
190                for address in addresses {
191                    self.validate_andr_address(deps, address, vfs_address.clone())?;
192                }
193                Ok(())
194            }
195            Err(_) => {
196                for address in addresses {
197                    ensure!(address.is_addr(deps.api), ContractError::InvalidAddress {});
198                }
199                Ok(())
200            }
201        }
202    }
203
204    /// Validates the given `AndrAddr` address.
205    pub(crate) fn validate_andr_address(
206        &self,
207        deps: &Deps,
208        address: AndrAddr,
209        vfs_address: Addr,
210    ) -> Result<(), ContractError> {
211        address.validate(deps.api)?;
212        if !address.is_addr(deps.api) {
213            address.get_raw_address_from_vfs(deps, vfs_address)?;
214        }
215        Ok(())
216    }
217
218    #[inline]
219    /// Gets the stored address for the Kernel contract
220    pub fn get_kernel_address(&self, storage: &dyn Storage) -> Result<Addr, ContractError> {
221        let kernel_address = self.kernel_address.load(storage)?;
222        Ok(kernel_address)
223    }
224
225    #[inline]
226    /// Gets the current address for the VFS contract.
227    pub fn get_vfs_address(
228        &self,
229        storage: &dyn Storage,
230        querier: &QuerierWrapper,
231    ) -> Result<Addr, ContractError> {
232        let kernel_address = self.get_kernel_address(storage)?;
233        AOSQuerier::vfs_address_getter(querier, &kernel_address)
234    }
235
236    #[inline]
237    /// Gets the current address for the VFS contract.
238    pub fn get_adodb_address(
239        &self,
240        storage: &dyn Storage,
241        querier: &QuerierWrapper,
242    ) -> Result<Addr, ContractError> {
243        let kernel_address = self.get_kernel_address(storage)?;
244        AOSQuerier::adodb_address_getter(querier, &kernel_address)
245    }
246
247    /// Handles receiving and verifies an AMPPkt from the Kernel before executing the appropriate messages.
248    ///
249    /// Calls the provided handler with the AMP packet attached within the context.
250    pub fn execute_amp_receive<M: DeserializeOwned>(
251        &self,
252        ctx: ExecuteContext,
253        mut packet: AMPPkt,
254        handler: ExecuteContextFunction<M>,
255    ) -> Result<Response, ContractError> {
256        packet.verify_origin(&ctx.info, &ctx.deps.as_ref())?;
257        let ctx = ctx.with_ctx(packet.clone());
258        ensure!(
259            packet.messages.len() == 1,
260            ContractError::InvalidPacket {
261                error: Some("Invalid packet length".to_string())
262            }
263        );
264        let msg = packet.messages.pop().unwrap();
265        let msg: M = from_json(msg.message)?;
266        let response = handler(ctx, msg)?;
267        Ok(response)
268    }
269
270    /// Generates a message to pay a fee for a given action by the given payee
271    ///
272    /// Fees are paid in the following fallthrough priority:
273    /// 1. ADO Contract
274    /// 2. App Contract for sending ADO
275    /// 3. Provided Payee
276    ///
277    /// If any of the above cannot pay the fee the remainder is paid by the next in the list until no remainder remains.
278    /// If there is still a remainder after all 3 payments then the fee cannot be paid and the message will error.
279    pub fn pay_fee(
280        &self,
281        storage: &dyn Storage,
282        querier: &QuerierWrapper,
283        action: String,
284        payee: Addr,
285    ) -> Result<SubMsg, ContractError> {
286        let kernel_address = self.get_kernel_address(storage)?;
287        let economics_contract_address =
288            AOSQuerier::kernel_address_getter(querier, &kernel_address, "economics")?;
289        let economics_msg = EconomicsExecuteMsg::PayFee { action, payee };
290        let msg = SubMsg::reply_on_error(
291            CosmosMsg::Wasm(WasmMsg::Execute {
292                contract_addr: economics_contract_address.to_string(),
293                msg: to_json_binary(&economics_msg)?,
294                funds: vec![],
295            }),
296            ReplyId::PayFee.repr(),
297        );
298
299        Ok(msg)
300    }
301
302    /// Updates the current kernel address used by the ADO
303    /// Requires the sender to be the owner of the ADO
304    pub fn update_kernel_address(
305        &self,
306        deps: DepsMut,
307        info: MessageInfo,
308        address: Addr,
309    ) -> Result<Response, ContractError> {
310        ensure!(
311            self.is_contract_owner(deps.storage, info.sender.as_str())?,
312            ContractError::Unauthorized {}
313        );
314        self.kernel_address.save(deps.storage, &address)?;
315        Ok(Response::new()
316            .add_attribute("action", "update_kernel_address")
317            .add_attribute("address", address))
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324    #[cfg(feature = "modules")]
325    use crate::ado_base::modules::Module;
326    use crate::testing::mock_querier::MOCK_KERNEL_CONTRACT;
327    #[cfg(feature = "modules")]
328    use crate::testing::mock_querier::{mock_dependencies_custom, MOCK_APP_CONTRACT};
329    #[cfg(feature = "modules")]
330    use cosmwasm_std::Uint64;
331    use cosmwasm_std::{
332        testing::{mock_dependencies, mock_env, mock_info},
333        Addr,
334    };
335
336    #[test]
337    #[cfg(feature = "modules")]
338    fn test_register_module_invalid_identifier() {
339        let contract = ADOContract::default();
340        let mut deps = mock_dependencies_custom(&[]);
341
342        let info = mock_info("owner", &[]);
343        let deps_mut = deps.as_mut();
344        contract
345            .instantiate(
346                deps_mut.storage,
347                mock_env(),
348                deps_mut.api,
349                &deps_mut.querier,
350                info.clone(),
351                InstantiateMsg {
352                    ado_type: "type".to_string(),
353
354                    ado_version: "version".to_string(),
355                    kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
356                    owner: None,
357                },
358            )
359            .unwrap();
360
361        contract
362            .app_contract
363            .save(deps_mut.storage, &Addr::unchecked(MOCK_APP_CONTRACT))
364            .unwrap();
365
366        let module = Module::new("module".to_owned(), "z".to_string(), false);
367
368        let msg = AndromedaMsg::RegisterModule { module };
369
370        let res = contract.execute(ExecuteContext::new(deps.as_mut(), info, mock_env()), msg);
371        assert!(res.is_err())
372    }
373
374    #[test]
375    #[cfg(feature = "modules")]
376    fn test_alter_module_invalid_identifier() {
377        let contract = ADOContract::default();
378        let mut deps = mock_dependencies_custom(&[]);
379
380        let info = mock_info("owner", &[]);
381        let deps_mut = deps.as_mut();
382        contract
383            .instantiate(
384                deps_mut.storage,
385                mock_env(),
386                deps_mut.api,
387                &deps_mut.querier,
388                info.clone(),
389                InstantiateMsg {
390                    ado_type: "type".to_string(),
391                    ado_version: "version".to_string(),
392
393                    kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
394                    owner: None,
395                },
396            )
397            .unwrap();
398        contract
399            .register_modules(
400                info.sender.as_str(),
401                deps_mut.storage,
402                Some(vec![Module::new("module", "cosmos1...".to_string(), false)]),
403            )
404            .unwrap();
405
406        contract
407            .app_contract
408            .save(deps_mut.storage, &Addr::unchecked(MOCK_APP_CONTRACT))
409            .unwrap();
410
411        let module = Module::new("/m".to_owned(), "z".to_string(), false);
412
413        let msg = AndromedaMsg::AlterModule {
414            module_idx: Uint64::new(1),
415            module,
416        };
417
418        let res = contract.execute(ExecuteContext::new(deps.as_mut(), info, mock_env()), msg);
419        assert!(res.is_err())
420    }
421
422    #[test]
423    fn test_update_app_contract() {
424        let contract = ADOContract::default();
425        let mut deps = mock_dependencies();
426
427        let info = mock_info("owner", &[]);
428        let deps_mut = deps.as_mut();
429        contract
430            .instantiate(
431                deps_mut.storage,
432                mock_env(),
433                deps_mut.api,
434                &deps_mut.querier,
435                info.clone(),
436                InstantiateMsg {
437                    ado_type: "type".to_string(),
438                    ado_version: "version".to_string(),
439
440                    kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
441                    owner: None,
442                },
443            )
444            .unwrap();
445
446        let address = String::from("address");
447
448        let msg = AndromedaMsg::UpdateAppContract {
449            address: address.clone(),
450        };
451
452        let res = contract
453            .execute(ExecuteContext::new(deps.as_mut(), info, mock_env()), msg)
454            .unwrap();
455
456        assert_eq!(
457            Response::new()
458                .add_attribute("action", "update_app_contract")
459                .add_attribute("address", address),
460            res
461        );
462    }
463
464    #[test]
465    #[cfg(feature = "modules")]
466    fn test_update_app_contract_invalid_module() {
467        let contract = ADOContract::default();
468        let mut deps = mock_dependencies_custom(&[]);
469
470        let info = mock_info("owner", &[]);
471        let deps_mut = deps.as_mut();
472        contract
473            .instantiate(
474                deps_mut.storage,
475                mock_env(),
476                deps_mut.api,
477                &deps_mut.querier,
478                info.clone(),
479                InstantiateMsg {
480                    ado_type: "type".to_string(),
481                    ado_version: "version".to_string(),
482                    owner: None,
483
484                    kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
485                },
486            )
487            .unwrap();
488        contract
489            .register_modules(
490                info.sender.as_str(),
491                deps_mut.storage,
492                Some(vec![Module::new("module", "cosmos1...".to_string(), false)]),
493            )
494            .unwrap();
495    }
496
497    #[test]
498    fn test_update_kernel_address() {
499        let contract = ADOContract::default();
500        let mut deps = mock_dependencies();
501
502        let info = mock_info("owner", &[]);
503        let deps_mut = deps.as_mut();
504        contract
505            .instantiate(
506                deps_mut.storage,
507                mock_env(),
508                deps_mut.api,
509                &deps_mut.querier,
510                info.clone(),
511                InstantiateMsg {
512                    ado_type: "type".to_string(),
513                    ado_version: "version".to_string(),
514                    owner: None,
515
516                    kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
517                },
518            )
519            .unwrap();
520
521        let address = String::from("address");
522
523        let msg = AndromedaMsg::UpdateKernelAddress {
524            address: Addr::unchecked(address.clone()),
525        };
526
527        let res = contract
528            .execute(ExecuteContext::new(deps.as_mut(), info, mock_env()), msg)
529            .unwrap();
530
531        let msg = AndromedaMsg::UpdateKernelAddress {
532            address: Addr::unchecked(address.clone()),
533        };
534
535        assert_eq!(
536            Response::new()
537                .add_attribute("action", "update_kernel_address")
538                .add_attribute("address", address),
539            res
540        );
541
542        let res = contract.execute(
543            ExecuteContext::new(deps.as_mut(), mock_info("not_owner", &[]), mock_env()),
544            msg,
545        );
546        assert!(res.is_err())
547    }
548}