1use std::collections::HashSet;
2
3#[cfg(not(feature = "library"))]
4use cosmwasm_std::entry_point;
5use cosmwasm_std::{
6 from_binary, to_binary, Addr, Attribute, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo,
7 Reply, Response, StdError, StdResult, SubMsg, Uint128, WasmMsg,
8};
9use croncat_sdk_core::internal_messages::agents::AgentWithdrawOnRemovalArgs;
10use croncat_sdk_core::internal_messages::manager::{ManagerCreateTaskBalance, ManagerRemoveTask};
11use croncat_sdk_manager::msg::{AgentWithdrawCallback, ManagerExecuteMsg::ProxyCallForwarded};
12use croncat_sdk_manager::types::{TaskBalance, TaskBalanceResponse, UpdateConfig};
13use croncat_sdk_tasks::types::{Interval, Task, TaskExecutionInfo, TaskInfo};
14use cw2::set_contract_version;
15use cw_utils::{may_pay, parse_reply_execute_data};
16
17use crate::balances::{
18 add_fee_rewards, execute_owner_withdraw, execute_receive_cw20, execute_refill_native_balance,
19 execute_refill_task_cw20, execute_user_withdraw, query_users_balances, sub_user_cw20,
20};
21use crate::error::ContractError;
22use crate::helpers::{
23 assert_caller_is_agent_contract, attached_natives, calculate_required_natives,
24 check_if_sender_is_tasks, check_ready_for_execution, create_bank_send_message,
25 create_task_completed_msg, finalize_task, gas_with_fees, get_agents_addr, get_tasks_addr,
26 is_after_boundary, is_before_boundary, parse_reply_msg, process_queries, query_agent,
27 recalculate_cw20, remove_task_balance, replace_values, task_sub_msgs,
28};
29use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
30use crate::state::{
31 Config, QueueItem, AGENT_REWARDS, CONFIG, LAST_TASK_EXECUTION_INFO, PAUSED, REPLY_QUEUE,
32 TASKS_BALANCES, TREASURY_BALANCE,
33};
34use crate::ContractError::InvalidPercentage;
35
36pub(crate) const CONTRACT_NAME: &str = "crate:croncat-manager";
37pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
38
39pub(crate) const DEFAULT_FEE: u16 = 5;
40
41pub(crate) const TASK_REPLY: u64 = u64::from_be_bytes(*b"croncat1");
43
44#[cfg_attr(not(feature = "library"), entry_point)]
51pub fn instantiate(
52 deps: DepsMut,
53 _env: Env,
54 info: MessageInfo,
55 msg: InstantiateMsg,
56) -> Result<Response, ContractError> {
57 let InstantiateMsg {
59 version,
60 croncat_tasks_key,
61 croncat_agents_key,
62 pause_admin,
63 gas_price,
64 treasury_addr,
65 cw20_whitelist,
66 } = msg;
67
68 if info.funds.is_empty() {
70 return Err(ContractError::EmptyBalance {});
71 }
72
73 let funds_denom = info
77 .funds
78 .iter()
79 .find(|coin| coin.amount == Uint128::new(1))
80 .map(|coin| coin.denom.clone());
81
82 let denom = if let Some(d) = funds_denom {
83 d
84 } else {
85 return Err(ContractError::EmptyBalance {});
86 };
87
88 let treasury_funds = may_pay(&info, denom.as_str());
90 if treasury_funds.is_err() {
91 return Err(ContractError::RedundantFunds {});
92 }
93 TREASURY_BALANCE.save(deps.storage, &treasury_funds.unwrap())?;
94
95 let gas_price = gas_price.unwrap_or_default();
96 if !gas_price.is_valid() {
98 return Err(ContractError::InvalidGasPrice {});
99 }
100
101 let owner_addr = info.sender.clone();
102
103 if !(61usize..=74usize).contains(&pause_admin.to_string().len()) {
109 return Err(ContractError::InvalidPauseAdmin {});
110 }
111
112 let cw20_whitelist: Vec<Addr> = cw20_whitelist
113 .unwrap_or_default()
114 .into_iter()
115 .map(Addr::unchecked)
116 .collect();
117
118 let config = Config {
119 owner_addr,
120 pause_admin,
121 croncat_factory_addr: info.sender,
122 croncat_tasks_key,
123 croncat_agents_key,
124 agent_fee: DEFAULT_FEE,
125 treasury_fee: DEFAULT_FEE,
126 gas_price,
127 cw20_whitelist,
128 native_denom: denom,
129 limit: 100,
130 treasury_addr: treasury_addr
131 .map(|human| deps.api.addr_validate(&human))
132 .transpose()?,
133 };
134
135 CONFIG.save(deps.storage, &config)?;
137 PAUSED.save(deps.storage, &false)?;
138 LAST_TASK_EXECUTION_INFO.save(deps.storage, &TaskExecutionInfo::default())?;
139 set_contract_version(
140 deps.storage,
141 CONTRACT_NAME,
142 version.unwrap_or_else(|| CONTRACT_VERSION.to_string()),
143 )?;
144
145 Ok(Response::new()
146 .add_attribute("action", "instantiate")
147 .add_attribute("owner_id", config.owner_addr.to_string()))
148}
149
150#[cfg_attr(not(feature = "library"), entry_point)]
151pub fn execute(
152 deps: DepsMut,
153 env: Env,
154 info: MessageInfo,
155 msg: ExecuteMsg,
156) -> Result<Response, ContractError> {
157 match msg {
158 ExecuteMsg::UpdateConfig(msg) => execute_update_config(deps, info, *msg),
159 ExecuteMsg::ProxyCall { task_hash } => execute_proxy_call(deps, env, info, task_hash),
160 ExecuteMsg::ProxyBatch(proxy_calls) => execute_proxy_batch(info, env, proxy_calls),
161 ExecuteMsg::ProxyCallForwarded {
162 agent_addr,
163 task_hash,
164 } => execute_proxy_call_forwarded(deps, env, info, task_hash, agent_addr),
165 ExecuteMsg::Receive(msg) => execute_receive_cw20(deps, info, msg),
166 ExecuteMsg::RefillTaskBalance { task_hash } => {
167 execute_refill_native_balance(deps, info, task_hash)
168 }
169 ExecuteMsg::RefillTaskCw20Balance { task_hash, cw20 } => {
170 execute_refill_task_cw20(deps, info, task_hash, cw20)
171 }
172 ExecuteMsg::CreateTaskBalance(msg) => execute_create_task_balance(deps, info, *msg),
173 ExecuteMsg::RemoveTask(msg) => execute_remove_task(deps, info, msg),
174 ExecuteMsg::OwnerWithdraw {} => execute_owner_withdraw(deps, info),
175 ExecuteMsg::UserWithdraw { limit } => execute_user_withdraw(deps, info, limit),
176 ExecuteMsg::AgentWithdraw(args) => execute_withdraw_agent_rewards(deps, info, args),
177 ExecuteMsg::PauseContract {} => execute_pause(deps, info),
178 ExecuteMsg::UnpauseContract {} => execute_unpause(deps, info),
179 }
180}
181
182fn execute_remove_task(
183 deps: DepsMut,
184 info: MessageInfo,
185 msg: ManagerRemoveTask,
186) -> Result<Response, ContractError> {
187 let config = CONFIG.load(deps.storage)?;
188 check_if_sender_is_tasks(&deps.querier, &config, &info.sender)?;
189 let task_owner = msg.sender;
190 let task_balance = TASKS_BALANCES.load(deps.storage, &msg.task_hash)?;
191 let coins_transfer = remove_task_balance(
192 deps.storage,
193 task_balance,
194 &task_owner,
195 &config.native_denom,
196 &msg.task_hash,
197 )?;
198
199 let bank_send = BankMsg::Send {
200 to_address: task_owner.into_string(),
201 amount: coins_transfer,
202 };
203 Ok(Response::new()
204 .add_attribute("action", "remove_task")
205 .add_message(bank_send))
206}
207
208fn execute_proxy_call_internal(
209 deps: DepsMut,
210 env: Env,
211 info: MessageInfo,
212 task_hash: Option<String>,
213 agent_fwd_addr: Option<Addr>,
214) -> Result<Response, ContractError> {
215 let config: Config = CONFIG.load(deps.storage)?;
216
217 let agent_addr = if let Some(a) = agent_fwd_addr {
218 if env.contract.address == info.sender {
220 a
221 } else {
222 return Err(ContractError::Unauthorized {});
223 }
224 } else {
225 info.sender
226 };
227 let agents_addr = get_agents_addr(&deps.querier, &config)?;
228 let tasks_addr = get_tasks_addr(&deps.querier, &config)?;
229
230 let current_task: croncat_sdk_tasks::types::TaskResponse = if let Some(hash) = task_hash {
233 let agent_response: croncat_sdk_agents::msg::AgentResponse =
235 deps.querier.query_wasm_smart(
236 agents_addr,
237 &croncat_sdk_agents::msg::QueryMsg::GetAgent {
238 account_id: agent_addr.to_string(),
239 },
240 )?;
241 if agent_response.agent.map_or(true, |agent| {
242 agent.status != croncat_sdk_agents::types::AgentStatus::Active
243 }) {
244 return Err(ContractError::AgentNotActive {});
245 }
246
247 let task_data: croncat_sdk_tasks::types::TaskResponse = deps.querier.query_wasm_smart(
249 tasks_addr.clone(),
250 &croncat_sdk_tasks::msg::TasksQueryMsg::Task { task_hash: hash },
251 )?;
252
253 if let Some(task) = task_data.clone().task {
255 let t = Task {
256 owner_addr: task.owner_addr,
257 interval: task.interval,
258 boundary: task.boundary,
259 stop_on_fail: task.stop_on_fail,
260 amount_for_one_task: task.amount_for_one_task,
261 actions: task.actions,
262 queries: task.queries.unwrap_or_default(),
263 transforms: task.transforms,
264 version: task.version,
265 };
266 if !t.is_evented() {
267 return Err(ContractError::NoTaskForAgent {});
268 }
269 }
270 task_data
271 } else {
272 let agent_tasks: croncat_sdk_agents::msg::AgentTaskResponse =
274 deps.querier.query_wasm_smart(
275 agents_addr,
276 &croncat_sdk_agents::msg::QueryMsg::GetAgentTasks {
277 account_id: agent_addr.to_string(),
278 },
279 )?;
280 if agent_tasks.stats.num_block_tasks.is_zero() && agent_tasks.stats.num_cron_tasks.is_zero()
281 {
282 return Err(ContractError::NoTaskForAgent {});
283 }
284
285 deps.querier.query_wasm_smart(
287 tasks_addr.clone(),
288 &croncat_sdk_tasks::msg::TasksQueryMsg::CurrentTask {},
289 )?
290 };
291
292 let Some(mut task) = current_task.task else {
293 return Err(ContractError::NoTask {});
295 };
296 let task_hash = task.task_hash.to_owned();
297 let task_version = task.version.to_owned();
298
299 if is_before_boundary(&env.block, Some(&task.boundary)) {
301 return Err(ContractError::TaskNotReady {});
302 }
303 if is_after_boundary(&env.block, Some(&task.boundary)) {
304 return end_task(
306 deps,
307 task,
308 config,
309 agent_addr,
310 tasks_addr,
311 Some(vec![
312 Attribute::new("lifecycle", "task_ended"),
313 Attribute::new("task_hash", task_hash),
314 Attribute::new("task_version", task_version),
315 ]),
316 true,
317 );
318 }
319
320 if task.queries.is_some() {
321 let query_responses = process_queries(&deps, &task)?;
323 if !query_responses.is_empty() {
324 replace_values(&mut task, query_responses)?;
325 }
326
327 let invalidated_after_transform = if let Ok(amounts) =
329 recalculate_cw20(&task, &config, deps.as_ref(), &env.contract.address)
330 {
331 task.amount_for_one_task.cw20 = amounts;
332 false
333 } else {
334 true
335 };
336
337 let task_balance = TASKS_BALANCES.load(deps.storage, task_hash.as_bytes())?;
340 if invalidated_after_transform
341 || task_balance
342 .verify_enough_cw20(task.amount_for_one_task.cw20.clone(), Uint128::new(1))
343 .is_err()
344 {
345 return end_task(
347 deps,
348 task,
349 config,
350 agent_addr,
351 tasks_addr,
352 Some(vec![
353 Attribute::new("lifecycle", "task_invalidated"),
354 Attribute::new("task_hash", task_hash),
355 Attribute::new("task_version", task_version),
356 ]),
357 false,
358 );
359 }
360 }
361
362 let sub_msgs = task_sub_msgs(&task);
363 let queue_item = QueueItem {
364 task: task.clone(),
365 agent_addr,
366 failures: Default::default(),
367 };
368
369 REPLY_QUEUE.save(deps.storage, &queue_item)?;
370
371 let last_task_execution_info = TaskExecutionInfo {
376 block_height: env.block.height,
377 tx_info: env.transaction,
378 task_hash: task.task_hash,
379 owner_addr: task.owner_addr,
380 amount_for_one_task: task.amount_for_one_task,
381 version: task_version,
382 };
383
384 LAST_TASK_EXECUTION_INFO.save(deps.storage, &last_task_execution_info)?;
385
386 Ok(Response::new()
387 .add_attribute("action", "proxy_call")
388 .add_submessages(sub_msgs))
389}
390
391fn execute_proxy_call(
393 deps: DepsMut,
394 env: Env,
395 info: MessageInfo,
396 task_hash: Option<String>,
397) -> Result<Response, ContractError> {
398 let paused = PAUSED.load(deps.storage)?;
399 check_ready_for_execution(&info, paused)?;
400
401 execute_proxy_call_internal(deps, env, info, task_hash, None)
402}
403
404fn execute_proxy_call_forwarded(
407 deps: DepsMut,
408 env: Env,
409 info: MessageInfo,
410 task_hash: Option<String>,
411 agent_addr: Addr,
412) -> Result<Response, ContractError> {
413 let paused = PAUSED.load(deps.storage)?;
414 check_ready_for_execution(&info, paused)?;
415
416 execute_proxy_call_internal(deps, env, info, task_hash, Some(agent_addr))
417}
418
419fn execute_proxy_batch(
424 info: MessageInfo,
425 env: Env,
426 proxy_calls: Vec<Option<String>>,
427) -> Result<Response, ContractError> {
428 let mut sub_msgs = Vec::with_capacity(proxy_calls.len());
429 let mut unique_hashes = HashSet::new();
430 let agent_addr = info.sender;
431
432 for task_hash in proxy_calls {
433 let msg = SubMsg::new(WasmMsg::Execute {
435 contract_addr: env.contract.address.to_string(),
437 msg: to_binary(&ProxyCallForwarded {
439 task_hash: task_hash.clone(),
440 agent_addr: agent_addr.clone(),
441 })?,
442 funds: vec![],
443 });
444
445 match task_hash {
446 Some(th) => {
447 if unique_hashes.insert(th) {
449 sub_msgs.push(msg);
450 }
451 }
452 None => {
453 sub_msgs.push(msg);
454 }
455 }
456 }
457
458 Ok(Response::new().add_submessages(sub_msgs))
459}
460
461fn end_task(
462 deps: DepsMut,
463 task: TaskInfo,
464 config: Config,
465 agent_addr: Addr,
466 tasks_addr: Addr,
467 attrs: Option<Vec<Attribute>>,
468 reimburse_only: bool,
469) -> Result<Response, ContractError> {
470 let gas_with_fees = if reimburse_only {
472 gas_with_fees(task.amount_for_one_task.gas, 0u64)?
473 } else {
474 gas_with_fees(
475 task.amount_for_one_task.gas,
476 (task.amount_for_one_task.agent_fee + task.amount_for_one_task.treasury_fee) as u64,
477 )?
478 };
479 let native_for_gas_required = task
480 .amount_for_one_task
481 .gas_price
482 .calculate(gas_with_fees)
483 .unwrap();
484 let mut task_balance = TASKS_BALANCES.load(deps.storage, task.task_hash.as_bytes())?;
485 task_balance.native_balance = task_balance
486 .native_balance
487 .checked_sub(Uint128::new(native_for_gas_required))
488 .map_err(StdError::overflow)?;
489
490 add_fee_rewards(
493 deps.storage,
494 task.amount_for_one_task.gas,
495 &task.amount_for_one_task.gas_price,
496 &agent_addr,
497 task.amount_for_one_task.agent_fee,
498 task.amount_for_one_task.treasury_fee,
499 reimburse_only,
500 )?;
501
502 let coins_transfer = remove_task_balance(
504 deps.storage,
505 task_balance,
506 &task.owner_addr,
507 &config.native_denom,
508 task.task_hash.as_bytes(),
509 )?;
510 let msg = croncat_sdk_core::internal_messages::tasks::TasksRemoveTaskByManager {
511 task_hash: task.task_hash.into_bytes(),
512 }
513 .into_cosmos_msg(tasks_addr)?;
514 let bank_send = BankMsg::Send {
515 to_address: task.owner_addr.into_string(),
516 amount: coins_transfer,
517 };
518 Ok(Response::new()
519 .add_attribute("action", "end_task")
520 .add_attributes(attrs.unwrap_or_default())
521 .add_message(msg)
522 .add_message(bank_send))
523}
524
525pub fn execute_update_config(
530 deps: DepsMut,
531 info: MessageInfo,
532 msg: UpdateConfig,
533) -> Result<Response, ContractError> {
534 CONFIG.update(deps.storage, |mut config| {
535 let UpdateConfig {
537 agent_fee,
538 treasury_fee,
539 gas_price,
540 croncat_tasks_key,
541 croncat_agents_key,
542 treasury_addr,
543 cw20_whitelist,
544 } = msg;
545
546 if info.sender != config.owner_addr {
547 return Err(ContractError::Unauthorized {});
548 }
549
550 let updated_agent_fee = if let Some(agent_fee) = agent_fee {
551 validate_percentage_value(&agent_fee, "agent_fee")?;
553 agent_fee
554 } else {
555 config.agent_fee
557 };
558
559 let updated_treasury_fee = if let Some(treasury_fee) = treasury_fee {
560 validate_percentage_value(&treasury_fee, "treasury_fee")?;
561 treasury_fee
562 } else {
563 config.treasury_fee
564 };
565
566 let gas_price = gas_price.unwrap_or(config.gas_price);
567 if !gas_price.is_valid() {
568 return Err(ContractError::InvalidGasPrice {});
569 }
570
571 let treasury_addr = if let Some(human) = treasury_addr {
572 Some(deps.api.addr_validate(&human)?)
573 } else {
574 config.treasury_addr
575 };
576
577 let cw20_whitelist: Vec<Addr> = cw20_whitelist
578 .unwrap_or_default()
579 .into_iter()
580 .map(|human| deps.api.addr_validate(&human))
581 .collect::<StdResult<_>>()?;
582
583 config.cw20_whitelist.extend(cw20_whitelist);
584
585 let new_config = Config {
586 owner_addr: config.owner_addr,
587 pause_admin: config.pause_admin,
588 croncat_factory_addr: config.croncat_factory_addr,
589 croncat_tasks_key: croncat_tasks_key.unwrap_or(config.croncat_tasks_key),
590 croncat_agents_key: croncat_agents_key.unwrap_or(config.croncat_agents_key),
591 agent_fee: updated_agent_fee,
592 treasury_fee: updated_treasury_fee,
593 gas_price,
594 cw20_whitelist: config.cw20_whitelist,
595 native_denom: config.native_denom,
596 limit: config.limit,
597 treasury_addr,
598 };
599 Ok(new_config)
600 })?;
601
602 Ok(Response::new().add_attribute("action", "update_config"))
603}
604
605fn execute_create_task_balance(
606 deps: DepsMut,
607 info: MessageInfo,
608 msg: ManagerCreateTaskBalance,
609) -> Result<Response, ContractError> {
610 let config = CONFIG.load(deps.storage)?;
611 check_if_sender_is_tasks(&deps.querier, &config, &info.sender)?;
612 let (native, ibc) = attached_natives(&config.native_denom, info.funds)?;
613 let cw20 = msg.cw20;
614 if let Some(attached_cw20) = &cw20 {
615 sub_user_cw20(deps.storage, &msg.sender, attached_cw20)?;
616 }
617 let tasks_balance = TaskBalance {
618 native_balance: native,
619 cw20_balance: cw20,
620 ibc_balance: ibc,
621 };
622 {
624 let gas_with_fees = gas_with_fees(
625 msg.amount_for_one_task.gas,
626 (config.agent_fee + config.treasury_fee) as u64,
627 )?;
628 let native_for_gas_required = config.gas_price.calculate(gas_with_fees).unwrap();
629 let (native_for_sends_required, ibc_required) =
630 calculate_required_natives(msg.amount_for_one_task.coin, &config.native_denom)?;
631 tasks_balance.verify_enough_attached(
632 Uint128::from(native_for_gas_required) + native_for_sends_required,
633 msg.amount_for_one_task.cw20,
634 ibc_required,
635 msg.recurring,
636 &config.native_denom,
637 )?;
638 }
639 TASKS_BALANCES.save(deps.storage, &msg.task_hash, &tasks_balance)?;
640
641 Ok(Response::new().add_attribute("action", "create_task_balance"))
642}
643
644fn execute_withdraw_agent_rewards(
646 deps: DepsMut,
647 info: MessageInfo,
648 args: Option<AgentWithdrawOnRemovalArgs>,
649) -> Result<Response, ContractError> {
650 let paused = PAUSED.load(deps.storage)?;
652 check_ready_for_execution(&info, paused)?;
653 let config: Config = CONFIG.load(deps.storage)?;
654
655 let agent_id: Addr;
656 let payable_account_id: Addr;
657 let mut fail_on_zero_balance = true;
658
659 if let Some(arg) = args {
660 assert_caller_is_agent_contract(&deps.querier, &config, &info.sender)?;
661 agent_id = Addr::unchecked(arg.agent_id);
662 payable_account_id = Addr::unchecked(arg.payable_account_id);
663 fail_on_zero_balance = false;
664 } else {
665 agent_id = info.sender;
666 let agent = query_agent(&deps.querier, &config, agent_id.to_string())?
667 .agent
668 .ok_or(ContractError::NoRewardsOwnerAgentFound {})?;
669 payable_account_id = agent.payable_account_id;
670 }
671 let agent_rewards = AGENT_REWARDS
672 .may_load(deps.storage, &agent_id)?
673 .unwrap_or_default();
674
675 AGENT_REWARDS.remove(deps.storage, &agent_id);
676
677 let mut msgs = vec![];
678 let msg = create_bank_send_message(
680 &payable_account_id,
681 &config.native_denom,
682 agent_rewards.u128(),
683 )?;
684
685 if !agent_rewards.is_zero() {
686 msgs.push(msg);
687 } else if fail_on_zero_balance {
688 return Err(ContractError::NoWithdrawRewardsAvailable {});
689 }
690
691 Ok(Response::new()
692 .add_messages(msgs)
693 .set_data(to_binary(&AgentWithdrawCallback {
694 agent_id: agent_id.to_string(),
695 amount: agent_rewards,
696 payable_account_id: payable_account_id.to_string(),
697 })?)
698 .add_attribute("action", "withdraw_rewards")
699 .add_attribute("payment_account_id", &payable_account_id)
700 .add_attribute("rewards", agent_rewards))
701}
702
703pub fn execute_pause(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
704 if PAUSED.load(deps.storage)? {
705 return Err(ContractError::ContractPaused);
706 }
707 let config = CONFIG.load(deps.storage)?;
708 if info.sender != config.pause_admin {
709 return Err(ContractError::Unauthorized {});
710 }
711 PAUSED.save(deps.storage, &true)?;
712 Ok(Response::new().add_attribute("action", "pause_contract"))
713}
714
715pub fn execute_unpause(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
716 if !PAUSED.load(deps.storage)? {
717 return Err(ContractError::ContractUnpaused);
718 }
719 let config = CONFIG.load(deps.storage)?;
720 if info.sender != config.owner_addr {
721 return Err(ContractError::Unauthorized {});
722 }
723 PAUSED.save(deps.storage, &false)?;
724 Ok(Response::new().add_attribute("action", "unpause_contract"))
725}
726
727#[cfg_attr(not(feature = "library"), entry_point)]
728pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
729 match msg {
730 QueryMsg::Config {} => to_binary(&CONFIG.load(deps.storage)?),
731 QueryMsg::Paused {} => to_binary(&PAUSED.load(deps.storage)?),
732 QueryMsg::TreasuryBalance {} => to_binary(&TREASURY_BALANCE.load(deps.storage)?),
733 QueryMsg::UsersBalances {
734 address,
735 from_index,
736 limit,
737 } => to_binary(&query_users_balances(deps, address, from_index, limit)?),
738 QueryMsg::TaskBalance { task_hash } => to_binary(&TaskBalanceResponse {
739 balance: TASKS_BALANCES.may_load(deps.storage, task_hash.as_bytes())?,
740 }),
741 QueryMsg::AgentRewards { agent_id } => to_binary(
742 &AGENT_REWARDS
743 .may_load(deps.storage, &Addr::unchecked(agent_id))?
744 .unwrap_or(Uint128::zero()),
745 ),
746 }
747}
748
749#[cfg_attr(not(feature = "library"), entry_point)]
750pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result<Response, ContractError> {
751 match msg.id {
752 TASK_REPLY => {
753 let execute_data = parse_reply_execute_data(msg)?;
754 let remove_task_msg: Option<ManagerRemoveTask> =
755 from_binary(&execute_data.data.unwrap())?;
756 let Some(msg) = remove_task_msg else {
757 return Ok(Response::new());
758 };
759 let config = CONFIG.load(deps.storage)?;
760 let task_owner = msg.sender;
761 let task_balance = TASKS_BALANCES.load(deps.storage, &msg.task_hash)?;
762 let coins_transfer = remove_task_balance(
763 deps.storage,
764 task_balance,
765 &task_owner,
766 &config.native_denom,
767 &msg.task_hash,
768 )?;
769
770 let bank_send = BankMsg::Send {
771 to_address: task_owner.into_string(),
772 amount: coins_transfer,
773 };
774 Ok(Response::new().add_message(bank_send))
775 }
776 _ => {
777 let mut queue_item = REPLY_QUEUE.load(deps.storage)?;
778 let last = parse_reply_msg(deps.storage, &mut queue_item, msg);
779 if last {
780 let failures: Vec<Attribute> = queue_item
781 .failures
782 .iter()
783 .map(|(idx, failure)| Attribute::new(format!("action{}_failure", idx), failure))
784 .collect();
785 let config = CONFIG.load(deps.storage)?;
786 let complete_msg = create_task_completed_msg(
787 &deps.querier,
788 &config,
789 &queue_item.agent_addr,
790 !matches!(queue_item.task.interval, Interval::Cron(_)),
791 )?;
792 Ok(finalize_task(deps, queue_item)?
793 .add_message(complete_msg)
794 .add_attributes(failures))
795 } else {
796 Ok(Response::new())
797 }
798 }
799 }
800}
801
802fn validate_percentage_value(val: &u16, field_name: &str) -> Result<(), ContractError> {
808 if val > &10_000u16 {
809 Err(InvalidPercentage {
810 field: field_name.to_string(),
811 })
812 } else {
813 Ok(())
814 }
815}