use crate::ado_contract::ADOContract;
use crate::amp::addresses::AndrAddr;
use crate::amp::messages::AMPPkt;
use crate::common::context::ExecuteContext;
use crate::common::reply::ReplyId;
use crate::error::from_semver;
use crate::os::{aos_querier::AOSQuerier, economics::ExecuteMsg as EconomicsExecuteMsg};
use crate::{
ado_base::{AndromedaMsg, InstantiateMsg},
error::ContractError,
};
use cosmwasm_std::{
attr, ensure, from_json, to_json_binary, Addr, Api, ContractInfoResponse, CosmosMsg, Deps,
DepsMut, Env, MessageInfo, QuerierWrapper, Response, Storage, SubMsg, WasmMsg,
};
use cw2::{get_contract_version, set_contract_version};
use semver::Version;
use serde::de::DeserializeOwned;
use serde::Serialize;
type ExecuteContextFunction<M, E = ContractError> = fn(ExecuteContext, M) -> Result<Response, E>;
impl<'a> ADOContract<'a> {
pub fn instantiate(
&self,
storage: &mut dyn Storage,
env: Env,
api: &dyn Api,
querier: &QuerierWrapper,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let ado_type = if msg.ado_type.starts_with("crates.io:andromeda-") {
msg.ado_type.strip_prefix("crates.io:andromeda-").unwrap()
} else if msg.ado_type.starts_with("crates.io:") {
msg.ado_type.strip_prefix("crates.io:").unwrap()
} else {
&msg.ado_type
};
cw2::set_contract_version(storage, ado_type, msg.ado_version)?;
let mut owner = api.addr_validate(&msg.owner.unwrap_or(info.sender.to_string()))?;
self.original_publisher.save(storage, &info.sender)?;
self.block_height.save(storage, &env.block.height)?;
self.ado_type.save(storage, &ado_type.to_string())?;
self.kernel_address
.save(storage, &api.addr_validate(&msg.kernel_address)?)?;
let mut attributes = vec![
attr("method", "instantiate"),
attr("type", ado_type),
attr("kernel_address", msg.kernel_address),
];
let is_kernel_contract = ado_type.contains("kernel");
if is_kernel_contract {
self.owner.save(storage, &owner)?;
attributes.push(attr("owner", owner));
return Ok(Response::new().add_attributes(attributes));
}
let maybe_contract_info = querier.query_wasm_contract_info(info.sender.clone());
let is_sender_contract = maybe_contract_info.is_ok();
if is_sender_contract {
let ContractInfoResponse { code_id, .. } = maybe_contract_info?;
let sender_ado_type = AOSQuerier::ado_type_getter(
querier,
&self.get_adodb_address(storage, querier)?,
code_id,
)?;
let is_sender_app = Some("app-contract".to_string()) == sender_ado_type;
if is_sender_app {
self.app_contract
.save(storage, &Addr::unchecked(info.sender.to_string()))?;
let app_owner = AOSQuerier::ado_owner_getter(querier, &info.sender)?;
owner = app_owner;
attributes.push(attr("app_contract", info.sender.to_string()));
}
}
self.owner.save(storage, &owner)?;
attributes.push(attr("owner", owner));
Ok(Response::new().add_attributes(attributes))
}
pub fn execute(
&self,
ctx: ExecuteContext,
msg: impl Serialize,
) -> Result<Response, ContractError> {
let msg = to_json_binary(&msg)?;
match from_json::<AndromedaMsg>(&msg) {
Ok(msg) => match msg {
AndromedaMsg::Ownership(msg) => {
self.execute_ownership(ctx.deps, ctx.env, ctx.info, msg)
}
AndromedaMsg::UpdateAppContract { address } => {
self.execute_update_app_contract(ctx.deps, ctx.info, address, None)
}
AndromedaMsg::UpdateKernelAddress { address } => {
self.update_kernel_address(ctx.deps, ctx.info, address)
}
#[cfg(feature = "modules")]
AndromedaMsg::RegisterModule { module } => {
self.validate_module_address(&ctx.deps.as_ref(), &module)?;
self.execute_register_module(
ctx.deps.storage,
ctx.info.sender.as_str(),
module,
true,
)
}
#[cfg(feature = "modules")]
AndromedaMsg::DeregisterModule { module_idx } => {
self.execute_deregister_module(ctx.deps, ctx.info, module_idx)
}
#[cfg(feature = "modules")]
AndromedaMsg::AlterModule { module_idx, module } => {
self.validate_module_address(&ctx.deps.as_ref(), &module)?;
self.execute_alter_module(ctx.deps, ctx.info, module_idx, module)
}
AndromedaMsg::Permissioning(msg) => self.execute_permissioning(ctx, msg),
AndromedaMsg::AMPReceive(_) => panic!("AMP Receive should be handled separately"),
},
_ => Err(ContractError::NotImplemented { msg: None }),
}
}
pub fn migrate(
&self,
deps: DepsMut,
contract_name: &str,
contract_version: &str,
) -> Result<Response, ContractError> {
let version: Version = contract_version.parse().map_err(from_semver)?;
let stored = get_contract_version(deps.storage)?;
let storage_version: Version = stored.version.parse().map_err(from_semver)?;
let contract_name = if contract_name.starts_with("crates.io:andromeda-") {
contract_name.strip_prefix("crates.io:andromeda-").unwrap()
} else if contract_name.starts_with("crates.io:") {
contract_name.strip_prefix("crates.io:").unwrap()
} else {
contract_name
};
ensure!(
stored.contract == contract_name,
ContractError::CannotMigrate {
previous_contract: stored.contract,
}
);
ensure!(
storage_version < version,
ContractError::CannotMigrate {
previous_contract: stored.version,
}
);
set_contract_version(deps.storage, contract_name, contract_version)?;
Ok(Response::default())
}
pub fn validate_andr_addresses(
&self,
deps: &Deps,
addresses: Vec<AndrAddr>,
) -> Result<(), ContractError> {
let vfs_address = self.get_vfs_address(deps.storage, &deps.querier);
match vfs_address {
Ok(vfs_address) => {
#[cfg(feature = "modules")]
{
let mut addresses = addresses.clone();
let modules = self.load_modules(deps.storage)?;
if !modules.is_empty() {
let andr_addresses: Vec<AndrAddr> =
modules.into_iter().map(|m| m.address).collect();
addresses.extend(andr_addresses);
}
}
for address in addresses {
self.validate_andr_address(deps, address, vfs_address.clone())?;
}
Ok(())
}
Err(_) => {
for address in addresses {
ensure!(address.is_addr(deps.api), ContractError::InvalidAddress {});
}
Ok(())
}
}
}
pub(crate) fn validate_andr_address(
&self,
deps: &Deps,
address: AndrAddr,
vfs_address: Addr,
) -> Result<(), ContractError> {
address.validate(deps.api)?;
if !address.is_addr(deps.api) {
address.get_raw_address_from_vfs(deps, vfs_address)?;
}
Ok(())
}
#[inline]
pub fn get_kernel_address(&self, storage: &dyn Storage) -> Result<Addr, ContractError> {
let kernel_address = self.kernel_address.load(storage)?;
Ok(kernel_address)
}
#[inline]
pub fn get_vfs_address(
&self,
storage: &dyn Storage,
querier: &QuerierWrapper,
) -> Result<Addr, ContractError> {
let kernel_address = self.get_kernel_address(storage)?;
AOSQuerier::vfs_address_getter(querier, &kernel_address)
}
#[inline]
pub fn get_adodb_address(
&self,
storage: &dyn Storage,
querier: &QuerierWrapper,
) -> Result<Addr, ContractError> {
let kernel_address = self.get_kernel_address(storage)?;
AOSQuerier::adodb_address_getter(querier, &kernel_address)
}
pub fn execute_amp_receive<M: DeserializeOwned>(
&self,
ctx: ExecuteContext,
mut packet: AMPPkt,
handler: ExecuteContextFunction<M>,
) -> Result<Response, ContractError> {
packet.verify_origin(&ctx.info, &ctx.deps.as_ref())?;
let ctx = ctx.with_ctx(packet.clone());
ensure!(
packet.messages.len() == 1,
ContractError::InvalidPacket {
error: Some("Invalid packet length".to_string())
}
);
let msg = packet.messages.pop().unwrap();
let msg: M = from_json(msg.message)?;
let response = handler(ctx, msg)?;
Ok(response)
}
pub fn pay_fee(
&self,
storage: &dyn Storage,
querier: &QuerierWrapper,
action: String,
payee: Addr,
) -> Result<SubMsg, ContractError> {
let kernel_address = self.get_kernel_address(storage)?;
let economics_contract_address =
AOSQuerier::kernel_address_getter(querier, &kernel_address, "economics")?;
let economics_msg = EconomicsExecuteMsg::PayFee { action, payee };
let msg = SubMsg::reply_on_error(
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: economics_contract_address.to_string(),
msg: to_json_binary(&economics_msg)?,
funds: vec![],
}),
ReplyId::PayFee.repr(),
);
Ok(msg)
}
pub fn update_kernel_address(
&self,
deps: DepsMut,
info: MessageInfo,
address: Addr,
) -> Result<Response, ContractError> {
ensure!(
self.is_contract_owner(deps.storage, info.sender.as_str())?,
ContractError::Unauthorized {}
);
self.kernel_address.save(deps.storage, &address)?;
Ok(Response::new()
.add_attribute("action", "update_kernel_address")
.add_attribute("address", address))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "modules")]
use crate::ado_base::modules::Module;
use crate::testing::mock_querier::MOCK_KERNEL_CONTRACT;
#[cfg(feature = "modules")]
use crate::testing::mock_querier::{mock_dependencies_custom, MOCK_APP_CONTRACT};
#[cfg(feature = "modules")]
use cosmwasm_std::Uint64;
use cosmwasm_std::{
testing::{mock_dependencies, mock_env, mock_info},
Addr,
};
#[test]
#[cfg(feature = "modules")]
fn test_register_module_invalid_identifier() {
let contract = ADOContract::default();
let mut deps = mock_dependencies_custom(&[]);
let info = mock_info("owner", &[]);
let deps_mut = deps.as_mut();
contract
.instantiate(
deps_mut.storage,
mock_env(),
deps_mut.api,
&deps_mut.querier,
info.clone(),
InstantiateMsg {
ado_type: "type".to_string(),
ado_version: "version".to_string(),
kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
owner: None,
},
)
.unwrap();
contract
.app_contract
.save(deps_mut.storage, &Addr::unchecked(MOCK_APP_CONTRACT))
.unwrap();
let module = Module::new("module".to_owned(), "z".to_string(), false);
let msg = AndromedaMsg::RegisterModule { module };
let res = contract.execute(ExecuteContext::new(deps.as_mut(), info, mock_env()), msg);
assert!(res.is_err())
}
#[test]
#[cfg(feature = "modules")]
fn test_alter_module_invalid_identifier() {
let contract = ADOContract::default();
let mut deps = mock_dependencies_custom(&[]);
let info = mock_info("owner", &[]);
let deps_mut = deps.as_mut();
contract
.instantiate(
deps_mut.storage,
mock_env(),
deps_mut.api,
&deps_mut.querier,
info.clone(),
InstantiateMsg {
ado_type: "type".to_string(),
ado_version: "version".to_string(),
kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
owner: None,
},
)
.unwrap();
contract
.register_modules(
info.sender.as_str(),
deps_mut.storage,
Some(vec![Module::new("module", "cosmos1...".to_string(), false)]),
)
.unwrap();
contract
.app_contract
.save(deps_mut.storage, &Addr::unchecked(MOCK_APP_CONTRACT))
.unwrap();
let module = Module::new("/m".to_owned(), "z".to_string(), false);
let msg = AndromedaMsg::AlterModule {
module_idx: Uint64::new(1),
module,
};
let res = contract.execute(ExecuteContext::new(deps.as_mut(), info, mock_env()), msg);
assert!(res.is_err())
}
#[test]
fn test_update_app_contract() {
let contract = ADOContract::default();
let mut deps = mock_dependencies();
let info = mock_info("owner", &[]);
let deps_mut = deps.as_mut();
contract
.instantiate(
deps_mut.storage,
mock_env(),
deps_mut.api,
&deps_mut.querier,
info.clone(),
InstantiateMsg {
ado_type: "type".to_string(),
ado_version: "version".to_string(),
kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
owner: None,
},
)
.unwrap();
let address = String::from("address");
let msg = AndromedaMsg::UpdateAppContract {
address: address.clone(),
};
let res = contract
.execute(ExecuteContext::new(deps.as_mut(), info, mock_env()), msg)
.unwrap();
assert_eq!(
Response::new()
.add_attribute("action", "update_app_contract")
.add_attribute("address", address),
res
);
}
#[test]
#[cfg(feature = "modules")]
fn test_update_app_contract_invalid_module() {
let contract = ADOContract::default();
let mut deps = mock_dependencies_custom(&[]);
let info = mock_info("owner", &[]);
let deps_mut = deps.as_mut();
contract
.instantiate(
deps_mut.storage,
mock_env(),
deps_mut.api,
&deps_mut.querier,
info.clone(),
InstantiateMsg {
ado_type: "type".to_string(),
ado_version: "version".to_string(),
owner: None,
kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
},
)
.unwrap();
contract
.register_modules(
info.sender.as_str(),
deps_mut.storage,
Some(vec![Module::new("module", "cosmos1...".to_string(), false)]),
)
.unwrap();
}
#[test]
fn test_update_kernel_address() {
let contract = ADOContract::default();
let mut deps = mock_dependencies();
let info = mock_info("owner", &[]);
let deps_mut = deps.as_mut();
contract
.instantiate(
deps_mut.storage,
mock_env(),
deps_mut.api,
&deps_mut.querier,
info.clone(),
InstantiateMsg {
ado_type: "type".to_string(),
ado_version: "version".to_string(),
owner: None,
kernel_address: MOCK_KERNEL_CONTRACT.to_string(),
},
)
.unwrap();
let address = String::from("address");
let msg = AndromedaMsg::UpdateKernelAddress {
address: Addr::unchecked(address.clone()),
};
let res = contract
.execute(ExecuteContext::new(deps.as_mut(), info, mock_env()), msg)
.unwrap();
let msg = AndromedaMsg::UpdateKernelAddress {
address: Addr::unchecked(address.clone()),
};
assert_eq!(
Response::new()
.add_attribute("action", "update_kernel_address")
.add_attribute("address", address),
res
);
let res = contract.execute(
ExecuteContext::new(deps.as_mut(), mock_info("not_owner", &[]), mock_env()),
msg,
);
assert!(res.is_err())
}
}