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 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 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 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 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 let version: Version = contract_version.parse().map_err(from_semver)?;
139
140 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 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 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 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 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 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 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 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 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 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}