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
use crate::error::CronCatContractError;
use crate::types::HandleIncomingTaskParams;
use cosmwasm_std::{Addr, Env, MessageInfo, QuerierWrapper};
use croncat_sdk_factory::state::CONTRACT_ADDRS;
use croncat_sdk_manager::state::LAST_TASK_EXECUTION_INFO;
use croncat_sdk_tasks::types::TaskExecutionInfo;
/// Handles and validates an incoming CronCat task
/// Specifically, it checks:
/// - Sender is a sanctioned CronCat manager contract (CronCat factory knows the manager contract addresses and versions.)
/// - 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.)
/// - 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.)
/// For contracts storing task hashes, you can take the [`TaskExecutionInfo`](croncat_sdk_tasks::types::TaskExecutionInfo) returned and check it against state.
pub fn handle_incoming_task(
querier: &QuerierWrapper,
env: Env,
info: MessageInfo,
croncat_factory_address: Addr,
custom_validation: Option<HandleIncomingTaskParams>,
) -> Result<TaskExecutionInfo, CronCatContractError> {
// First we'll create helper vars addressing any custom validation
let HandleIncomingTaskParams {
disable_sync_check,
disable_owner_check,
expected_owner,
} = custom_validation.unwrap_or_default();
// If a custom owner is specified, use it. Otherwise, use this contract.
let owner = expected_owner.unwrap_or(env.contract.address);
let sender = info.sender;
// We want to confirm this comes from a sanctioned, CronCat manager
// contract, which we'll do when we query the factory a bit later
// This does an efficient query to the sender contract (which may or may not be a sanction manager, which comes later)
// Pertinent info containing, among other things, the task version
let latest_task_execution: TaskExecutionInfo = LAST_TASK_EXECUTION_INFO
.query(querier, sender.clone())
.map_err(|_| CronCatContractError::LatestTaskInfoFailed {
manager_addr: sender.clone(),
})?;
// We turn (for example) "0.1" into [0, 1] so we can query the factory with this value and the contract name ("manager")
let versions = latest_task_execution
.version
.split('.')
.map(|v| -> u8 { v.parse().unwrap() })
.collect::<Vec<u8>>();
let sanctioned_manager_res: Option<Addr> = CONTRACT_ADDRS.query(
querier,
croncat_factory_address,
("manager", versions.as_slice()),
)?;
if sanctioned_manager_res.is_none() {
return Err(CronCatContractError::FactoryManagerQueryFailed {
manager_addr: sender,
version: latest_task_execution.version,
});
}
let sanctioned_manager_address = sanctioned_manager_res.unwrap();
// If the sender and the sanctioned manager address differ,
// then this isn't being called by CronCat
if sanctioned_manager_address != sender {
return Err(CronCatContractError::UnsanctionedInvocation {
manager_addr: sender,
version: latest_task_execution.version,
});
}
// If this method is called normally (with disable_sync_check defaulting to false)
// This will check for synchronous invocation from the CronCat manager.
// This method can be called, ignoring this check by setting it to `true`.
if !disable_sync_check {
// Require that this is both in the same block…
let is_same_block_bool = env.block.height == latest_task_execution.block_height;
// …and the same transaction index, meaning we're in the
// middle of a cross-contract call from a sanctioned
// CronCat manager contract.
let is_same_tx_id_bool = env.transaction == latest_task_execution.tx_info;
if !is_same_block_bool || !is_same_tx_id_bool {
return Err(CronCatContractError::NotSameBlockTxIndex {});
}
}
// Last, we check if the task creator is this contract, ensuring
// this invocation hasn't happened from someone else's task.
// In cases where that's too restrictive, you may specify
if !disable_owner_check && latest_task_execution.owner_addr != owner {
return Err(CronCatContractError::WrongTaskOwner {
expected_owner: owner,
});
}
Ok(latest_task_execution)
}