use super::{Contract, WasmPath};
use crate::{
environment::{
ChainInfoOwned, ChainState, CwEnv, QueryHandler, TxHandler, TxResponse, WasmQuerier,
},
error::CwEnvError,
log::contract_target,
};
use cosmwasm_std::{Addr, Binary, Coin, Empty};
use cw_multi_test::Contract as MockContract;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
pub trait ContractInstance<Chain: ChainState> {
fn as_instance(&self) -> &Contract<Chain>;
fn as_instance_mut(&mut self) -> &mut Contract<Chain>;
fn id(&self) -> String {
self.as_instance().id.clone()
}
fn address(&self) -> Result<Addr, CwEnvError> {
Contract::address(self.as_instance())
}
fn addr_str(&self) -> Result<String, CwEnvError> {
Contract::address(self.as_instance()).map(|addr| addr.into_string())
}
fn code_id(&self) -> Result<u64, CwEnvError> {
Contract::code_id(self.as_instance())
}
fn set_address(&self, address: &Addr) {
Contract::set_address(self.as_instance(), address)
}
fn set_default_address(&mut self, address: &Addr) {
Contract::set_default_address(self.as_instance_mut(), address)
}
fn set_code_id(&self, code_id: u64) {
Contract::set_code_id(self.as_instance(), code_id)
}
fn set_default_code_id(&mut self, code_id: u64) {
Contract::set_default_code_id(self.as_instance_mut(), code_id)
}
fn get_chain(&self) -> &Chain {
Contract::get_chain(self.as_instance())
}
}
pub trait InstantiableContract {
type InstantiateMsg: Serialize + Debug;
}
pub trait ExecutableContract {
type ExecuteMsg: Serialize + Debug;
}
pub trait QueryableContract {
type QueryMsg: Serialize + Debug;
}
pub trait MigratableContract {
type MigrateMsg: Serialize + Debug;
}
pub trait CwOrchExecute<Chain: TxHandler>: ExecutableContract + ContractInstance<Chain> {
fn execute(
&self,
execute_msg: &Self::ExecuteMsg,
coins: Option<&[Coin]>,
) -> Result<Chain::Response, CwEnvError> {
self.as_instance().execute(&execute_msg, coins)
}
}
impl<T: ExecutableContract + ContractInstance<Chain>, Chain: TxHandler> CwOrchExecute<Chain> for T {}
pub trait CwOrchInstantiate<Chain: TxHandler>:
InstantiableContract + ContractInstance<Chain>
{
fn instantiate(
&self,
instantiate_msg: &Self::InstantiateMsg,
admin: Option<&Addr>,
coins: Option<&[Coin]>,
) -> Result<Chain::Response, CwEnvError> {
self.as_instance()
.instantiate(instantiate_msg, admin, coins)
}
fn instantiate2(
&self,
instantiate_msg: &Self::InstantiateMsg,
admin: Option<&Addr>,
coins: Option<&[Coin]>,
salt: Binary,
) -> Result<Chain::Response, CwEnvError> {
self.as_instance()
.instantiate2(instantiate_msg, admin, coins, salt)
}
}
impl<T: InstantiableContract + ContractInstance<Chain>, Chain: TxHandler> CwOrchInstantiate<Chain>
for T
{
}
pub trait CwOrchQuery<Chain: QueryHandler + ChainState>:
QueryableContract + ContractInstance<Chain>
{
fn query<G: Serialize + DeserializeOwned + Debug>(
&self,
query_msg: &Self::QueryMsg,
) -> Result<G, CwEnvError> {
self.as_instance().query(query_msg)
}
}
impl<T: QueryableContract + ContractInstance<Chain>, Chain: QueryHandler + ChainState>
CwOrchQuery<Chain> for T
{
}
pub trait CwOrchMigrate<Chain: TxHandler>: MigratableContract + ContractInstance<Chain> {
fn migrate(
&self,
migrate_msg: &Self::MigrateMsg,
new_code_id: u64,
) -> Result<Chain::Response, CwEnvError> {
self.as_instance().migrate(migrate_msg, new_code_id)
}
}
impl<T: MigratableContract + ContractInstance<Chain>, Chain: TxHandler> CwOrchMigrate<Chain> for T {}
pub trait Uploadable {
fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
unimplemented!("no wasm file provided for this contract")
}
fn wrapper() -> Box<dyn MockContract<Empty, Empty>> {
unimplemented!("no wrapper function implemented for this contract")
}
}
pub trait CwOrchUpload<Chain: TxHandler>: ContractInstance<Chain> + Uploadable + Sized {
fn upload(&self) -> Result<Chain::Response, CwEnvError> {
self.as_instance().upload(self)
}
}
impl<T: ContractInstance<Chain> + Uploadable, Chain: TxHandler> CwOrchUpload<Chain> for T {}
pub trait CallAs<Chain: TxHandler>: CwOrchExecute<Chain> + ContractInstance<Chain> + Clone {
fn set_sender(&mut self, sender: &<Chain as TxHandler>::Sender) {
self.as_instance_mut().chain.set_sender(sender.clone())
}
fn call_as(&self, sender: &<Chain as TxHandler>::Sender) -> Self {
let mut contract = self.clone();
contract.set_sender(sender);
contract
}
}
impl<T: CwOrchExecute<Chain> + ContractInstance<Chain> + Clone, Chain: TxHandler> CallAs<Chain>
for T
{
}
pub trait ConditionalUpload<Chain: CwEnv>: CwOrchUpload<Chain> {
fn upload_if_needed(&self) -> Result<Option<TxResponse<Chain>>, CwEnvError> {
if self.latest_is_uploaded()? {
Ok(None)
} else {
Some(self.upload()).transpose().map_err(Into::into)
}
}
fn latest_is_uploaded(&self) -> Result<bool, CwEnvError> {
let Some(latest_uploaded_code_id) = self.code_id().ok() else {
return Ok(false);
};
let chain = self.get_chain();
let on_chain_hash = chain
.wasm_querier()
.code_id_hash(latest_uploaded_code_id)
.map_err(Into::into)?;
let local_hash = self.get_chain().wasm_querier().local_hash(self)?;
Ok(local_hash == on_chain_hash)
}
fn is_running_latest(&self) -> Result<bool, CwEnvError> {
let Some(latest_uploaded_code_id) = self.code_id().ok() else {
return Ok(false);
};
let chain = self.get_chain();
let info = chain
.wasm_querier()
.contract_info(self.address()?)
.map_err(Into::into)?;
Ok(latest_uploaded_code_id == info.code_id)
}
}
impl<T, Chain: CwEnv> ConditionalUpload<Chain> for T where T: CwOrchUpload<Chain> {}
pub trait ConditionalMigrate<Chain: CwEnv>:
CwOrchMigrate<Chain> + ConditionalUpload<Chain>
{
fn migrate_if_needed(
&self,
migrate_msg: &Self::MigrateMsg,
) -> Result<Option<TxResponse<Chain>>, CwEnvError> {
if self.is_running_latest()? {
log::info!(target: &contract_target(), "Skipped migration. {} is already running the latest code", self.id());
Ok(None)
} else {
Some(self.migrate(migrate_msg, self.code_id()?))
.transpose()
.map_err(Into::into)
}
}
fn upload_and_migrate_if_needed(
&self,
migrate_msg: &Self::MigrateMsg,
) -> Result<Option<Vec<TxResponse<Chain>>>, CwEnvError> {
let mut txs = Vec::with_capacity(2);
if let Some(tx) = self.upload_if_needed()? {
txs.push(tx);
};
if let Some(tx) = self.migrate_if_needed(migrate_msg)? {
txs.push(tx);
};
if txs.is_empty() {
Ok(None)
} else {
Ok(Some(txs))
}
}
}
impl<T, Chain: CwEnv> ConditionalMigrate<Chain> for T where
T: CwOrchMigrate<Chain> + ConditionalUpload<Chain>
{
}