1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use crate::error::CronCatContractError;
use crate::types::{CronCatTaskSubmessageParams, SubMessageReplyType};
use crate::{REPLY_CRONCAT_TASK_CREATION, TASKS_NAME};
use cosmwasm_std::CosmosMsg::Wasm;
use cosmwasm_std::WasmMsg::Execute;
use cosmwasm_std::{to_binary, Addr, CosmosMsg, MessageInfo, QuerierWrapper, SubMsg};
use croncat_sdk_factory::msg::ContractMetadataResponse;
use croncat_sdk_factory::msg::FactoryQueryMsg::LatestContract;
use croncat_sdk_tasks::msg::TasksExecuteMsg::CreateTask;
use croncat_sdk_tasks::types::TaskRequest;

/// Given the CronCat factory address, returns the proper contract address if it exists.
/// See [TASKS_NAME](crate::TASKS_NAME), [MANAGER_NAME](crate::MANAGER_NAME), and [AGENTS_NAME](crate::AGENTS_NAME)
pub fn get_latest_croncat_contract(
    querier: &QuerierWrapper,
    croncat_factory_address: Addr,
    croncat_contract_name: String,
) -> Result<Addr, CronCatContractError> {
    let query_factory_msg = LatestContract {
        contract_name: croncat_contract_name.clone(),
    };
    let latest_contract_res: ContractMetadataResponse =
        querier.query_wasm_smart(&croncat_factory_address, &query_factory_msg)?;

    // Check validity of result
    let contract_metadata =
        latest_contract_res
            .metadata
            .ok_or(CronCatContractError::NoSuchContractOnFactory {
                contract_name: croncat_contract_name,
                factory_addr: croncat_factory_address,
                version: "latest".to_owned(),
            })?;

    Ok(contract_metadata.contract_addr)
}

/// Given the CronCat factory address and a version, returns the proper contract address if it exists.
/// Use [get_latest_croncat_contract] to get latest version
/// See [TASKS_NAME](crate::TASKS_NAME), [MANAGER_NAME](crate::MANAGER_NAME), and [AGENTS_NAME](crate::AGENTS_NAME)
pub fn get_croncat_contract(
    querier: &QuerierWrapper,
    croncat_factory_address: Addr,
    croncat_contract_name: String,
    croncat_version: String,
) -> Result<Addr, CronCatContractError> {
    // Parse string to a version array, for example from "1.0" to [1,0]
    let croncat_version_parsed = croncat_version
        .split('.')
        .map(|ver| {
            ver.parse()
                .map_err(|_| CronCatContractError::InvalidVersionString {})
        })
        .collect::<Result<Vec<u8>, _>>()?;

    // Raw query the factory
    let contract_addr = croncat_factory::state::CONTRACT_ADDRS
        .query(
            querier,
            croncat_factory_address.clone(),
            (&croncat_contract_name, &croncat_version_parsed),
        )?
        .ok_or(CronCatContractError::NoSuchContractOnFactory {
            contract_name: croncat_contract_name,
            factory_addr: croncat_factory_address,
            version: croncat_version,
        })?;

    Ok(contract_addr)
}

/// Returns a SubMsg
/// This can be conveniently used when returning a Response
/// where you might handle what happened in the reply entry point.
/// `Ok(Response::new().add_submessage(returned_val))`
pub fn create_croncat_task_submessage(
    querier: &QuerierWrapper,
    info: MessageInfo,
    croncat_factory_address: Addr,
    task: TaskRequest,
    reply_type: Option<CronCatTaskSubmessageParams>,
) -> Result<SubMsg, CronCatContractError> {
    croncat_basic_validation(&info)?;
    let wasm_exec_msg =
        create_croncat_task_cosmos_msg(querier, info, croncat_factory_address, task)?;

    // If no reply_type is provided, will use "always"
    let (reply_id, sub_reply_type) = match reply_type {
        None => (REPLY_CRONCAT_TASK_CREATION, SubMessageReplyType::Always),
        Some(params) => (
            params.reply_id.unwrap_or(REPLY_CRONCAT_TASK_CREATION),
            params.reply_type.unwrap_or(SubMessageReplyType::Always),
        ),
    };

    let sub_message = match sub_reply_type {
        SubMessageReplyType::Always => SubMsg::reply_always(wasm_exec_msg, reply_id),
        SubMessageReplyType::OnError => SubMsg::reply_on_error(wasm_exec_msg, reply_id),
        SubMessageReplyType::OnSuccess => SubMsg::reply_on_success(wasm_exec_msg, reply_id),
    };

    Ok(sub_message)
}

/// Returns a CosmosMsg
/// This can be conveniently used when returning a Response
/// `Ok(Response::new().add_message(returned_val))`
pub fn create_croncat_task_message(
    querier: &QuerierWrapper,
    info: MessageInfo,
    croncat_factory_address: Addr,
    task: TaskRequest,
) -> Result<CosmosMsg, CronCatContractError> {
    croncat_basic_validation(&info)?;
    let wasm_exec_msg =
        create_croncat_task_cosmos_msg(querier, info, croncat_factory_address, task)?;

    Ok(wasm_exec_msg)
}

/// This returns a CosmosMsg Execute object
/// It's a helper in this crate, but is exposed
/// for external usage as well.
pub fn create_croncat_task_cosmos_msg(
    querier: &QuerierWrapper,
    info: MessageInfo,
    croncat_factory_address: Addr,
    task: TaskRequest,
) -> Result<CosmosMsg, CronCatContractError> {
    let tasks_addr =
        get_latest_croncat_contract(querier, croncat_factory_address, TASKS_NAME.to_string())?;

    Ok(Wasm(Execute {
        contract_addr: String::from(tasks_addr),
        msg: to_binary(&CreateTask {
            task: Box::new(task),
        })?,
        funds: info.funds,
    }))
}

pub fn croncat_basic_validation(info: &MessageInfo) -> Result<(), CronCatContractError> {
    // To create a CronCat task you will need to provide funds. All funds sent
    // to this method will be used for task creation
    // Because we cannot detect detailed error information from replies
    // (See CosmWasm Discord: https://discord.com/channels/737637324434833438/737643344712171600/1040920787512725574)
    // We'll add a check here to ensure they've attached funds
    if info.funds.is_empty() {
        return Err(CronCatContractError::TaskCreationNoFunds {});
    }

    Ok(())
}