croncat_integration_utils/
handle_incoming_task.rs

1use crate::error::CronCatContractError;
2use crate::types::HandleIncomingTaskParams;
3use cosmwasm_std::{Addr, Env, MessageInfo, QuerierWrapper};
4use croncat_sdk_factory::state::CONTRACT_ADDRS;
5use croncat_sdk_manager::state::LAST_TASK_EXECUTION_INFO;
6use croncat_sdk_tasks::types::TaskExecutionInfo;
7
8/// Handles and validates an incoming CronCat task
9/// Specifically, it checks:
10/// - Sender is a sanctioned CronCat manager contract (CronCat factory knows the manager contract addresses and versions.)
11/// - We're in the same block and transaction index as the latest executed transaction. In other words, this task is happening in an atomic, synchronous transaction, and this invocation is the result of a cross-contract call (message or submessage) from a manager contract. (Note: you can disable this check via the `disable_sync_check` field of `custom_validation` if, for instance, you're doing IBC calls where the execution is asynchronous, spanning blocks.)
12/// - The owner of the task that just called is the calling contract (Note: this can be changed by setting the `expected_owner` field in `custom_validation`. If unset, it will default to this contract.)
13/// For contracts storing task hashes, you can take the [`TaskExecutionInfo`](croncat_sdk_tasks::types::TaskExecutionInfo) returned and check it against state.
14pub fn handle_incoming_task(
15    querier: &QuerierWrapper,
16    env: Env,
17    info: MessageInfo,
18    croncat_factory_address: Addr,
19    custom_validation: Option<HandleIncomingTaskParams>,
20) -> Result<TaskExecutionInfo, CronCatContractError> {
21    // First we'll create helper vars addressing any custom validation
22    let HandleIncomingTaskParams {
23        disable_sync_check,
24        disable_owner_check,
25        expected_owner,
26    } = custom_validation.unwrap_or_default();
27
28    // If a custom owner is specified, use it. Otherwise, use this contract.
29    let owner = expected_owner.unwrap_or(env.contract.address);
30    let sender = info.sender;
31
32    // We want to confirm this comes from a sanctioned, CronCat manager
33    // contract, which we'll do when we query the factory a bit later
34    // This does an efficient query to the sender contract (which may or may not be a sanction manager, which comes later)
35
36    // Pertinent info containing, among other things, the task version
37    let latest_task_execution: TaskExecutionInfo = LAST_TASK_EXECUTION_INFO
38        .query(querier, sender.clone())
39        .map_err(|_| CronCatContractError::LatestTaskInfoFailed {
40            manager_addr: sender.clone(),
41        })?;
42
43    // We turn (for example) "0.1" into [0, 1] so we can query the factory with this value and the contract name ("manager")
44    let versions = latest_task_execution
45        .version
46        .split('.')
47        .map(|v| -> u8 { v.parse().unwrap() })
48        .collect::<Vec<u8>>();
49
50    let sanctioned_manager_res: Option<Addr> = CONTRACT_ADDRS.query(
51        querier,
52        croncat_factory_address,
53        ("manager", versions.as_slice()),
54    )?;
55
56    if sanctioned_manager_res.is_none() {
57        return Err(CronCatContractError::FactoryManagerQueryFailed {
58            manager_addr: sender,
59            version: latest_task_execution.version,
60        });
61    }
62
63    let sanctioned_manager_address = sanctioned_manager_res.unwrap();
64
65    // If the sender and the sanctioned manager address differ,
66    // then this isn't being called by CronCat
67    if sanctioned_manager_address != sender {
68        return Err(CronCatContractError::UnsanctionedInvocation {
69            manager_addr: sender,
70            version: latest_task_execution.version,
71        });
72    }
73
74    // If this method is called normally (with disable_sync_check defaulting to false)
75    // This will check for synchronous invocation from the CronCat manager.
76    // This method can be called, ignoring this check by setting it to `true`.
77    if !disable_sync_check {
78        // Require that this is both in the same block…
79        let is_same_block_bool = env.block.height == latest_task_execution.block_height;
80        // …and the same transaction index, meaning we're in the
81        // middle of a cross-contract call from a sanctioned
82        // CronCat manager contract.
83        let is_same_tx_id_bool = env.transaction == latest_task_execution.tx_info;
84
85        if !is_same_block_bool || !is_same_tx_id_bool {
86            return Err(CronCatContractError::NotSameBlockTxIndex {});
87        }
88    }
89
90    // Last, we check if the task creator is this contract, ensuring
91    // this invocation hasn't happened from someone else's task.
92    // In cases where that's too restrictive, you may specify
93    if !disable_owner_check && latest_task_execution.owner_addr != owner {
94        return Err(CronCatContractError::WrongTaskOwner {
95            expected_owner: owner,
96        });
97    }
98
99    Ok(latest_task_execution)
100}