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}